@osmix/core 0.0.1 → 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 +52 -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/core/src/ids.js +236 -0
- 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/core/src/typed-arrays.js +198 -0
- 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/core/src/utils.js +26 -0
- 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/core/test/mock-osm.d.ts +13 -0
- package/dist/core/test/mock-osm.d.ts.map +1 -0
- package/dist/core/test/mock-osm.js +122 -0
- 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 +2 -0
- package/dist/core/test/typed-arrays.test.d.ts.map +1 -0
- package/dist/core/test/typed-arrays.test.js +102 -0
- 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 +2 -0
- package/dist/osmix/test/extract.bench.d.ts.map +1 -0
- package/dist/osmix/test/extract.bench.js +26 -0
- package/dist/osmix/test/extract.bench.js.map +1 -0
- package/dist/osmix/test/extract.test.d.ts +2 -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 +11 -11
- 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/src/osmix.ts +0 -556
- package/test/extract.bench.ts +0 -29
- package/test/extract.test.ts +0 -156
- package/test/read.test.ts +0 -22
- package/test/write.test.ts +0 -66
package/src/ways.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { OsmTags, OsmWay } from "@osmix/json"
|
|
2
1
|
import type { OsmPbfWay } from "@osmix/pbf"
|
|
3
|
-
import type { GeoBbox2D, LonLat } from "@osmix/shared/types"
|
|
2
|
+
import type { GeoBbox2D, LonLat, OsmTags, OsmWay } from "@osmix/shared/types"
|
|
4
3
|
import Flatbush from "flatbush"
|
|
4
|
+
import { around as geoAround } from "geoflatbush"
|
|
5
5
|
import { Entities, type EntitiesTransferables } from "./entities"
|
|
6
6
|
import { type IdOrIndex, Ids } from "./ids"
|
|
7
7
|
import type { Nodes } from "./nodes"
|
|
@@ -10,69 +10,76 @@ import { Tags } from "./tags"
|
|
|
10
10
|
import {
|
|
11
11
|
BufferConstructor,
|
|
12
12
|
type BufferType,
|
|
13
|
-
CoordinateArrayType,
|
|
14
13
|
IdArrayType,
|
|
15
|
-
ResizeableTypedArray,
|
|
14
|
+
ResizeableTypedArray as RTA,
|
|
16
15
|
} from "./typed-arrays"
|
|
17
16
|
|
|
18
|
-
export interface WaysTransferables extends
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
export interface WaysTransferables<T extends BufferType = BufferType>
|
|
18
|
+
extends EntitiesTransferables<T> {
|
|
19
|
+
refStart: T
|
|
20
|
+
refCount: T
|
|
21
|
+
refs: T
|
|
22
|
+
bbox: T
|
|
23
|
+
/** Optional - can be rebuilt via buildSpatialIndex() */
|
|
24
|
+
spatialIndex?: T
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export class Ways extends Entities<OsmWay> {
|
|
27
|
-
spatialIndex: Flatbush = new Flatbush(1)
|
|
28
|
+
private spatialIndex: Flatbush = new Flatbush(1)
|
|
29
|
+
// Track if spatial index was properly built (vs default empty)
|
|
30
|
+
private spatialIndexBuilt = false
|
|
28
31
|
|
|
29
|
-
refStart:
|
|
30
|
-
refCount:
|
|
32
|
+
private refStart: RTA<Uint32Array>
|
|
33
|
+
private refCount: RTA<Uint16Array> // Maximum 2,000 nodes per way
|
|
31
34
|
|
|
32
35
|
// Node IDs
|
|
33
|
-
refs:
|
|
36
|
+
private refs: RTA<Float64Array>
|
|
34
37
|
|
|
35
38
|
// Bounding box of the way in geographic coordinates
|
|
36
|
-
bbox:
|
|
37
|
-
|
|
38
|
-
static from(stringTable: StringTable, wits: WaysTransferables) {
|
|
39
|
-
const idIndex = Ids.from(wits)
|
|
40
|
-
const tagIndex = Tags.from(stringTable, wits)
|
|
41
|
-
const wayIndex = new Ways(stringTable, idIndex, tagIndex)
|
|
42
|
-
wayIndex.refStart = ResizeableTypedArray.from(Uint32Array, wits.refStart)
|
|
43
|
-
wayIndex.refCount = ResizeableTypedArray.from(Uint16Array, wits.refCount)
|
|
44
|
-
wayIndex.refs = ResizeableTypedArray.from(IdArrayType, wits.refs)
|
|
45
|
-
wayIndex.bbox = ResizeableTypedArray.from(CoordinateArrayType, wits.bbox)
|
|
46
|
-
wayIndex.spatialIndex = Flatbush.from(wits.spatialIndex)
|
|
47
|
-
return wayIndex
|
|
48
|
-
}
|
|
39
|
+
private bbox: RTA<Float64Array>
|
|
49
40
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.refStart = new ResizeableTypedArray(Uint32Array)
|
|
53
|
-
this.refCount = new ResizeableTypedArray(Uint16Array)
|
|
54
|
-
this.refs = new ResizeableTypedArray(IdArrayType)
|
|
55
|
-
this.bbox = new ResizeableTypedArray(CoordinateArrayType)
|
|
56
|
-
}
|
|
41
|
+
// Node reference index
|
|
42
|
+
private nodes: Nodes
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Create a new Ways index.
|
|
46
|
+
*/
|
|
47
|
+
constructor(
|
|
48
|
+
stringTable: StringTable,
|
|
49
|
+
nodes: Nodes,
|
|
50
|
+
transferables?: WaysTransferables,
|
|
51
|
+
) {
|
|
52
|
+
if (transferables) {
|
|
53
|
+
super("way", new Ids(transferables), new Tags(stringTable, transferables))
|
|
54
|
+
this.refStart = RTA.from(Uint32Array, transferables.refStart)
|
|
55
|
+
this.refCount = RTA.from(Uint16Array, transferables.refCount)
|
|
56
|
+
this.refs = RTA.from(IdArrayType, transferables.refs)
|
|
57
|
+
this.bbox = RTA.from(Float64Array, transferables.bbox)
|
|
58
|
+
// Only load spatial index if provided (not stored in IndexedDB)
|
|
59
|
+
if (transferables.spatialIndex?.byteLength) {
|
|
60
|
+
this.spatialIndex = Flatbush.from(transferables.spatialIndex)
|
|
61
|
+
this.spatialIndexBuilt = true
|
|
62
|
+
}
|
|
63
|
+
this.indexBuilt = true
|
|
64
|
+
} else {
|
|
65
|
+
super("way", new Ids(), new Tags(stringTable))
|
|
66
|
+
this.refStart = new RTA(Uint32Array)
|
|
67
|
+
this.refCount = new RTA(Uint16Array)
|
|
68
|
+
this.refs = new RTA(IdArrayType)
|
|
69
|
+
this.bbox = new RTA(Float64Array)
|
|
67
70
|
}
|
|
71
|
+
this.nodes = nodes
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Add a single way to the index.
|
|
76
|
+
*/
|
|
70
77
|
addWay(way: OsmWay) {
|
|
71
|
-
this.
|
|
72
|
-
this.tags.addTags(way.tags)
|
|
78
|
+
const wayIndex = this.addEntity(way.id, way.tags ?? {})
|
|
73
79
|
this.refStart.push(this.refs.length)
|
|
74
80
|
this.refCount.push(way.refs.length)
|
|
75
81
|
for (const ref of way.refs) this.refs.push(ref)
|
|
82
|
+
return wayIndex
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
/**
|
|
@@ -80,7 +87,7 @@ export class Ways extends Entities<OsmWay> {
|
|
|
80
87
|
*/
|
|
81
88
|
addWays(
|
|
82
89
|
ways: OsmPbfWay[],
|
|
83
|
-
blockStringIndexMap:
|
|
90
|
+
blockStringIndexMap: Uint32Array,
|
|
84
91
|
filter?: (way: OsmWay) => OsmWay | null,
|
|
85
92
|
) {
|
|
86
93
|
let added = 0
|
|
@@ -99,67 +106,108 @@ export class Ways extends Entities<OsmWay> {
|
|
|
99
106
|
: null
|
|
100
107
|
if (filter && filteredWay === null) continue
|
|
101
108
|
|
|
102
|
-
this.ids.add(way.id)
|
|
103
|
-
this.refStart.push(this.refs.length)
|
|
104
|
-
this.refCount.push(filteredWay?.refs.length ?? refs.length)
|
|
105
|
-
this.refs.pushMany(filteredWay?.refs ?? refs)
|
|
106
|
-
|
|
107
109
|
const tagKeys: number[] = way.keys.map((key) => {
|
|
108
|
-
const index = blockStringIndexMap
|
|
110
|
+
const index = blockStringIndexMap[key]
|
|
109
111
|
if (index === undefined) throw Error("Tag key not found")
|
|
110
112
|
return index
|
|
111
113
|
})
|
|
112
114
|
const tagValues: number[] = way.vals.map((val) => {
|
|
113
|
-
const index = blockStringIndexMap
|
|
115
|
+
const index = blockStringIndexMap[val]
|
|
114
116
|
if (index === undefined) throw Error("Tag value not found")
|
|
115
117
|
return index
|
|
116
118
|
})
|
|
117
|
-
|
|
119
|
+
|
|
120
|
+
this.addEntity(way.id, tagKeys, tagValues)
|
|
121
|
+
this.refStart.push(this.refs.length)
|
|
122
|
+
this.refCount.push(filteredWay?.refs.length ?? refs.length)
|
|
123
|
+
this.refs.pushMany(filteredWay?.refs ?? refs)
|
|
118
124
|
added++
|
|
119
125
|
}
|
|
120
126
|
return added
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Compact the internal arrays to free up memory.
|
|
131
|
+
*/
|
|
123
132
|
buildEntityIndex() {
|
|
124
133
|
this.refStart.compact()
|
|
125
134
|
this.refCount.compact()
|
|
126
135
|
this.refs.compact()
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Build the spatial index for ways.
|
|
140
|
+
* If bbox data already exists (e.g., loaded from storage), reuses it.
|
|
141
|
+
*/
|
|
142
|
+
buildSpatialIndex() {
|
|
143
|
+
if (!this.nodes.isReady()) throw Error("Node index is not ready.")
|
|
144
|
+
if (this.size === 0) return this.spatialIndex
|
|
130
145
|
console.time("WayIndex.buildSpatialIndex")
|
|
146
|
+
|
|
131
147
|
this.spatialIndex = new Flatbush(
|
|
132
148
|
this.size,
|
|
133
149
|
128,
|
|
134
150
|
Float64Array,
|
|
135
151
|
BufferConstructor,
|
|
136
152
|
)
|
|
153
|
+
|
|
154
|
+
// If bbox already has data (loaded from storage), use it directly
|
|
155
|
+
const hasBboxData = this.bbox.length >= this.size * 4
|
|
137
156
|
for (let i = 0; i < this.size; i++) {
|
|
138
|
-
let minX
|
|
139
|
-
let minY
|
|
140
|
-
let maxX
|
|
141
|
-
let maxY
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
let minX: number
|
|
158
|
+
let minY: number
|
|
159
|
+
let maxX: number
|
|
160
|
+
let maxY: number
|
|
161
|
+
|
|
162
|
+
if (hasBboxData) {
|
|
163
|
+
// Use stored bbox values
|
|
164
|
+
minX = this.bbox.at(i * 4)
|
|
165
|
+
minY = this.bbox.at(i * 4 + 1)
|
|
166
|
+
maxX = this.bbox.at(i * 4 + 2)
|
|
167
|
+
maxY = this.bbox.at(i * 4 + 3)
|
|
168
|
+
} else {
|
|
169
|
+
// Calculate bbox from node coordinates
|
|
170
|
+
minX = Number.POSITIVE_INFINITY
|
|
171
|
+
minY = Number.POSITIVE_INFINITY
|
|
172
|
+
maxX = Number.NEGATIVE_INFINITY
|
|
173
|
+
maxY = Number.NEGATIVE_INFINITY
|
|
174
|
+
const start = this.refStart.at(i)
|
|
175
|
+
const count = this.refCount.at(i)
|
|
176
|
+
for (let j = start; j < start + count; j++) {
|
|
177
|
+
const refId = this.refs.at(j)
|
|
178
|
+
const [lon, lat] = this.nodes.getNodeLonLat({ id: refId })
|
|
179
|
+
if (lon < minX) minX = lon
|
|
180
|
+
if (lon > maxX) maxX = lon
|
|
181
|
+
if (lat < minY) minY = lat
|
|
182
|
+
if (lat > maxY) maxY = lat
|
|
183
|
+
}
|
|
184
|
+
this.bbox.push(minX)
|
|
185
|
+
this.bbox.push(minY)
|
|
186
|
+
this.bbox.push(maxX)
|
|
187
|
+
this.bbox.push(maxY)
|
|
151
188
|
}
|
|
152
|
-
this.bbox.push(minX)
|
|
153
|
-
this.bbox.push(minY)
|
|
154
|
-
this.bbox.push(maxX)
|
|
155
|
-
this.bbox.push(maxY)
|
|
156
189
|
this.spatialIndex.add(minX, minY, maxX, maxY)
|
|
157
190
|
}
|
|
191
|
+
// Compact bbox if we just calculated it
|
|
192
|
+
if (!hasBboxData) {
|
|
193
|
+
this.bbox.compact()
|
|
194
|
+
}
|
|
158
195
|
this.spatialIndex.finish()
|
|
196
|
+
this.spatialIndexBuilt = true
|
|
159
197
|
console.timeEnd("WayIndex.buildSpatialIndex")
|
|
160
198
|
return this.spatialIndex
|
|
161
199
|
}
|
|
162
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Check if the spatial index has been built.
|
|
203
|
+
*/
|
|
204
|
+
hasSpatialIndex(): boolean {
|
|
205
|
+
return this.spatialIndexBuilt
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the full way entity.
|
|
210
|
+
*/
|
|
163
211
|
getFullEntity(index: number, id: number, tags?: OsmTags): OsmWay {
|
|
164
212
|
return {
|
|
165
213
|
id,
|
|
@@ -168,13 +216,19 @@ export class Ways extends Entities<OsmWay> {
|
|
|
168
216
|
}
|
|
169
217
|
}
|
|
170
218
|
|
|
171
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Get the node IDs referenced by a way.
|
|
221
|
+
*/
|
|
222
|
+
getRefIds(index: number): number[] {
|
|
172
223
|
const start = this.refStart.at(index)
|
|
173
224
|
const count = this.refCount.at(index)
|
|
174
|
-
return this.refs.slice(start, start + count)
|
|
225
|
+
return Array.from(this.refs.slice(start, start + count))
|
|
175
226
|
}
|
|
176
227
|
|
|
177
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Get the bounding box of a way.
|
|
230
|
+
*/
|
|
231
|
+
getEntityBbox(idOrIndex: IdOrIndex): GeoBbox2D {
|
|
178
232
|
const index =
|
|
179
233
|
"index" in idOrIndex ? idOrIndex.index : this.ids.idOrIndex(idOrIndex)[0]
|
|
180
234
|
return [
|
|
@@ -185,32 +239,38 @@ export class Ways extends Entities<OsmWay> {
|
|
|
185
239
|
]
|
|
186
240
|
}
|
|
187
241
|
|
|
188
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Get the coordinates of a way as a flat array.
|
|
244
|
+
*/
|
|
245
|
+
getLine(index: number) {
|
|
189
246
|
const count = this.refCount.at(index)
|
|
190
247
|
const start = this.refStart.at(index)
|
|
191
248
|
const line = new Float64Array(count * 2)
|
|
192
249
|
for (let i = 0; i < count; i++) {
|
|
193
250
|
const ref = this.refs.at(start + i)
|
|
194
|
-
const [lon, lat] =
|
|
251
|
+
const [lon, lat] = this.nodes.getNodeLonLat({ id: ref })
|
|
195
252
|
line[i * 2] = lon
|
|
196
253
|
line[i * 2 + 1] = lat
|
|
197
254
|
}
|
|
198
255
|
return line
|
|
199
256
|
}
|
|
200
257
|
|
|
201
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Get the coordinates of a way as an array of [lon, lat] pairs.
|
|
260
|
+
*/
|
|
261
|
+
getCoordinates(index: number): LonLat[] {
|
|
202
262
|
const count = this.refCount.at(index)
|
|
203
263
|
const start = this.refStart.at(index)
|
|
204
264
|
const coords: [number, number][] = []
|
|
205
265
|
for (let refIndex = start; refIndex < start + count; refIndex++) {
|
|
206
266
|
const ref = this.refs.at(refIndex)
|
|
207
|
-
const coord =
|
|
267
|
+
const coord = this.nodes.getNodeLonLat({ id: ref })
|
|
208
268
|
if (
|
|
209
269
|
coord === undefined ||
|
|
210
270
|
coord[0] === undefined ||
|
|
211
271
|
coord[1] === undefined
|
|
212
272
|
) {
|
|
213
|
-
console.error("node index has ref",
|
|
273
|
+
console.error("node index has ref", this.nodes.ids.has(ref))
|
|
214
274
|
throw Error(
|
|
215
275
|
`Invalid coordinate for way id ${this.ids.at(index)}, index ${index}, node ref ${ref}, ref index ${refIndex}`,
|
|
216
276
|
)
|
|
@@ -220,7 +280,11 @@ export class Ways extends Entities<OsmWay> {
|
|
|
220
280
|
return coords
|
|
221
281
|
}
|
|
222
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Find way indexes that intersect a bounding box.
|
|
285
|
+
*/
|
|
223
286
|
intersects(bbox: GeoBbox2D, filterFn?: (index: number) => boolean): number[] {
|
|
287
|
+
if (this.size === 0) return []
|
|
224
288
|
return this.spatialIndex.search(
|
|
225
289
|
bbox[0],
|
|
226
290
|
bbox[1],
|
|
@@ -230,12 +294,112 @@ export class Ways extends Entities<OsmWay> {
|
|
|
230
294
|
)
|
|
231
295
|
}
|
|
232
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Find way indexes near a point using great-circle distance.
|
|
299
|
+
* @param lon - Longitude in degrees.
|
|
300
|
+
* @param lat - Latitude in degrees.
|
|
301
|
+
* @param maxResults - Maximum number of results to return.
|
|
302
|
+
* @param maxDistanceKm - Maximum distance in kilometers.
|
|
303
|
+
* @returns Array of way indexes sorted by distance.
|
|
304
|
+
*/
|
|
233
305
|
neighbors(
|
|
234
|
-
|
|
235
|
-
|
|
306
|
+
lon: number,
|
|
307
|
+
lat: number,
|
|
236
308
|
maxResults?: number,
|
|
237
|
-
|
|
309
|
+
maxDistanceKm?: number,
|
|
238
310
|
): number[] {
|
|
239
|
-
|
|
311
|
+
if (this.size === 0) return []
|
|
312
|
+
// Use geoflatbush for proper geographic distance calculations
|
|
313
|
+
return geoAround(this.spatialIndex, lon, lat, maxResults, maxDistanceKm)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get ways within a bounding box.
|
|
318
|
+
*/
|
|
319
|
+
withinBbox(
|
|
320
|
+
bbox: GeoBbox2D,
|
|
321
|
+
include?: (index: number) => boolean,
|
|
322
|
+
): {
|
|
323
|
+
ids: Float64Array
|
|
324
|
+
positions: Float64Array
|
|
325
|
+
startIndices: Uint32Array
|
|
326
|
+
} {
|
|
327
|
+
console.time("Ways.withinBbox")
|
|
328
|
+
const wayCandidates = this.intersects(bbox, include)
|
|
329
|
+
const ids = new Float64Array(wayCandidates.length)
|
|
330
|
+
const wayPositions: Float64Array[] = []
|
|
331
|
+
const wayStartIndices = new Uint32Array(wayCandidates.length + 1)
|
|
332
|
+
wayStartIndices[0] = 0
|
|
333
|
+
|
|
334
|
+
console.time("Ways.withinBbox.loop")
|
|
335
|
+
let size = 0
|
|
336
|
+
wayCandidates.forEach((wayIndex, i) => {
|
|
337
|
+
ids[i] = this.ids.at(wayIndex)
|
|
338
|
+
const way = this.getLine(wayIndex)
|
|
339
|
+
size += way.length
|
|
340
|
+
wayPositions.push(way)
|
|
341
|
+
const prevIndex = wayStartIndices[i]
|
|
342
|
+
if (prevIndex === undefined) throw Error("Previous index is undefined")
|
|
343
|
+
wayStartIndices[i + 1] = prevIndex + way.length / 2
|
|
344
|
+
})
|
|
345
|
+
console.timeEnd("Ways.withinBbox.loop")
|
|
346
|
+
const wayPositionsArray = new Float64Array(size)
|
|
347
|
+
let pIndex = 0
|
|
348
|
+
for (const way of wayPositions) {
|
|
349
|
+
wayPositionsArray.set(way, pIndex)
|
|
350
|
+
pIndex += way.length
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.timeEnd("Ways.withinBbox")
|
|
354
|
+
return {
|
|
355
|
+
ids,
|
|
356
|
+
positions: wayPositionsArray,
|
|
357
|
+
startIndices: wayStartIndices,
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get transferable objects for passing to another thread.
|
|
363
|
+
* Only includes spatialIndex if it has been built.
|
|
364
|
+
*/
|
|
365
|
+
override transferables(): WaysTransferables {
|
|
366
|
+
const base = {
|
|
367
|
+
...super.transferables(),
|
|
368
|
+
refStart: this.refStart.array.buffer,
|
|
369
|
+
refCount: this.refCount.array.buffer,
|
|
370
|
+
refs: this.refs.array.buffer,
|
|
371
|
+
bbox: this.bbox.array.buffer,
|
|
372
|
+
}
|
|
373
|
+
// Only include spatial index if it was built
|
|
374
|
+
if (this.spatialIndexBuilt) {
|
|
375
|
+
return { ...base, spatialIndex: this.spatialIndex.data }
|
|
376
|
+
}
|
|
377
|
+
return base
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get the approximate memory requirements for a given number of ways in bytes.
|
|
382
|
+
*/
|
|
383
|
+
static getBytesRequired(count: number) {
|
|
384
|
+
if (count === 0) return 0
|
|
385
|
+
// Approximate nodes per way
|
|
386
|
+
let numNodes = count
|
|
387
|
+
let n = count
|
|
388
|
+
while (n !== 1) {
|
|
389
|
+
n = Math.ceil(n / 128)
|
|
390
|
+
numNodes += n
|
|
391
|
+
}
|
|
392
|
+
const indexBytes = (numNodes < 16384 ? 2 : 4) * numNodes
|
|
393
|
+
const boxesBytes = numNodes * 4 * Float64Array.BYTES_PER_ELEMENT
|
|
394
|
+
const spatialIndexBytes = 8 + indexBytes + boxesBytes
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
Ids.getBytesRequired(count) +
|
|
398
|
+
Tags.getBytesRequired(count) +
|
|
399
|
+
count * Uint32Array.BYTES_PER_ELEMENT + // refStart
|
|
400
|
+
count * Uint16Array.BYTES_PER_ELEMENT + // refCount
|
|
401
|
+
count * 4 * Float64Array.BYTES_PER_ELEMENT + // bbox
|
|
402
|
+
spatialIndexBytes
|
|
403
|
+
)
|
|
240
404
|
}
|
|
241
405
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { Nodes, Relations, Ways } from "../src"
|
|
3
|
+
|
|
4
|
+
export function getOsmCoreMemoryRequirements(
|
|
5
|
+
nodeCount: number,
|
|
6
|
+
wayCount: number,
|
|
7
|
+
relationCount: number,
|
|
8
|
+
) {
|
|
9
|
+
const nodesBytes = Nodes.getBytesRequired(nodeCount)
|
|
10
|
+
const waysBytes = Ways.getBytesRequired(wayCount)
|
|
11
|
+
const relationsBytes = Relations.getBytesRequired(relationCount)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
nodes: nodesBytes,
|
|
15
|
+
ways: waysBytes,
|
|
16
|
+
relations: relationsBytes,
|
|
17
|
+
total: nodesBytes + waysBytes + relationsBytes,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("Memory Requirements", () => {
|
|
22
|
+
it("computes memory requirements for small datasets", () => {
|
|
23
|
+
const breakdown = getOsmCoreMemoryRequirements(10, 2, 1)
|
|
24
|
+
expect(breakdown.nodes).toBeGreaterThan(0)
|
|
25
|
+
expect(breakdown.ways).toBeGreaterThan(0)
|
|
26
|
+
expect(breakdown.relations).toBeGreaterThan(0)
|
|
27
|
+
expect(breakdown.total).toBe(
|
|
28
|
+
breakdown.nodes + breakdown.ways + breakdown.relations,
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("computes memory requirements for large datasets", () => {
|
|
33
|
+
const breakdown = getOsmCoreMemoryRequirements(70000, 20000, 0)
|
|
34
|
+
expect(breakdown.nodes).toBeGreaterThan(0)
|
|
35
|
+
expect(breakdown.ways).toBeGreaterThan(0)
|
|
36
|
+
expect(breakdown.total).toBe(breakdown.nodes + breakdown.ways)
|
|
37
|
+
})
|
|
38
|
+
})
|
package/test/mock-osm.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OsmNode } from "@osmix/
|
|
2
|
-
import {
|
|
1
|
+
import type { OsmNode } from "@osmix/shared/types"
|
|
2
|
+
import { Osm } from "../src/osm"
|
|
3
3
|
|
|
4
4
|
const YAKIM_LAT = 46.60207
|
|
5
5
|
const YAKIM_LON = -120.505898
|
|
@@ -19,12 +19,12 @@ const node1: OsmNode = {
|
|
|
19
19
|
lon: YAKIM_LON - ONE_KM_LON, // approximately 1km west
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function addBaseNodes(osm:
|
|
22
|
+
function addBaseNodes(osm: Osm) {
|
|
23
23
|
osm.nodes.addNode(node0)
|
|
24
24
|
osm.nodes.addNode(node1)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function addBaseWays(osm:
|
|
27
|
+
function addBaseWays(osm: Osm) {
|
|
28
28
|
osm.ways.addWay({
|
|
29
29
|
id: 1,
|
|
30
30
|
refs: [0, 1],
|
|
@@ -37,8 +37,8 @@ function addBaseWays(osm: Osmix) {
|
|
|
37
37
|
/**
|
|
38
38
|
* Create a base OSM with one way and two nodes.
|
|
39
39
|
*/
|
|
40
|
-
export function createBaseOsm():
|
|
41
|
-
const base = new
|
|
40
|
+
export function createBaseOsm(): Osm {
|
|
41
|
+
const base = new Osm()
|
|
42
42
|
addBaseNodes(base)
|
|
43
43
|
base.nodes.buildIndex()
|
|
44
44
|
addBaseWays(base)
|
|
@@ -52,8 +52,8 @@ export function createBaseOsm(): Osmix {
|
|
|
52
52
|
* - A way that has an overlapping node with the first way, but does not share a node and so starts disconnected
|
|
53
53
|
* - A way that crosses over the first way, but does not share a node and so starts disconnected
|
|
54
54
|
*/
|
|
55
|
-
export function createPatchOsm():
|
|
56
|
-
const osm = new
|
|
55
|
+
export function createPatchOsm(): Osm {
|
|
56
|
+
const osm = new Osm()
|
|
57
57
|
|
|
58
58
|
addBaseNodes(osm)
|
|
59
59
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import type { OsmNode, OsmWay } from "@osmix/shared/types"
|
|
3
|
+
import { Nodes } from "../src/nodes"
|
|
4
|
+
import StringTable from "../src/stringtable"
|
|
5
|
+
import { Ways } from "../src/ways"
|
|
6
|
+
|
|
7
|
+
describe("Tags.search on Nodes", () => {
|
|
8
|
+
it("finds by key and key+value", () => {
|
|
9
|
+
const st = new StringTable()
|
|
10
|
+
const nodes = new Nodes(st)
|
|
11
|
+
const node1: OsmNode = { id: 1, lon: -1, lat: 1, tags: { curb: "yes" } }
|
|
12
|
+
const node2: OsmNode = { id: 2, lon: -2, lat: 2, tags: { curb: "no" } }
|
|
13
|
+
const node3: OsmNode = { id: 3, lon: -3, lat: 3 }
|
|
14
|
+
nodes.addNode(node1)
|
|
15
|
+
nodes.addNode(node2)
|
|
16
|
+
nodes.addNode(node3)
|
|
17
|
+
nodes.buildIndex()
|
|
18
|
+
|
|
19
|
+
const allCurb = nodes.search("curb")
|
|
20
|
+
expect(allCurb).toEqual([node1, node2])
|
|
21
|
+
const curbYes = nodes.search("curb", "yes")
|
|
22
|
+
expect(curbYes).toEqual([node1])
|
|
23
|
+
const curbNo = nodes.search("curb", "no")
|
|
24
|
+
expect(curbNo).toEqual([node2])
|
|
25
|
+
const none = nodes.search("highway")
|
|
26
|
+
expect(none).toEqual([])
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe("Tags.search on Ways", () => {
|
|
31
|
+
it("finds by key and key+value", () => {
|
|
32
|
+
const st = new StringTable()
|
|
33
|
+
const nodes = new Nodes(st)
|
|
34
|
+
const ways = new Ways(st, nodes)
|
|
35
|
+
const way1: OsmWay = {
|
|
36
|
+
id: 10,
|
|
37
|
+
refs: [1, 2],
|
|
38
|
+
tags: { highway: "residential" },
|
|
39
|
+
}
|
|
40
|
+
const way2: OsmWay = { id: 11, refs: [2, 3], tags: { area: "yes" } }
|
|
41
|
+
const way3: OsmWay = {
|
|
42
|
+
id: 12,
|
|
43
|
+
refs: [3, 4],
|
|
44
|
+
tags: { highway: "primary", area: "no" },
|
|
45
|
+
}
|
|
46
|
+
ways.addWay(way1)
|
|
47
|
+
ways.addWay(way2)
|
|
48
|
+
ways.addWay(way3)
|
|
49
|
+
ways.buildIndex()
|
|
50
|
+
|
|
51
|
+
const allHighway = ways.search("highway")
|
|
52
|
+
expect(allHighway).toEqual([way1, way3])
|
|
53
|
+
const areaYes = ways.search("area", "yes")
|
|
54
|
+
expect(areaYes).toEqual([way2])
|
|
55
|
+
const areaAll = ways.search("area")
|
|
56
|
+
expect(areaAll).toEqual([way2, way3])
|
|
57
|
+
const areaNo = ways.search("area", "no")
|
|
58
|
+
expect(areaNo).toEqual([way3])
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from "
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
2
|
import { BufferConstructor, ResizeableTypedArray } from "../src/typed-arrays"
|
|
3
3
|
|
|
4
4
|
describe("typed array helpers", () => {
|
|
@@ -106,9 +106,8 @@ describe("typed array helpers", () => {
|
|
|
106
106
|
expect(Array.from(arr.slice(0, arr.length))).toEqual([1, 2, 3, 4, 5])
|
|
107
107
|
})
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
"grows shared buffers in place",
|
|
111
|
-
() => {
|
|
109
|
+
if (typeof SharedArrayBuffer !== "undefined") {
|
|
110
|
+
it("grows shared buffers in place", () => {
|
|
112
111
|
const backing = new SharedArrayBuffer(8, {
|
|
113
112
|
maxByteLength: 32,
|
|
114
113
|
})
|
|
@@ -129,7 +128,7 @@ describe("typed array helpers", () => {
|
|
|
129
128
|
expect(arr.array.length).toBe(16)
|
|
130
129
|
expect(arr.buffer.byteLength).toBe(16)
|
|
131
130
|
expect(Array.from(arr.slice(0, 9))).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9])
|
|
132
|
-
}
|
|
133
|
-
|
|
131
|
+
})
|
|
132
|
+
}
|
|
134
133
|
})
|
|
135
134
|
})
|
package/tsconfig.json
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
3
|
"extends": "@osmix/shared/tsconfig/base.json",
|
|
4
4
|
"exclude": ["node_modules", "dist"],
|
|
5
|
-
"include": [
|
|
5
|
+
"include": [
|
|
6
|
+
"src",
|
|
7
|
+
"test",
|
|
8
|
+
"../osmix/src/extract.ts",
|
|
9
|
+
"../osmix/test/extract.test.ts",
|
|
10
|
+
"../osmix/test/extract.bench.ts"
|
|
11
|
+
],
|
|
6
12
|
"compilerOptions": {
|
|
7
13
|
"outDir": "./dist"
|
|
8
14
|
}
|