@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/entities.ts
CHANGED
|
@@ -7,24 +7,66 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ContentHasher } from "@osmix/shared/content-hasher"
|
|
11
|
-
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
import type { ContentHasher } from "@osmix/shared/content-hasher";
|
|
11
|
+
import type { GeoBbox2D, OsmEntity, OsmEntityType, OsmTags } from "@osmix/types";
|
|
12
|
+
|
|
13
|
+
import type { IdOrIndex, Ids, IdsTransferables } from "./ids.ts";
|
|
14
|
+
import type { Tags, TagsTransferables } from "./tags.ts";
|
|
15
|
+
import {
|
|
16
|
+
TypedBufferAllocationError,
|
|
17
|
+
type BufferType,
|
|
18
|
+
type TypedBufferAllocationOperation,
|
|
19
|
+
type TypedBufferType,
|
|
20
|
+
} from "./typed-arrays.ts";
|
|
21
|
+
|
|
22
|
+
export type OsmEntityIndexComponent = "ids" | "tags" | "entity-data";
|
|
23
|
+
|
|
24
|
+
/** Whether bbox coordinates describe a non-inverted spatial item. */
|
|
25
|
+
export function isValidSpatialBbox(
|
|
26
|
+
minX: number,
|
|
27
|
+
minY: number,
|
|
28
|
+
maxX: number,
|
|
29
|
+
maxY: number,
|
|
30
|
+
): boolean {
|
|
31
|
+
return minX <= maxX && minY <= maxY;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Structured entity-finalization failure suitable for actionable load diagnostics. */
|
|
35
|
+
export class OsmEntityIndexBuildError extends Error {
|
|
36
|
+
readonly code = "OSM_ENTITY_INDEX_BUILD_FAILED";
|
|
37
|
+
readonly stage = "entity-index-finalization";
|
|
38
|
+
readonly entityType: OsmEntityType;
|
|
39
|
+
readonly component: OsmEntityIndexComponent;
|
|
40
|
+
readonly operation?: TypedBufferAllocationOperation;
|
|
41
|
+
readonly typedArray?: string;
|
|
42
|
+
readonly bufferType?: TypedBufferType;
|
|
43
|
+
readonly elementCount?: number;
|
|
44
|
+
readonly bytesPerElement?: number;
|
|
45
|
+
readonly requiredBytes?: number;
|
|
46
|
+
|
|
47
|
+
constructor(entityType: OsmEntityType, component: OsmEntityIndexComponent, cause: unknown) {
|
|
48
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
49
|
+
super(`Failed to finalize ${entityType} ${component}: ${detail}`, { cause });
|
|
50
|
+
this.name = "OsmEntityIndexBuildError";
|
|
51
|
+
this.entityType = entityType;
|
|
52
|
+
this.component = component;
|
|
53
|
+
if (cause instanceof TypedBufferAllocationError) {
|
|
54
|
+
this.operation = cause.operation;
|
|
55
|
+
this.typedArray = cause.typedArray;
|
|
56
|
+
this.bufferType = cause.bufferType;
|
|
57
|
+
this.elementCount = cause.elementCount;
|
|
58
|
+
this.bytesPerElement = cause.bytesPerElement;
|
|
59
|
+
this.requiredBytes = cause.requiredBytes;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
20
63
|
|
|
21
64
|
/**
|
|
22
65
|
* Serializable representation of an Entities collection for worker transfer.
|
|
23
66
|
* Combines ID and tag transferables; subclasses add entity-specific data.
|
|
24
67
|
*/
|
|
25
68
|
export interface EntitiesTransferables<T extends BufferType = BufferType>
|
|
26
|
-
|
|
27
|
-
TagsTransferables<T> {}
|
|
69
|
+
extends IdsTransferables<T>, TagsTransferables<T> {}
|
|
28
70
|
|
|
29
71
|
/**
|
|
30
72
|
* Abstract base for typed entity collections.
|
|
@@ -35,186 +77,192 @@ export interface EntitiesTransferables<T extends BufferType = BufferType>
|
|
|
35
77
|
* 3. **Query**: Lookups and iteration enabled.
|
|
36
78
|
*/
|
|
37
79
|
export abstract class Entities<T extends OsmEntity> {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
80
|
+
/** The type of entity stored in this collection ("node", "way", or "relation"). */
|
|
81
|
+
indexType: OsmEntityType;
|
|
82
|
+
/** ID storage and lookup */
|
|
83
|
+
ids: Ids;
|
|
84
|
+
/** Tag storage and search */
|
|
85
|
+
tags: Tags;
|
|
86
|
+
|
|
87
|
+
/** Whether buildIndex() has been called */
|
|
88
|
+
protected indexBuilt = false;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a new Entities collection.
|
|
92
|
+
* @param indexType - The entity type ("node", "way", or "relation").
|
|
93
|
+
* @param ids - The ID storage instance.
|
|
94
|
+
* @param tags - The tag storage instance.
|
|
95
|
+
*/
|
|
96
|
+
constructor(indexType: OsmEntityType, ids: Ids, tags: Tags) {
|
|
97
|
+
this.indexType = indexType;
|
|
98
|
+
this.ids = ids;
|
|
99
|
+
this.tags = tags;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get transferable objects for passing to another thread.
|
|
104
|
+
*/
|
|
105
|
+
transferables(): EntitiesTransferables {
|
|
106
|
+
return {
|
|
107
|
+
...this.ids.transferables(),
|
|
108
|
+
...this.tags.transferables(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if the index is built and ready for use.
|
|
114
|
+
*/
|
|
115
|
+
isReady() {
|
|
116
|
+
return this.ids.isReady() && this.tags.isReady() && this.indexBuilt;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Number of entities in this collection. */
|
|
120
|
+
get size() {
|
|
121
|
+
return this.ids.size;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Compact entity-specific typed arrays.
|
|
126
|
+
* Called by `buildIndex()` after ID and tag indexes are built.
|
|
127
|
+
* @abstract
|
|
128
|
+
*/
|
|
129
|
+
abstract buildEntityIndex(): void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Finalize indexes.
|
|
133
|
+
* Must be called after adding entities and before querying.
|
|
134
|
+
*/
|
|
135
|
+
buildIndex() {
|
|
136
|
+
if (this.indexBuilt) return;
|
|
137
|
+
console.time(`${this.indexType}Index.buildIndex`);
|
|
138
|
+
this.buildIndexComponent("ids", () => this.ids.buildIndex());
|
|
139
|
+
this.buildIndexComponent("tags", () => this.tags.buildIndex());
|
|
140
|
+
this.buildIndexComponent("entity-data", () => this.buildEntityIndex());
|
|
141
|
+
this.indexBuilt = true;
|
|
142
|
+
console.timeEnd(`${this.indexType}Index.buildIndex`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private buildIndexComponent(component: OsmEntityIndexComponent, build: () => void): void {
|
|
146
|
+
try {
|
|
147
|
+
build();
|
|
148
|
+
} catch (cause) {
|
|
149
|
+
if (cause instanceof OsmEntityIndexBuildError) throw cause;
|
|
150
|
+
throw new OsmEntityIndexBuildError(this.indexType, component, cause);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get the bounding box of an entity.
|
|
156
|
+
* @param idOrIndex - Entity identifier (by ID or internal index).
|
|
157
|
+
* @returns Geographic bounding box [minLon, minLat, maxLon, maxLat].
|
|
158
|
+
* @abstract
|
|
159
|
+
*/
|
|
160
|
+
abstract getEntityBbox(idOrIndex: IdOrIndex): GeoBbox2D;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Reconstruct a full entity object from index data.
|
|
164
|
+
* @param index - Internal array index.
|
|
165
|
+
* @param id - OSM entity ID.
|
|
166
|
+
* @param tags - Optional pre-fetched tags.
|
|
167
|
+
* @returns The complete entity object.
|
|
168
|
+
* @abstract
|
|
169
|
+
*/
|
|
170
|
+
abstract getFullEntity(index: number, id: number, tags?: OsmTags): T;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Add an entity to the index. Tags can be provided as an array of keys and values, or as an object.
|
|
174
|
+
*/
|
|
175
|
+
addEntity(id: number, tags: number[], values: number[]): number;
|
|
176
|
+
addEntity(id: number, tags: OsmTags): number;
|
|
177
|
+
addEntity(id: number, tags: OsmTags | number[], values?: number[]): number {
|
|
178
|
+
const entityIndex = this.ids.add(id);
|
|
179
|
+
if (Array.isArray(tags)) {
|
|
180
|
+
if (values === undefined) throw Error("Values are required when tags is an array");
|
|
181
|
+
this.tags.addTagKeysAndValues(entityIndex, tags, values);
|
|
182
|
+
} else {
|
|
183
|
+
this.tags.addTags(entityIndex, tags);
|
|
184
|
+
}
|
|
185
|
+
return entityIndex;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get an entity by ID or index.
|
|
190
|
+
*/
|
|
191
|
+
get(idOrIndex: IdOrIndex): T | null {
|
|
192
|
+
const [index, id] = this.ids.idOrIndex(idOrIndex);
|
|
193
|
+
if (index === -1) return null;
|
|
194
|
+
return this.getFullEntity(index, id, this.tags.getTags(index));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get an entity by index.
|
|
199
|
+
*/
|
|
200
|
+
getByIndex(index: number): T {
|
|
201
|
+
return this.getFullEntity(index, this.ids.at(index), this.tags.getTags(index));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get multiple entities by their indexes.
|
|
206
|
+
*/
|
|
207
|
+
getEntitiesByIndex(indexes: number[]): T[] {
|
|
208
|
+
const entities: T[] = [];
|
|
209
|
+
for (const index of indexes) {
|
|
210
|
+
const entity = this.getByIndex(index);
|
|
211
|
+
if (entity) entities.push(entity);
|
|
212
|
+
else throw Error(`Entity not found at index ${index}`);
|
|
213
|
+
}
|
|
214
|
+
return entities;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Iterate over all entities in the index.
|
|
219
|
+
*/
|
|
220
|
+
*[Symbol.iterator](): Generator<T> {
|
|
221
|
+
for (let i = 0; i < this.size; i++) {
|
|
222
|
+
yield this.getFullEntity(i, this.ids.at(i), this.tags.getTags(i));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get an entity by ID.
|
|
228
|
+
*/
|
|
229
|
+
getById(id: number): T | null {
|
|
230
|
+
const index = this.ids.getIndexFromId(id);
|
|
231
|
+
if (index !== -1) return this.getFullEntity(index, id, this.tags.getTags(index));
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Iterate over entities sorted by ID.
|
|
237
|
+
*/
|
|
238
|
+
*sorted(): Generator<T> {
|
|
239
|
+
for (const [id, index] of this.ids.sortedEntries()) {
|
|
240
|
+
yield this.getFullEntity(index, id, this.tags.getTags(index));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** @internal Iterate entities in canonical OSM file order. */
|
|
245
|
+
*osmSorted(): Generator<T> {
|
|
246
|
+
for (const [id, index] of this.ids.osmSortedEntries()) {
|
|
247
|
+
yield this.getFullEntity(index, id, this.tags.getTags(index));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Search for entities with a specific tag key and optional value.
|
|
253
|
+
*/
|
|
254
|
+
search(key: string, val?: string): T[] {
|
|
255
|
+
const keyIndex = this.tags.find(key);
|
|
256
|
+
const entities = this.tags.hasKey(keyIndex).map((index) => this.getByIndex(index));
|
|
257
|
+
if (val === undefined) return entities;
|
|
258
|
+
return entities.filter((entity) => entity.tags?.[key] === val);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Update a ContentHasher with this entity collection's base data (IDs and tags).
|
|
263
|
+
* Subclasses should override to add entity-specific data.
|
|
264
|
+
*/
|
|
265
|
+
updateHash(hasher: ContentHasher): ContentHasher {
|
|
266
|
+
return this.ids.updateHash(this.tags.updateHash(hasher));
|
|
267
|
+
}
|
|
220
268
|
}
|