@gridworkjs/quadtree 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/package.json +43 -0
- package/src/index.js +1 -0
- package/src/quadtree.js +312 -0
- package/types/index.d.ts +1 -0
- package/types/quadtree.d.ts +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" width="256" height="256" alt="@gridworkjs/quadtree">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@gridworkjs/quadtree</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">quadtree spatial index for sparse, uneven point and region data</p>
|
|
8
|
+
|
|
9
|
+
## install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm install @gridworkjs/quadtree
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## usage
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { createQuadtree } from '@gridworkjs/quadtree'
|
|
19
|
+
import { point, rect, bounds } from '@gridworkjs/core'
|
|
20
|
+
|
|
21
|
+
// create a quadtree with a bounds accessor
|
|
22
|
+
const tree = createQuadtree(item => bounds(item.position))
|
|
23
|
+
|
|
24
|
+
// insert items - any shape, the accessor extracts bounds
|
|
25
|
+
tree.insert({ id: 1, position: point(10, 20) })
|
|
26
|
+
tree.insert({ id: 2, position: point(50, 60) })
|
|
27
|
+
tree.insert({ id: 3, position: rect(70, 70, 90, 90) })
|
|
28
|
+
|
|
29
|
+
// search for items intersecting a region
|
|
30
|
+
tree.search({ minX: 0, minY: 0, maxX: 55, maxY: 65 })
|
|
31
|
+
// => [{ id: 1, ... }, { id: 2, ... }]
|
|
32
|
+
|
|
33
|
+
// also accepts geometry objects as queries
|
|
34
|
+
tree.search(rect(0, 0, 55, 65))
|
|
35
|
+
|
|
36
|
+
// find nearest neighbors
|
|
37
|
+
tree.nearest({ x: 12, y: 22 }, 2)
|
|
38
|
+
// => [{ id: 1, ... }, { id: 2, ... }]
|
|
39
|
+
|
|
40
|
+
// remove by identity
|
|
41
|
+
tree.remove(item)
|
|
42
|
+
|
|
43
|
+
tree.size // number of items
|
|
44
|
+
tree.bounds // current root bounds
|
|
45
|
+
tree.clear()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## options
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
createQuadtree(accessor, {
|
|
52
|
+
bounds: { minX: 0, minY: 0, maxX: 1000, maxY: 1000 }, // world bounds (optional, auto-grows)
|
|
53
|
+
maxItems: 16, // items per node before splitting
|
|
54
|
+
maxDepth: 8 // maximum tree depth
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If `bounds` is omitted, the tree auto-creates bounds from the first insert and grows as needed.
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### `createQuadtree(accessor, options?)`
|
|
63
|
+
|
|
64
|
+
Creates a new quadtree. The `accessor` function maps each item to its bounding box (`{ minX, minY, maxX, maxY }`). Use `bounds()` from `@gridworkjs/core` to convert geometries.
|
|
65
|
+
|
|
66
|
+
Returns a spatial index implementing the gridwork protocol.
|
|
67
|
+
|
|
68
|
+
### `index.insert(item)`
|
|
69
|
+
|
|
70
|
+
Adds an item to the tree.
|
|
71
|
+
|
|
72
|
+
### `index.remove(item)`
|
|
73
|
+
|
|
74
|
+
Removes an item by identity (`===`). Returns `true` if found and removed.
|
|
75
|
+
|
|
76
|
+
### `index.search(query)`
|
|
77
|
+
|
|
78
|
+
Returns all items whose bounds intersect the query. Accepts bounds objects or geometry objects (point, rect, circle).
|
|
79
|
+
|
|
80
|
+
### `index.nearest(point, k?)`
|
|
81
|
+
|
|
82
|
+
Returns the `k` nearest items to the given point, sorted by distance. Defaults to `k=1`. Accepts `{ x, y }` or a point geometry.
|
|
83
|
+
|
|
84
|
+
### `index.clear()`
|
|
85
|
+
|
|
86
|
+
Removes all items. Preserves fixed bounds if provided at construction.
|
|
87
|
+
|
|
88
|
+
### `index.size`
|
|
89
|
+
|
|
90
|
+
Number of items in the tree.
|
|
91
|
+
|
|
92
|
+
### `index.bounds`
|
|
93
|
+
|
|
94
|
+
Current root bounds, or `null` if empty and no fixed bounds were set.
|
|
95
|
+
|
|
96
|
+
## license
|
|
97
|
+
|
|
98
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gridworkjs/quadtree",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "quadtree spatial index for sparse, uneven point and region data",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "./types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./types/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test",
|
|
16
|
+
"prepublishOnly": "npx tsc --declaration --allowJs --emitDeclarationOnly --skipLibCheck --target es2020 --module nodenext --moduleResolution nodenext --strict false --esModuleInterop true --outDir ./types src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"quadtree",
|
|
20
|
+
"spatial",
|
|
21
|
+
"spatial-index",
|
|
22
|
+
"gridwork",
|
|
23
|
+
"point",
|
|
24
|
+
"region",
|
|
25
|
+
"search",
|
|
26
|
+
"nearest-neighbor"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/gridworkjs/quadtree.git"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"src/",
|
|
35
|
+
"types/"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@gridworkjs/core": "^1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createQuadtree } from './quadtree.js'
|
package/src/quadtree.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SPATIAL_INDEX, bounds as toBounds,
|
|
3
|
+
intersects, contains, distanceToPoint
|
|
4
|
+
} from '@gridworkjs/core'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {{ minX: number, minY: number, maxX: number, maxY: number }} Bounds
|
|
8
|
+
* @typedef {{ x: number, y: number }} Point
|
|
9
|
+
* @typedef {(item: T) => Bounds} Accessor
|
|
10
|
+
* @template T
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} QuadtreeOptions
|
|
15
|
+
* @property {Bounds} [bounds] - fixed world bounds (auto-grows if omitted)
|
|
16
|
+
* @property {number} [maxItems=16] - items per node before splitting
|
|
17
|
+
* @property {number} [maxDepth=8] - maximum tree depth
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} SpatialIndex
|
|
22
|
+
* @property {true} [SPATIAL_INDEX]
|
|
23
|
+
* @property {number} size
|
|
24
|
+
* @property {Bounds | null} bounds
|
|
25
|
+
* @property {(item: any) => void} insert
|
|
26
|
+
* @property {(item: any) => boolean} remove
|
|
27
|
+
* @property {(query: Bounds | object) => any[]} search
|
|
28
|
+
* @property {(point: Point, k?: number) => any[]} nearest
|
|
29
|
+
* @property {() => void} clear
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
function createNode(bounds) {
|
|
33
|
+
return { bounds, items: [], children: null }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function childBounds(bounds) {
|
|
37
|
+
const midX = (bounds.minX + bounds.maxX) / 2
|
|
38
|
+
const midY = (bounds.minY + bounds.maxY) / 2
|
|
39
|
+
return [
|
|
40
|
+
{ minX: bounds.minX, minY: bounds.minY, maxX: midX, maxY: midY },
|
|
41
|
+
{ minX: midX, minY: bounds.minY, maxX: bounds.maxX, maxY: midY },
|
|
42
|
+
{ minX: bounds.minX, minY: midY, maxX: midX, maxY: bounds.maxY },
|
|
43
|
+
{ minX: midX, minY: midY, maxX: bounds.maxX, maxY: bounds.maxY }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function split(node, maxItems, maxDepth, depth) {
|
|
48
|
+
const quads = childBounds(node.bounds)
|
|
49
|
+
node.children = quads.map(b => createNode(b))
|
|
50
|
+
|
|
51
|
+
const kept = []
|
|
52
|
+
for (const entry of node.items) {
|
|
53
|
+
if (!pushDown(node.children, entry, maxItems, maxDepth, depth + 1)) {
|
|
54
|
+
kept.push(entry)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
node.items = kept
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pushDown(children, entry, maxItems, maxDepth, depth) {
|
|
61
|
+
for (const child of children) {
|
|
62
|
+
if (contains(child.bounds, entry.bounds)) {
|
|
63
|
+
insertEntry(child, entry, maxItems, maxDepth, depth)
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function insertEntry(node, entry, maxItems, maxDepth, depth) {
|
|
71
|
+
if (node.children) {
|
|
72
|
+
if (!pushDown(node.children, entry, maxItems, maxDepth, depth + 1)) {
|
|
73
|
+
node.items.push(entry)
|
|
74
|
+
}
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
node.items.push(entry)
|
|
79
|
+
|
|
80
|
+
if (node.items.length > maxItems && depth < maxDepth) {
|
|
81
|
+
split(node, maxItems, maxDepth, depth)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function searchNode(node, query, results) {
|
|
86
|
+
if (!intersects(node.bounds, query)) return
|
|
87
|
+
|
|
88
|
+
for (const entry of node.items) {
|
|
89
|
+
if (intersects(entry.bounds, query)) {
|
|
90
|
+
results.push(entry.item)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (node.children) {
|
|
95
|
+
for (const child of node.children) {
|
|
96
|
+
searchNode(child, query, results)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function removeEntry(node, item, itemBounds) {
|
|
102
|
+
const idx = node.items.findIndex(e => e.item === item)
|
|
103
|
+
if (idx !== -1) {
|
|
104
|
+
node.items.splice(idx, 1)
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (node.children) {
|
|
109
|
+
for (const child of node.children) {
|
|
110
|
+
if (intersects(child.bounds, itemBounds)) {
|
|
111
|
+
if (removeEntry(child, item, itemBounds)) {
|
|
112
|
+
tryCollapse(node)
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function tryCollapse(node) {
|
|
123
|
+
if (!node.children) return
|
|
124
|
+
let total = node.items.length
|
|
125
|
+
for (const child of node.children) {
|
|
126
|
+
if (child.children) return
|
|
127
|
+
total += child.items.length
|
|
128
|
+
}
|
|
129
|
+
if (total === 0) {
|
|
130
|
+
node.children = null
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function growToContain(root, target) {
|
|
135
|
+
while (!contains(root.bounds, target)) {
|
|
136
|
+
const { minX, minY, maxX, maxY } = root.bounds
|
|
137
|
+
const w = Math.max(maxX - minX, 1)
|
|
138
|
+
const h = Math.max(maxY - minY, 1)
|
|
139
|
+
|
|
140
|
+
const tcx = (target.minX + target.maxX) / 2
|
|
141
|
+
const rcx = (minX + maxX) / 2
|
|
142
|
+
const tcy = (target.minY + target.maxY) / 2
|
|
143
|
+
const rcy = (minY + maxY) / 2
|
|
144
|
+
|
|
145
|
+
let newBounds, quadrant
|
|
146
|
+
|
|
147
|
+
if (tcx < rcx && tcy < rcy) {
|
|
148
|
+
newBounds = { minX: minX - w, minY: minY - h, maxX, maxY }
|
|
149
|
+
quadrant = 3
|
|
150
|
+
} else if (tcx >= rcx && tcy < rcy) {
|
|
151
|
+
newBounds = { minX, minY: minY - h, maxX: maxX + w, maxY }
|
|
152
|
+
quadrant = 2
|
|
153
|
+
} else if (tcx < rcx && tcy >= rcy) {
|
|
154
|
+
newBounds = { minX: minX - w, minY, maxX, maxY: maxY + h }
|
|
155
|
+
quadrant = 1
|
|
156
|
+
} else {
|
|
157
|
+
newBounds = { minX, minY, maxX: maxX + w, maxY: maxY + h }
|
|
158
|
+
quadrant = 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const newRoot = createNode(newBounds)
|
|
162
|
+
const quads = childBounds(newBounds)
|
|
163
|
+
newRoot.children = quads.map((b, i) => i === quadrant ? root : createNode(b))
|
|
164
|
+
root = newRoot
|
|
165
|
+
}
|
|
166
|
+
return root
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeBounds(input) {
|
|
170
|
+
if (input != null && typeof input === 'object' &&
|
|
171
|
+
'minX' in input && 'minY' in input && 'maxX' in input && 'maxY' in input) {
|
|
172
|
+
return input
|
|
173
|
+
}
|
|
174
|
+
return toBounds(input)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function heapPush(heap, entry) {
|
|
178
|
+
heap.push(entry)
|
|
179
|
+
let i = heap.length - 1
|
|
180
|
+
while (i > 0) {
|
|
181
|
+
const p = (i - 1) >> 1
|
|
182
|
+
if (heap[p].dist <= heap[i].dist) break
|
|
183
|
+
;[heap[p], heap[i]] = [heap[i], heap[p]]
|
|
184
|
+
i = p
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function heapPop(heap) {
|
|
189
|
+
const top = heap[0]
|
|
190
|
+
const last = heap.pop()
|
|
191
|
+
if (heap.length > 0) {
|
|
192
|
+
heap[0] = last
|
|
193
|
+
let i = 0
|
|
194
|
+
for (;;) {
|
|
195
|
+
let s = i
|
|
196
|
+
const l = 2 * i + 1
|
|
197
|
+
const r = 2 * i + 2
|
|
198
|
+
if (l < heap.length && heap[l].dist < heap[s].dist) s = l
|
|
199
|
+
if (r < heap.length && heap[r].dist < heap[s].dist) s = r
|
|
200
|
+
if (s === i) break
|
|
201
|
+
;[heap[i], heap[s]] = [heap[s], heap[i]]
|
|
202
|
+
i = s
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return top
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function nearestSearch(root, px, py, k) {
|
|
209
|
+
if (!root || k <= 0) return []
|
|
210
|
+
|
|
211
|
+
const results = []
|
|
212
|
+
const heap = []
|
|
213
|
+
|
|
214
|
+
heapPush(heap, { kind: 'node', node: root, dist: distanceToPoint(root.bounds, px, py) })
|
|
215
|
+
|
|
216
|
+
while (heap.length > 0 && results.length < k) {
|
|
217
|
+
const cur = heapPop(heap)
|
|
218
|
+
|
|
219
|
+
if (cur.kind === 'item') {
|
|
220
|
+
results.push(cur.item)
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const node = cur.node
|
|
225
|
+
|
|
226
|
+
for (const entry of node.items) {
|
|
227
|
+
heapPush(heap, { kind: 'item', item: entry.item, dist: distanceToPoint(entry.bounds, px, py) })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (node.children) {
|
|
231
|
+
for (const child of node.children) {
|
|
232
|
+
heapPush(heap, { kind: 'node', node: child, dist: distanceToPoint(child.bounds, px, py) })
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return results
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Creates a quadtree spatial index.
|
|
242
|
+
*
|
|
243
|
+
* @param {(item: any) => Bounds} accessor - maps items to their bounding boxes
|
|
244
|
+
* @param {QuadtreeOptions} [options]
|
|
245
|
+
* @returns {SpatialIndex}
|
|
246
|
+
*/
|
|
247
|
+
export function createQuadtree(accessor, options = {}) {
|
|
248
|
+
const maxItems = options.maxItems ?? 16
|
|
249
|
+
const maxDepth = options.maxDepth ?? 8
|
|
250
|
+
const fixedBounds = options.bounds ? normalizeBounds(options.bounds) : null
|
|
251
|
+
|
|
252
|
+
let root = fixedBounds ? createNode(fixedBounds) : null
|
|
253
|
+
let size = 0
|
|
254
|
+
|
|
255
|
+
const index = {
|
|
256
|
+
[SPATIAL_INDEX]: true,
|
|
257
|
+
|
|
258
|
+
get size() { return size },
|
|
259
|
+
|
|
260
|
+
get bounds() { return root ? root.bounds : null },
|
|
261
|
+
|
|
262
|
+
insert(item) {
|
|
263
|
+
const itemBounds = accessor(item)
|
|
264
|
+
const entry = { item, bounds: itemBounds }
|
|
265
|
+
|
|
266
|
+
if (!root) {
|
|
267
|
+
const { minX, minY, maxX, maxY } = itemBounds
|
|
268
|
+
const w = Math.max(maxX - minX, 1)
|
|
269
|
+
const h = Math.max(maxY - minY, 1)
|
|
270
|
+
root = createNode({
|
|
271
|
+
minX: minX - w, minY: minY - h,
|
|
272
|
+
maxX: maxX + w, maxY: maxY + h
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!contains(root.bounds, itemBounds)) {
|
|
277
|
+
root = growToContain(root, itemBounds)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
insertEntry(root, entry, maxItems, maxDepth, 0)
|
|
281
|
+
size++
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
remove(item) {
|
|
285
|
+
if (!root) return false
|
|
286
|
+
const itemBounds = accessor(item)
|
|
287
|
+
const removed = removeEntry(root, item, itemBounds)
|
|
288
|
+
if (removed) size--
|
|
289
|
+
return removed
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
search(query) {
|
|
293
|
+
if (!root) return []
|
|
294
|
+
const queryBounds = normalizeBounds(query)
|
|
295
|
+
const results = []
|
|
296
|
+
searchNode(root, queryBounds, results)
|
|
297
|
+
return results
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
nearest(point, k = 1) {
|
|
301
|
+
if (!root) return []
|
|
302
|
+
return nearestSearch(root, point.x, point.y, k)
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
clear() {
|
|
306
|
+
root = fixedBounds ? createNode(fixedBounds) : null
|
|
307
|
+
size = 0
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return index
|
|
312
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createQuadtree } from "./quadtree.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a quadtree spatial index.
|
|
3
|
+
*
|
|
4
|
+
* @param {(item: any) => Bounds} accessor - maps items to their bounding boxes
|
|
5
|
+
* @param {QuadtreeOptions} [options]
|
|
6
|
+
* @returns {SpatialIndex}
|
|
7
|
+
*/
|
|
8
|
+
export function createQuadtree(accessor: (item: any) => Bounds, options?: QuadtreeOptions): SpatialIndex;
|
|
9
|
+
export type Bounds<T> = {
|
|
10
|
+
minX: number;
|
|
11
|
+
minY: number;
|
|
12
|
+
maxX: number;
|
|
13
|
+
maxY: number;
|
|
14
|
+
};
|
|
15
|
+
export type Point<T> = {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
export type Accessor<T> = (item: T) => Bounds;
|
|
20
|
+
export type QuadtreeOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* - fixed world bounds (auto-grows if omitted)
|
|
23
|
+
*/
|
|
24
|
+
bounds?: Bounds;
|
|
25
|
+
/**
|
|
26
|
+
* - items per node before splitting
|
|
27
|
+
*/
|
|
28
|
+
maxItems?: number;
|
|
29
|
+
/**
|
|
30
|
+
* - maximum tree depth
|
|
31
|
+
*/
|
|
32
|
+
maxDepth?: number;
|
|
33
|
+
};
|
|
34
|
+
export type SpatialIndex = {
|
|
35
|
+
SPATIAL_INDEX?: true;
|
|
36
|
+
size: number;
|
|
37
|
+
bounds: Bounds | null;
|
|
38
|
+
insert: (item: any) => void;
|
|
39
|
+
remove: (item: any) => boolean;
|
|
40
|
+
search: (query: Bounds | object) => any[];
|
|
41
|
+
nearest: (point: Point, k?: number) => any[];
|
|
42
|
+
clear: () => void;
|
|
43
|
+
};
|