@osmix/core 0.0.2 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +118 -107
- package/dist/core/src/entities.d.ts +114 -0
- package/dist/core/src/entities.d.ts.map +1 -0
- package/dist/core/src/entities.js +152 -0
- package/dist/core/src/entities.js.map +1 -0
- package/dist/core/src/ids.d.ts +113 -0
- package/dist/core/src/ids.d.ts.map +1 -0
- package/dist/{src → core/src}/ids.js +96 -31
- package/dist/core/src/ids.js.map +1 -0
- package/dist/core/src/index.d.ts +32 -0
- package/dist/core/src/index.d.ts.map +1 -0
- package/dist/core/src/index.js +35 -0
- package/dist/core/src/index.js.map +1 -0
- package/dist/core/src/nodes.d.ts +101 -0
- package/dist/core/src/nodes.d.ts.map +1 -0
- package/dist/core/src/nodes.js +309 -0
- package/dist/core/src/nodes.js.map +1 -0
- package/dist/core/src/osm.d.ts +74 -0
- package/dist/core/src/osm.d.ts.map +1 -0
- package/dist/core/src/osm.js +120 -0
- package/dist/core/src/osm.js.map +1 -0
- package/dist/core/src/relations.d.ts +118 -0
- package/dist/core/src/relations.d.ts.map +1 -0
- package/dist/core/src/relations.js +412 -0
- package/dist/core/src/relations.js.map +1 -0
- package/dist/core/src/stringtable.d.ts +95 -0
- package/dist/core/src/stringtable.d.ts.map +1 -0
- package/dist/core/src/stringtable.js +170 -0
- package/dist/core/src/stringtable.js.map +1 -0
- package/dist/core/src/tags.d.ts +126 -0
- package/dist/core/src/tags.d.ts.map +1 -0
- package/dist/core/src/tags.js +228 -0
- package/dist/core/src/tags.js.map +1 -0
- package/dist/core/src/typed-arrays.d.ts +124 -0
- package/dist/core/src/typed-arrays.d.ts.map +1 -0
- package/dist/{src → core/src}/typed-arrays.js +88 -9
- package/dist/core/src/typed-arrays.js.map +1 -0
- package/dist/core/src/utils.d.ts +11 -0
- package/dist/core/src/utils.d.ts.map +1 -0
- package/dist/{src → core/src}/utils.js +8 -10
- package/dist/core/src/utils.js.map +1 -0
- package/dist/core/src/ways.d.ts +101 -0
- package/dist/core/src/ways.d.ts.map +1 -0
- package/dist/core/src/ways.js +336 -0
- package/dist/core/src/ways.js.map +1 -0
- package/dist/core/test/memory.test.d.ts +7 -0
- package/dist/core/test/memory.test.d.ts.map +1 -0
- package/dist/core/test/memory.test.js +29 -0
- package/dist/core/test/memory.test.js.map +1 -0
- package/dist/{test → core/test}/mock-osm.d.ts +3 -3
- package/dist/core/test/mock-osm.d.ts.map +1 -0
- package/dist/{test → core/test}/mock-osm.js +3 -3
- package/dist/core/test/mock-osm.js.map +1 -0
- package/dist/core/test/tags-search.test.d.ts +2 -0
- package/dist/core/test/tags-search.test.d.ts.map +1 -0
- package/dist/core/test/tags-search.test.js +56 -0
- package/dist/core/test/tags-search.test.js.map +1 -0
- package/dist/core/test/typed-arrays.test.d.ts.map +1 -0
- package/dist/{test → core/test}/typed-arrays.test.js +19 -17
- package/dist/core/test/typed-arrays.test.js.map +1 -0
- package/dist/osmix/src/extract.d.ts +43 -0
- package/dist/osmix/src/extract.d.ts.map +1 -0
- package/dist/osmix/src/extract.js +156 -0
- package/dist/osmix/src/extract.js.map +1 -0
- package/dist/osmix/src/pbf.d.ts +55 -0
- package/dist/osmix/src/pbf.d.ts.map +1 -0
- package/dist/osmix/src/pbf.js +187 -0
- package/dist/osmix/src/pbf.js.map +1 -0
- package/dist/osmix/src/utils.d.ts +36 -0
- package/dist/osmix/src/utils.d.ts.map +1 -0
- package/dist/osmix/src/utils.js +108 -0
- package/dist/osmix/src/utils.js.map +1 -0
- package/dist/osmix/test/extract.bench.d.ts.map +1 -0
- package/dist/{test → osmix/test}/extract.bench.js +8 -5
- package/dist/osmix/test/extract.bench.js.map +1 -0
- package/dist/osmix/test/extract.test.d.ts.map +1 -0
- package/dist/osmix/test/extract.test.js +392 -0
- package/dist/osmix/test/extract.test.js.map +1 -0
- package/package.json +10 -12
- package/src/entities.ts +138 -22
- package/src/ids.ts +117 -45
- package/src/index.ts +34 -5
- package/src/nodes.ts +237 -58
- package/src/osm.ts +174 -0
- package/src/relations.ts +383 -54
- package/src/stringtable.ts +111 -36
- package/src/tags.ts +207 -46
- package/src/typed-arrays.ts +97 -10
- package/src/types/geoflatbush.d.ts +37 -0
- package/src/types/geokdbush.d.ts +37 -0
- package/src/types/kdbush.d.ts +24 -6
- package/src/utils.ts +9 -14
- package/src/ways.ts +248 -84
- package/test/memory.test.ts +38 -0
- package/test/mock-osm.ts +8 -8
- package/test/tags-search.test.ts +60 -0
- package/test/typed-arrays.test.ts +5 -6
- package/tsconfig.json +7 -1
- package/dist/src/entities.d.ts +0 -26
- package/dist/src/entities.d.ts.map +0 -1
- package/dist/src/entities.js +0 -69
- package/dist/src/entities.js.map +0 -1
- package/dist/src/ids.d.ts +0 -50
- package/dist/src/ids.d.ts.map +0 -1
- package/dist/src/ids.js.map +0 -1
- package/dist/src/index.d.ts +0 -6
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -7
- package/dist/src/index.js.map +0 -1
- package/dist/src/nodes.d.ts +0 -36
- package/dist/src/nodes.d.ts.map +0 -1
- package/dist/src/nodes.js +0 -171
- package/dist/src/nodes.js.map +0 -1
- package/dist/src/osmix.d.ts +0 -88
- package/dist/src/osmix.d.ts.map +0 -1
- package/dist/src/osmix.js +0 -438
- package/dist/src/osmix.js.map +0 -1
- package/dist/src/relations.d.ts +0 -31
- package/dist/src/relations.d.ts.map +0 -1
- package/dist/src/relations.js +0 -161
- package/dist/src/relations.js.map +0 -1
- package/dist/src/stringtable.d.ts +0 -37
- package/dist/src/stringtable.d.ts.map +0 -1
- package/dist/src/stringtable.js +0 -101
- package/dist/src/stringtable.js.map +0 -1
- package/dist/src/tags.d.ts +0 -33
- package/dist/src/tags.d.ts.map +0 -1
- package/dist/src/tags.js +0 -92
- package/dist/src/tags.js.map +0 -1
- package/dist/src/typed-arrays.d.ts +0 -50
- package/dist/src/typed-arrays.d.ts.map +0 -1
- package/dist/src/typed-arrays.js.map +0 -1
- package/dist/src/utils.d.ts +0 -4
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js.map +0 -1
- package/dist/src/ways.d.ts +0 -42
- package/dist/src/ways.d.ts.map +0 -1
- package/dist/src/ways.js +0 -186
- package/dist/src/ways.js.map +0 -1
- package/dist/test/extract.bench.d.ts.map +0 -1
- package/dist/test/extract.bench.js.map +0 -1
- package/dist/test/extract.test.d.ts.map +0 -1
- package/dist/test/extract.test.js +0 -127
- package/dist/test/extract.test.js.map +0 -1
- package/dist/test/mock-osm.d.ts.map +0 -1
- package/dist/test/mock-osm.js.map +0 -1
- package/dist/test/read.test.d.ts +0 -2
- package/dist/test/read.test.d.ts.map +0 -1
- package/dist/test/read.test.js +0 -17
- package/dist/test/read.test.js.map +0 -1
- package/dist/test/typed-arrays.test.d.ts.map +0 -1
- package/dist/test/typed-arrays.test.js.map +0 -1
- package/dist/test/write.test.d.ts +0 -2
- package/dist/test/write.test.d.ts.map +0 -1
- package/dist/test/write.test.js +0 -55
- package/dist/test/write.test.js.map +0 -1
- package/src/osmix.ts +0 -556
- package/test/extract.bench.ts +0 -29
- package/test/extract.test.ts +0 -161
- package/test/read.test.ts +0 -22
- package/test/write.test.ts +0 -69
- /package/dist/{test → core/test}/typed-arrays.test.d.ts +0 -0
- /package/dist/{test → osmix/test}/extract.bench.d.ts +0 -0
- /package/dist/{test → osmix/test}/extract.test.d.ts +0 -0
package/src/relations.ts
CHANGED
|
@@ -1,76 +1,122 @@
|
|
|
1
|
+
import type { OsmPbfRelation } from "@osmix/pbf"
|
|
2
|
+
import { assertValue } from "@osmix/shared/assert"
|
|
3
|
+
import {
|
|
4
|
+
buildRelationLineStrings,
|
|
5
|
+
collectRelationPoints,
|
|
6
|
+
isAreaRelation,
|
|
7
|
+
isLineRelation,
|
|
8
|
+
isPointRelation,
|
|
9
|
+
isSuperRelation,
|
|
10
|
+
resolveRelationMembers,
|
|
11
|
+
} from "@osmix/shared/relation-kind"
|
|
12
|
+
import { buildRelationRings } from "@osmix/shared/relation-multipolygon"
|
|
1
13
|
import type {
|
|
14
|
+
GeoBbox2D,
|
|
15
|
+
LonLat,
|
|
2
16
|
OsmEntityType,
|
|
3
17
|
OsmRelation,
|
|
4
18
|
OsmRelationMember,
|
|
5
19
|
OsmTags,
|
|
6
|
-
} from "@osmix/
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
20
|
+
} from "@osmix/shared/types"
|
|
21
|
+
import Flatbush from "flatbush"
|
|
22
|
+
import { around as geoAround } from "geoflatbush"
|
|
9
23
|
import { Entities, type EntitiesTransferables } from "./entities"
|
|
10
|
-
import { Ids } from "./ids"
|
|
24
|
+
import { type IdOrIndex, Ids } from "./ids"
|
|
25
|
+
import type { Nodes } from "./nodes"
|
|
11
26
|
import type StringTable from "./stringtable"
|
|
12
27
|
import { Tags } from "./tags"
|
|
13
28
|
import {
|
|
29
|
+
BufferConstructor,
|
|
14
30
|
type BufferType,
|
|
15
31
|
IdArrayType,
|
|
16
|
-
ResizeableTypedArray,
|
|
32
|
+
ResizeableTypedArray as RTA,
|
|
17
33
|
} from "./typed-arrays"
|
|
34
|
+
import { bboxFromLonLats } from "./utils"
|
|
35
|
+
import type { Ways } from "./ways"
|
|
18
36
|
|
|
19
37
|
const RELATION_MEMBER_TYPES: OsmEntityType[] = ["node", "way", "relation"]
|
|
20
38
|
|
|
21
|
-
export interface RelationsTransferables extends
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
export interface RelationsTransferables<T extends BufferType = BufferType>
|
|
40
|
+
extends EntitiesTransferables<T> {
|
|
41
|
+
memberStart: T
|
|
42
|
+
memberCount: T
|
|
43
|
+
memberRefs: T
|
|
44
|
+
memberTypes: T
|
|
45
|
+
memberRoles: T
|
|
46
|
+
bbox: T
|
|
47
|
+
/** Optional - can be rebuilt via buildSpatialIndex() */
|
|
48
|
+
spatialIndex?: T
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
export class Relations extends Entities<OsmRelation> {
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
private stringTable: StringTable
|
|
53
|
+
|
|
54
|
+
private memberStart: RTA<Uint32Array>
|
|
55
|
+
private memberCount: RTA<Uint16Array> // Maximum 65,535 members per relation
|
|
32
56
|
|
|
33
57
|
// Store the ID of the member because relations have other relations as members.
|
|
34
|
-
memberRefs:
|
|
35
|
-
memberTypes:
|
|
36
|
-
memberRoles:
|
|
37
|
-
|
|
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
|
-
|
|
58
|
+
private memberRefs: RTA<Float64Array>
|
|
59
|
+
private memberTypes: RTA<Uint8Array>
|
|
60
|
+
private memberRoles: RTA<Uint32Array>
|
|
61
|
+
|
|
62
|
+
// Spatial index
|
|
63
|
+
private spatialIndex: Flatbush = new Flatbush(1)
|
|
64
|
+
// Track if spatial index was properly built (vs default empty)
|
|
65
|
+
private spatialIndexBuilt = false
|
|
66
|
+
|
|
67
|
+
// Bounding box of the relation in geographic coordinates
|
|
68
|
+
private bbox: RTA<Float64Array>
|
|
69
|
+
|
|
70
|
+
// Node and Way indexes
|
|
71
|
+
private nodes: Nodes
|
|
72
|
+
private ways: Ways
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a new Relations index.
|
|
76
|
+
*/
|
|
77
|
+
constructor(
|
|
78
|
+
stringTable: StringTable,
|
|
79
|
+
nodes: Nodes,
|
|
80
|
+
ways: Ways,
|
|
81
|
+
transferables?: RelationsTransferables,
|
|
82
|
+
) {
|
|
83
|
+
if (transferables) {
|
|
84
|
+
super(
|
|
85
|
+
"relation",
|
|
86
|
+
new Ids(transferables),
|
|
87
|
+
new Tags(stringTable, transferables),
|
|
88
|
+
)
|
|
89
|
+
this.memberStart = RTA.from(Uint32Array, transferables.memberStart)
|
|
90
|
+
this.memberCount = RTA.from(Uint16Array, transferables.memberCount)
|
|
91
|
+
this.memberRefs = RTA.from(IdArrayType, transferables.memberRefs)
|
|
92
|
+
this.memberTypes = RTA.from(Uint8Array, transferables.memberTypes)
|
|
93
|
+
this.memberRoles = RTA.from(Uint32Array, transferables.memberRoles)
|
|
94
|
+
this.bbox = RTA.from(Float64Array, transferables.bbox)
|
|
95
|
+
// Only load spatial index if provided (not stored in IndexedDB)
|
|
96
|
+
if (transferables.spatialIndex?.byteLength) {
|
|
97
|
+
this.spatialIndex = Flatbush.from(transferables.spatialIndex)
|
|
98
|
+
this.spatialIndexBuilt = true
|
|
99
|
+
}
|
|
100
|
+
this.indexBuilt = true
|
|
101
|
+
} else {
|
|
102
|
+
super("relation", new Ids(), new Tags(stringTable))
|
|
103
|
+
this.memberStart = new RTA(Uint32Array)
|
|
104
|
+
this.memberCount = new RTA(Uint16Array)
|
|
105
|
+
this.memberRefs = new RTA(IdArrayType)
|
|
106
|
+
this.memberTypes = new RTA(Uint8Array)
|
|
107
|
+
this.memberRoles = new RTA(Uint32Array)
|
|
108
|
+
this.bbox = new RTA(Float64Array)
|
|
68
109
|
}
|
|
110
|
+
this.nodes = nodes
|
|
111
|
+
this.ways = ways
|
|
112
|
+
this.stringTable = stringTable
|
|
69
113
|
}
|
|
70
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Add a single relation to the index.
|
|
117
|
+
*/
|
|
71
118
|
addRelation(relation: OsmRelation) {
|
|
72
|
-
this.
|
|
73
|
-
this.tags.addTags(relation.tags)
|
|
119
|
+
const relationIndex = this.addEntity(relation.id, relation.tags ?? {})
|
|
74
120
|
this.memberStart.push(this.memberRefs.length)
|
|
75
121
|
this.memberCount.push(relation.members.length)
|
|
76
122
|
for (const member of relation.members) {
|
|
@@ -78,15 +124,19 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
78
124
|
this.memberTypes.push(RELATION_MEMBER_TYPES.indexOf(member.type))
|
|
79
125
|
this.memberRoles.push(this.stringTable.add(member.role ?? ""))
|
|
80
126
|
}
|
|
127
|
+
return relationIndex
|
|
81
128
|
}
|
|
82
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Bulk add relations directly from a PBF PrimitiveBlock.
|
|
132
|
+
*/
|
|
83
133
|
addRelations(
|
|
84
134
|
relations: OsmPbfRelation[],
|
|
85
|
-
blockStringIndexMap:
|
|
135
|
+
blockStringIndexMap: Uint32Array,
|
|
86
136
|
filter?: (relation: OsmRelation) => OsmRelation | null,
|
|
87
137
|
): number {
|
|
88
138
|
const blockToStringTable = (k: number) => {
|
|
89
|
-
const index = blockStringIndexMap
|
|
139
|
+
const index = blockStringIndexMap[k]
|
|
90
140
|
if (index === undefined) throw Error("Tag key not found")
|
|
91
141
|
return index
|
|
92
142
|
}
|
|
@@ -138,7 +188,7 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
138
188
|
if (filter && filteredRelation === null) continue
|
|
139
189
|
added++
|
|
140
190
|
|
|
141
|
-
this.
|
|
191
|
+
this.addEntity(relation.id, tagKeys, tagValues)
|
|
142
192
|
this.memberStart.push(this.memberRefs.length)
|
|
143
193
|
this.memberCount.push(
|
|
144
194
|
filteredRelation?.members.length ?? memberRefs.length,
|
|
@@ -156,12 +206,13 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
156
206
|
this.stringTable.add(m.role ?? ""),
|
|
157
207
|
) ?? memberRoles,
|
|
158
208
|
)
|
|
159
|
-
|
|
160
|
-
this.tags.addTagKeysAndValues(tagKeys, tagValues)
|
|
161
209
|
}
|
|
162
210
|
return added
|
|
163
211
|
}
|
|
164
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Compact the internal arrays to free up memory.
|
|
215
|
+
*/
|
|
165
216
|
buildEntityIndex() {
|
|
166
217
|
this.memberStart.compact()
|
|
167
218
|
this.memberCount.compact()
|
|
@@ -170,6 +221,121 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
170
221
|
this.memberRoles.compact()
|
|
171
222
|
}
|
|
172
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Build the spatial index for relations.
|
|
226
|
+
* Handles nested relations by resolving all descendant nodes and ways.
|
|
227
|
+
* If bbox data already exists (e.g., loaded from storage), reuses it.
|
|
228
|
+
*/
|
|
229
|
+
buildSpatialIndex() {
|
|
230
|
+
if (!this.nodes.isReady()) throw Error("Node index is not ready.")
|
|
231
|
+
if (!this.ways.isReady()) throw Error("Way index is not ready.")
|
|
232
|
+
if (this.size === 0) return this.spatialIndex
|
|
233
|
+
console.time("RelationIndex.buildSpatialIndex")
|
|
234
|
+
|
|
235
|
+
this.spatialIndex = new Flatbush(
|
|
236
|
+
this.size,
|
|
237
|
+
128,
|
|
238
|
+
Float64Array,
|
|
239
|
+
BufferConstructor,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
// If bbox already has data (loaded from storage), use it directly
|
|
243
|
+
const hasBboxData = this.bbox.length >= this.size * 4
|
|
244
|
+
for (let i = 0; i < this.size; i++) {
|
|
245
|
+
let minX: number
|
|
246
|
+
let minY: number
|
|
247
|
+
let maxX: number
|
|
248
|
+
let maxY: number
|
|
249
|
+
|
|
250
|
+
if (hasBboxData) {
|
|
251
|
+
// Use stored bbox values
|
|
252
|
+
minX = this.bbox.at(i * 4)
|
|
253
|
+
minY = this.bbox.at(i * 4 + 1)
|
|
254
|
+
maxX = this.bbox.at(i * 4 + 2)
|
|
255
|
+
maxY = this.bbox.at(i * 4 + 3)
|
|
256
|
+
} else {
|
|
257
|
+
// Calculate bbox from coordinates
|
|
258
|
+
const lls = this.collectRelationCoordinates(i)
|
|
259
|
+
const bbox = bboxFromLonLats(lls)
|
|
260
|
+
minX = bbox[0]
|
|
261
|
+
minY = bbox[1]
|
|
262
|
+
maxX = bbox[2]
|
|
263
|
+
maxY = bbox[3]
|
|
264
|
+
this.bbox.push(minX)
|
|
265
|
+
this.bbox.push(minY)
|
|
266
|
+
this.bbox.push(maxX)
|
|
267
|
+
this.bbox.push(maxY)
|
|
268
|
+
}
|
|
269
|
+
this.spatialIndex.add(minX, minY, maxX, maxY)
|
|
270
|
+
}
|
|
271
|
+
if (!hasBboxData) {
|
|
272
|
+
this.bbox.compact()
|
|
273
|
+
}
|
|
274
|
+
this.spatialIndex.finish()
|
|
275
|
+
this.spatialIndexBuilt = true
|
|
276
|
+
console.timeEnd("RelationIndex.buildSpatialIndex")
|
|
277
|
+
return this.spatialIndex
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if the spatial index has been built.
|
|
282
|
+
*/
|
|
283
|
+
hasSpatialIndex(): boolean {
|
|
284
|
+
return this.spatialIndexBuilt
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Collect all coordinates from a relation, including nested relations.
|
|
289
|
+
* Used for building bounding boxes and spatial indexes.
|
|
290
|
+
*/
|
|
291
|
+
private collectRelationCoordinates(index: number): LonLat[] {
|
|
292
|
+
const lls: LonLat[] = []
|
|
293
|
+
const relation = this.getByIndex(index)
|
|
294
|
+
|
|
295
|
+
// Resolve nested relations to get all descendant nodes and ways
|
|
296
|
+
const resolved = resolveRelationMembers(
|
|
297
|
+
relation,
|
|
298
|
+
(relId) => {
|
|
299
|
+
const relIndex = this.ids.getIndexFromId(relId)
|
|
300
|
+
if (relIndex === -1) return null
|
|
301
|
+
return this.getByIndex(relIndex)
|
|
302
|
+
},
|
|
303
|
+
10, // max depth
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
// Collect coordinates from resolved nodes
|
|
307
|
+
for (const nodeId of resolved.nodes) {
|
|
308
|
+
const ll = this.nodes.getNodeLonLat({ id: nodeId })
|
|
309
|
+
if (ll) lls.push(ll)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Collect coordinates from resolved ways
|
|
313
|
+
for (const wayId of resolved.ways) {
|
|
314
|
+
const wayIndex = this.ways.ids.getIndexFromId(wayId)
|
|
315
|
+
if (wayIndex === -1) continue
|
|
316
|
+
const wayPositions = this.ways.getCoordinates(wayIndex)
|
|
317
|
+
lls.push(...wayPositions)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return lls
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get the bounding box of a relation.
|
|
325
|
+
*/
|
|
326
|
+
getEntityBbox(i: IdOrIndex): GeoBbox2D {
|
|
327
|
+
const index = "index" in i ? i.index : this.ids.idOrIndex(i)[0]
|
|
328
|
+
return [
|
|
329
|
+
this.bbox.at(index * 4),
|
|
330
|
+
this.bbox.at(index * 4 + 1),
|
|
331
|
+
this.bbox.at(index * 4 + 2),
|
|
332
|
+
this.bbox.at(index * 4 + 3),
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get the full relation entity.
|
|
338
|
+
*/
|
|
173
339
|
getFullEntity(index: number, id: number, tags?: OsmTags): OsmRelation {
|
|
174
340
|
return {
|
|
175
341
|
id,
|
|
@@ -178,6 +344,9 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
178
344
|
}
|
|
179
345
|
}
|
|
180
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Get the members of a relation.
|
|
349
|
+
*/
|
|
181
350
|
getMembersByIndex(
|
|
182
351
|
index: number,
|
|
183
352
|
relationMemberTypes = RELATION_MEMBER_TYPES,
|
|
@@ -196,6 +365,9 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
196
365
|
return members
|
|
197
366
|
}
|
|
198
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Check if a relation includes a specific member.
|
|
370
|
+
*/
|
|
199
371
|
includesMember(
|
|
200
372
|
index: number,
|
|
201
373
|
memberRef: number,
|
|
@@ -218,4 +390,161 @@ export class Relations extends Entities<OsmRelation> {
|
|
|
218
390
|
}
|
|
219
391
|
return false
|
|
220
392
|
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get all way IDs that are members of relations, including nested relations.
|
|
396
|
+
* Used to exclude these ways from individual rendering.
|
|
397
|
+
*/
|
|
398
|
+
getWayMemberIds(): Set<number> {
|
|
399
|
+
const wayIds = new Set<number>()
|
|
400
|
+
for (let i = 0; i < this.size; i++) {
|
|
401
|
+
const relation = this.getByIndex(i)
|
|
402
|
+
const resolved = resolveRelationMembers(
|
|
403
|
+
relation,
|
|
404
|
+
(relId) => {
|
|
405
|
+
const relIndex = this.ids.getIndexFromId(relId)
|
|
406
|
+
if (relIndex === -1) return null
|
|
407
|
+
return this.getByIndex(relIndex)
|
|
408
|
+
},
|
|
409
|
+
10, // max depth
|
|
410
|
+
)
|
|
411
|
+
for (const wayId of resolved.ways) {
|
|
412
|
+
wayIds.add(wayId)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return wayIds
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Get relation geometry based on its kind.
|
|
420
|
+
* Returns coordinates suitable for rendering based on relation type.
|
|
421
|
+
* @param index - Relation index
|
|
422
|
+
* @returns Object with geometry data based on relation kind
|
|
423
|
+
*/
|
|
424
|
+
getRelationGeometry(index: number): {
|
|
425
|
+
points?: LonLat[]
|
|
426
|
+
lineStrings?: LonLat[][]
|
|
427
|
+
rings?: LonLat[][][]
|
|
428
|
+
} {
|
|
429
|
+
const relation = this.getByIndex(index)
|
|
430
|
+
if (isAreaRelation(relation)) {
|
|
431
|
+
return {
|
|
432
|
+
rings: buildRelationRings(
|
|
433
|
+
relation,
|
|
434
|
+
(ref) => this.ways.getById(ref),
|
|
435
|
+
(id) => this.nodes.getNodeLonLat({ id }),
|
|
436
|
+
),
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Point relations
|
|
441
|
+
if (isPointRelation(relation)) {
|
|
442
|
+
return {
|
|
443
|
+
points: collectRelationPoints(relation, (id) =>
|
|
444
|
+
this.nodes.getNodeLonLat({ id }),
|
|
445
|
+
),
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Line relations
|
|
450
|
+
if (isLineRelation(relation)) {
|
|
451
|
+
return {
|
|
452
|
+
lineStrings: buildRelationLineStrings(
|
|
453
|
+
relation,
|
|
454
|
+
(ref) => this.ways.getById(ref),
|
|
455
|
+
(id) => this.nodes.getNodeLonLat({ id }),
|
|
456
|
+
),
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Super relations are handled by individual relation geometry methods
|
|
461
|
+
if (isSuperRelation(relation)) {
|
|
462
|
+
return {}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Area relations are handled by buildRelationRings in shared/relation-multipolygon
|
|
466
|
+
// This method doesn't duplicate that logic
|
|
467
|
+
|
|
468
|
+
return {}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Find relations that intersect a bounding box.
|
|
473
|
+
*/
|
|
474
|
+
intersects(bbox: GeoBbox2D, filterFn?: (index: number) => boolean): number[] {
|
|
475
|
+
if (this.size === 0) return []
|
|
476
|
+
return this.spatialIndex.search(
|
|
477
|
+
bbox[0],
|
|
478
|
+
bbox[1],
|
|
479
|
+
bbox[2],
|
|
480
|
+
bbox[3],
|
|
481
|
+
filterFn,
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Find relation indexes near a point using great-circle distance.
|
|
487
|
+
* @param lon - Longitude in degrees.
|
|
488
|
+
* @param lat - Latitude in degrees.
|
|
489
|
+
* @param maxResults - Maximum number of results to return.
|
|
490
|
+
* @param maxDistanceKm - Maximum distance in kilometers.
|
|
491
|
+
* @returns Array of relation indexes sorted by distance.
|
|
492
|
+
*/
|
|
493
|
+
neighbors(
|
|
494
|
+
lon: number,
|
|
495
|
+
lat: number,
|
|
496
|
+
maxResults?: number,
|
|
497
|
+
maxDistanceKm?: number,
|
|
498
|
+
): number[] {
|
|
499
|
+
if (this.size === 0) return []
|
|
500
|
+
// Use geoflatbush for proper geographic distance calculations
|
|
501
|
+
return geoAround(this.spatialIndex, lon, lat, maxResults, maxDistanceKm)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get transferable objects for passing to another thread.
|
|
506
|
+
* Only includes spatialIndex if it has been built.
|
|
507
|
+
*/
|
|
508
|
+
override transferables(): RelationsTransferables {
|
|
509
|
+
const base = {
|
|
510
|
+
...super.transferables(),
|
|
511
|
+
memberStart: this.memberStart.array.buffer,
|
|
512
|
+
memberCount: this.memberCount.array.buffer,
|
|
513
|
+
memberRefs: this.memberRefs.array.buffer,
|
|
514
|
+
memberTypes: this.memberTypes.array.buffer,
|
|
515
|
+
memberRoles: this.memberRoles.array.buffer,
|
|
516
|
+
bbox: this.bbox.array.buffer,
|
|
517
|
+
}
|
|
518
|
+
// Only include spatial index if it was built
|
|
519
|
+
if (this.spatialIndexBuilt) {
|
|
520
|
+
return { ...base, spatialIndex: this.spatialIndex.data }
|
|
521
|
+
}
|
|
522
|
+
return base
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Get the approximate memory requirements for a given number of relations in bytes.
|
|
527
|
+
*/
|
|
528
|
+
static getBytesRequired(count: number) {
|
|
529
|
+
if (count === 0) return 0
|
|
530
|
+
// Approximate members per relation
|
|
531
|
+
let numNodes = count
|
|
532
|
+
let n = count
|
|
533
|
+
while (n !== 1) {
|
|
534
|
+
n = Math.ceil(n / 128)
|
|
535
|
+
numNodes += n
|
|
536
|
+
}
|
|
537
|
+
const indexBytes = (numNodes < 16384 ? 2 : 4) * numNodes
|
|
538
|
+
const boxesBytes = numNodes * 4 * Float64Array.BYTES_PER_ELEMENT
|
|
539
|
+
const spatialIndexBytes = 8 + indexBytes + boxesBytes
|
|
540
|
+
|
|
541
|
+
return (
|
|
542
|
+
Ids.getBytesRequired(count) +
|
|
543
|
+
Tags.getBytesRequired(count) +
|
|
544
|
+
count * Uint32Array.BYTES_PER_ELEMENT + // memberStart
|
|
545
|
+
count * Uint16Array.BYTES_PER_ELEMENT + // memberCount
|
|
546
|
+
count * 4 * Float64Array.BYTES_PER_ELEMENT + // bbox
|
|
547
|
+
spatialIndexBytes
|
|
548
|
+
)
|
|
549
|
+
}
|
|
221
550
|
}
|