@gridworkjs/hashgrid 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 +102 -0
- package/package.json +44 -0
- package/src/hashgrid.js +324 -0
- package/src/index.js +1 -0
- package/types/hashgrid.d.ts +25 -0
- package/types/index.d.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" width="256" height="256" alt="@gridworkjs/hashgrid">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@gridworkjs/hashgrid</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">Spatial hash grid for uniform distributions and fast neighbor lookups</p>
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm install @gridworkjs/hashgrid
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { createHashGrid } from '@gridworkjs/hashgrid'
|
|
19
|
+
import { point, rect, bounds } from '@gridworkjs/core'
|
|
20
|
+
|
|
21
|
+
// create a hash grid - cellSize should match your data's density
|
|
22
|
+
const grid = createHashGrid(item => bounds(item.position), { cellSize: 50 })
|
|
23
|
+
|
|
24
|
+
// insert items
|
|
25
|
+
grid.insert({ id: 1, position: point(10, 20) })
|
|
26
|
+
grid.insert({ id: 2, position: point(50, 60) })
|
|
27
|
+
grid.insert({ id: 3, position: rect(70, 70, 90, 90) })
|
|
28
|
+
|
|
29
|
+
// search for items intersecting a region
|
|
30
|
+
grid.search({ minX: 0, minY: 0, maxX: 55, maxY: 65 })
|
|
31
|
+
// => [{ id: 1, ... }, { id: 2, ... }]
|
|
32
|
+
|
|
33
|
+
// also accepts geometry objects as queries
|
|
34
|
+
grid.search(rect(0, 0, 55, 65))
|
|
35
|
+
|
|
36
|
+
// find nearest neighbors
|
|
37
|
+
grid.nearest({ x: 12, y: 22 }, 2)
|
|
38
|
+
// => [{ id: 1, ... }, { id: 2, ... }]
|
|
39
|
+
|
|
40
|
+
// remove by identity
|
|
41
|
+
grid.remove(item)
|
|
42
|
+
|
|
43
|
+
grid.size // number of items
|
|
44
|
+
grid.bounds // bounding box of all items
|
|
45
|
+
grid.clear()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## When to Use a Hash Grid
|
|
49
|
+
|
|
50
|
+
Hash grids are ideal when your data is **uniformly distributed** and items are roughly the same size. They offer O(1) cell lookups, making them excellent for collision detection, particle systems, and game engines.
|
|
51
|
+
|
|
52
|
+
If your data is clustered or varies widely in size, consider `@gridworkjs/quadtree` or `@gridworkjs/rtree` instead.
|
|
53
|
+
|
|
54
|
+
## Choosing a Cell Size
|
|
55
|
+
|
|
56
|
+
The `cellSize` parameter controls the width and height of each grid cell. For best performance:
|
|
57
|
+
|
|
58
|
+
- Set `cellSize` to roughly the size of your items or the typical query range
|
|
59
|
+
- Too small: items span many cells, increasing memory and insert cost
|
|
60
|
+
- Too large: cells contain many items, reducing search selectivity
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `createHashGrid(accessor, options)`
|
|
65
|
+
|
|
66
|
+
Creates a new hash grid. The `accessor` function maps each item to its bounding box (`{ minX, minY, maxX, maxY }`). Use `bounds()` from `@gridworkjs/core` to convert geometries.
|
|
67
|
+
|
|
68
|
+
The `cellSize` option is required.
|
|
69
|
+
|
|
70
|
+
Returns a spatial index implementing the gridwork protocol.
|
|
71
|
+
|
|
72
|
+
### `grid.insert(item)`
|
|
73
|
+
|
|
74
|
+
Adds an item to the grid. The item is placed into every cell its bounding box overlaps.
|
|
75
|
+
|
|
76
|
+
### `grid.remove(item)`
|
|
77
|
+
|
|
78
|
+
Removes an item by identity (`===`). Returns `true` if found and removed.
|
|
79
|
+
|
|
80
|
+
### `grid.search(query)`
|
|
81
|
+
|
|
82
|
+
Returns all items whose bounds intersect the query. Accepts bounds objects or geometry objects (point, rect, circle).
|
|
83
|
+
|
|
84
|
+
### `grid.nearest(point, k?)`
|
|
85
|
+
|
|
86
|
+
Returns the `k` nearest items to the given point, sorted by distance. Defaults to `k=1`. Accepts `{ x, y }` or a point geometry.
|
|
87
|
+
|
|
88
|
+
### `grid.clear()`
|
|
89
|
+
|
|
90
|
+
Removes all items from the grid.
|
|
91
|
+
|
|
92
|
+
### `grid.size`
|
|
93
|
+
|
|
94
|
+
Number of items in the grid.
|
|
95
|
+
|
|
96
|
+
### `grid.bounds`
|
|
97
|
+
|
|
98
|
+
Bounding box of all items, or `null` if empty.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gridworkjs/hashgrid",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Spatial hash grid for uniform distributions and fast neighbor lookups",
|
|
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
|
+
"hashgrid",
|
|
20
|
+
"spatial-hash",
|
|
21
|
+
"spatial",
|
|
22
|
+
"spatial-index",
|
|
23
|
+
"gridwork",
|
|
24
|
+
"uniform",
|
|
25
|
+
"collision",
|
|
26
|
+
"search",
|
|
27
|
+
"nearest-neighbor"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/gridworkjs/hashgrid.git"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src/",
|
|
36
|
+
"types/"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@gridworkjs/core": "^1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/hashgrid.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SPATIAL_INDEX, bounds as toBounds,
|
|
3
|
+
intersects, 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} HashGridOptions
|
|
15
|
+
* @property {number} cellSize - Width and height of each grid cell
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function validateAccessorBounds(b) {
|
|
19
|
+
if (b === null || typeof b !== 'object') {
|
|
20
|
+
throw new Error('accessor must return a bounds object')
|
|
21
|
+
}
|
|
22
|
+
if (!Number.isFinite(b.minX) || !Number.isFinite(b.minY) ||
|
|
23
|
+
!Number.isFinite(b.maxX) || !Number.isFinite(b.maxY)) {
|
|
24
|
+
throw new Error('accessor returned non-finite bounds')
|
|
25
|
+
}
|
|
26
|
+
if (b.minX > b.maxX || b.minY > b.maxY) {
|
|
27
|
+
throw new Error('accessor returned inverted bounds (minX > maxX or minY > maxY)')
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeBounds(input) {
|
|
32
|
+
if (input != null && typeof input === 'object' &&
|
|
33
|
+
'minX' in input && 'minY' in input && 'maxX' in input && 'maxY' in input) {
|
|
34
|
+
return input
|
|
35
|
+
}
|
|
36
|
+
return toBounds(input)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function cellKey(cx, cy) {
|
|
40
|
+
return cx + ',' + cy
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function heapPush(heap, entry) {
|
|
44
|
+
heap.push(entry)
|
|
45
|
+
let i = heap.length - 1
|
|
46
|
+
while (i > 0) {
|
|
47
|
+
const p = (i - 1) >> 1
|
|
48
|
+
if (heap[p].dist <= heap[i].dist) break
|
|
49
|
+
;[heap[p], heap[i]] = [heap[i], heap[p]]
|
|
50
|
+
i = p
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function heapPop(heap) {
|
|
55
|
+
const top = heap[0]
|
|
56
|
+
const last = heap.pop()
|
|
57
|
+
if (heap.length > 0) {
|
|
58
|
+
heap[0] = last
|
|
59
|
+
let i = 0
|
|
60
|
+
for (;;) {
|
|
61
|
+
let s = i
|
|
62
|
+
const l = 2 * i + 1
|
|
63
|
+
const r = 2 * i + 2
|
|
64
|
+
if (l < heap.length && heap[l].dist < heap[s].dist) s = l
|
|
65
|
+
if (r < heap.length && heap[r].dist < heap[s].dist) s = r
|
|
66
|
+
if (s === i) break
|
|
67
|
+
;[heap[i], heap[s]] = [heap[s], heap[i]]
|
|
68
|
+
i = s
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return top
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a spatial hash grid index.
|
|
76
|
+
*
|
|
77
|
+
* @param {(item: any) => Bounds | object} accessor - Maps items to their bounding boxes or geometries
|
|
78
|
+
* @param {HashGridOptions} options
|
|
79
|
+
* @returns {import('@gridworkjs/core').SpatialIndex}
|
|
80
|
+
*/
|
|
81
|
+
export function createHashGrid(accessor, options = {}) {
|
|
82
|
+
const cellSize = options.cellSize
|
|
83
|
+
|
|
84
|
+
if (cellSize == null) {
|
|
85
|
+
throw new Error('cellSize is required')
|
|
86
|
+
}
|
|
87
|
+
if (!Number.isFinite(cellSize) || cellSize <= 0) {
|
|
88
|
+
throw new Error('cellSize must be a positive finite number')
|
|
89
|
+
}
|
|
90
|
+
if (typeof accessor !== 'function') {
|
|
91
|
+
throw new Error('accessor must be a function')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const invCellSize = 1 / cellSize
|
|
95
|
+
const cells = new Map()
|
|
96
|
+
let size = 0
|
|
97
|
+
let totalBounds = null
|
|
98
|
+
|
|
99
|
+
function toCell(v) {
|
|
100
|
+
return Math.floor(v * invCellSize)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cellRange(b) {
|
|
104
|
+
return {
|
|
105
|
+
minCX: Math.floor(b.minX * invCellSize),
|
|
106
|
+
minCY: Math.floor(b.minY * invCellSize),
|
|
107
|
+
maxCX: Math.floor(b.maxX * invCellSize),
|
|
108
|
+
maxCY: Math.floor(b.maxY * invCellSize)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addToCell(cx, cy, entry) {
|
|
113
|
+
const key = cellKey(cx, cy)
|
|
114
|
+
let bucket = cells.get(key)
|
|
115
|
+
if (!bucket) {
|
|
116
|
+
bucket = []
|
|
117
|
+
cells.set(key, bucket)
|
|
118
|
+
}
|
|
119
|
+
bucket.push(entry)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function removeFromCell(cx, cy, entry) {
|
|
123
|
+
const key = cellKey(cx, cy)
|
|
124
|
+
const bucket = cells.get(key)
|
|
125
|
+
if (!bucket) return
|
|
126
|
+
const idx = bucket.indexOf(entry)
|
|
127
|
+
if (idx !== -1) {
|
|
128
|
+
bucket.splice(idx, 1)
|
|
129
|
+
if (bucket.length === 0) cells.delete(key)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function recalcBounds() {
|
|
134
|
+
if (size === 0) {
|
|
135
|
+
totalBounds = null
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
|
|
139
|
+
const seen = new Set()
|
|
140
|
+
for (const bucket of cells.values()) {
|
|
141
|
+
for (const entry of bucket) {
|
|
142
|
+
if (seen.has(entry)) continue
|
|
143
|
+
seen.add(entry)
|
|
144
|
+
const b = entry.bounds
|
|
145
|
+
if (b.minX < minX) minX = b.minX
|
|
146
|
+
if (b.minY < minY) minY = b.minY
|
|
147
|
+
if (b.maxX > maxX) maxX = b.maxX
|
|
148
|
+
if (b.maxY > maxY) maxY = b.maxY
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
totalBounds = { minX, minY, maxX, maxY }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const index = {
|
|
155
|
+
[SPATIAL_INDEX]: true,
|
|
156
|
+
|
|
157
|
+
get size() { return size },
|
|
158
|
+
|
|
159
|
+
get bounds() { return totalBounds },
|
|
160
|
+
|
|
161
|
+
insert(item) {
|
|
162
|
+
const raw = accessor(item)
|
|
163
|
+
const itemBounds = normalizeBounds(raw)
|
|
164
|
+
validateAccessorBounds(itemBounds)
|
|
165
|
+
|
|
166
|
+
const entry = { item, bounds: itemBounds }
|
|
167
|
+
const { minCX, minCY, maxCX, maxCY } = cellRange(itemBounds)
|
|
168
|
+
|
|
169
|
+
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
170
|
+
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
171
|
+
addToCell(cx, cy, entry)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (totalBounds === null) {
|
|
176
|
+
totalBounds = { ...itemBounds }
|
|
177
|
+
} else {
|
|
178
|
+
if (itemBounds.minX < totalBounds.minX) totalBounds.minX = itemBounds.minX
|
|
179
|
+
if (itemBounds.minY < totalBounds.minY) totalBounds.minY = itemBounds.minY
|
|
180
|
+
if (itemBounds.maxX > totalBounds.maxX) totalBounds.maxX = itemBounds.maxX
|
|
181
|
+
if (itemBounds.maxY > totalBounds.maxY) totalBounds.maxY = itemBounds.maxY
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
size++
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
remove(item) {
|
|
188
|
+
const raw = accessor(item)
|
|
189
|
+
const itemBounds = normalizeBounds(raw)
|
|
190
|
+
const { minCX, minCY, maxCX, maxCY } = cellRange(itemBounds)
|
|
191
|
+
|
|
192
|
+
let entry = null
|
|
193
|
+
const firstKey = cellKey(minCX, minCY)
|
|
194
|
+
const bucket = cells.get(firstKey)
|
|
195
|
+
if (bucket) {
|
|
196
|
+
entry = bucket.find(e => e.item === item) ?? null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!entry) {
|
|
200
|
+
for (let cx = minCX; cx <= maxCX && !entry; cx++) {
|
|
201
|
+
for (let cy = minCY; cy <= maxCY && !entry; cy++) {
|
|
202
|
+
if (cx === minCX && cy === minCY) continue
|
|
203
|
+
const b = cells.get(cellKey(cx, cy))
|
|
204
|
+
if (b) entry = b.find(e => e.item === item) ?? null
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!entry) return false
|
|
210
|
+
|
|
211
|
+
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
212
|
+
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
213
|
+
removeFromCell(cx, cy, entry)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
size--
|
|
218
|
+
recalcBounds()
|
|
219
|
+
return true
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
search(query) {
|
|
223
|
+
if (size === 0) return []
|
|
224
|
+
const queryBounds = normalizeBounds(query)
|
|
225
|
+
const { minCX, minCY, maxCX, maxCY } = cellRange(queryBounds)
|
|
226
|
+
|
|
227
|
+
const results = []
|
|
228
|
+
const seen = new Set()
|
|
229
|
+
|
|
230
|
+
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
231
|
+
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
232
|
+
const bucket = cells.get(cellKey(cx, cy))
|
|
233
|
+
if (!bucket) continue
|
|
234
|
+
for (const entry of bucket) {
|
|
235
|
+
if (seen.has(entry)) continue
|
|
236
|
+
seen.add(entry)
|
|
237
|
+
if (intersects(entry.bounds, queryBounds)) {
|
|
238
|
+
results.push(entry.item)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
nearest(queryPoint, k = 1) {
|
|
248
|
+
if (size === 0 || k <= 0) return []
|
|
249
|
+
|
|
250
|
+
const px = queryPoint.x
|
|
251
|
+
const py = queryPoint.y
|
|
252
|
+
|
|
253
|
+
const results = []
|
|
254
|
+
const seen = new Set()
|
|
255
|
+
let ring = 0
|
|
256
|
+
const centerCX = toCell(px)
|
|
257
|
+
const centerCY = toCell(py)
|
|
258
|
+
|
|
259
|
+
const heap = []
|
|
260
|
+
|
|
261
|
+
// expand in rings until we have k candidates and the ring is far enough
|
|
262
|
+
while (true) {
|
|
263
|
+
const minCX = centerCX - ring
|
|
264
|
+
const maxCX = centerCX + ring
|
|
265
|
+
const minCY = centerCY - ring
|
|
266
|
+
const maxCY = centerCY + ring
|
|
267
|
+
|
|
268
|
+
for (let cx = minCX; cx <= maxCX; cx++) {
|
|
269
|
+
for (let cy = minCY; cy <= maxCY; cy++) {
|
|
270
|
+
if (ring > 0 && cx > minCX && cx < maxCX && cy > minCY && cy < maxCY) continue
|
|
271
|
+
const bucket = cells.get(cellKey(cx, cy))
|
|
272
|
+
if (!bucket) continue
|
|
273
|
+
for (const entry of bucket) {
|
|
274
|
+
if (seen.has(entry)) continue
|
|
275
|
+
seen.add(entry)
|
|
276
|
+
heapPush(heap, { dist: distanceToPoint(entry.bounds, px, py), item: entry.item })
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// the minimum possible distance to any item in the next ring
|
|
282
|
+
const nextRingDist = ring * cellSize
|
|
283
|
+
const haveCandidates = heap.length >= k
|
|
284
|
+
|
|
285
|
+
if (haveCandidates) {
|
|
286
|
+
let kthDist = peekKth(heap, k)
|
|
287
|
+
if (kthDist <= nextRingDist) break
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// if we've checked all cells, stop
|
|
291
|
+
if (seen.size >= size) break
|
|
292
|
+
ring++
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
while (heap.length > 0 && results.length < k) {
|
|
296
|
+
results.push(heapPop(heap).item)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return results
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
clear() {
|
|
303
|
+
cells.clear()
|
|
304
|
+
size = 0
|
|
305
|
+
totalBounds = null
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return index
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function peekKth(heap, k) {
|
|
313
|
+
// extract k items, record the kth distance, then put them back
|
|
314
|
+
if (heap.length < k) return Infinity
|
|
315
|
+
const extracted = []
|
|
316
|
+
for (let i = 0; i < k; i++) {
|
|
317
|
+
extracted.push(heapPop(heap))
|
|
318
|
+
}
|
|
319
|
+
const kthDist = extracted[extracted.length - 1].dist
|
|
320
|
+
for (const e of extracted) {
|
|
321
|
+
heapPush(heap, e)
|
|
322
|
+
}
|
|
323
|
+
return kthDist
|
|
324
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createHashGrid } from './hashgrid.js'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a spatial hash grid index.
|
|
3
|
+
*
|
|
4
|
+
* @param {(item: any) => Bounds | object} accessor - Maps items to their bounding boxes or geometries
|
|
5
|
+
* @param {HashGridOptions} options
|
|
6
|
+
* @returns {import('@gridworkjs/core').SpatialIndex}
|
|
7
|
+
*/
|
|
8
|
+
export function createHashGrid(accessor: (item: any) => Bounds | object, options?: HashGridOptions): any;
|
|
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 HashGridOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* - Width and height of each grid cell
|
|
23
|
+
*/
|
|
24
|
+
cellSize: number;
|
|
25
|
+
};
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createHashGrid } from "./hashgrid.js";
|