@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +118 -107
  3. package/dist/core/src/entities.d.ts +114 -0
  4. package/dist/core/src/entities.d.ts.map +1 -0
  5. package/dist/core/src/entities.js +152 -0
  6. package/dist/core/src/entities.js.map +1 -0
  7. package/dist/core/src/ids.d.ts +113 -0
  8. package/dist/core/src/ids.d.ts.map +1 -0
  9. package/dist/core/src/ids.js +236 -0
  10. package/dist/core/src/ids.js.map +1 -0
  11. package/dist/core/src/index.d.ts +32 -0
  12. package/dist/core/src/index.d.ts.map +1 -0
  13. package/dist/core/src/index.js +35 -0
  14. package/dist/core/src/index.js.map +1 -0
  15. package/dist/core/src/nodes.d.ts +101 -0
  16. package/dist/core/src/nodes.d.ts.map +1 -0
  17. package/dist/core/src/nodes.js +309 -0
  18. package/dist/core/src/nodes.js.map +1 -0
  19. package/dist/core/src/osm.d.ts +74 -0
  20. package/dist/core/src/osm.d.ts.map +1 -0
  21. package/dist/core/src/osm.js +120 -0
  22. package/dist/core/src/osm.js.map +1 -0
  23. package/dist/core/src/relations.d.ts +118 -0
  24. package/dist/core/src/relations.d.ts.map +1 -0
  25. package/dist/core/src/relations.js +412 -0
  26. package/dist/core/src/relations.js.map +1 -0
  27. package/dist/core/src/stringtable.d.ts +95 -0
  28. package/dist/core/src/stringtable.d.ts.map +1 -0
  29. package/dist/core/src/stringtable.js +170 -0
  30. package/dist/core/src/stringtable.js.map +1 -0
  31. package/dist/core/src/tags.d.ts +126 -0
  32. package/dist/core/src/tags.d.ts.map +1 -0
  33. package/dist/core/src/tags.js +228 -0
  34. package/dist/core/src/tags.js.map +1 -0
  35. package/dist/core/src/typed-arrays.d.ts +124 -0
  36. package/dist/core/src/typed-arrays.d.ts.map +1 -0
  37. package/dist/core/src/typed-arrays.js +198 -0
  38. package/dist/core/src/typed-arrays.js.map +1 -0
  39. package/dist/core/src/utils.d.ts +11 -0
  40. package/dist/core/src/utils.d.ts.map +1 -0
  41. package/dist/core/src/utils.js +26 -0
  42. package/dist/core/src/utils.js.map +1 -0
  43. package/dist/core/src/ways.d.ts +101 -0
  44. package/dist/core/src/ways.d.ts.map +1 -0
  45. package/dist/core/src/ways.js +336 -0
  46. package/dist/core/src/ways.js.map +1 -0
  47. package/dist/core/test/memory.test.d.ts +7 -0
  48. package/dist/core/test/memory.test.d.ts.map +1 -0
  49. package/dist/core/test/memory.test.js +29 -0
  50. package/dist/core/test/memory.test.js.map +1 -0
  51. package/dist/core/test/mock-osm.d.ts +13 -0
  52. package/dist/core/test/mock-osm.d.ts.map +1 -0
  53. package/dist/core/test/mock-osm.js +122 -0
  54. package/dist/core/test/mock-osm.js.map +1 -0
  55. package/dist/core/test/tags-search.test.d.ts +2 -0
  56. package/dist/core/test/tags-search.test.d.ts.map +1 -0
  57. package/dist/core/test/tags-search.test.js +56 -0
  58. package/dist/core/test/tags-search.test.js.map +1 -0
  59. package/dist/core/test/typed-arrays.test.d.ts +2 -0
  60. package/dist/core/test/typed-arrays.test.d.ts.map +1 -0
  61. package/dist/core/test/typed-arrays.test.js +102 -0
  62. package/dist/core/test/typed-arrays.test.js.map +1 -0
  63. package/dist/osmix/src/extract.d.ts +43 -0
  64. package/dist/osmix/src/extract.d.ts.map +1 -0
  65. package/dist/osmix/src/extract.js +156 -0
  66. package/dist/osmix/src/extract.js.map +1 -0
  67. package/dist/osmix/src/pbf.d.ts +55 -0
  68. package/dist/osmix/src/pbf.d.ts.map +1 -0
  69. package/dist/osmix/src/pbf.js +187 -0
  70. package/dist/osmix/src/pbf.js.map +1 -0
  71. package/dist/osmix/src/utils.d.ts +36 -0
  72. package/dist/osmix/src/utils.d.ts.map +1 -0
  73. package/dist/osmix/src/utils.js +108 -0
  74. package/dist/osmix/src/utils.js.map +1 -0
  75. package/dist/osmix/test/extract.bench.d.ts +2 -0
  76. package/dist/osmix/test/extract.bench.d.ts.map +1 -0
  77. package/dist/osmix/test/extract.bench.js +26 -0
  78. package/dist/osmix/test/extract.bench.js.map +1 -0
  79. package/dist/osmix/test/extract.test.d.ts +2 -0
  80. package/dist/osmix/test/extract.test.d.ts.map +1 -0
  81. package/dist/osmix/test/extract.test.js +392 -0
  82. package/dist/osmix/test/extract.test.js.map +1 -0
  83. package/package.json +11 -11
  84. package/src/entities.ts +138 -22
  85. package/src/ids.ts +117 -45
  86. package/src/index.ts +34 -5
  87. package/src/nodes.ts +237 -58
  88. package/src/osm.ts +174 -0
  89. package/src/relations.ts +383 -54
  90. package/src/stringtable.ts +111 -36
  91. package/src/tags.ts +207 -46
  92. package/src/typed-arrays.ts +97 -10
  93. package/src/types/geoflatbush.d.ts +37 -0
  94. package/src/types/geokdbush.d.ts +37 -0
  95. package/src/types/kdbush.d.ts +24 -6
  96. package/src/utils.ts +9 -14
  97. package/src/ways.ts +248 -84
  98. package/test/memory.test.ts +38 -0
  99. package/test/mock-osm.ts +8 -8
  100. package/test/tags-search.test.ts +60 -0
  101. package/test/typed-arrays.test.ts +5 -6
  102. package/tsconfig.json +7 -1
  103. package/src/osmix.ts +0 -556
  104. package/test/extract.bench.ts +0 -29
  105. package/test/extract.test.ts +0 -156
  106. package/test/read.test.ts +0 -22
  107. 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 EntitiesTransferables {
19
- refStart: BufferType
20
- refCount: BufferType
21
- refs: BufferType
22
- bbox: BufferType
23
- spatialIndex: BufferType
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: ResizeableTypedArray<Uint32Array>
30
- refCount: ResizeableTypedArray<Uint16Array> // Maximum 2,000 nodes per way
32
+ private refStart: RTA<Uint32Array>
33
+ private refCount: RTA<Uint16Array> // Maximum 2,000 nodes per way
31
34
 
32
35
  // Node IDs
33
- refs: ResizeableTypedArray<Float64Array>
36
+ private refs: RTA<Float64Array>
34
37
 
35
38
  // Bounding box of the way in geographic coordinates
36
- bbox: ResizeableTypedArray<Float64Array>
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
- constructor(stringTable: StringTable, idIndex?: Ids, tagIndex?: Tags) {
51
- super("way", stringTable, idIndex, tagIndex)
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
- transferables(): WaysTransferables {
59
- return {
60
- refStart: this.refStart.array.buffer,
61
- refCount: this.refCount.array.buffer,
62
- refs: this.refs.array.buffer,
63
- bbox: this.bbox.array.buffer,
64
- spatialIndex: this.spatialIndex.data,
65
- ...this.ids.transferables(),
66
- ...this.tags.transferables(),
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.ids.add(way.id)
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: Map<number, number>,
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.get(key)
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.get(val)
115
+ const index = blockStringIndexMap[val]
114
116
  if (index === undefined) throw Error("Tag value not found")
115
117
  return index
116
118
  })
117
- this.tags.addTagKeysAndValues(tagKeys, tagValues)
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
- buildSpatialIndex(nodeIndex: Nodes) {
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 = Number.POSITIVE_INFINITY
139
- let minY = Number.POSITIVE_INFINITY
140
- let maxX = Number.NEGATIVE_INFINITY
141
- let maxY = Number.NEGATIVE_INFINITY
142
- const start = this.refStart.at(i)
143
- const count = this.refCount.at(i)
144
- for (let j = start; j < start + count; j++) {
145
- const refId = this.refs.at(j)
146
- const [lon, lat] = nodeIndex.getNodeLonLat({ id: refId })
147
- if (lon < minX) minX = lon
148
- if (lon > maxX) maxX = lon
149
- if (lat < minY) minY = lat
150
- if (lat > maxY) maxY = lat
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
- getRefIds(index: number) {
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
- getBbox(idOrIndex: IdOrIndex): GeoBbox2D {
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
- getLine(index: number, nodeIndex: Nodes) {
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] = nodeIndex.getNodeLonLat({ id: ref })
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
- getCoordinates(index: number, nodeIndex: Nodes): LonLat[] {
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 = nodeIndex.getNodeLonLat({ id: ref })
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", nodeIndex.ids.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
- x: number,
235
- y: number,
306
+ lon: number,
307
+ lat: number,
236
308
  maxResults?: number,
237
- maxDistance?: number,
309
+ maxDistanceKm?: number,
238
310
  ): number[] {
239
- return this.spatialIndex.neighbors(x, y, maxResults, maxDistance)
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/json"
2
- import { Osmix } from "../src/osmix"
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: Osmix) {
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: Osmix) {
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(): Osmix {
41
- const base = new Osmix()
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(): Osmix {
56
- const osm = new Osmix()
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 "vitest"
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
- it.runIf(typeof SharedArrayBuffer !== "undefined")(
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": ["src", "test"],
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
  }