@osmix/core 0.1.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -72
  4. package/dist/contracts.d.ts +27 -0
  5. package/dist/contracts.d.ts.map +1 -0
  6. package/dist/contracts.js +1 -0
  7. package/dist/contracts.js.map +1 -0
  8. package/dist/entities.d.ts +22 -2
  9. package/dist/entities.d.ts.map +1 -1
  10. package/dist/entities.js +54 -10
  11. package/dist/entities.js.map +1 -1
  12. package/dist/ids.d.ts +20 -5
  13. package/dist/ids.d.ts.map +1 -1
  14. package/dist/ids.js +87 -29
  15. package/dist/ids.js.map +1 -1
  16. package/dist/index.d.ts +7 -4
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +6 -4
  19. package/dist/index.js.map +1 -1
  20. package/dist/indirect-kd-index.d.ts +28 -0
  21. package/dist/indirect-kd-index.d.ts.map +1 -0
  22. package/dist/indirect-kd-index.js +163 -0
  23. package/dist/indirect-kd-index.js.map +1 -0
  24. package/dist/limits.d.ts +30 -0
  25. package/dist/limits.d.ts.map +1 -0
  26. package/dist/limits.js +51 -0
  27. package/dist/limits.js.map +1 -0
  28. package/dist/mocks.d.ts.map +1 -1
  29. package/dist/mocks.js.map +1 -1
  30. package/dist/nodes.d.ts +36 -15
  31. package/dist/nodes.d.ts.map +1 -1
  32. package/dist/nodes.js +185 -62
  33. package/dist/nodes.js.map +1 -1
  34. package/dist/osm.d.ts +60 -4
  35. package/dist/osm.d.ts.map +1 -1
  36. package/dist/osm.js +32 -6
  37. package/dist/osm.js.map +1 -1
  38. package/dist/relations.d.ts +11 -4
  39. package/dist/relations.d.ts.map +1 -1
  40. package/dist/relations.js +73 -29
  41. package/dist/relations.js.map +1 -1
  42. package/dist/renumber.d.ts +23 -0
  43. package/dist/renumber.d.ts.map +1 -0
  44. package/dist/renumber.js +64 -0
  45. package/dist/renumber.js.map +1 -0
  46. package/dist/stringtable.d.ts +2 -1
  47. package/dist/stringtable.d.ts.map +1 -1
  48. package/dist/stringtable.js +9 -14
  49. package/dist/stringtable.js.map +1 -1
  50. package/dist/tags.d.ts +45 -12
  51. package/dist/tags.d.ts.map +1 -1
  52. package/dist/tags.js +153 -27
  53. package/dist/tags.js.map +1 -1
  54. package/dist/typed-arrays.d.ts +63 -48
  55. package/dist/typed-arrays.d.ts.map +1 -1
  56. package/dist/typed-arrays.js +184 -107
  57. package/dist/typed-arrays.js.map +1 -1
  58. package/dist/ways.d.ts +52 -3
  59. package/dist/ways.d.ts.map +1 -1
  60. package/dist/ways.js +223 -46
  61. package/dist/ways.js.map +1 -1
  62. package/package.json +45 -31
  63. package/src/contracts.ts +29 -0
  64. package/src/entities.ts +242 -194
  65. package/src/ids.ts +278 -235
  66. package/src/index.ts +22 -13
  67. package/src/indirect-kd-index.ts +212 -0
  68. package/src/limits.ts +73 -0
  69. package/src/mocks.ts +114 -113
  70. package/src/nodes.ts +538 -397
  71. package/src/osm.ts +282 -207
  72. package/src/relations.ts +580 -558
  73. package/src/renumber.ts +80 -0
  74. package/src/stringtable.ts +189 -184
  75. package/src/tags.ts +406 -272
  76. package/src/typed-arrays.ts +307 -205
  77. package/src/types/geoflatbush.d.ts +28 -33
  78. package/src/ways.ts +577 -406
  79. package/test/entity-index-errors.test.ts +41 -0
  80. package/test/ids.bench.ts +33 -0
  81. package/test/ids.test.ts +122 -0
  82. package/test/limits.test.ts +83 -0
  83. package/test/memory.test.ts +29 -30
  84. package/test/no-shared-array-buffer.test.ts +41 -0
  85. package/test/nodes-spatial-index.test.ts +261 -0
  86. package/test/osm-bbox.test.ts +30 -0
  87. package/test/relations.bench.ts +32 -0
  88. package/test/relations.test.ts +124 -0
  89. package/test/renumber.test.ts +54 -0
  90. package/test/spatial-index-rebuild.test.ts +33 -0
  91. package/test/storage-compaction.test.ts +269 -0
  92. package/test/tags-search.test.ts +89 -56
  93. package/test/typed-arrays.test.ts +252 -132
  94. package/tsconfig.build.json +6 -6
  95. package/tsconfig.json +7 -7
  96. package/src/types/geokdbush.d.ts +0 -37
  97. package/src/types/kdbush.d.ts +0 -47
