@osmix/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/nodes.ts ADDED
@@ -0,0 +1,214 @@
1
+ import type { OsmNode, OsmTags } from "@osmix/json"
2
+ import type { OsmPbfBlock, OsmPbfDenseNodes } from "@osmix/pbf"
3
+ import { assertValue } from "@osmix/shared/assert"
4
+ import type { GeoBbox2D } from "@osmix/shared/types"
5
+ import KDBush from "kdbush"
6
+ import { Entities, type EntitiesTransferables } from "./entities"
7
+ import { type IdOrIndex, Ids } from "./ids"
8
+ import type StringTable from "./stringtable"
9
+ import { Tags } from "./tags"
10
+ import {
11
+ BufferConstructor,
12
+ type BufferType,
13
+ CoordinateArrayType,
14
+ ResizeableTypedArray,
15
+ } from "./typed-arrays"
16
+
17
+ export interface NodesTransferables extends EntitiesTransferables {
18
+ lons: BufferType
19
+ lats: BufferType
20
+ bbox: GeoBbox2D
21
+ spatialIndex: BufferType
22
+ }
23
+
24
+ export interface AddNodeOptions {
25
+ filter?: (node: OsmNode) => boolean
26
+ }
27
+
28
+ export class Nodes extends Entities<OsmNode> {
29
+ lons: ResizeableTypedArray<Float64Array>
30
+ lats: ResizeableTypedArray<Float64Array>
31
+ bbox: GeoBbox2D = [
32
+ Number.POSITIVE_INFINITY,
33
+ Number.POSITIVE_INFINITY,
34
+ Number.NEGATIVE_INFINITY,
35
+ Number.NEGATIVE_INFINITY,
36
+ ]
37
+ spatialIndex: KDBush = new KDBush(0, 128, Float64Array, BufferConstructor)
38
+
39
+ static from(stringTable: StringTable, nits: NodesTransferables) {
40
+ const idIndex = Ids.from(nits)
41
+ const tagIndex = Tags.from(stringTable, nits)
42
+ const nodeIndex = new Nodes(stringTable, idIndex, tagIndex)
43
+ nodeIndex.lons = ResizeableTypedArray.from(CoordinateArrayType, nits.lons)
44
+ nodeIndex.lats = ResizeableTypedArray.from(CoordinateArrayType, nits.lats)
45
+ nodeIndex.bbox = nits.bbox
46
+ nodeIndex.spatialIndex = KDBush.from(nits.spatialIndex)
47
+ return nodeIndex
48
+ }
49
+
50
+ constructor(stringTable: StringTable, idIndex?: Ids, tagIndex?: Tags) {
51
+ super("node", stringTable, idIndex, tagIndex)
52
+ this.lons = new ResizeableTypedArray(CoordinateArrayType)
53
+ this.lats = new ResizeableTypedArray(CoordinateArrayType)
54
+ }
55
+
56
+ transferables(): NodesTransferables {
57
+ return {
58
+ lons: this.lons.array.buffer,
59
+ lats: this.lats.array.buffer,
60
+ bbox: this.bbox,
61
+ spatialIndex: this.spatialIndex.data,
62
+ ...this.ids.transferables(),
63
+ ...this.tags.transferables(),
64
+ }
65
+ }
66
+
67
+ addNode(node: OsmNode) {
68
+ this.ids.add(node.id)
69
+ this.tags.addTags(node.tags)
70
+
71
+ this.lons.push(node.lon)
72
+ this.lats.push(node.lat)
73
+
74
+ if (node.lon < this.bbox[0]) this.bbox[0] = node.lon
75
+ if (node.lat < this.bbox[1]) this.bbox[1] = node.lat
76
+ if (node.lon > this.bbox[2]) this.bbox[2] = node.lon
77
+ if (node.lat > this.bbox[3]) this.bbox[3] = node.lat
78
+ }
79
+
80
+ addDenseNodes(
81
+ dense: OsmPbfDenseNodes,
82
+ block: OsmPbfBlock,
83
+ blockStringIndexMap: Map<number, number>,
84
+ filter?: (node: OsmNode) => boolean,
85
+ ): number {
86
+ const lon_offset = block.lon_offset ?? 0
87
+ const lat_offset = block.lat_offset ?? 0
88
+ const granularity = block.granularity ?? 1e7
89
+ const delta = {
90
+ id: 0,
91
+ lat: 0,
92
+ lon: 0,
93
+ timestamp: 0,
94
+ changeset: 0,
95
+ uid: 0,
96
+ user_sid: 0,
97
+ }
98
+
99
+ const getStringTableIndex = (keyIndex: number) => {
100
+ const key = dense.keys_vals[keyIndex]
101
+ assertValue(key, "Block string key is undefined")
102
+ const index = blockStringIndexMap.get(key)
103
+ assertValue(index, "Block string not found")
104
+ return index
105
+ }
106
+
107
+ let keysValsIndex = 0
108
+ let added = 0
109
+ for (let i = 0; i < dense.id.length; i++) {
110
+ const idSid = dense.id[i]
111
+ const latSid = dense.lat[i]
112
+ const lonSid = dense.lon[i]
113
+ assertValue(idSid, "ID SID is undefined")
114
+ assertValue(latSid, "Latitude SID is undefined")
115
+ assertValue(lonSid, "Longitude SID is undefined")
116
+
117
+ delta.id += idSid
118
+ delta.lat += latSid
119
+ delta.lon += lonSid
120
+
121
+ const lon = lon_offset + delta.lon / granularity
122
+ const lat = lat_offset + delta.lat / granularity
123
+
124
+ const tagKeys: number[] = []
125
+ const tagValues: number[] = []
126
+ if (dense.keys_vals.length > 0) {
127
+ while (dense.keys_vals[keysValsIndex] !== 0) {
128
+ const key = getStringTableIndex(keysValsIndex)
129
+ const val = getStringTableIndex(keysValsIndex + 1)
130
+ if (key && val) {
131
+ tagKeys.push(key)
132
+ tagValues.push(val)
133
+ }
134
+ keysValsIndex += 2
135
+ }
136
+ keysValsIndex++
137
+ }
138
+
139
+ const shouldInclude = filter
140
+ ? filter({
141
+ id: delta.id,
142
+ lon,
143
+ lat,
144
+ tags: this.tags.getTagsFromIndices(tagKeys, tagValues),
145
+ })
146
+ : true
147
+ if (!shouldInclude) continue
148
+
149
+ this.ids.add(delta.id)
150
+ this.lons.push(lon)
151
+ this.lats.push(lat)
152
+
153
+ if (lon < this.bbox[0]) this.bbox[0] = lon
154
+ if (lat < this.bbox[1]) this.bbox[1] = lat
155
+ if (lon > this.bbox[2]) this.bbox[2] = lon
156
+ if (lat > this.bbox[3]) this.bbox[3] = lat
157
+ this.tags.addTagKeysAndValues(tagKeys, tagValues)
158
+ added++
159
+ }
160
+
161
+ return added
162
+ }
163
+
164
+ buildEntityIndex() {
165
+ this.lons.compact()
166
+ this.lats.compact()
167
+ }
168
+
169
+ buildSpatialIndex() {
170
+ console.time("NodeIndex.buildSpatialIndex")
171
+ this.spatialIndex = new KDBush(
172
+ this.size,
173
+ 128,
174
+ Float64Array,
175
+ BufferConstructor,
176
+ )
177
+ for (let i = 0; i < this.size; i++) {
178
+ this.spatialIndex.add(this.lons.at(i), this.lats.at(i))
179
+ }
180
+ this.spatialIndex.finish()
181
+ console.timeEnd("NodeIndex.buildSpatialIndex")
182
+ }
183
+
184
+ getNodeLonLat(i: IdOrIndex): [number, number] {
185
+ const index = "index" in i ? i.index : this.ids.idOrIndex(i)[0]
186
+ return [this.lons.at(index), this.lats.at(index)]
187
+ }
188
+
189
+ getFullEntity(index: number, id: number, tags?: OsmTags): OsmNode {
190
+ const [lon, lat] = this.getNodeLonLat({ index })
191
+ if (tags) {
192
+ return {
193
+ id,
194
+ lat,
195
+ lon,
196
+ tags,
197
+ }
198
+ }
199
+ return {
200
+ id,
201
+ lat,
202
+ lon,
203
+ }
204
+ }
205
+
206
+ // Spatial operations
207
+ withinBbox(bbox: GeoBbox2D): number[] {
208
+ return this.spatialIndex.range(bbox[0], bbox[1], bbox[2], bbox[3])
209
+ }
210
+
211
+ withinRadius(x: number, y: number, radius: number): number[] {
212
+ return this.spatialIndex.within(x, y, radius)
213
+ }
214
+ }