@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.
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +136 -72
- package/dist/contracts.d.ts +27 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +1 -0
- package/dist/contracts.js.map +1 -0
- package/dist/entities.d.ts +22 -2
- package/dist/entities.d.ts.map +1 -1
- package/dist/entities.js +54 -10
- package/dist/entities.js.map +1 -1
- package/dist/ids.d.ts +20 -5
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +87 -29
- package/dist/ids.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/indirect-kd-index.d.ts +28 -0
- package/dist/indirect-kd-index.d.ts.map +1 -0
- package/dist/indirect-kd-index.js +163 -0
- package/dist/indirect-kd-index.js.map +1 -0
- package/dist/limits.d.ts +30 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +51 -0
- package/dist/limits.js.map +1 -0
- package/dist/mocks.d.ts.map +1 -1
- package/dist/mocks.js.map +1 -1
- package/dist/nodes.d.ts +36 -15
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +185 -62
- package/dist/nodes.js.map +1 -1
- package/dist/osm.d.ts +60 -4
- package/dist/osm.d.ts.map +1 -1
- package/dist/osm.js +32 -6
- package/dist/osm.js.map +1 -1
- package/dist/relations.d.ts +11 -4
- package/dist/relations.d.ts.map +1 -1
- package/dist/relations.js +73 -29
- package/dist/relations.js.map +1 -1
- package/dist/renumber.d.ts +23 -0
- package/dist/renumber.d.ts.map +1 -0
- package/dist/renumber.js +64 -0
- package/dist/renumber.js.map +1 -0
- package/dist/stringtable.d.ts +2 -1
- package/dist/stringtable.d.ts.map +1 -1
- package/dist/stringtable.js +9 -14
- package/dist/stringtable.js.map +1 -1
- package/dist/tags.d.ts +45 -12
- package/dist/tags.d.ts.map +1 -1
- package/dist/tags.js +153 -27
- package/dist/tags.js.map +1 -1
- package/dist/typed-arrays.d.ts +63 -48
- package/dist/typed-arrays.d.ts.map +1 -1
- package/dist/typed-arrays.js +184 -107
- package/dist/typed-arrays.js.map +1 -1
- package/dist/ways.d.ts +52 -3
- package/dist/ways.d.ts.map +1 -1
- package/dist/ways.js +223 -46
- package/dist/ways.js.map +1 -1
- package/package.json +45 -31
- package/src/contracts.ts +29 -0
- package/src/entities.ts +242 -194
- package/src/ids.ts +278 -235
- package/src/index.ts +22 -13
- package/src/indirect-kd-index.ts +212 -0
- package/src/limits.ts +73 -0
- package/src/mocks.ts +114 -113
- package/src/nodes.ts +538 -397
- package/src/osm.ts +282 -207
- package/src/relations.ts +580 -558
- package/src/renumber.ts +80 -0
- package/src/stringtable.ts +189 -184
- package/src/tags.ts +406 -272
- package/src/typed-arrays.ts +307 -205
- package/src/types/geoflatbush.d.ts +28 -33
- package/src/ways.ts +577 -406
- package/test/entity-index-errors.test.ts +41 -0
- package/test/ids.bench.ts +33 -0
- package/test/ids.test.ts +122 -0
- package/test/limits.test.ts +83 -0
- package/test/memory.test.ts +29 -30
- package/test/no-shared-array-buffer.test.ts +41 -0
- package/test/nodes-spatial-index.test.ts +261 -0
- package/test/osm-bbox.test.ts +30 -0
- package/test/relations.bench.ts +32 -0
- package/test/relations.test.ts +124 -0
- package/test/renumber.test.ts +54 -0
- package/test/spatial-index-rebuild.test.ts +33 -0
- package/test/storage-compaction.test.ts +269 -0
- package/test/tags-search.test.ts +89 -56
- package/test/typed-arrays.test.ts +252 -132
- package/tsconfig.build.json +6 -6
- package/tsconfig.json +7 -7
- package/src/types/geokdbush.d.ts +0 -37
- 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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
8
|
-
* - **Spatial indexing**:
|
|
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
|
-
|
|
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
|
|
31
|
-
export
|
|
32
|
-
export * from "./
|
|
33
|
-
export * from "./
|
|
34
|
-
export * from "./
|
|
35
|
-
export * from "./
|
|
36
|
-
export {
|
|
37
|
-
export * from "./
|
|
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";
|