package/src/ids.ts CHANGED
@@ -7,40 +7,37 @@
7
7
  * @module
8
8
  */
9
9
 
10
- import { assertValue } from "@osmix/shared/assert"
11
- import type { ContentHasher } from "@osmix/shared/content-hasher"
12
- import {
13
- BufferConstructor,
14
- type BufferType,
15
- ResizeableTypedArray as RTA,
16
- } from "./typed-arrays.ts"
10
+ import { assertValue } from "@osmix/shared/assert";
11
+ import type { ContentHasher } from "@osmix/shared/content-hasher";
12
+
13
+ import { BufferConstructor, type BufferType, ResizeableTypedArray as RTA } from "./typed-arrays.ts";
17
14
 
18
15
  /**
19
16
  * Union type for looking up entities by either OSM ID or internal array index.
20
17
  */
21
- export type IdOrIndex = { id: number } | { index: number }
18
+ export type IdOrIndex = { id: number } | { index: number };
22
19
 
23
20
  /**
24
21
  * Number of IDs per anchor block for the two-level binary search.
25
22
  * Smaller values increase anchor array size but speed up block-level search;
26
23
  * larger values reduce anchor overhead but slow the final search.
27
24
  */
28
- const BLOCK_SIZE = 256
25
+ const BLOCK_SIZE = 256;
29
26
 
30
27
  /**
31
28
  * Serializable state for worker transfer.
32
29
  */
33
30
  export interface IdsTransferables<T extends BufferType = BufferType> {
34
- /** Entity IDs in insertion order. */
35
- ids: T
36
- /** Sorted IDs for binary search (aliases `ids` if already sorted). */
37
- sortedIds: T
38
- /** Maps sorted position → original insertion index. */
39
- sortedIdPositionToIndex: T
40
- /** Anchor array: every BLOCK_SIZE-th sorted ID. */
41
- anchors: T
42
- /** True if IDs were inserted in ascending order. */
43
- idsAreSorted: boolean
31
+ /** Entity IDs in insertion order. */
32
+ ids: T;
33
+ /** Sorted IDs for binary search. Omitted when `idsAreSorted` is true. */
34
+ sortedIds?: T;
35
+ /** Maps sorted position → original insertion index. Omitted for sorted IDs. */
36
+ sortedIdPositionToIndex?: T;
37
+ /** Anchor array: every BLOCK_SIZE-th sorted ID. */
38
+ anchors: T;
39
+ /** True if IDs were inserted in ascending order. */
40
+ idsAreSorted: boolean;
44
41
  }
45
42
 
46
43
  /**
@@ -50,241 +47,287 @@ export interface IdsTransferables<T extends BufferType = BufferType> {
50
47
  * Max capacity: ~2^32 entities.
51
48
  */
