@osmix/core 0.0.2 → 0.1.1

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