@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/CHANGELOG.md +11 -0
- package/README.md +160 -0
- package/package.json +53 -0
- package/src/entities.ts +95 -0
- package/src/ids.ts +210 -0
- package/src/index.ts +7 -0
- package/src/nodes.ts +214 -0
- package/src/osmix.ts +556 -0
- package/src/relations.ts +221 -0
- package/src/stringtable.ts +120 -0
- package/src/tags.ts +122 -0
- package/src/typed-arrays.ts +164 -0
- package/src/types/kdbush.d.ts +29 -0
- package/src/utils.ts +29 -0
- package/src/ways.ts +241 -0
- package/test/extract.bench.ts +29 -0
- package/test/extract.test.ts +156 -0
- package/test/mock-osm.ts +140 -0
- package/test/read.test.ts +22 -0
- package/test/typed-arrays.test.ts +135 -0
- package/test/write.test.ts +66 -0
- package/tsconfig.json +9 -0
package/src/ways.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { OsmTags, OsmWay } from "@osmix/json"
|
|
2
|
+
import type { OsmPbfWay } from "@osmix/pbf"
|
|
3
|
+
import type { GeoBbox2D, LonLat } from "@osmix/shared/types"
|
|
4
|
+
import Flatbush from "flatbush"
|
|
5
|
+
import { Entities, type EntitiesTransferables } from "./entities"
|
|
6
|
+
import { type IdOrIndex, Ids } from "./ids"
|
|
7
|
+
import type { Nodes } from "./nodes"
|
|
8
|
+
import type StringTable from "./stringtable"
|
|
9
|
+
import { Tags } from "./tags"
|
|
10
|
+
import {
|
|
11
|
+
BufferConstructor,
|
|
12
|
+
type BufferType,
|
|
13
|
+
CoordinateArrayType,
|
|
14
|
+
IdArrayType,
|
|
15
|
+
ResizeableTypedArray,
|
|
16
|
+
} from "./typed-arrays"
|
|
17
|
+
|
|
18
|
+
export interface WaysTransferables extends EntitiesTransferables {
|
|
19
|
+
refStart: BufferType
|
|
20
|
+
refCount: BufferType
|
|
21
|
+
refs: BufferType
|
|
22
|
+
bbox: BufferType
|
|
23
|
+
spatialIndex: BufferType
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Ways extends Entities<OsmWay> {
|
|
27
|
+
spatialIndex: Flatbush = new Flatbush(1)
|
|
28
|
+
|
|
29
|
+
refStart: ResizeableTypedArray<Uint32Array>
|
|
30
|
+
refCount: ResizeableTypedArray<Uint16Array> // Maximum 2,000 nodes per way
|
|
31
|
+
|
|
32
|
+
// Node IDs
|
|
33
|
+
refs: ResizeableTypedArray<Float64Array>
|
|
34
|
+
|
|
35
|
+
// Bounding box of the way in geographic coordinates
|
|
36
|
+
bbox: ResizeableTypedArray<Float64Array>
|
|
37
|
+
|
|
38
|
+
static from(stringTable: StringTable, wits: WaysTransferables) {
|
|
39
|
+
const idIndex = Ids.from(wits)
|
|
40
|
+
const tagIndex = Tags.from(stringTable, wits)
|
|
41
|
+
const wayIndex = new Ways(stringTable, idIndex, tagIndex)
|
|
42
|
+
wayIndex.refStart = ResizeableTypedArray.from(Uint32Array, wits.refStart)
|
|
43
|
+
wayIndex.refCount = ResizeableTypedArray.from(Uint16Array, wits.refCount)
|
|
44
|
+
wayIndex.refs = ResizeableTypedArray.from(IdArrayType, wits.refs)
|
|
45
|
+
wayIndex.bbox = ResizeableTypedArray.from(CoordinateArrayType, wits.bbox)
|
|
46
|
+
wayIndex.spatialIndex = Flatbush.from(wits.spatialIndex)
|
|
47
|
+
return wayIndex
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
constructor(stringTable: StringTable, idIndex?: Ids, tagIndex?: Tags) {
|
|
51
|
+
super("way", stringTable, idIndex, tagIndex)
|
|
52
|
+
this.refStart = new ResizeableTypedArray(Uint32Array)
|
|
53
|
+
this.refCount = new ResizeableTypedArray(Uint16Array)
|
|
54
|
+
this.refs = new ResizeableTypedArray(IdArrayType)
|
|
55
|
+
this.bbox = new ResizeableTypedArray(CoordinateArrayType)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
transferables(): WaysTransferables {
|
|
59
|
+
return {
|
|
60
|
+
refStart: this.refStart.array.buffer,
|
|
61
|
+
refCount: this.refCount.array.buffer,
|
|
62
|
+
refs: this.refs.array.buffer,
|
|
63
|
+
bbox: this.bbox.array.buffer,
|
|
64
|
+
spatialIndex: this.spatialIndex.data,
|
|
65
|
+
...this.ids.transferables(),
|
|
66
|
+
...this.tags.transferables(),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
addWay(way: OsmWay) {
|
|
71
|
+
this.ids.add(way.id)
|
|
72
|
+
this.tags.addTags(way.tags)
|
|
73
|
+
this.refStart.push(this.refs.length)
|
|
74
|
+
this.refCount.push(way.refs.length)
|
|
75
|
+
for (const ref of way.refs) this.refs.push(ref)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Bulk add ways directly from a PBF PrimitiveBlock.
|
|
80
|
+
*/
|
|
81
|
+
addWays(
|
|
82
|
+
ways: OsmPbfWay[],
|
|
83
|
+
blockStringIndexMap: Map<number, number>,
|
|
84
|
+
filter?: (way: OsmWay) => OsmWay | null,
|
|
85
|
+
) {
|
|
86
|
+
let added = 0
|
|
87
|
+
for (const way of ways) {
|
|
88
|
+
let prevRefId = 0
|
|
89
|
+
const refs = way.refs.map((refId) => {
|
|
90
|
+
prevRefId += refId
|
|
91
|
+
return prevRefId
|
|
92
|
+
})
|
|
93
|
+
const filteredWay = filter
|
|
94
|
+
? filter({
|
|
95
|
+
id: way.id,
|
|
96
|
+
refs,
|
|
97
|
+
tags: this.tags.getTagsFromIndices(way.keys, way.vals),
|
|
98
|
+
})
|
|
99
|
+
: null
|
|
100
|
+
if (filter && filteredWay === null) continue
|
|
101
|
+
|
|
102
|
+
this.ids.add(way.id)
|
|
103
|
+
this.refStart.push(this.refs.length)
|
|
104
|
+
this.refCount.push(filteredWay?.refs.length ?? refs.length)
|
|
105
|
+
this.refs.pushMany(filteredWay?.refs ?? refs)
|
|
106
|
+
|
|
107
|
+
const tagKeys: number[] = way.keys.map((key) => {
|
|
108
|
+
const index = blockStringIndexMap.get(key)
|
|
109
|
+
if (index === undefined) throw Error("Tag key not found")
|
|
110
|
+
return index
|
|
111
|
+
})
|
|
112
|
+
const tagValues: number[] = way.vals.map((val) => {
|
|
113
|
+
const index = blockStringIndexMap.get(val)
|
|
114
|
+
if (index === undefined) throw Error("Tag value not found")
|
|
115
|
+
return index
|
|
116
|
+
})
|
|
117
|
+
this.tags.addTagKeysAndValues(tagKeys, tagValues)
|
|
118
|
+
added++
|
|
119
|
+
}
|
|
120
|
+
return added
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
buildEntityIndex() {
|
|
124
|
+
this.refStart.compact()
|
|
125
|
+
this.refCount.compact()
|
|
126
|
+
this.refs.compact()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
buildSpatialIndex(nodeIndex: Nodes) {
|
|
130
|
+
console.time("WayIndex.buildSpatialIndex")
|
|
131
|
+
this.spatialIndex = new Flatbush(
|
|
132
|
+
this.size,
|
|
133
|
+
128,
|
|
134
|
+
Float64Array,
|
|
135
|
+
BufferConstructor,
|
|
136
|
+
)
|
|
137
|
+
for (let i = 0; i < this.size; i++) {
|
|
138
|
+
let minX = Number.POSITIVE_INFINITY
|
|
139
|
+
let minY = Number.POSITIVE_INFINITY
|
|
140
|
+
let maxX = Number.NEGATIVE_INFINITY
|
|
141
|
+
let maxY = Number.NEGATIVE_INFINITY
|
|
142
|
+
const start = this.refStart.at(i)
|
|
143
|
+
const count = this.refCount.at(i)
|
|
144
|
+
for (let j = start; j < start + count; j++) {
|
|
145
|
+
const refId = this.refs.at(j)
|
|
146
|
+
const [lon, lat] = nodeIndex.getNodeLonLat({ id: refId })
|
|
147
|
+
if (lon < minX) minX = lon
|
|
148
|
+
if (lon > maxX) maxX = lon
|
|
149
|
+
if (lat < minY) minY = lat
|
|
150
|
+
if (lat > maxY) maxY = lat
|
|
151
|
+
}
|
|
152
|
+
this.bbox.push(minX)
|
|
153
|
+
this.bbox.push(minY)
|
|
154
|
+
this.bbox.push(maxX)
|
|
155
|
+
this.bbox.push(maxY)
|
|
156
|
+
this.spatialIndex.add(minX, minY, maxX, maxY)
|
|
157
|
+
}
|
|
158
|
+
this.spatialIndex.finish()
|
|
159
|
+
console.timeEnd("WayIndex.buildSpatialIndex")
|
|
160
|
+
return this.spatialIndex
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getFullEntity(index: number, id: number, tags?: OsmTags): OsmWay {
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
refs: [...this.getRefIds(index)],
|
|
167
|
+
tags,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getRefIds(index: number) {
|
|
172
|
+
const start = this.refStart.at(index)
|
|
173
|
+
const count = this.refCount.at(index)
|
|
174
|
+
return this.refs.slice(start, start + count)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getBbox(idOrIndex: IdOrIndex): GeoBbox2D {
|
|
178
|
+
const index =
|
|
179
|
+
"index" in idOrIndex ? idOrIndex.index : this.ids.idOrIndex(idOrIndex)[0]
|
|
180
|
+
return [
|
|
181
|
+
this.bbox.at(index * 4),
|
|
182
|
+
this.bbox.at(index * 4 + 1),
|
|
183
|
+
this.bbox.at(index * 4 + 2),
|
|
184
|
+
this.bbox.at(index * 4 + 3),
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getLine(index: number, nodeIndex: Nodes) {
|
|
189
|
+
const count = this.refCount.at(index)
|
|
190
|
+
const start = this.refStart.at(index)
|
|
191
|
+
const line = new Float64Array(count * 2)
|
|
192
|
+
for (let i = 0; i < count; i++) {
|
|
193
|
+
const ref = this.refs.at(start + i)
|
|
194
|
+
const [lon, lat] = nodeIndex.getNodeLonLat({ id: ref })
|
|
195
|
+
line[i * 2] = lon
|
|
196
|
+
line[i * 2 + 1] = lat
|
|
197
|
+
}
|
|
198
|
+
return line
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getCoordinates(index: number, nodeIndex: Nodes): LonLat[] {
|
|
202
|
+
const count = this.refCount.at(index)
|
|
203
|
+
const start = this.refStart.at(index)
|
|
204
|
+
const coords: [number, number][] = []
|
|
205
|
+
for (let refIndex = start; refIndex < start + count; refIndex++) {
|
|
206
|
+
const ref = this.refs.at(refIndex)
|
|
207
|
+
const coord = nodeIndex.getNodeLonLat({ id: ref })
|
|
208
|
+
if (
|
|
209
|
+
coord === undefined ||
|
|
210
|
+
coord[0] === undefined ||
|
|
211
|
+
coord[1] === undefined
|
|
212
|
+
) {
|
|
213
|
+
console.error("node index has ref", nodeIndex.ids.has(ref))
|
|
214
|
+
throw Error(
|
|
215
|
+
`Invalid coordinate for way id ${this.ids.at(index)}, index ${index}, node ref ${ref}, ref index ${refIndex}`,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
coords.push(coord)
|
|
219
|
+
}
|
|
220
|
+
return coords
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
intersects(bbox: GeoBbox2D, filterFn?: (index: number) => boolean): number[] {
|
|
224
|
+
return this.spatialIndex.search(
|
|
225
|
+
bbox[0],
|
|
226
|
+
bbox[1],
|
|
227
|
+
bbox[2],
|
|
228
|
+
bbox[3],
|
|
229
|
+
filterFn,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
neighbors(
|
|
234
|
+
x: number,
|
|
235
|
+
y: number,
|
|
236
|
+
maxResults?: number,
|
|
237
|
+
maxDistance?: number,
|
|
238
|
+
): number[] {
|
|
239
|
+
return this.spatialIndex.neighbors(x, y, maxResults, maxDistance)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getFixtureFile } from "@osmix/shared/test/fixtures"
|
|
2
|
+
import type { GeoBbox2D } from "@osmix/shared/types"
|
|
3
|
+
import { beforeAll, bench, describe } from "vitest"
|
|
4
|
+
import { Osmix } from "../src/osmix"
|
|
5
|
+
|
|
6
|
+
const MONACO_BBOX: GeoBbox2D = [7.4053929, 43.7232244, 7.4447259, 43.7543687]
|
|
7
|
+
// const SEATTLE_BBOX: GeoBbox2D = [-122.33, 47.48, -122.29, 47.52]
|
|
8
|
+
|
|
9
|
+
const BBOX = MONACO_BBOX
|
|
10
|
+
const PBF = "monaco.pbf"
|
|
11
|
+
|
|
12
|
+
let buffer: ArrayBufferLike
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
buffer = await getFixtureFile(PBF)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe("simple extract benchmark", () => {
|
|
19
|
+
bench("two-step parse then extract", async () => {
|
|
20
|
+
const data = buffer.slice(0)
|
|
21
|
+
const full = await Osmix.fromPbf(data)
|
|
22
|
+
full.extract(BBOX)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
bench("streaming extract during parse", async () => {
|
|
26
|
+
const data = buffer.slice(0)
|
|
27
|
+
await Osmix.fromPbf(data, { extractBbox: BBOX })
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { getFixtureFileReadStream } from "@osmix/shared/test/fixtures"
|
|
2
|
+
import type { GeoBbox2D } from "@osmix/shared/types"
|
|
3
|
+
import { assert, test } from "vitest"
|
|
4
|
+
import { Osmix } from "../src/osmix"
|
|
5
|
+
|
|
6
|
+
const TEST_BBOX: GeoBbox2D = [-0.1, -0.1, 1, 1]
|
|
7
|
+
const SEATTLE_BBOX: GeoBbox2D = [-122.463226, 47.469878, -122.180328, 47.82883]
|
|
8
|
+
|
|
9
|
+
function buildSourceOsm() {
|
|
10
|
+
const osm = new Osmix({ id: "source" })
|
|
11
|
+
osm.nodes.addNode({
|
|
12
|
+
id: 1,
|
|
13
|
+
lat: 0,
|
|
14
|
+
lon: 0,
|
|
15
|
+
tags: {
|
|
16
|
+
name: "inside-a",
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
osm.nodes.addNode({
|
|
20
|
+
id: 2,
|
|
21
|
+
lat: 0,
|
|
22
|
+
lon: 2,
|
|
23
|
+
tags: {
|
|
24
|
+
name: "outside-a",
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
osm.nodes.addNode({
|
|
28
|
+
id: 3,
|
|
29
|
+
lat: 0.5,
|
|
30
|
+
lon: 0.5,
|
|
31
|
+
})
|
|
32
|
+
osm.nodes.addNode({
|
|
33
|
+
id: 4,
|
|
34
|
+
lat: 0.5,
|
|
35
|
+
lon: 1.5,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
osm.ways.addWay({
|
|
39
|
+
id: 10,
|
|
40
|
+
refs: [1, 2],
|
|
41
|
+
tags: {
|
|
42
|
+
highway: "residential",
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
osm.ways.addWay({
|
|
46
|
+
id: 11,
|
|
47
|
+
refs: [3, 4],
|
|
48
|
+
tags: {
|
|
49
|
+
highway: "service",
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
osm.relations.addRelation({
|
|
54
|
+
id: 20,
|
|
55
|
+
members: [
|
|
56
|
+
{
|
|
57
|
+
type: "way",
|
|
58
|
+
ref: 10,
|
|
59
|
+
role: "outer",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
tags: {
|
|
63
|
+
type: "multipolygon",
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
osm.buildIndexes()
|
|
68
|
+
return osm
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
test("extract a BBOX while reading a PBF", async () => {
|
|
72
|
+
const source = buildSourceOsm()
|
|
73
|
+
const transform = new TransformStream<Uint8Array, ArrayBufferLike>()
|
|
74
|
+
const extract = new Osmix()
|
|
75
|
+
const extractPromise = extract.readPbf(transform.readable, {
|
|
76
|
+
extractBbox: TEST_BBOX,
|
|
77
|
+
})
|
|
78
|
+
await source.toPbfStream().pipeTo(transform.writable)
|
|
79
|
+
await extractPromise
|
|
80
|
+
|
|
81
|
+
assert.equal(extract.nodes.size, 2)
|
|
82
|
+
assert.isTrue(extract.nodes.ids.has(1))
|
|
83
|
+
assert.isTrue(extract.nodes.ids.has(3))
|
|
84
|
+
assert.isNotTrue(extract.nodes.ids.has(2))
|
|
85
|
+
assert.isNotTrue(extract.nodes.ids.has(4))
|
|
86
|
+
|
|
87
|
+
const truncatedWay = extract.ways.getById(10)
|
|
88
|
+
assert.exists(truncatedWay)
|
|
89
|
+
assert.deepEqual(truncatedWay?.refs, [1])
|
|
90
|
+
|
|
91
|
+
assert.isTrue(extract.ways.ids.has(11))
|
|
92
|
+
assert.isTrue(extract.relations.ids.has(20))
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("extract a BBOX after reading a PBF", async () => {
|
|
96
|
+
const source = buildSourceOsm()
|
|
97
|
+
const buffer = await source.toPbfBuffer()
|
|
98
|
+
|
|
99
|
+
const streaming = new Osmix()
|
|
100
|
+
await streaming.readPbf(buffer.slice(0), { extractBbox: TEST_BBOX })
|
|
101
|
+
|
|
102
|
+
const twoStepOsmix = new Osmix()
|
|
103
|
+
await twoStepOsmix.readPbf(buffer.slice(0))
|
|
104
|
+
const twoStep = twoStepOsmix.extract(TEST_BBOX)
|
|
105
|
+
|
|
106
|
+
assert.equal(streaming.nodes.size, twoStep.nodes.size)
|
|
107
|
+
assert.equal(streaming.ways.size, twoStep.ways.size)
|
|
108
|
+
assert.equal(streaming.relations.size, twoStep.relations.size)
|
|
109
|
+
|
|
110
|
+
assert.deepEqual(
|
|
111
|
+
Array.from(streaming.nodes.ids.sorted),
|
|
112
|
+
Array.from(twoStep.nodes.ids.sorted),
|
|
113
|
+
)
|
|
114
|
+
assert.deepEqual(
|
|
115
|
+
Array.from(streaming.ways.ids.sorted),
|
|
116
|
+
Array.from(twoStep.ways.ids.sorted),
|
|
117
|
+
)
|
|
118
|
+
assert.deepEqual(
|
|
119
|
+
Array.from(streaming.relations.ids.sorted),
|
|
120
|
+
Array.from(twoStep.relations.ids.sorted),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
for (const nodeId of streaming.nodes.ids.sorted) {
|
|
124
|
+
const streamingNode = streaming.nodes.getById(nodeId)
|
|
125
|
+
const twoStepNode = twoStep.nodes.getById(nodeId)
|
|
126
|
+
assert.deepEqual(streamingNode, twoStepNode)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const wayId of streaming.ways.ids.sorted) {
|
|
130
|
+
const streamingWay = streaming.ways.getById(wayId)
|
|
131
|
+
const twoStepWay = twoStep.ways.getById(wayId)
|
|
132
|
+
assert.deepEqual(streamingWay, twoStepWay)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const relationId of streaming.relations.ids.sorted) {
|
|
136
|
+
const streamingRelation = streaming.relations.getById(relationId)
|
|
137
|
+
const twoStepRelation = twoStep.relations.getById(relationId)
|
|
138
|
+
assert.deepEqual(streamingRelation, twoStepRelation)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test.skip("extract from a large PBF", { timeout: 500_000 }, async () => {
|
|
143
|
+
const seattle = new Osmix({ logger: console.error })
|
|
144
|
+
await seattle.readPbf(getFixtureFileReadStream("usa.pbf"), {
|
|
145
|
+
extractBbox: SEATTLE_BBOX,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
assert.deepEqual(
|
|
149
|
+
{
|
|
150
|
+
nodes: seattle.nodes.size,
|
|
151
|
+
ways: seattle.ways.size,
|
|
152
|
+
relations: seattle.relations.size,
|
|
153
|
+
},
|
|
154
|
+
{ nodes: 802_541, ways: 241_248, relations: 3_622 },
|
|
155
|
+
)
|
|
156
|
+
})
|
package/test/mock-osm.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { OsmNode } from "@osmix/json"
|
|
2
|
+
import { Osmix } from "../src/osmix"
|
|
3
|
+
|
|
4
|
+
const YAKIM_LAT = 46.60207
|
|
5
|
+
const YAKIM_LON = -120.505898
|
|
6
|
+
|
|
7
|
+
const ONE_KM_LON = 0.0131 // approximately
|
|
8
|
+
const ONE_KM_LAT = 0.009
|
|
9
|
+
|
|
10
|
+
const node0: OsmNode = {
|
|
11
|
+
id: 0,
|
|
12
|
+
lat: YAKIM_LAT,
|
|
13
|
+
lon: YAKIM_LON,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const node1: OsmNode = {
|
|
17
|
+
id: 1,
|
|
18
|
+
lat: YAKIM_LAT,
|
|
19
|
+
lon: YAKIM_LON - ONE_KM_LON, // approximately 1km west
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function addBaseNodes(osm: Osmix) {
|
|
23
|
+
osm.nodes.addNode(node0)
|
|
24
|
+
osm.nodes.addNode(node1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function addBaseWays(osm: Osmix) {
|
|
28
|
+
osm.ways.addWay({
|
|
29
|
+
id: 1,
|
|
30
|
+
refs: [0, 1],
|
|
31
|
+
tags: {
|
|
32
|
+
highway: "primary",
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a base OSM with one way and two nodes.
|
|
39
|
+
*/
|
|
40
|
+
export function createBaseOsm(): Osmix {
|
|
41
|
+
const base = new Osmix()
|
|
42
|
+
addBaseNodes(base)
|
|
43
|
+
base.nodes.buildIndex()
|
|
44
|
+
addBaseWays(base)
|
|
45
|
+
base.buildIndexes()
|
|
46
|
+
return base
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a patch OSM with:
|
|
51
|
+
* - The same way as the base OSM, with new tags
|
|
52
|
+
* - A way that has an overlapping node with the first way, but does not share a node and so starts disconnected
|
|
53
|
+
* - A way that crosses over the first way, but does not share a node and so starts disconnected
|
|
54
|
+
*/
|
|
55
|
+
export function createPatchOsm(): Osmix {
|
|
56
|
+
const osm = new Osmix()
|
|
57
|
+
|
|
58
|
+
addBaseNodes(osm)
|
|
59
|
+
|
|
60
|
+
// Add way the connects with base way node 0 (will replace it)
|
|
61
|
+
osm.nodes.addNode({
|
|
62
|
+
...node0,
|
|
63
|
+
id: 2,
|
|
64
|
+
tags: {
|
|
65
|
+
crossing: "yes",
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
osm.nodes.addNode({
|
|
69
|
+
...node1,
|
|
70
|
+
id: 3,
|
|
71
|
+
lon: YAKIM_LON + ONE_KM_LON, // ends 1km east of the center point
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Add nodes for disconnected way
|
|
75
|
+
osm.nodes.addNode({
|
|
76
|
+
id: 4,
|
|
77
|
+
lat: YAKIM_LAT - ONE_KM_LAT, // 1km south
|
|
78
|
+
lon: YAKIM_LON + ONE_KM_LON / 4, // 250m east
|
|
79
|
+
})
|
|
80
|
+
osm.nodes.addNode({
|
|
81
|
+
id: 5,
|
|
82
|
+
lat: YAKIM_LAT + ONE_KM_LAT, // 1km north
|
|
83
|
+
lon: YAKIM_LON + ONE_KM_LON / 4, // 250m east
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Add nodes for crossing way
|
|
87
|
+
osm.nodes.addNode({
|
|
88
|
+
id: 6,
|
|
89
|
+
lat: YAKIM_LAT - ONE_KM_LAT, // 1km south
|
|
90
|
+
lon: YAKIM_LON + ONE_KM_LON / 2, // 500m east
|
|
91
|
+
})
|
|
92
|
+
osm.nodes.addNode({
|
|
93
|
+
id: 7,
|
|
94
|
+
lat: YAKIM_LAT + ONE_KM_LAT, // 1km north
|
|
95
|
+
lon: YAKIM_LON + ONE_KM_LON / 2, // 500m east
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
osm.nodes.buildIndex()
|
|
99
|
+
|
|
100
|
+
// Add same base way with new tags
|
|
101
|
+
osm.ways.addWay({
|
|
102
|
+
id: 1,
|
|
103
|
+
refs: [0, 1],
|
|
104
|
+
tags: {
|
|
105
|
+
highway: "primary",
|
|
106
|
+
version: "2",
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// Add way that overlaps with base way node 1. Node should be de-duplicated.
|
|
111
|
+
osm.ways.addWay({
|
|
112
|
+
id: 2,
|
|
113
|
+
refs: [2, 3],
|
|
114
|
+
tags: {
|
|
115
|
+
highway: "secondary",
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Add way that crosses way 2, and should generate 1 intersection.
|
|
120
|
+
osm.ways.addWay({
|
|
121
|
+
id: 3,
|
|
122
|
+
refs: [4, 5],
|
|
123
|
+
tags: {
|
|
124
|
+
highway: "primary",
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Add a way that crosses way 2, but has a tag indicating it is an underpass and should be left alone
|
|
129
|
+
osm.ways.addWay({
|
|
130
|
+
id: 4,
|
|
131
|
+
refs: [6, 7],
|
|
132
|
+
tags: {
|
|
133
|
+
highway: "underpass",
|
|
134
|
+
tunnel: "yes",
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
osm.buildIndexes()
|
|
139
|
+
return osm
|
|
140
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getFixtureFile,
|
|
3
|
+
getFixtureFileReadStream,
|
|
4
|
+
PBFs,
|
|
5
|
+
} from "@osmix/shared/test/fixtures"
|
|
6
|
+
import { assert, beforeAll, describe, it } from "vitest"
|
|
7
|
+
import { Osmix } from "../src/osmix"
|
|
8
|
+
|
|
9
|
+
describe("read", () => {
|
|
10
|
+
describe.each(Object.entries(PBFs))("%s", async (name, pbf) => {
|
|
11
|
+
beforeAll(() => getFixtureFile(pbf.url))
|
|
12
|
+
|
|
13
|
+
it("into OSM class", async () => {
|
|
14
|
+
const fileStream = getFixtureFileReadStream(pbf.url)
|
|
15
|
+
const osm = await Osmix.fromPbf(fileStream, { id: name })
|
|
16
|
+
assert.equal(osm.nodes.size, pbf.nodes)
|
|
17
|
+
assert.equal(osm.stringTable.length, pbf.uniqueStrings)
|
|
18
|
+
assert.deepEqual(osm.nodes.getByIndex(0), pbf.node0)
|
|
19
|
+
assert.equal(osm.ways.size, pbf.ways)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
})
|