52
49
  export class Ids {
53
- /** All IDs in insertion order */
54
- private ids: RTA<Float64Array>
55
- /** Whether buildIndex() has been called */
56
- private indexBuilt = false
57
- /** True if IDs were added in ascending order (allows index optimization) */
58
- private idsAreSorted = true
59
- /** Sorted view of IDs for binary search */
60
- private idsSorted: Float64Array
61
- /** Maps position in sorted array → original insertion index */
62
- private sortedIdPositionToIndex: Uint32Array
63
- /** Anchor array for two-level binary search: every BLOCK_SIZE-th sorted ID */
64
- private anchors: Float64Array
50
+ /** All IDs in insertion order */
51
+ private ids: RTA<Float64Array>;
52
+ /** Whether buildIndex() has been called */
53
+ private indexBuilt = false;
54
+ /** True if IDs were added in ascending order (allows index optimization) */
55
+ private idsAreSorted = true;
56
+ /** Sorted view of IDs for binary search */
57
+ private idsSorted: Float64Array;
58
+ /** Maps position in sorted array → original insertion index */
59
+ private sortedIdPositionToIndex: Uint32Array;
60
+ /** Anchor array for two-level binary search: every BLOCK_SIZE-th sorted ID */
61
+ private anchors: Float64Array;
62
+
63
+ /**
64
+ * Create a new Ids index.
65
+ * @param transferables - Optional serialized state to reconstruct from.
66
+ */
67
+ constructor(transferables?: IdsTransferables) {
68
+ if (transferables) {
69
+ this.ids = RTA.from(Float64Array, transferables.ids);
70
+ if (transferables.idsAreSorted) {
71
+ this.idsSorted = this.ids.array;
72
+ this.sortedIdPositionToIndex = new Uint32Array(new BufferConstructor(0));
73
+ } else {
74
+ if (!transferables.sortedIds || !transferables.sortedIdPositionToIndex) {
75
+ throw Error("Unsorted IDs require sorted lookup buffers.");
76
+ }
77
+ this.idsSorted = new Float64Array(transferables.sortedIds);
78
+ this.sortedIdPositionToIndex = new Uint32Array(transferables.sortedIdPositionToIndex);
79
+ }
80
+ this.anchors = new Float64Array(transferables.anchors);
81
+ this.idsAreSorted = transferables.idsAreSorted;
82
+ this.indexBuilt = true;
83
+ } else {
84
+ this.ids = new RTA(Float64Array);
85
+ this.idsSorted = new Float64Array(new BufferConstructor(0));
86
+ this.sortedIdPositionToIndex = new Uint32Array(new BufferConstructor(0));
87
+ this.anchors = new Float64Array(new BufferConstructor(0));
88
+ }
89
+ }
90
+
91
+ /** Number of IDs stored in this index. */
92
+ get size() {
93
+ return this.ids.length;
94
+ }
95
+
96
+ /** Returns true if buildIndex() has been called and lookups are enabled. */
97
+ isReady() {
98
+ return this.indexBuilt;
99
+ }
100
+
101
+ /** Returns true if IDs were inserted in ascending order. */
102
+ isSorted() {
103
+ return this.idsAreSorted;
104
+ }
65
105
 
66
- /**
67
- * Create a new Ids index.
68
- * @param transferables - Optional serialized state to reconstruct from.
69
- */
70
- constructor(transferables?: IdsTransferables) {
71
- if (transferables) {
72
- this.ids = RTA.from(Float64Array, transferables.ids)
73
- this.idsSorted = new Float64Array(transferables.sortedIds)
74
- this.sortedIdPositionToIndex = new Uint32Array(
75
- transferables.sortedIdPositionToIndex,
76
- )
77
- this.anchors = new Float64Array(transferables.anchors)
78
- this.idsAreSorted = transferables.idsAreSorted
79
- this.indexBuilt = true
80
- } else {
81
- this.ids = new RTA(Float64Array)
82
- this.idsSorted = new Float64Array(new BufferConstructor(0))
83
- this.sortedIdPositionToIndex = new Uint32Array(new BufferConstructor(0))
84
- this.anchors = new Float64Array(new BufferConstructor(0))
85
- }
86
- }
106
+ /**
107
+ * Add an ID to the index.
108
+ * @param id - The OSM entity ID to add.
109
+ * @returns The internal array index where this ID was stored.
110
+ * @throws If the index has already been built.
111
+ */
112
+ add(id: number): number {
113
+ if (this.indexBuilt) throw Error("ID index already built.");
114
+ if (this.ids.length > 0 && id < this.ids.at(-1)) this.idsAreSorted = false;
115
+ return this.ids.push(id);
116
+ }
87
117
 
88
- /** Number of IDs stored in this index. */
89
- get size() {
90
- return this.ids.length
91
- }
118
+ /**
119
+ * Get the ID at a specific internal index.
120
+ * @param index - The internal array index.
121
+ * @returns The OSM entity ID at that index.
122
+ */
123
+ at(index: number): number {
124
+ return this.ids.at(index);
125
+ }
92
126
 
93
- /** Returns true if buildIndex() has been called and lookups are enabled. */
94
- isReady() {
95
- return this.indexBuilt
96
- }
127
+ /**
128
+ * Check if an ID exists in this index.
129
+ * @param id - The OSM entity ID to check.
130
+ * @returns True if the ID exists in this index.
131
+ */
132
+ has(id: number): boolean {
133
+ return this.getIndexFromId(id) !== -1;
134
+ }
97
135
 
98
- /** Returns true if IDs were inserted in ascending order. */
99
- isSorted() {
100
- return this.idsAreSorted
101
- }
136
+ /**
137
+ * Build the index of IDs to positions.
138
+ *
139
+ * If the IDs are not sorted, we need to sort them and build a new index.
140
+ * If the IDs are sorted, we can use the existing index.
141
+ */
142
+ buildIndex() {
143
+ if (this.indexBuilt) throw Error("ID index already built.");
144
+ this.ids.compact();
145
+ if (!this.idsAreSorted) {
146
+ // Build the sorted index
147
+ const idsBuffer = new BufferConstructor(this.size * Float64Array.BYTES_PER_ELEMENT);
148
+ const posBuffer = new BufferConstructor(this.size * Uint32Array.BYTES_PER_ELEMENT);
149
+ this.idsSorted = new Float64Array(idsBuffer);
150
+ this.sortedIdPositionToIndex = new Uint32Array(posBuffer);
102
151
 
103
- /**
104
- * Add an ID to the index.
105
- * @param id - The OSM entity ID to add.
106
- * @returns The internal array index where this ID was stored.
107
- * @throws If the index has already been built.
108
- */
109
- add(id: number): number {
110
- if (this.indexBuilt) throw Error("ID index already built.")
111
- if (this.ids.length > 0 && id < this.ids.at(-1)) this.idsAreSorted = false
112
- return this.ids.push(id)
113
- }
152
+ // Sort positions by ID, using the original position as a deterministic
153
+ // tie-breaker for duplicate IDs.
154
+ const sortedPositions = new Uint32Array(this.size);
155
+ for (let i = 0; i < this.size; i++) sortedPositions[i] = i;
156
+ sortedPositions.sort((a, b) => {
157
+ const idDifference = this.ids.array[a]! - this.ids.array[b]!;
158
+ return idDifference === 0 ? a - b : idDifference;
159
+ });
160
+ for (let i = 0; i < this.size; i++) {
161
+ const position = sortedPositions[i]!;
162
+ this.idsSorted[i] = this.ids.array[position]!;
163
+ this.sortedIdPositionToIndex[i] = position;
164
+ }
165
+ } else {
166
+ // Point to the same array
167
+ this.idsSorted = this.ids.array;
168
+ // Sorted position is already the insertion index; no identity map is needed.
169
+ this.sortedIdPositionToIndex = new Uint32Array(new BufferConstructor(0));
170
+ }
114
171
 
115
- /**
116
- * Get the ID at a specific internal index.
117
- * @param index - The internal array index.
118
- * @returns The OSM entity ID at that index.
119
- */
120
- at(index: number): number {
121
- return this.ids.at(index)
122
- }
172
+ // Build anchors (every blockSize-th key)
173
+ const aLen = Math.ceil(this.size / BLOCK_SIZE);
174
+ const sab = new BufferConstructor(aLen * Float64Array.BYTES_PER_ELEMENT);
175
+ this.anchors = new Float64Array(sab, 0, aLen);
176
+ for (let j = 0; j < aLen; j++) {
177
+ const id = this.idsSorted[Math.min(j * BLOCK_SIZE, this.size - 1)];
178
+ assertValue(id, "ID is undefined");
179
+ this.anchors[j] = id;
180
+ }
123
181
 
124
- /**
125
- * Check if an ID exists in this index.
126
- * @param id - The OSM entity ID to check.
127
- * @returns True if the ID exists in this index.
128
- */
129
- has(id: number): boolean {
130
- return this.getIndexFromId(id) !== -1
131
- }
182
+ this.indexBuilt = true;
183
+ }
132
184
 
133
- /**
134
- * Build the index of IDs to positions.
135
- *
136
- * If the IDs are not sorted, we need to sort them and build a new index.
137
- * If the IDs are sorted, we can use the existing index.
138
- */
139
- buildIndex() {
140
- if (this.indexBuilt) throw Error("ID index already built.")
141
- this.ids.compact()
142
- if (!this.idsAreSorted) {
143
- // Build the sorted index
144
- const idsBuffer = new BufferConstructor(
145
- this.size * Float64Array.BYTES_PER_ELEMENT,
146
- )
147
- const posBuffer = new BufferConstructor(
148
- this.size * Uint32Array.BYTES_PER_ELEMENT,
149
- )
150
- this.idsSorted = new Float64Array(idsBuffer)
151
- this.sortedIdPositionToIndex = new Uint32Array(posBuffer)
185
+ /**
186
+ * Look up the internal array index for an OSM entity ID.
187
+ *
188
+ * @param id - The OSM entity ID to look up.
189
+ * @returns The internal array index, or -1 if not found.
190
+ * @throws If the index has not been built.
191
+ */
192
+ getIndexFromId(id: number): number {
193
+ if (!this.indexBuilt) throw Error("IdIndex not built.");
152
194
 
153
- // Fill and sort with positions.
154
- for (let i = 0; i < this.size; i++) {
155
- this.idsSorted[i] = this.ids.at(i)
156
- this.sortedIdPositionToIndex[i] = i
157
- }
195
+ // binary search anchors
196
+ let lo = 0;
197
+ let hi = this.anchors.length - 1;
198
+ while (lo < hi) {
199
+ const mid = (lo + hi + 1) >>> 1;
200
+ const anchor = this.anchors[mid];
201
+ assertValue(anchor, "Anchor is undefined");
202
+ if (anchor <= id) lo = mid;
203
+ else hi = mid - 1;
204
+ }
205
+ const start = lo * BLOCK_SIZE;
206
+ const end = Math.min(start + BLOCK_SIZE, this.idsSorted.length);
158
207
 
159
- // Sort by id, carrying position; use native sort on chunks or a custom radix/merge for stability.
160
- // For simplicity:
161
- const tmp: { id: number; pos: number }[] = Array.from(
162
- { length: this.size },
163
- (_, i) => ({
164
- id: this.idsSorted[i] as number,
165
- pos: this.sortedIdPositionToIndex[i] as number,
166
- }),
167
- )
168
- tmp.sort((a, b) => a.id - b.id)
169
- tmp.forEach(({ id, pos }, i) => {
170
- this.idsSorted[i] = id
171
- this.sortedIdPositionToIndex[i] = pos
172
- })
173
- } else {
174
- // Point to the same array
175
- this.idsSorted = this.ids.array
176
- // Create the sortedIdPositionToIndex array
177
- const posBuffer = new BufferConstructor(
178
- this.size * Uint32Array.BYTES_PER_ELEMENT,
179
- )
180
- this.sortedIdPositionToIndex = new Uint32Array(posBuffer)
181
- for (let i = 0; i < this.size; i++) {
182
- this.sortedIdPositionToIndex[i] = i
183
- }
184
- }
208
+ // binary search within block
209
+ let l = start;
210
+ let r = end - 1;
211
+ while (l <= r) {
212
+ const m = (l + r) >>> 1;
213
+ const v = this.idsSorted[m];
214
+ assertValue(v, "Value is undefined");
215
+ if (v === id) {
216
+ if (this.idsAreSorted) return m;
217
+ const index = this.sortedIdPositionToIndex[m];
218
+ assertValue(index, "Position is undefined");
219
+ return index;
220
+ }
221
+ if (v < id) l = m + 1;
222
+ else r = m - 1;
223
+ }
185
224
 
186
- // Build anchors (every blockSize-th key)
187
- const aLen = Math.ceil(this.size / BLOCK_SIZE)
188
- const sab = new BufferConstructor(aLen * Float64Array.BYTES_PER_ELEMENT)
189
- this.anchors = new Float64Array(sab, 0, aLen)
190
- for (let j = 0; j < aLen; j++) {
191
- const id = this.idsSorted[Math.min(j * BLOCK_SIZE, this.size - 1)]
192
- assertValue(id, "ID is undefined")
193
- this.anchors[j] = id
194
- }
225
+ // ID not found
226
+ return -1;
227
+ }
195
228
 
196
- this.indexBuilt = true
197
- }
229
+ /**
230
+ * Pass an ID or an index, get both.
231
+ */
232
+ idOrIndex(i: IdOrIndex): [index: number, id: number] {
233
+ if ("id" in i) return [this.getIndexFromId(i.id), i.id];
234
+ return [i.index, this.at(i.index)];
235
+ }
198
236
 
199
- /**
200
- * Look up the internal array index for an OSM entity ID.
201
- *
202
- * @param id - The OSM entity ID to look up.
203
- * @returns The internal array index, or -1 if not found.
204
- * @throws If the index has not been built.
205
- */
206
- getIndexFromId(id: number): number {
207
- if (!this.indexBuilt) throw Error("IdIndex not built.")
237
+ /** Returns the sorted array of IDs for iteration. */
238
+ get sorted() {
239
+ return this.idsSorted;
240
+ }
208
241
 
209
- // binary search anchors
210
- let lo = 0
211
- let hi = this.anchors.length - 1
212
- while (lo < hi) {
213
- const mid = (lo + hi + 1) >>> 1
214
- const anchor = this.anchors[mid]
215
- assertValue(anchor, "Anchor is undefined")
216
- if (anchor <= id) lo = mid
217
- else hi = mid - 1
218
- }
219
- const start = lo * BLOCK_SIZE
220
- const end = Math.min(start + BLOCK_SIZE, this.idsSorted.length)
242
+ /** @internal Iterate sorted IDs with their original storage positions. */
243
+ *sortedEntries(): Generator<readonly [id: number, index: number]> {
244
+ for (let i = 0; i < this.idsSorted.length; i++) {
245
+ yield this.sortedEntry(i);
246
+ }
247
+ }
221
248
 
222
- // binary search within block
223
- let l = start
224
- let r = end - 1
225
- while (l <= r) {
226
- const m = (l + r) >>> 1
227
- const v = this.idsSorted[m]
228
- assertValue(v, "Value is undefined")
229
- if (v === id) {
230
- if (this.idsAreSorted) return m
231
- const index = this.sortedIdPositionToIndex[m]
232
- assertValue(index, "Position is undefined")
233
- return index
234
- }
235
- if (v < id) l = m + 1
236
- else r = m - 1
237
- }
249
+ /**
250
+ * Iterate in canonical OSM file order: negative IDs first by increasing
251
+ * absolute value, followed by non-negative IDs in ascending order.
252
+ *
253
+ * @internal
254
+ */
255
+ *osmSortedEntries(): Generator<readonly [id: number, index: number]> {
256
+ let firstNonNegative = 0;
257
+ while (firstNonNegative < this.idsSorted.length && this.idsSorted[firstNonNegative]! < 0) {
258
+ firstNonNegative++;
259
+ }
238
260
 
239
- // ID not found
240
- return -1
241
- }
261
+ // Numeric sorting places negative IDs in the opposite of canonical OSM
262
+ // order. Reverse ID groups while preserving duplicate insertion order.
263
+ let groupEnd = firstNonNegative;
264
+ while (groupEnd > 0) {
265
+ const id = this.idsSorted[groupEnd - 1];
266
+ assertValue(id, "Sorted ID is undefined");
267
+ let groupStart = groupEnd - 1;
268
+ while (groupStart > 0 && this.idsSorted[groupStart - 1] === id) groupStart--;
269
+ for (let position = groupStart; position < groupEnd; position++) {
270
+ yield this.sortedEntry(position);
271
+ }
272
+ groupEnd = groupStart;
273
+ }
242
274
 
243
- /**
244
- * Pass an ID or an index, get both.
245
- */
246
- idOrIndex(i: IdOrIndex): [index: number, id: number] {
247
- if ("id" in i) return [this.getIndexFromId(i.id), i.id]
248
- return [i.index, this.at(i.index)]
249
- }
275
+ for (let position = firstNonNegative; position < this.idsSorted.length; position++) {
276
+ yield this.sortedEntry(position);
277
+ }
278
+ }
250
279
 
251
- /** Returns the sorted array of IDs for iteration. */
252
- get sorted() {
253
- return this.idsSorted
254
- }
280
+ private sortedEntry(position: number): readonly [id: number, index: number] {
281
+ const id = this.idsSorted[position];
282
+ assertValue(id, "Sorted ID is undefined");
283
+ if (this.idsAreSorted) return [id, position];
284
+ const index = this.sortedIdPositionToIndex[position];
285
+ assertValue(index, "Sorted position is undefined");
286
+ return [id, index];
287
+ }
255
288
 
256
- /**
257
- * Get transferable buffers for passing to another thread.
258
- * @returns Serializable representation of this index.
259
- */
260
- transferables(): IdsTransferables {
261
- return {
262
- ids: this.ids.array.buffer,
263
- sortedIds: this.idsSorted.buffer,
264
- sortedIdPositionToIndex: this.sortedIdPositionToIndex.buffer,
265
- anchors: this.anchors.buffer,
266
- idsAreSorted: this.idsAreSorted,
267
- }
268
- }
289
+ /**
290
+ * Get transferable buffers for passing to another thread.
291
+ * @returns Serializable representation of this index.
292
+ */
293
+ transferables(): IdsTransferables {
294
+ const base: IdsTransferables = {
295
+ ids: this.ids.array.buffer,
296
+ anchors: this.anchors.buffer,
297
+ idsAreSorted: this.idsAreSorted,
298
+ };
299
+ if (this.idsAreSorted) return base;
300
+ return {
301
+ ...base,
302
+ sortedIds: this.idsSorted.buffer,
303
+ sortedIdPositionToIndex: this.sortedIdPositionToIndex.buffer,
304
+ };
305
+ }
269
306
 
270
- /**
271
- * Get the approximate memory requirements for a given number of IDs in bytes.
272
- */
273
- static getBytesRequired(count: number) {
274
- if (count === 0) return 0
275
- return (
276
- count * Float64Array.BYTES_PER_ELEMENT + // ids
277
- count * Float64Array.BYTES_PER_ELEMENT + // sortedIds
278
- count * Uint32Array.BYTES_PER_ELEMENT + // sortedIdPositionToIndex
279
- Math.ceil(count / BLOCK_SIZE) * Float64Array.BYTES_PER_ELEMENT // anchors
280
- )
281
- }
307
+ /**
308
+ * Get the approximate memory requirements for a given number of IDs in bytes.
309
+ *
310
+ * `idsAreSorted` defaults to true because PBF entity collections arrive in
311
+ * ascending ID order and retain no separate sort buffers. Pass false when
312
+ * estimating a programmatically built (unsorted) collection, which also
313
+ * retains sorted IDs plus a position map.
314
+ */
315
+ static getBytesRequired(count: number, idsAreSorted = true) {
316
+ if (count === 0) return 0;
317
+ return (
318
+ count * Float64Array.BYTES_PER_ELEMENT + // ids
319
+ (idsAreSorted
320
+ ? 0
321
+ : count * (Float64Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT)) + // sort index
322
+ Math.ceil(count / BLOCK_SIZE) * Float64Array.BYTES_PER_ELEMENT // anchors
323
+ );
324
+ }
282
325
 
283
- /**
284
- * Update a ContentHasher with the IDs data.
285
- * Uses the sorted IDs for consistent hashing regardless of insertion order.
286
- */
287
- updateHash(hasher: ContentHasher): ContentHasher {
288
- return hasher.update(this.idsSorted)
289
- }
326
+ /**
327
+ * Update a ContentHasher with the IDs data.
328
+ * Uses the sorted IDs for consistent hashing regardless of insertion order.
329
+ */
330
+ updateHash(hasher: ContentHasher): ContentHasher {
331
+ return hasher.update(this.idsSorted);
332
+ }
290
333
  }
