@osmix/geoparquet 0.1.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 +21 -0
- package/README.md +47 -0
- package/dist/src/from-geoparquet.d.ts +68 -0
- package/dist/src/from-geoparquet.d.ts.map +1 -0
- package/dist/src/from-geoparquet.js +455 -0
- package/dist/src/from-geoparquet.js.map +1 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +27 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +47 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/wkb.d.ts +22 -0
- package/dist/src/wkb.d.ts.map +1 -0
- package/dist/src/wkb.js +181 -0
- package/dist/src/wkb.js.map +1 -0
- package/dist/test/from-geoparquet.test.d.ts +2 -0
- package/dist/test/from-geoparquet.test.d.ts.map +1 -0
- package/dist/test/from-geoparquet.test.js +445 -0
- package/dist/test/from-geoparquet.test.js.map +1 -0
- package/dist/test/monaco-parquet.test.d.ts +2 -0
- package/dist/test/monaco-parquet.test.d.ts.map +1 -0
- package/dist/test/monaco-parquet.test.js +200 -0
- package/dist/test/monaco-parquet.test.js.map +1 -0
- package/dist/test/wkb.test.d.ts +2 -0
- package/dist/test/wkb.test.d.ts.map +1 -0
- package/dist/test/wkb.test.js +234 -0
- package/dist/test/wkb.test.js.map +1 -0
- package/package.json +53 -0
- package/src/from-geoparquet.ts +565 -0
- package/src/index.ts +27 -0
- package/src/types.ts +51 -0
- package/src/wkb.ts +218 -0
- package/test/download-monaco-highways.sh +40 -0
- package/test/from-geoparquet.test.ts +520 -0
- package/test/monaco-parquet.test.ts +249 -0
- package/test/wkb.test.ts +296 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { GeoParquetOsmBuilder } from "../src/from-geoparquet"
|
|
3
|
+
import type { GeoParquetRow } from "../src/types"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create WKB Point geometry.
|
|
7
|
+
*/
|
|
8
|
+
function createPointWkb(lon: number, lat: number): Uint8Array {
|
|
9
|
+
const buffer = new ArrayBuffer(21)
|
|
10
|
+
const view = new DataView(buffer)
|
|
11
|
+
let offset = 0
|
|
12
|
+
|
|
13
|
+
view.setUint8(offset, 1) // little endian
|
|
14
|
+
offset += 1
|
|
15
|
+
view.setUint32(offset, 1, true) // Point type
|
|
16
|
+
offset += 4
|
|
17
|
+
view.setFloat64(offset, lon, true)
|
|
18
|
+
offset += 8
|
|
19
|
+
view.setFloat64(offset, lat, true)
|
|
20
|
+
|
|
21
|
+
return new Uint8Array(buffer)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create WKB LineString geometry.
|
|
26
|
+
*/
|
|
27
|
+
function createLineStringWkb(coords: [number, number][]): Uint8Array {
|
|
28
|
+
const buffer = new ArrayBuffer(1 + 4 + 4 + coords.length * 16)
|
|
29
|
+
const view = new DataView(buffer)
|
|
30
|
+
let offset = 0
|
|
31
|
+
|
|
32
|
+
view.setUint8(offset, 1) // little endian
|
|
33
|
+
offset += 1
|
|
34
|
+
view.setUint32(offset, 2, true) // LineString type
|
|
35
|
+
offset += 4
|
|
36
|
+
view.setUint32(offset, coords.length, true) // num points
|
|
37
|
+
offset += 4
|
|
38
|
+
|
|
39
|
+
for (const [lon, lat] of coords) {
|
|
40
|
+
view.setFloat64(offset, lon, true)
|
|
41
|
+
offset += 8
|
|
42
|
+
view.setFloat64(offset, lat, true)
|
|
43
|
+
offset += 8
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return new Uint8Array(buffer)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create WKB Polygon geometry.
|
|
51
|
+
*/
|
|
52
|
+
function createPolygonWkb(rings: [number, number][][]): Uint8Array {
|
|
53
|
+
const totalPoints = rings.reduce((sum, ring) => sum + ring.length, 0)
|
|
54
|
+
const buffer = new ArrayBuffer(
|
|
55
|
+
1 + 4 + 4 + rings.length * 4 + totalPoints * 16,
|
|
56
|
+
)
|
|
57
|
+
const view = new DataView(buffer)
|
|
58
|
+
let offset = 0
|
|
59
|
+
|
|
60
|
+
view.setUint8(offset, 1) // little endian
|
|
61
|
+
offset += 1
|
|
62
|
+
view.setUint32(offset, 3, true) // Polygon type
|
|
63
|
+
offset += 4
|
|
64
|
+
view.setUint32(offset, rings.length, true) // num rings
|
|
65
|
+
offset += 4
|
|
66
|
+
|
|
67
|
+
for (const ring of rings) {
|
|
68
|
+
view.setUint32(offset, ring.length, true)
|
|
69
|
+
offset += 4
|
|
70
|
+
for (const [lon, lat] of ring) {
|
|
71
|
+
view.setFloat64(offset, lon, true)
|
|
72
|
+
offset += 8
|
|
73
|
+
view.setFloat64(offset, lat, true)
|
|
74
|
+
offset += 8
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return new Uint8Array(buffer)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Helper function to process rows using the builder.
|
|
83
|
+
*/
|
|
84
|
+
function processRows(
|
|
85
|
+
rows: GeoParquetRow[],
|
|
86
|
+
options?: { idColumn?: string; geometryColumn?: string; tagsColumn?: string },
|
|
87
|
+
) {
|
|
88
|
+
const builder = new GeoParquetOsmBuilder({}, options, () => {})
|
|
89
|
+
builder.processGeoParquetRows(rows as unknown as Record<string, unknown>[])
|
|
90
|
+
return builder.buildOsm()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("@osmix/geoparquet: GeoParquetOsmBuilder", () => {
|
|
94
|
+
it("should convert Point features to Nodes", () => {
|
|
95
|
+
const rows: GeoParquetRow[] = [
|
|
96
|
+
{
|
|
97
|
+
type: "node",
|
|
98
|
+
id: 100n,
|
|
99
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
100
|
+
tags: { name: "San Francisco", population: "873965" },
|
|
101
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: "node",
|
|
105
|
+
id: 200n,
|
|
106
|
+
geometry: createPointWkb(-122.4094, 37.7849),
|
|
107
|
+
tags: { name: "Another Point" },
|
|
108
|
+
bbox: [-122.4094, 37.7849, -122.4094, 37.7849],
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
const osm = processRows(rows)
|
|
113
|
+
|
|
114
|
+
expect(osm.nodes.size).toBe(2)
|
|
115
|
+
expect(osm.ways.size).toBe(0)
|
|
116
|
+
|
|
117
|
+
// Get nodes by index to check values
|
|
118
|
+
const node1 = osm.nodes.getByIndex(0)
|
|
119
|
+
const node2 = osm.nodes.getByIndex(1)
|
|
120
|
+
|
|
121
|
+
expect(node1).toBeDefined()
|
|
122
|
+
expect(node1?.lon).toBeCloseTo(-122.4194, 4)
|
|
123
|
+
expect(node1?.lat).toBeCloseTo(37.7749, 4)
|
|
124
|
+
expect(node1?.tags?.["name"]).toBe("San Francisco")
|
|
125
|
+
expect(node1?.tags?.["population"]).toBe("873965")
|
|
126
|
+
|
|
127
|
+
expect(node2).toBeDefined()
|
|
128
|
+
expect(node2?.lon).toBeCloseTo(-122.4094, 4)
|
|
129
|
+
expect(node2?.lat).toBeCloseTo(37.7849, 4)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("should convert Point features to Nodes with auto-generated IDs", () => {
|
|
133
|
+
const rows: GeoParquetRow[] = [
|
|
134
|
+
{
|
|
135
|
+
type: "node",
|
|
136
|
+
id: undefined as unknown as bigint, // No ID provided
|
|
137
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
138
|
+
tags: { name: "San Francisco" },
|
|
139
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
140
|
+
},
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
const osm = processRows(rows)
|
|
144
|
+
|
|
145
|
+
expect(osm.nodes.size).toBe(1)
|
|
146
|
+
|
|
147
|
+
// Auto-generated IDs start at -1
|
|
148
|
+
const node = osm.nodes.getById(-1)
|
|
149
|
+
expect(node).toBeDefined()
|
|
150
|
+
expect(node?.lon).toBeCloseTo(-122.4194, 4)
|
|
151
|
+
expect(node?.lat).toBeCloseTo(37.7749, 4)
|
|
152
|
+
expect(node?.tags?.["name"]).toBe("San Francisco")
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it("should convert LineString features to Ways with Nodes", () => {
|
|
156
|
+
const rows: GeoParquetRow[] = [
|
|
157
|
+
{
|
|
158
|
+
type: "way",
|
|
159
|
+
id: undefined as unknown as bigint, // Auto-generate IDs
|
|
160
|
+
geometry: createLineStringWkb([
|
|
161
|
+
[-122.4194, 37.7749],
|
|
162
|
+
[-122.4094, 37.7849],
|
|
163
|
+
[-122.3994, 37.7949],
|
|
164
|
+
]),
|
|
165
|
+
tags: { highway: "primary", name: "Main Street" },
|
|
166
|
+
bbox: [-122.4194, 37.7749, -122.3994, 37.7949],
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
const osm = processRows(rows)
|
|
171
|
+
|
|
172
|
+
expect(osm.nodes.size).toBe(3)
|
|
173
|
+
expect(osm.ways.size).toBe(1)
|
|
174
|
+
|
|
175
|
+
const way = osm.ways.getById(-1)
|
|
176
|
+
expect(way).toBeDefined()
|
|
177
|
+
expect(way?.refs).toHaveLength(3)
|
|
178
|
+
expect(way?.tags?.["highway"]).toBe("primary")
|
|
179
|
+
expect(way?.tags?.["name"]).toBe("Main Street")
|
|
180
|
+
|
|
181
|
+
// Verify nodes were created with auto-generated IDs
|
|
182
|
+
const node1 = osm.nodes.getById(way!.refs[0]!)
|
|
183
|
+
const node2 = osm.nodes.getById(way!.refs[1]!)
|
|
184
|
+
const node3 = osm.nodes.getById(way!.refs[2]!)
|
|
185
|
+
|
|
186
|
+
expect(node1?.lon).toBeCloseTo(-122.4194, 4)
|
|
187
|
+
expect(node1?.lat).toBeCloseTo(37.7749, 4)
|
|
188
|
+
expect(node2?.lon).toBeCloseTo(-122.4094, 4)
|
|
189
|
+
expect(node2?.lat).toBeCloseTo(37.7849, 4)
|
|
190
|
+
expect(node3?.lon).toBeCloseTo(-122.3994, 4)
|
|
191
|
+
expect(node3?.lat).toBeCloseTo(37.7949, 4)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("should convert Polygon features to Ways with area tags", () => {
|
|
195
|
+
const rows: GeoParquetRow[] = [
|
|
196
|
+
{
|
|
197
|
+
type: "way",
|
|
198
|
+
id: undefined as unknown as bigint, // Auto-generate IDs
|
|
199
|
+
geometry: createPolygonWkb([
|
|
200
|
+
[
|
|
201
|
+
[-122.4194, 37.7749],
|
|
202
|
+
[-122.4094, 37.7749],
|
|
203
|
+
[-122.4094, 37.7849],
|
|
204
|
+
[-122.4194, 37.7849],
|
|
205
|
+
[-122.4194, 37.7749], // closed
|
|
206
|
+
],
|
|
207
|
+
]),
|
|
208
|
+
tags: { building: "yes", name: "Test Building" },
|
|
209
|
+
bbox: [-122.4194, 37.7749, -122.4094, 37.7849],
|
|
210
|
+
},
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
const osm = processRows(rows)
|
|
214
|
+
|
|
215
|
+
expect(osm.nodes.size).toBe(4) // 4 unique nodes
|
|
216
|
+
expect(osm.ways.size).toBe(1)
|
|
217
|
+
expect(osm.relations.size).toBe(0)
|
|
218
|
+
|
|
219
|
+
const way = osm.ways.getById(-1)
|
|
220
|
+
expect(way).toBeDefined()
|
|
221
|
+
expect(way?.tags?.["building"]).toBe("yes")
|
|
222
|
+
expect(way?.tags?.["name"]).toBe("Test Building")
|
|
223
|
+
expect(way?.tags?.["area"]).toBe("yes")
|
|
224
|
+
expect(way?.refs).toHaveLength(5) // 4 unique + closing
|
|
225
|
+
expect(way?.refs[0]).toBe(way?.refs[4]) // Ring is closed
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it("should convert Polygon with holes to relation with multiple Ways", () => {
|
|
229
|
+
const rows: GeoParquetRow[] = [
|
|
230
|
+
{
|
|
231
|
+
type: "relation",
|
|
232
|
+
id: undefined as unknown as bigint, // Auto-generate IDs
|
|
233
|
+
geometry: createPolygonWkb([
|
|
234
|
+
// Outer ring
|
|
235
|
+
[
|
|
236
|
+
[-122.4194, 37.7749],
|
|
237
|
+
[-122.4094, 37.7749],
|
|
238
|
+
[-122.4094, 37.7849],
|
|
239
|
+
[-122.4194, 37.7849],
|
|
240
|
+
[-122.4194, 37.7749],
|
|
241
|
+
],
|
|
242
|
+
// Hole
|
|
243
|
+
[
|
|
244
|
+
[-122.4164, 37.7779],
|
|
245
|
+
[-122.4144, 37.7779],
|
|
246
|
+
[-122.4144, 37.7799],
|
|
247
|
+
[-122.4164, 37.7799],
|
|
248
|
+
[-122.4164, 37.7779],
|
|
249
|
+
],
|
|
250
|
+
]),
|
|
251
|
+
tags: { building: "yes" },
|
|
252
|
+
bbox: [-122.4194, 37.7749, -122.4094, 37.7849],
|
|
253
|
+
},
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
const osm = processRows(rows)
|
|
257
|
+
|
|
258
|
+
expect(osm.ways.size).toBe(2)
|
|
259
|
+
expect(osm.relations.size).toBe(1)
|
|
260
|
+
|
|
261
|
+
// Get ways by index since IDs are auto-generated
|
|
262
|
+
const outerWay = osm.ways.getByIndex(0)
|
|
263
|
+
expect(outerWay).toBeDefined()
|
|
264
|
+
expect(outerWay?.tags?.["area"]).toBe("yes")
|
|
265
|
+
expect(outerWay?.tags?.["building"]).toBeUndefined() // Tags go on relation
|
|
266
|
+
|
|
267
|
+
const holeWay = osm.ways.getByIndex(1)
|
|
268
|
+
expect(holeWay).toBeDefined()
|
|
269
|
+
expect(holeWay?.tags?.["area"]).toBe("yes")
|
|
270
|
+
|
|
271
|
+
const relation = osm.relations.getById(-1)
|
|
272
|
+
expect(relation).toBeDefined()
|
|
273
|
+
expect(relation?.tags?.["type"]).toBe("multipolygon")
|
|
274
|
+
expect(relation?.tags?.["building"]).toBe("yes")
|
|
275
|
+
expect(relation?.members).toHaveLength(2)
|
|
276
|
+
expect(relation?.members[0]?.type).toBe("way")
|
|
277
|
+
expect(relation?.members[0]?.role).toBe("outer")
|
|
278
|
+
expect(relation?.members[1]?.type).toBe("way")
|
|
279
|
+
expect(relation?.members[1]?.role).toBe("inner")
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it("should handle JSON string tags", () => {
|
|
283
|
+
const rows: GeoParquetRow[] = [
|
|
284
|
+
{
|
|
285
|
+
type: "node",
|
|
286
|
+
id: undefined as unknown as bigint,
|
|
287
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
288
|
+
tags: '{"name":"Test","highway":"primary"}',
|
|
289
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
290
|
+
},
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
const osm = processRows(rows)
|
|
294
|
+
|
|
295
|
+
const node = osm.nodes.getById(-1)
|
|
296
|
+
expect(node?.tags?.["name"]).toBe("Test")
|
|
297
|
+
expect(node?.tags?.["highway"]).toBe("primary")
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it("should handle null tags", () => {
|
|
301
|
+
const rows: GeoParquetRow[] = [
|
|
302
|
+
{
|
|
303
|
+
type: "node",
|
|
304
|
+
id: undefined as unknown as bigint,
|
|
305
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
306
|
+
tags: null as unknown as string,
|
|
307
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
308
|
+
},
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
const osm = processRows(rows)
|
|
312
|
+
|
|
313
|
+
const node = osm.nodes.getById(-1)
|
|
314
|
+
expect(node?.tags).toBeUndefined()
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it("should skip rows with missing geometry", () => {
|
|
318
|
+
const rows: GeoParquetRow[] = [
|
|
319
|
+
{
|
|
320
|
+
type: "node",
|
|
321
|
+
id: undefined as unknown as bigint,
|
|
322
|
+
geometry: undefined as unknown as Uint8Array,
|
|
323
|
+
tags: { name: "Missing geometry" },
|
|
324
|
+
bbox: [0, 0, 0, 0],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
type: "node",
|
|
328
|
+
id: undefined as unknown as bigint,
|
|
329
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
330
|
+
tags: { name: "Valid point" },
|
|
331
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
332
|
+
},
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
const osm = processRows(rows)
|
|
336
|
+
|
|
337
|
+
expect(osm.nodes.size).toBe(1)
|
|
338
|
+
const node = osm.nodes.getById(-1)
|
|
339
|
+
expect(node?.tags?.["name"]).toBe("Valid point")
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it("should reuse nodes when features share coordinates", () => {
|
|
343
|
+
const rows: GeoParquetRow[] = [
|
|
344
|
+
{
|
|
345
|
+
type: "way",
|
|
346
|
+
id: undefined as unknown as bigint,
|
|
347
|
+
geometry: createLineStringWkb([
|
|
348
|
+
[-122.4194, 37.7749],
|
|
349
|
+
[-122.4094, 37.7849],
|
|
350
|
+
]),
|
|
351
|
+
tags: { highway: "primary" },
|
|
352
|
+
bbox: [-122.4194, 37.7749, -122.4094, 37.7849],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: "way",
|
|
356
|
+
id: undefined as unknown as bigint,
|
|
357
|
+
geometry: createLineStringWkb([
|
|
358
|
+
[-122.4094, 37.7849], // Shared coordinate
|
|
359
|
+
[-122.3994, 37.7949],
|
|
360
|
+
]),
|
|
361
|
+
tags: { highway: "secondary" },
|
|
362
|
+
bbox: [-122.4094, 37.7849, -122.3994, 37.7949],
|
|
363
|
+
},
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
const osm = processRows(rows)
|
|
367
|
+
|
|
368
|
+
// Should have 3 nodes (shared coordinate is reused)
|
|
369
|
+
expect(osm.nodes.size).toBe(3)
|
|
370
|
+
expect(osm.ways.size).toBe(2)
|
|
371
|
+
|
|
372
|
+
const way1 = osm.ways.getById(-1)
|
|
373
|
+
const way2 = osm.ways.getById(-2)
|
|
374
|
+
|
|
375
|
+
expect(way1?.refs).toHaveLength(2)
|
|
376
|
+
expect(way2?.refs).toHaveLength(2)
|
|
377
|
+
// Shared node
|
|
378
|
+
expect(way1?.refs[1]).toBe(way2?.refs[0])
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it("should build indexes after processing", () => {
|
|
382
|
+
const rows: GeoParquetRow[] = [
|
|
383
|
+
{
|
|
384
|
+
type: "node",
|
|
385
|
+
id: undefined as unknown as bigint,
|
|
386
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
387
|
+
tags: { name: "Test" },
|
|
388
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
389
|
+
},
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
const osm = processRows(rows)
|
|
393
|
+
|
|
394
|
+
expect(osm.isReady()).toBe(true)
|
|
395
|
+
expect(osm.nodes.isReady()).toBe(true)
|
|
396
|
+
expect(osm.ways.isReady()).toBe(true)
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it("should handle object tags", () => {
|
|
400
|
+
const rows: GeoParquetRow[] = [
|
|
401
|
+
{
|
|
402
|
+
type: "node",
|
|
403
|
+
id: undefined as unknown as bigint,
|
|
404
|
+
geometry: createPointWkb(-122.4194, 37.7749),
|
|
405
|
+
tags: { name: "Test", highway: "primary" },
|
|
406
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
407
|
+
},
|
|
408
|
+
]
|
|
409
|
+
|
|
410
|
+
const osm = processRows(rows)
|
|
411
|
+
|
|
412
|
+
const node = osm.nodes.getById(-1)
|
|
413
|
+
expect(node?.tags?.["name"]).toBe("Test")
|
|
414
|
+
expect(node?.tags?.["highway"]).toBe("primary")
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
it("should handle custom column names", () => {
|
|
418
|
+
const rows = [
|
|
419
|
+
{
|
|
420
|
+
type: "node",
|
|
421
|
+
osm_id: undefined,
|
|
422
|
+
geom: createPointWkb(-122.4194, 37.7749),
|
|
423
|
+
properties: { name: "Custom columns" },
|
|
424
|
+
bbox: [-122.4194, 37.7749, -122.4194, 37.7749],
|
|
425
|
+
},
|
|
426
|
+
] as unknown as GeoParquetRow[]
|
|
427
|
+
|
|
428
|
+
const osm = processRows(rows, {
|
|
429
|
+
idColumn: "osm_id",
|
|
430
|
+
geometryColumn: "geom",
|
|
431
|
+
tagsColumn: "properties",
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
expect(osm.nodes.size).toBe(1)
|
|
435
|
+
const node = osm.nodes.getById(-1)
|
|
436
|
+
expect(node?.tags?.["name"]).toBe("Custom columns")
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it("should infer type from geometry when type column is missing", () => {
|
|
440
|
+
// Simple polygon without holes - should only create a way, not a relation
|
|
441
|
+
const simplePolygonRows: GeoParquetRow[] = [
|
|
442
|
+
{
|
|
443
|
+
type: undefined as unknown as "way", // Missing type
|
|
444
|
+
id: 123n,
|
|
445
|
+
geometry: createPolygonWkb([
|
|
446
|
+
[
|
|
447
|
+
[-122.4194, 37.7749],
|
|
448
|
+
[-122.4094, 37.7749],
|
|
449
|
+
[-122.4094, 37.7849],
|
|
450
|
+
[-122.4194, 37.7849],
|
|
451
|
+
[-122.4194, 37.7749],
|
|
452
|
+
],
|
|
453
|
+
]),
|
|
454
|
+
tags: { building: "yes" },
|
|
455
|
+
bbox: [-122.4194, 37.7749, -122.4094, 37.7849],
|
|
456
|
+
},
|
|
457
|
+
]
|
|
458
|
+
|
|
459
|
+
const osm1 = processRows(simplePolygonRows)
|
|
460
|
+
|
|
461
|
+
// Should create just a way, no relation
|
|
462
|
+
expect(osm1.ways.size).toBe(1)
|
|
463
|
+
expect(osm1.relations.size).toBe(0)
|
|
464
|
+
|
|
465
|
+
// Way should have the feature ID and tags
|
|
466
|
+
const way = osm1.ways.getById(123)
|
|
467
|
+
expect(way).toBeDefined()
|
|
468
|
+
expect(way?.tags?.["building"]).toBe("yes")
|
|
469
|
+
expect(way?.tags?.["area"]).toBe("yes")
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it("should infer relation type for polygon with holes when type is missing", () => {
|
|
473
|
+
// Polygon with hole - should create a relation
|
|
474
|
+
const polygonWithHoleRows: GeoParquetRow[] = [
|
|
475
|
+
{
|
|
476
|
+
type: undefined as unknown as "relation", // Missing type
|
|
477
|
+
id: 456n,
|
|
478
|
+
geometry: createPolygonWkb([
|
|
479
|
+
// Outer ring
|
|
480
|
+
[
|
|
481
|
+
[-122.4194, 37.7749],
|
|
482
|
+
[-122.4094, 37.7749],
|
|
483
|
+
[-122.4094, 37.7849],
|
|
484
|
+
[-122.4194, 37.7849],
|
|
485
|
+
[-122.4194, 37.7749],
|
|
486
|
+
],
|
|
487
|
+
// Hole
|
|
488
|
+
[
|
|
489
|
+
[-122.4164, 37.7779],
|
|
490
|
+
[-122.4144, 37.7779],
|
|
491
|
+
[-122.4144, 37.7799],
|
|
492
|
+
[-122.4164, 37.7799],
|
|
493
|
+
[-122.4164, 37.7779],
|
|
494
|
+
],
|
|
495
|
+
]),
|
|
496
|
+
tags: { building: "yes" },
|
|
497
|
+
bbox: [-122.4194, 37.7749, -122.4094, 37.7849],
|
|
498
|
+
},
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
const osm2 = processRows(polygonWithHoleRows)
|
|
502
|
+
|
|
503
|
+
// Should create 2 ways (outer + hole) and 1 relation
|
|
504
|
+
expect(osm2.ways.size).toBe(2)
|
|
505
|
+
expect(osm2.relations.size).toBe(1)
|
|
506
|
+
|
|
507
|
+
// Relation should have the feature ID and tags
|
|
508
|
+
const relation = osm2.relations.getById(456)
|
|
509
|
+
expect(relation).toBeDefined()
|
|
510
|
+
expect(relation?.tags?.["type"]).toBe("multipolygon")
|
|
511
|
+
expect(relation?.tags?.["building"]).toBe("yes")
|
|
512
|
+
expect(relation?.members).toHaveLength(2)
|
|
513
|
+
expect(relation?.members[0]?.role).toBe("outer")
|
|
514
|
+
expect(relation?.members[1]?.role).toBe("inner")
|
|
515
|
+
|
|
516
|
+
// Ways should not have the feature ID (456 goes to relation)
|
|
517
|
+
const outerWay = osm2.ways.getByIndex(0)
|
|
518
|
+
expect(outerWay?.id).not.toBe(456)
|
|
519
|
+
})
|
|
520
|
+
})
|