@osmix/core 0.0.2 → 0.0.6
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/CHANGELOG.md +42 -0
- package/README.md +118 -107
- package/dist/core/src/entities.d.ts +114 -0
- package/dist/core/src/entities.d.ts.map +1 -0
- package/dist/core/src/entities.js +152 -0
- package/dist/core/src/entities.js.map +1 -0
- package/dist/core/src/ids.d.ts +113 -0
- package/dist/core/src/ids.d.ts.map +1 -0
- package/dist/{src → core/src}/ids.js +96 -31
- package/dist/core/src/ids.js.map +1 -0
- package/dist/core/src/index.d.ts +32 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/index.js +35 -0
- package/dist/core/src/index.js.map +1 -0
- package/dist/core/src/nodes.d.ts +101 -0
- package/dist/core/src/nodes.d.ts.map +1 -0
- package/dist/core/src/nodes.js +309 -0
- package/dist/core/src/nodes.js.map +1 -0
- package/dist/core/src/osm.d.ts +74 -0
- package/dist/core/src/osm.d.ts.map +1 -0
- package/dist/core/src/osm.js +120 -0
- package/dist/core/src/osm.js.map +1 -0
- package/dist/core/src/relations.d.ts +118 -0
- package/dist/core/src/relations.d.ts.map +1 -0
- package/dist/core/src/relations.js +412 -0
- package/dist/core/src/relations.js.map +1 -0
- package/dist/core/src/stringtable.d.ts +95 -0
- package/dist/core/src/stringtable.d.ts.map +1 -0
- package/dist/core/src/stringtable.js +170 -0
- package/dist/core/src/stringtable.js.map +1 -0
- package/dist/core/src/tags.d.ts +126 -0
- package/dist/core/src/tags.d.ts.map +1 -0
- package/dist/core/src/tags.js +228 -0
- package/dist/core/src/tags.js.map +1 -0
- package/dist/core/src/typed-arrays.d.ts +124 -0
- package/dist/core/src/typed-arrays.d.ts.map +1 -0
- package/dist/{src → core/src}/typed-arrays.js +88 -9
- package/dist/core/src/typed-arrays.js.map +1 -0
- package/dist/core/src/utils.d.ts +11 -0
- package/dist/core/src/utils.d.ts.map +1 -0
- package/dist/{src → core/src}/utils.js +8 -10
- package/dist/core/src/utils.js.map +1 -0
- package/dist/core/src/ways.d.ts +101 -0
- package/dist/core/src/ways.d.ts.map +1 -0
- package/dist/core/src/ways.js +336 -0
- package/dist/core/src/ways.js.map +1 -0
- package/dist/core/test/memory.test.d.ts +7 -0
- package/dist/core/test/memory.test.d.ts.map +1 -0
- package/dist/core/test/memory.test.js +29 -0
- package/dist/core/test/memory.test.js.map +1 -0
- package/dist/{test → core/test}/mock-osm.d.ts +3 -3
- package/dist/core/test/mock-osm.d.ts.map +1 -0
- package/dist/{test → core/test}/mock-osm.js +3 -3
- package/dist/core/test/mock-osm.js.map +1 -0
- package/dist/core/test/tags-search.test.d.ts +2 -0
- package/dist/core/test/tags-search.test.d.ts.map +1 -0
- package/dist/core/test/tags-search.test.js +56 -0
- package/dist/core/test/tags-search.test.js.map +1 -0
- package/dist/core/test/typed-arrays.test.d.ts.map +1 -0
- package/dist/{test → core/test}/typed-arrays.test.js +19 -17
- package/dist/core/test/typed-arrays.test.js.map +1 -0
- package/dist/osmix/src/extract.d.ts +43 -0
- package/dist/osmix/src/extract.d.ts.map +1 -0
- package/dist/osmix/src/extract.js +156 -0
- package/dist/osmix/src/extract.js.map +1 -0
- package/dist/osmix/src/pbf.d.ts +55 -0
- package/dist/osmix/src/pbf.d.ts.map +1 -0
- package/dist/osmix/src/pbf.js +187 -0
- package/dist/osmix/src/pbf.js.map +1 -0
- package/dist/osmix/src/utils.d.ts +36 -0
- package/dist/osmix/src/utils.d.ts.map +1 -0
- package/dist/osmix/src/utils.js +108 -0
- package/dist/osmix/src/utils.js.map +1 -0
- package/dist/osmix/test/extract.bench.d.ts.map +1 -0
- package/dist/{test → osmix/test}/extract.bench.js +8 -5
- package/dist/osmix/test/extract.bench.js.map +1 -0
- package/dist/osmix/test/extract.test.d.ts.map +1 -0
- package/dist/osmix/test/extract.test.js +392 -0
- package/dist/osmix/test/extract.test.js.map +1 -0
- package/package.json +10 -12
- package/src/entities.ts +138 -22
- package/src/ids.ts +117 -45
- package/src/index.ts +34 -5
- package/src/nodes.ts +237 -58
- package/src/osm.ts +174 -0
- package/src/relations.ts +383 -54
- package/src/stringtable.ts +111 -36
- package/src/tags.ts +207 -46
- package/src/typed-arrays.ts +97 -10
- package/src/types/geoflatbush.d.ts +37 -0
- package/src/types/geokdbush.d.ts +37 -0
- package/src/types/kdbush.d.ts +24 -6
- package/src/utils.ts +9 -14
- package/src/ways.ts +248 -84
- package/test/memory.test.ts +38 -0
- package/test/mock-osm.ts +8 -8
- package/test/tags-search.test.ts +60 -0
- package/test/typed-arrays.test.ts +5 -6
- package/tsconfig.json +7 -1
- package/dist/src/entities.d.ts +0 -26
- package/dist/src/entities.d.ts.map +0 -1
- package/dist/src/entities.js +0 -69
- package/dist/src/entities.js.map +0 -1
- package/dist/src/ids.d.ts +0 -50
- package/dist/src/ids.d.ts.map +0 -1
- package/dist/src/ids.js.map +0 -1
- package/dist/src/index.d.ts +0 -6
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -7
- package/dist/src/index.js.map +0 -1
- package/dist/src/nodes.d.ts +0 -36
- package/dist/src/nodes.d.ts.map +0 -1
- package/dist/src/nodes.js +0 -171
- package/dist/src/nodes.js.map +0 -1
- package/dist/src/osmix.d.ts +0 -88
- package/dist/src/osmix.d.ts.map +0 -1
- package/dist/src/osmix.js +0 -438
- package/dist/src/osmix.js.map +0 -1
- package/dist/src/relations.d.ts +0 -31
- package/dist/src/relations.d.ts.map +0 -1
- package/dist/src/relations.js +0 -161
- package/dist/src/relations.js.map +0 -1
- package/dist/src/stringtable.d.ts +0 -37
- package/dist/src/stringtable.d.ts.map +0 -1
- package/dist/src/stringtable.js +0 -101
- package/dist/src/stringtable.js.map +0 -1
- package/dist/src/tags.d.ts +0 -33
- package/dist/src/tags.d.ts.map +0 -1
- package/dist/src/tags.js +0 -92
- package/dist/src/tags.js.map +0 -1
- package/dist/src/typed-arrays.d.ts +0 -50
- package/dist/src/typed-arrays.d.ts.map +0 -1
- package/dist/src/typed-arrays.js.map +0 -1
- package/dist/src/utils.d.ts +0 -4
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js.map +0 -1
- package/dist/src/ways.d.ts +0 -42
- package/dist/src/ways.d.ts.map +0 -1
- package/dist/src/ways.js +0 -186
- package/dist/src/ways.js.map +0 -1
- package/dist/test/extract.bench.d.ts.map +0 -1
- package/dist/test/extract.bench.js.map +0 -1
- package/dist/test/extract.test.d.ts.map +0 -1
- package/dist/test/extract.test.js +0 -127
- package/dist/test/extract.test.js.map +0 -1
- package/dist/test/mock-osm.d.ts.map +0 -1
- package/dist/test/mock-osm.js.map +0 -1
- package/dist/test/read.test.d.ts +0 -2
- package/dist/test/read.test.d.ts.map +0 -1
- package/dist/test/read.test.js +0 -17
- package/dist/test/read.test.js.map +0 -1
- package/dist/test/typed-arrays.test.d.ts.map +0 -1
- package/dist/test/typed-arrays.test.js.map +0 -1
- package/dist/test/write.test.d.ts +0 -2
- package/dist/test/write.test.d.ts.map +0 -1
- package/dist/test/write.test.js +0 -55
- package/dist/test/write.test.js.map +0 -1
- package/src/osmix.ts +0 -556
- package/test/extract.bench.ts +0 -29
- package/test/extract.test.ts +0 -161
- package/test/read.test.ts +0 -22
- package/test/write.test.ts +0 -69
- /package/dist/{test → core/test}/typed-arrays.test.d.ts +0 -0
- /package/dist/{test → osmix/test}/extract.bench.d.ts +0 -0
- /package/dist/{test → osmix/test}/extract.test.d.ts +0 -0
package/src/nodes.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { OsmNode, OsmTags } from "@osmix/json"
|
|
2
1
|
import type { OsmPbfBlock, OsmPbfDenseNodes } from "@osmix/pbf"
|
|
3
2
|
import { assertValue } from "@osmix/shared/assert"
|
|
4
|
-
import
|
|
3
|
+
import { microToDegrees, toMicroDegrees } from "@osmix/shared/coordinates"
|
|
4
|
+
import type { GeoBbox2D, OsmNode, OsmTags } from "@osmix/shared/types"
|
|
5
|
+
import { around as geoAround } from "geokdbush"
|
|
5
6
|
import KDBush from "kdbush"
|
|
6
7
|
import { Entities, type EntitiesTransferables } from "./entities"
|
|
7
8
|
import { type IdOrIndex, Ids } from "./ids"
|
|
@@ -10,15 +11,16 @@ import { Tags } from "./tags"
|
|
|
10
11
|
import {
|
|
11
12
|
BufferConstructor,
|
|
12
13
|
type BufferType,
|
|
13
|
-
|
|
14
|
-
ResizeableTypedArray,
|
|
14
|
+
ResizeableTypedArray as RTA,
|
|
15
15
|
} from "./typed-arrays"
|
|
16
16
|
|
|
17
|
-
export interface NodesTransferables extends
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export interface NodesTransferables<T extends BufferType = BufferType>
|
|
18
|
+
extends EntitiesTransferables<T> {
|
|
19
|
+
lons: T
|
|
20
|
+
lats: T
|
|
20
21
|
bbox: GeoBbox2D
|
|
21
|
-
|
|
22
|
+
/** Optional - can be rebuilt via buildSpatialIndex() */
|
|
23
|
+
spatialIndex?: T
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export interface AddNodeOptions {
|
|
@@ -26,66 +28,96 @@ export interface AddNodeOptions {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export class Nodes extends Entities<OsmNode> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Coordinates are stored as integer microdegrees (Int32Array).
|
|
33
|
+
* Use OSM_COORD_SCALE (1e7) to convert between degrees and microdegrees.
|
|
34
|
+
*/
|
|
35
|
+
private lons: RTA<Int32Array>
|
|
36
|
+
private lats: RTA<Int32Array>
|
|
37
|
+
// Bbox stored in microdegrees internally
|
|
38
|
+
private bbox: [
|
|
39
|
+
minLon: number,
|
|
40
|
+
minLat: number,
|
|
41
|
+
maxLon: number,
|
|
42
|
+
maxLat: number,
|
|
43
|
+
] = [
|
|
44
|
+
Number.MAX_SAFE_INTEGER,
|
|
45
|
+
Number.MAX_SAFE_INTEGER,
|
|
46
|
+
Number.MIN_SAFE_INTEGER,
|
|
47
|
+
Number.MIN_SAFE_INTEGER,
|
|
36
48
|
]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
nodeIndex.spatialIndex = KDBush.from(nits.spatialIndex)
|
|
47
|
-
return nodeIndex
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
constructor(stringTable: StringTable, idIndex?: Ids, tagIndex?: Tags) {
|
|
51
|
-
super("node", stringTable, idIndex, tagIndex)
|
|
52
|
-
this.lons = new ResizeableTypedArray(CoordinateArrayType)
|
|
53
|
-
this.lats = new ResizeableTypedArray(CoordinateArrayType)
|
|
54
|
-
}
|
|
49
|
+
// Spatial index stores coordinates in degrees (Float64Array) for geokdbush compatibility
|
|
50
|
+
private spatialIndex: KDBush = new KDBush(
|
|
51
|
+
0,
|
|
52
|
+
128,
|
|
53
|
+
Float64Array,
|
|
54
|
+
BufferConstructor,
|
|
55
|
+
)
|
|
56
|
+
// Track if spatial index was properly built (vs default empty)
|
|
57
|
+
private spatialIndexBuilt = false
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Create a new Nodes index.
|
|
61
|
+
*/
|
|
62
|
+
constructor(stringTable: StringTable, transferables?: NodesTransferables) {
|
|
63
|
+
if (transferables) {
|
|
64
|
+
super(
|
|
65
|
+
"node",
|
|
66
|
+
new Ids(transferables),
|
|
67
|
+
new Tags(stringTable, transferables),
|
|
68
|
+
)
|
|
69
|
+
this.lons = RTA.from(Int32Array, transferables.lons)
|
|
70
|
+
this.lats = RTA.from(Int32Array, transferables.lats)
|
|
71
|
+
// Only load spatial index if provided (not stored in IndexedDB)
|
|
72
|
+
if (transferables.spatialIndex?.byteLength) {
|
|
73
|
+
this.spatialIndex = KDBush.from(transferables.spatialIndex)
|
|
74
|
+
this.spatialIndexBuilt = true
|
|
75
|
+
}
|
|
76
|
+
this.bbox = transferables.bbox
|
|
77
|
+
this.indexBuilt = true
|
|
78
|
+
} else {
|
|
79
|
+
super("node", new Ids(), new Tags(stringTable))
|
|
80
|
+
this.lons = new RTA(Int32Array)
|
|
81
|
+
this.lats = new RTA(Int32Array)
|
|
82
|
+
// Spatial index uses Float64Array with degrees for geokdbush compatibility
|
|
83
|
+
this.spatialIndex = new KDBush(0, 128, Float64Array, BufferConstructor)
|
|
64
84
|
}
|
|
65
85
|
}
|
|
66
86
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Add a single node to the index.
|
|
89
|
+
*/
|
|
90
|
+
addNode(node: OsmNode): number {
|
|
91
|
+
const nodeIndex = this.addEntity(node.id, node.tags ?? {})
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
const lonMicro = toMicroDegrees(node.lon)
|
|
94
|
+
const latMicro = toMicroDegrees(node.lat)
|
|
95
|
+
|
|
96
|
+
this.lons.push(lonMicro)
|
|
97
|
+
this.lats.push(latMicro)
|
|
73
98
|
|
|
74
99
|
if (node.lon < this.bbox[0]) this.bbox[0] = node.lon
|
|
75
100
|
if (node.lat < this.bbox[1]) this.bbox[1] = node.lat
|
|
76
101
|
if (node.lon > this.bbox[2]) this.bbox[2] = node.lon
|
|
77
102
|
if (node.lat > this.bbox[3]) this.bbox[3] = node.lat
|
|
103
|
+
|
|
104
|
+
return nodeIndex
|
|
78
105
|
}
|
|
79
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Add dense nodes from a PBF block.
|
|
109
|
+
*/
|
|
80
110
|
addDenseNodes(
|
|
81
111
|
dense: OsmPbfDenseNodes,
|
|
82
112
|
block: OsmPbfBlock,
|
|
83
|
-
blockStringIndexMap:
|
|
113
|
+
blockStringIndexMap: Uint32Array,
|
|
84
114
|
filter?: (node: OsmNode) => boolean,
|
|
85
115
|
): number {
|
|
116
|
+
// PBF block already has offsets and granularity converted to degrees
|
|
86
117
|
const lon_offset = block.lon_offset ?? 0
|
|
87
118
|
const lat_offset = block.lat_offset ?? 0
|
|
88
119
|
const granularity = block.granularity ?? 1e7
|
|
120
|
+
|
|
89
121
|
const delta = {
|
|
90
122
|
id: 0,
|
|
91
123
|
lat: 0,
|
|
@@ -99,7 +131,7 @@ export class Nodes extends Entities<OsmNode> {
|
|
|
99
131
|
const getStringTableIndex = (keyIndex: number) => {
|
|
100
132
|
const key = dense.keys_vals[keyIndex]
|
|
101
133
|
assertValue(key, "Block string key is undefined")
|
|
102
|
-
const index = blockStringIndexMap
|
|
134
|
+
const index = blockStringIndexMap[key]
|
|
103
135
|
assertValue(index, "Block string not found")
|
|
104
136
|
return index
|
|
105
137
|
}
|
|
@@ -118,9 +150,14 @@ export class Nodes extends Entities<OsmNode> {
|
|
|
118
150
|
delta.lat += latSid
|
|
119
151
|
delta.lon += lonSid
|
|
120
152
|
|
|
153
|
+
// Calculate degrees from PBF delta encoding
|
|
121
154
|
const lon = lon_offset + delta.lon / granularity
|
|
122
155
|
const lat = lat_offset + delta.lat / granularity
|
|
123
156
|
|
|
157
|
+
// Convert to microdegrees for storage
|
|
158
|
+
const lonMicro = toMicroDegrees(lon)
|
|
159
|
+
const latMicro = toMicroDegrees(lat)
|
|
160
|
+
|
|
124
161
|
const tagKeys: number[] = []
|
|
125
162
|
const tagValues: number[] = []
|
|
126
163
|
if (dense.keys_vals.length > 0) {
|
|
@@ -146,46 +183,90 @@ export class Nodes extends Entities<OsmNode> {
|
|
|
146
183
|
: true
|
|
147
184
|
if (!shouldInclude) continue
|
|
148
185
|
|
|
149
|
-
this.
|
|
150
|
-
this.lons.push(
|
|
151
|
-
this.lats.push(
|
|
186
|
+
this.addEntity(delta.id, tagKeys, tagValues)
|
|
187
|
+
this.lons.push(lonMicro)
|
|
188
|
+
this.lats.push(latMicro)
|
|
152
189
|
|
|
153
190
|
if (lon < this.bbox[0]) this.bbox[0] = lon
|
|
154
191
|
if (lat < this.bbox[1]) this.bbox[1] = lat
|
|
155
192
|
if (lon > this.bbox[2]) this.bbox[2] = lon
|
|
156
193
|
if (lat > this.bbox[3]) this.bbox[3] = lat
|
|
157
|
-
this.tags.addTagKeysAndValues(tagKeys, tagValues)
|
|
158
194
|
added++
|
|
159
195
|
}
|
|
160
196
|
|
|
161
197
|
return added
|
|
162
198
|
}
|
|
163
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Compact the internal arrays to free up memory.
|
|
202
|
+
*/
|
|
164
203
|
buildEntityIndex() {
|
|
165
204
|
this.lons.compact()
|
|
166
205
|
this.lats.compact()
|
|
167
206
|
}
|
|
168
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Build the spatial index for nodes.
|
|
210
|
+
* Spatial index stores degrees (Float64Array) for geokdbush compatibility.
|
|
211
|
+
*/
|
|
169
212
|
buildSpatialIndex() {
|
|
170
213
|
console.time("NodeIndex.buildSpatialIndex")
|
|
214
|
+
// Use Float64Array with degrees for geokdbush geographic queries
|
|
171
215
|
this.spatialIndex = new KDBush(
|
|
172
216
|
this.size,
|
|
173
|
-
|
|
217
|
+
64,
|
|
174
218
|
Float64Array,
|
|
175
219
|
BufferConstructor,
|
|
176
220
|
)
|
|
177
221
|
for (let i = 0; i < this.size; i++) {
|
|
178
|
-
|
|
222
|
+
// Convert microdegrees to degrees for spatial index
|
|
223
|
+
const lon = microToDegrees(this.lons.at(i))
|
|
224
|
+
const lat = microToDegrees(this.lats.at(i))
|
|
225
|
+
this.spatialIndex.add(lon, lat)
|
|
179
226
|
}
|
|
180
227
|
this.spatialIndex.finish()
|
|
228
|
+
this.spatialIndexBuilt = true
|
|
181
229
|
console.timeEnd("NodeIndex.buildSpatialIndex")
|
|
182
230
|
}
|
|
183
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Check if the spatial index has been built.
|
|
234
|
+
*/
|
|
235
|
+
hasSpatialIndex(): boolean {
|
|
236
|
+
return this.spatialIndexBuilt
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get the bounding box of all nodes.
|
|
241
|
+
*/
|
|
242
|
+
getBbox(): GeoBbox2D {
|
|
243
|
+
return this.bbox
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get the bounding box of a specific node.
|
|
248
|
+
*/
|
|
249
|
+
getEntityBbox(i: IdOrIndex): GeoBbox2D {
|
|
250
|
+
const index = "index" in i ? i.index : this.ids.idOrIndex(i)[0]
|
|
251
|
+
const lon = microToDegrees(this.lons.at(index))
|
|
252
|
+
const lat = microToDegrees(this.lats.at(index))
|
|
253
|
+
return [lon, lat, lon, lat] as GeoBbox2D
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get the longitude and latitude of a specific node.
|
|
258
|
+
*/
|
|
184
259
|
getNodeLonLat(i: IdOrIndex): [number, number] {
|
|
185
260
|
const index = "index" in i ? i.index : this.ids.idOrIndex(i)[0]
|
|
186
|
-
return [
|
|
261
|
+
return [
|
|
262
|
+
microToDegrees(this.lons.at(index)),
|
|
263
|
+
microToDegrees(this.lats.at(index)),
|
|
264
|
+
]
|
|
187
265
|
}
|
|
188
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Get the full node entity.
|
|
269
|
+
*/
|
|
189
270
|
getFullEntity(index: number, id: number, tags?: OsmTags): OsmNode {
|
|
190
271
|
const [lon, lat] = this.getNodeLonLat({ index })
|
|
191
272
|
if (tags) {
|
|
@@ -204,11 +285,109 @@ export class Nodes extends Entities<OsmNode> {
|
|
|
204
285
|
}
|
|
205
286
|
|
|
206
287
|
// Spatial operations
|
|
207
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Find node indexes within a bounding box.
|
|
290
|
+
*/
|
|
291
|
+
findIndexesWithinBbox(bbox: GeoBbox2D): number[] {
|
|
292
|
+
// Spatial index stores degrees, so query with degrees directly
|
|
208
293
|
return this.spatialIndex.range(bbox[0], bbox[1], bbox[2], bbox[3])
|
|
209
294
|
}
|
|
210
295
|
|
|
211
|
-
|
|
212
|
-
|
|
296
|
+
/**
|
|
297
|
+
* Find node indexes within a radius of a point.
|
|
298
|
+
* Uses geokdbush for proper great-circle distance calculations.
|
|
299
|
+
* @param lon - Longitude in degrees.
|
|
300
|
+
* @param lat - Latitude in degrees.
|
|
301
|
+
* @param radiusKm - Radius in kilometers.
|
|
302
|
+
* @returns Array of node indexes within the radius.
|
|
303
|
+
*/
|
|
304
|
+
findIndexesWithinRadius(
|
|
305
|
+
lon: number,
|
|
306
|
+
lat: number,
|
|
307
|
+
radiusKm: number,
|
|
308
|
+
): number[] {
|
|
309
|
+
// Use geokdbush for proper geographic distance calculations
|
|
310
|
+
return geoAround(
|
|
311
|
+
this.spatialIndex,
|
|
312
|
+
lon,
|
|
313
|
+
lat,
|
|
314
|
+
Number.POSITIVE_INFINITY,
|
|
315
|
+
radiusKm,
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get nodes within a bounding box.
|
|
321
|
+
* @param bbox - The bounding box to search within.
|
|
322
|
+
* @param include - A function to filter nodes. If provided, only nodes for which the function returns true will be included.
|
|
323
|
+
* @returns An object containing the IDs and positions of the nodes within the bounding box.
|
|
324
|
+
*/
|
|
325
|
+
withinBbox(
|
|
326
|
+
bbox: GeoBbox2D,
|
|
327
|
+
include?: (i: number) => boolean,
|
|
328
|
+
): {
|
|
329
|
+
ids: Float64Array
|
|
330
|
+
positions: Float64Array
|
|
331
|
+
} {
|
|
332
|
+
console.time("Nodes.withinBbox")
|
|
333
|
+
const nodeCandidates = this.findIndexesWithinBbox(bbox)
|
|
334
|
+
const nodePositions = new Float64Array(nodeCandidates.length * 2)
|
|
335
|
+
const ids = new Float64Array(nodeCandidates.length)
|
|
336
|
+
|
|
337
|
+
let skipped = 0
|
|
338
|
+
nodeCandidates.forEach((nodeIndex, i) => {
|
|
339
|
+
if (include && !include(nodeIndex)) {
|
|
340
|
+
skipped++
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const [lon, lat] = this.getNodeLonLat({ index: nodeIndex })
|
|
345
|
+
ids[i - skipped] = this.ids.at(nodeIndex)
|
|
346
|
+
nodePositions[(i - skipped) * 2] = lon
|
|
347
|
+
nodePositions[(i - skipped) * 2 + 1] = lat
|
|
348
|
+
})
|
|
349
|
+
console.timeEnd("Nodes.withinBbox")
|
|
350
|
+
return {
|
|
351
|
+
ids: ids.subarray(0, nodeCandidates.length - skipped),
|
|
352
|
+
positions: nodePositions.slice(0, (nodeCandidates.length - skipped) * 2),
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get transferable objects for passing to another thread.
|
|
358
|
+
* Only includes spatialIndex if it has been built.
|
|
359
|
+
*/
|
|
360
|
+
override transferables(): NodesTransferables {
|
|
361
|
+
const base = {
|
|
362
|
+
...super.transferables(),
|
|
363
|
+
lons: this.lons.array.buffer,
|
|
364
|
+
lats: this.lats.array.buffer,
|
|
365
|
+
bbox: this.bbox,
|
|
366
|
+
}
|
|
367
|
+
// Only include spatial index if it was built
|
|
368
|
+
if (this.spatialIndexBuilt) {
|
|
369
|
+
return { ...base, spatialIndex: this.spatialIndex.data }
|
|
370
|
+
}
|
|
371
|
+
return base
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Get the approximate memory requirements for a given number of nodes in bytes.
|
|
376
|
+
*/
|
|
377
|
+
static getBytesRequired(count: number) {
|
|
378
|
+
if (count === 0) return 0
|
|
379
|
+
const indexBytes = (count < 65536 ? 2 : 4) * count
|
|
380
|
+
// Spatial index stores coordinates in degrees (Float64Array)
|
|
381
|
+
const coordsBytes = count * 2 * Float64Array.BYTES_PER_ELEMENT
|
|
382
|
+
const padding = (8 - (indexBytes % 8)) % 8
|
|
383
|
+
const spatialIndexBytes = 8 + indexBytes + coordsBytes + padding
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
Ids.getBytesRequired(count) +
|
|
387
|
+
Tags.getBytesRequired(count) +
|
|
388
|
+
count * Int32Array.BYTES_PER_ELEMENT + // lons (stored in microdegrees)
|
|
389
|
+
count * Int32Array.BYTES_PER_ELEMENT + // lats (stored in microdegrees)
|
|
390
|
+
spatialIndexBytes
|
|
391
|
+
)
|
|
213
392
|
}
|
|
214
393
|
}
|
package/src/osm.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { OsmPbfHeaderBlock } from "@osmix/pbf"
|
|
2
|
+
import type { GeoBbox2D } from "@osmix/shared/types"
|
|
3
|
+
import { Nodes, type NodesTransferables } from "./nodes"
|
|
4
|
+
import { Relations, type RelationsTransferables } from "./relations"
|
|
5
|
+
import StringTable, { type StringTableTransferables } from "./stringtable"
|
|
6
|
+
import type { BufferType } from "./typed-arrays"
|
|
7
|
+
import { Ways, type WaysTransferables } from "./ways"
|
|
8
|
+
|
|
9
|
+
export interface OsmTransferables<T extends BufferType = BufferType> {
|
|
10
|
+
id: string
|
|
11
|
+
header: OsmPbfHeaderBlock
|
|
12
|
+
stringTable: StringTableTransferables<T>
|
|
13
|
+
nodes: NodesTransferables<T>
|
|
14
|
+
ways: WaysTransferables<T>
|
|
15
|
+
relations: RelationsTransferables<T>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface OsmInfo {
|
|
19
|
+
id: string
|
|
20
|
+
bbox: GeoBbox2D
|
|
21
|
+
header: OsmPbfHeaderBlock
|
|
22
|
+
stats: {
|
|
23
|
+
nodes: number
|
|
24
|
+
ways: number
|
|
25
|
+
relations: number
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OsmOptions {
|
|
30
|
+
id: string
|
|
31
|
+
header: OsmPbfHeaderBlock
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* OSM Entity Index.
|
|
36
|
+
*/
|
|
37
|
+
export class Osm {
|
|
38
|
+
// Filename or ID of this OSM Entity index.
|
|
39
|
+
readonly id: string
|
|
40
|
+
readonly header: OsmPbfHeaderBlock
|
|
41
|
+
|
|
42
|
+
// Shared string lookup table for all nodes, ways, and relations
|
|
43
|
+
readonly stringTable: StringTable
|
|
44
|
+
readonly nodes: Nodes
|
|
45
|
+
readonly ways: Ways
|
|
46
|
+
readonly relations: Relations
|
|
47
|
+
|
|
48
|
+
private indexBuilt = false
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a new OSM Entity index.
|
|
52
|
+
*/
|
|
53
|
+
constructor(opts?: Partial<OsmOptions> | OsmTransferables | Osm) {
|
|
54
|
+
this.id = opts?.id ?? "unknown"
|
|
55
|
+
this.header = opts?.header ?? {
|
|
56
|
+
required_features: [],
|
|
57
|
+
optional_features: [],
|
|
58
|
+
}
|
|
59
|
+
if (opts && "stringTable" in opts) {
|
|
60
|
+
if (opts instanceof Osm) {
|
|
61
|
+
this.stringTable = new StringTable(opts.stringTable.transferables())
|
|
62
|
+
this.nodes = new Nodes(this.stringTable, opts.nodes.transferables())
|
|
63
|
+
this.ways = new Ways(
|
|
64
|
+
this.stringTable,
|
|
65
|
+
this.nodes,
|
|
66
|
+
opts.ways.transferables(),
|
|
67
|
+
)
|
|
68
|
+
this.relations = new Relations(
|
|
69
|
+
this.stringTable,
|
|
70
|
+
this.nodes,
|
|
71
|
+
this.ways,
|
|
72
|
+
opts.relations.transferables(),
|
|
73
|
+
)
|
|
74
|
+
this.indexBuilt = true
|
|
75
|
+
} else {
|
|
76
|
+
this.stringTable = new StringTable(opts.stringTable)
|
|
77
|
+
this.nodes = new Nodes(this.stringTable, opts.nodes)
|
|
78
|
+
this.ways = new Ways(this.stringTable, this.nodes, opts.ways)
|
|
79
|
+
this.relations = new Relations(
|
|
80
|
+
this.stringTable,
|
|
81
|
+
this.nodes,
|
|
82
|
+
this.ways,
|
|
83
|
+
opts.relations,
|
|
84
|
+
)
|
|
85
|
+
this.indexBuilt = true
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
this.stringTable = new StringTable()
|
|
89
|
+
this.nodes = new Nodes(this.stringTable)
|
|
90
|
+
this.ways = new Ways(this.stringTable, this.nodes)
|
|
91
|
+
this.relations = new Relations(this.stringTable, this.nodes, this.ways)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build the internal indexes for all entities.
|
|
97
|
+
*/
|
|
98
|
+
buildIndexes() {
|
|
99
|
+
this.stringTable.buildIndex()
|
|
100
|
+
this.nodes.buildIndex()
|
|
101
|
+
this.ways.buildIndex()
|
|
102
|
+
this.relations.buildIndex()
|
|
103
|
+
this.indexBuilt = true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if the index is built and ready for use.
|
|
108
|
+
*/
|
|
109
|
+
isReady() {
|
|
110
|
+
return (
|
|
111
|
+
this.nodes.isReady() &&
|
|
112
|
+
this.ways.isReady() &&
|
|
113
|
+
this.relations.isReady() &&
|
|
114
|
+
this.indexBuilt
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build spatial indexes for all entities.
|
|
120
|
+
*/
|
|
121
|
+
buildSpatialIndexes() {
|
|
122
|
+
this.nodes.buildSpatialIndex()
|
|
123
|
+
this.ways.buildSpatialIndex()
|
|
124
|
+
this.relations.buildSpatialIndex()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if spatial indexes have been built for all entity types.
|
|
129
|
+
*/
|
|
130
|
+
hasSpatialIndexes(): boolean {
|
|
131
|
+
return (
|
|
132
|
+
this.nodes.hasSpatialIndex() &&
|
|
133
|
+
this.ways.hasSpatialIndex() &&
|
|
134
|
+
this.relations.hasSpatialIndex()
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the bounding box of all entities in the OSM index.
|
|
140
|
+
*/
|
|
141
|
+
bbox(): GeoBbox2D {
|
|
142
|
+
return this.nodes.getBbox()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get information about the OSM index.
|
|
147
|
+
*/
|
|
148
|
+
info(): OsmInfo {
|
|
149
|
+
return {
|
|
150
|
+
id: this.id,
|
|
151
|
+
bbox: this.bbox(),
|
|
152
|
+
header: this.header,
|
|
153
|
+
stats: {
|
|
154
|
+
nodes: this.nodes.size,
|
|
155
|
+
ways: this.ways.size,
|
|
156
|
+
relations: this.relations.size,
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get transferable objects for passing to another thread.
|
|
163
|
+
*/
|
|
164
|
+
transferables(): OsmTransferables {
|
|
165
|
+
return {
|
|
166
|
+
id: this.id,
|
|
167
|
+
header: this.header,
|
|
168
|
+
stringTable: this.stringTable.transferables(),
|
|
169
|
+
nodes: this.nodes.transferables(),
|
|
170
|
+
ways: this.ways.transferables(),
|
|
171
|
+
relations: this.relations.transferables(),
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|