package/src/index.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  * Efficiently stores and queries OpenStreetMap entities (nodes, ways, relations).
5
5
  *
6
6
  * Features:
7
- * - **Memory-efficient**: Uses typed arrays and Int32Array microdegrees.
8
- * - **Spatial indexing**: KDBush (points) and Flatbush (bboxes) for fast geographic queries.
7
+ * - **Memory-efficient**: Uses typed arrays; coordinates are Int32 at 1e-7 degrees.
8
+ * - **Spatial indexing**: Compact indirect KD trees (points) and Flatbush (bboxes).
9
9
  * - **Worker-ready**: Zero-copy transfer via `transferables()`.
10
10
  * - **Tag indexing**: Fast reverse lookup by tag key.
11
11
  *
@@ -22,16 +22,25 @@
22
22
  * @module @osmix/core
23
23
  */
24
24
 
25
- /// <reference path="./types/kdbush.d.ts" />
26
- /// <reference path="./types/geokdbush.d.ts" />
25
+ // oxlint-disable-next-line typescript/triple-slash-reference -- ambient module declarations for untyped packages
27
26
  /// <reference path="./types/geoflatbush.d.ts" />
28
27
 
29
- export type { IdOrIndex } from "./ids.ts"
30
- export * from "./mocks.ts"
31
- export * from "./nodes.ts"
32
- export * from "./osm.ts"
33
- export * from "./relations.ts"
34
- export * from "./stringtable.ts"
35
- export * from "./tags.ts"
36
- export { BufferConstructor, type BufferType } from "./typed-arrays.ts"
37
- export * from "./ways.ts"
28
+ export type { IdOrIndex } from "./ids.ts";
29
+ export type { OsmReader, OsmWriter } from "./contracts.ts";
30
+ export { OsmEntityIndexBuildError, type OsmEntityIndexComponent } from "./entities.ts";
31
+ export * from "./limits.ts";
32
+ export * from "./nodes.ts";
33
+ export * from "./osm.ts";
34
+ export * from "./relations.ts";
35
+ export { negativeIdMap, renumberNegativeIds, type OsmIdMap } from "./renumber.ts";
36
+ export * from "./stringtable.ts";
37
+ export * from "./tags.ts";
38
+ export {
39
+ BufferConstructor,
40
+ ResizeableTypedArray,
41
+ TypedBufferAllocationError,
42
+ type BufferType,
43
+ type TypedBufferAllocationOperation,
44
+ type TypedBufferType,
45
+ } from "./typed-arrays.ts";
46
+ export * from "./ways.ts";