@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +47 -0
  3. package/dist/src/from-geoparquet.d.ts +68 -0
  4. package/dist/src/from-geoparquet.d.ts.map +1 -0
  5. package/dist/src/from-geoparquet.js +455 -0
  6. package/dist/src/from-geoparquet.js.map +1 -0
  7. package/dist/src/index.d.ts +27 -0
  8. package/dist/src/index.d.ts.map +1 -0
  9. package/dist/src/index.js +27 -0
  10. package/dist/src/index.js.map +1 -0
  11. package/dist/src/types.d.ts +47 -0
  12. package/dist/src/types.d.ts.map +1 -0
  13. package/dist/src/types.js +6 -0
  14. package/dist/src/types.js.map +1 -0
  15. package/dist/src/wkb.d.ts +22 -0
  16. package/dist/src/wkb.d.ts.map +1 -0
  17. package/dist/src/wkb.js +181 -0
  18. package/dist/src/wkb.js.map +1 -0
  19. package/dist/test/from-geoparquet.test.d.ts +2 -0
  20. package/dist/test/from-geoparquet.test.d.ts.map +1 -0
  21. package/dist/test/from-geoparquet.test.js +445 -0
  22. package/dist/test/from-geoparquet.test.js.map +1 -0
  23. package/dist/test/monaco-parquet.test.d.ts +2 -0
  24. package/dist/test/monaco-parquet.test.d.ts.map +1 -0
  25. package/dist/test/monaco-parquet.test.js +200 -0
  26. package/dist/test/monaco-parquet.test.js.map +1 -0
  27. package/dist/test/wkb.test.d.ts +2 -0
  28. package/dist/test/wkb.test.d.ts.map +1 -0
  29. package/dist/test/wkb.test.js +234 -0
  30. package/dist/test/wkb.test.js.map +1 -0
  31. package/package.json +53 -0
  32. package/src/from-geoparquet.ts +565 -0
  33. package/src/index.ts +27 -0
  34. package/src/types.ts +51 -0
  35. package/src/wkb.ts +218 -0
  36. package/test/download-monaco-highways.sh +40 -0
  37. package/test/from-geoparquet.test.ts +520 -0
  38. package/test/monaco-parquet.test.ts +249 -0
  39. package/test/wkb.test.ts +296 -0
  40. package/tsconfig.json +9 -0
@@ -0,0 +1,565 @@
1
+ /**
2
+ * GeoParquet to OSM conversion utilities.
3
+ *
4
+ * Imports GeoParquet files into Osm indexes, mapping geometry
5
+ * types to appropriate OSM entity structures.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { Osm, type OsmOptions } from "@osmix/core"
11
+ import {
12
+ logProgress,
13
+ type ProgressEvent,
14
+ progressEvent,
15
+ } from "@osmix/shared/progress"
16
+ import type { GeoBbox2D, OsmRelationMember, OsmTags } from "@osmix/shared/types"
17
+ import { rewindFeature } from "@placemarkio/geojson-rewind"
18
+ import type {
19
+ Geometry,
20
+ LineString,
21
+ MultiLineString,
22
+ MultiPolygon,
23
+ Point,
24
+ Polygon,
25
+ } from "geojson"
26
+ import {
27
+ type AsyncBuffer,
28
+ asyncBufferFromUrl,
29
+ type ParquetReadOptions,
30
+ parquetReadObjects,
31
+ } from "hyparquet"
32
+ import type { GeoParquetReadOptions, GeoParquetSource } from "./types"
33
+ import { parseWkb } from "./wkb"
34
+
35
+ /**
36
+ * Create an Osm index from GeoParquet data.
37
+ *
38
+ * Accepts various input formats (file path, URL, or buffer) and converts
39
+ * features to OSM entities:
40
+ * - Point → Node
41
+ * - LineString → Way with nodes
42
+ * - Polygon → Way (simple) or Relation (with holes)
43
+ * - MultiPolygon → Relation
44
+ *
45
+ * Feature IDs are preserved if available; otherwise sequential negative IDs
46
+ * are assigned. Feature tags become OSM tags.
47
+ *
48
+ * @param source - GeoParquet data source (file path, URL, or buffer)
49
+ * @param options - Osm index options (id, header)
50
+ * @param readOptions - Options for reading the parquet file
51
+ * @param onProgress - Progress callback for UI feedback
52
+ * @returns Populated Osm index with built indexes
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { fromGeoParquet } from "@osmix/geoparquet"
57
+ *
58
+ * // From file
59
+ * const osm = await fromGeoParquet("./roads.parquet")
60
+ *
61
+ * // Query the imported data
62
+ * const highways = osm.ways.search("highway")
63
+ * ```
64
+ */
65
+ export async function fromGeoParquet(
66
+ source: GeoParquetSource,
67
+ options: Partial<OsmOptions> = {},
68
+ readOptions: GeoParquetReadOptions = {},
69
+ onProgress: (progress: ProgressEvent) => void = logProgress,
70
+ ): Promise<Osm> {
71
+ const builder = new GeoParquetOsmBuilder(options, readOptions, onProgress)
72
+
73
+ onProgress(progressEvent("Loading GeoParquet file..."))
74
+
75
+ // Read rows from parquet file
76
+ const rows = await builder.readParquetRows(source)
77
+
78
+ onProgress(progressEvent(`Processing ${rows.length} features...`))
79
+
80
+ // Convert to OSM entities
81
+ builder.processGeoParquetRows(rows)
82
+
83
+ return builder.buildOsm()
84
+ }
85
+
86
+ export class GeoParquetOsmBuilder {
87
+ private osm: Osm
88
+ private readOptions: GeoParquetReadOptions
89
+ private onProgress: (progress: ProgressEvent) => void
90
+
91
+ constructor(
92
+ osmOptions: Partial<OsmOptions> = {},
93
+ readOptions: GeoParquetReadOptions = {},
94
+ onProgress: (progress: ProgressEvent) => void = logProgress,
95
+ ) {
96
+ this.osm = new Osm(osmOptions)
97
+ this.readOptions = readOptions
98
+ this.onProgress = onProgress
99
+ }
100
+
101
+ async readParquetRows(source: GeoParquetSource) {
102
+ let file: AsyncBuffer
103
+ if (typeof source === "string") {
104
+ // String sources are treated as URLs
105
+ this.onProgress(progressEvent(`Fetching from URL: ${source}`))
106
+ file = await asyncBufferFromUrl({ url: source })
107
+ } else if (source instanceof URL) {
108
+ this.onProgress(progressEvent(`Fetching from URL: ${source.href}`))
109
+ file = await asyncBufferFromUrl({ url: source.href })
110
+ } else if (source instanceof ArrayBuffer) {
111
+ // Wrap ArrayBuffer as AsyncBuffer
112
+ file = {
113
+ byteLength: source.byteLength,
114
+ slice: (start: number, end?: number) => source.slice(start, end),
115
+ }
116
+ } else {
117
+ // Assume it's already an AsyncBuffer
118
+ file = source
119
+ }
120
+
121
+ const readConfig: Omit<ParquetReadOptions, "onComplete"> = {
122
+ file,
123
+ ...this.readOptions,
124
+ columns: [
125
+ this.readOptions.typeColumn ?? "type",
126
+ this.readOptions.idColumn ?? "id",
127
+ this.readOptions.geometryColumn ?? "geometry",
128
+ this.readOptions.tagsColumn ?? "tags",
129
+ this.readOptions.bboxColumn ?? "bbox",
130
+ ],
131
+ }
132
+
133
+ this.onProgress(progressEvent("Reading parquet data..."))
134
+ return parquetReadObjects(readConfig)
135
+ }
136
+
137
+ /**
138
+ * Converts GeoParquet rows to OSM entities.
139
+ */
140
+ processGeoParquetRows(rows: Record<string, unknown>[]) {
141
+ this.onProgress(progressEvent("Converting GeoParquet to Osmix..."))
142
+
143
+ const idColumn = this.readOptions.idColumn ?? "id"
144
+ const typeColumn = this.readOptions.typeColumn ?? "type"
145
+ const geometryColumn = this.readOptions.geometryColumn ?? "geometry"
146
+ const tagsColumn = this.readOptions.tagsColumn ?? "tags"
147
+ const bboxColumn = this.readOptions.bboxColumn ?? "bbox"
148
+
149
+ // Process each row
150
+ let count = 0
151
+ for (const row of rows) {
152
+ // Extract values using column names
153
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic column access
154
+ const rowAny = row as any
155
+ const id = rowAny[idColumn] as bigint | number
156
+ const type = rowAny[typeColumn] as "node" | "way" | "relation"
157
+ const geometryData = rowAny[geometryColumn] as
158
+ | Uint8Array
159
+ | GeoJSON.Geometry
160
+ | string
161
+ const tagsData = rowAny[tagsColumn] as
162
+ | Record<string, string | number>
163
+ | string
164
+ // bboxData is read but currently unused - reserved for future optimization
165
+ void (rowAny[bboxColumn] as GeoBbox2D)
166
+
167
+ if (!geometryData) {
168
+ count++
169
+ continue
170
+ }
171
+
172
+ // Parse WKB geometry
173
+ let geometry: Geometry
174
+ try {
175
+ if (geometryData instanceof Uint8Array) {
176
+ geometry = parseWkb(geometryData)
177
+ } else if (typeof geometryData === "string") {
178
+ geometry = JSON.parse(geometryData) as Geometry
179
+ } else {
180
+ geometry = geometryData
181
+ }
182
+ } catch (_e) {
183
+ // Skip invalid geometries
184
+ count++
185
+ continue
186
+ }
187
+
188
+ // Parse tags
189
+ const tags = parseTags(tagsData)
190
+
191
+ // Get numeric ID from bigint or generate one
192
+ const numericId =
193
+ id !== undefined
194
+ ? typeof id === "bigint"
195
+ ? Number(id)
196
+ : id
197
+ : undefined
198
+
199
+ // Normalize geometry winding order
200
+ const normalizedGeometry = normalizeGeometry(geometry)
201
+
202
+ if (normalizedGeometry.type === "Point") {
203
+ this.processPoint(normalizedGeometry, numericId, tags)
204
+ } else if (normalizedGeometry.type === "LineString") {
205
+ this.processLineString(normalizedGeometry, numericId, tags)
206
+ } else if (normalizedGeometry.type === "Polygon") {
207
+ if (type === "node")
208
+ throw Error(
209
+ `ID: ${numericId} has type 'node' but geometry is a polygon`,
210
+ )
211
+ // Infer type from geometry if missing: relation if has holes, way otherwise
212
+ const polygonType =
213
+ type ??
214
+ (normalizedGeometry.coordinates.length > 1 ? "relation" : "way")
215
+ this.processPolygon(normalizedGeometry, polygonType, numericId, tags)
216
+ } else if (normalizedGeometry.type === "MultiPolygon") {
217
+ this.processMultiPolygon(normalizedGeometry, numericId, tags)
218
+ } else if (normalizedGeometry.type === "MultiLineString") {
219
+ this.processMultiLineString(normalizedGeometry, numericId, tags)
220
+ } else {
221
+ throw Error(`Unsupported geometry type: ${normalizedGeometry.type}`)
222
+ }
223
+
224
+ count++
225
+ if (count % 10000 === 0) {
226
+ this.onProgress(progressEvent(`Processed ${count} features...`))
227
+ }
228
+ }
229
+
230
+ this.onProgress(progressEvent(`Imported ${count} features`))
231
+ }
232
+
233
+ buildOsm() {
234
+ this.onProgress(
235
+ progressEvent(
236
+ "Finished converting GeoParquet to Osmix, building indexes...",
237
+ ),
238
+ )
239
+ this.osm.buildIndexes()
240
+ this.osm.buildSpatialIndexes()
241
+ return this.osm
242
+ }
243
+
244
+ private nextNodeId = -1
245
+ private nextWayId = -1
246
+ private nextRelationId = -1
247
+ getNextRelationId() {
248
+ return this.nextRelationId--
249
+ }
250
+ getNextWayId() {
251
+ return this.nextWayId--
252
+ }
253
+ getNextNodeId() {
254
+ return this.nextNodeId--
255
+ }
256
+
257
+ // Map to track nodes by coordinate string for reuse when creating ways
258
+ private nodeMap = new Map<string, number>()
259
+
260
+ // Helper to get or create a node for a coordinate
261
+ private getOrCreateNode(lon: number, lat: number): number {
262
+ const coordKey = `${lon},${lat}`
263
+ const existingNodeId = this.nodeMap.get(coordKey)
264
+ if (existingNodeId !== undefined) {
265
+ return existingNodeId
266
+ }
267
+
268
+ const nodeId = this.getNextNodeId()
269
+ this.nodeMap.set(coordKey, nodeId)
270
+ this.osm.nodes.addNode({
271
+ id: nodeId,
272
+ lon,
273
+ lat,
274
+ })
275
+ return nodeId
276
+ }
277
+
278
+ private processPoint(
279
+ geometry: Point,
280
+ featureId?: number,
281
+ tags?: OsmTags,
282
+ ): number {
283
+ const [lon, lat] = geometry.coordinates
284
+ if (lon === undefined || lat === undefined)
285
+ throw Error("Point must have lon and lat coordinates")
286
+
287
+ const nodeId = featureId ?? this.getNextNodeId()
288
+ this.osm.nodes.addNode({
289
+ id: nodeId,
290
+ lon,
291
+ lat,
292
+ tags,
293
+ })
294
+ this.nodeMap.set(`${lon},${lat}`, nodeId)
295
+ return nodeId
296
+ }
297
+
298
+ private processLineString(
299
+ geometry: LineString,
300
+ featureId?: number,
301
+ tags?: OsmTags,
302
+ ): number {
303
+ const coordinates = geometry.coordinates
304
+ if (coordinates.length < 2)
305
+ throw Error("LineString must have at least 2 coordinates")
306
+
307
+ const nodeRefs: number[] = []
308
+ for (const [lon, lat] of coordinates) {
309
+ if (lon === undefined || lat === undefined)
310
+ throw Error("LineString coordinates must have lon and lat")
311
+ nodeRefs.push(this.getOrCreateNode(lon, lat))
312
+ }
313
+
314
+ if (nodeRefs.length < 2)
315
+ throw Error("LineString must have at least 2 coordinates")
316
+
317
+ const wayId = featureId ?? this.getNextWayId()
318
+ this.osm.ways.addWay({
319
+ id: wayId,
320
+ refs: nodeRefs,
321
+ tags,
322
+ })
323
+
324
+ return wayId
325
+ }
326
+
327
+ private processMultiLineString(
328
+ geometry: MultiLineString,
329
+ featureId?: number,
330
+ tags?: OsmTags,
331
+ ) {
332
+ const coordinates = geometry.coordinates
333
+ if (coordinates.length === 0)
334
+ throw Error("MultiLineString must have at least one LineString")
335
+
336
+ const wayIds: number[] = []
337
+ for (const line of coordinates) {
338
+ if (line.length < 2)
339
+ throw Error("LineString must have at least 2 coordinates")
340
+ const wayId = this.processLineString(
341
+ { type: "LineString", coordinates: line },
342
+ this.getNextWayId(),
343
+ )
344
+ wayIds.push(wayId)
345
+ }
346
+
347
+ this.osm.relations.addRelation({
348
+ id: featureId ?? this.getNextRelationId(),
349
+ members: wayIds.map((id) => ({ type: "way", ref: id })),
350
+ tags: { type: "multilinestring", ...tags },
351
+ })
352
+ }
353
+
354
+ private processPolygon(
355
+ geometry: Polygon,
356
+ type: "way" | "relation",
357
+ featureId: number | undefined,
358
+ tags: OsmTags | undefined,
359
+ ) {
360
+ const coordinates = geometry.coordinates
361
+ if (coordinates.length === 0) return
362
+
363
+ const outerRing = coordinates[0]
364
+ if (!outerRing || outerRing.length < 3) return
365
+
366
+ // Create nodes for outer ring
367
+ const outerNodeRefs: number[] = []
368
+ for (const [lon, lat] of outerRing) {
369
+ if (lon === undefined || lat === undefined) continue
370
+ const nodeId = this.getOrCreateNode(lon, lat)
371
+ outerNodeRefs.push(nodeId)
372
+ }
373
+
374
+ if (outerNodeRefs.length < 3) return
375
+
376
+ // Ensure the outer ring is closed
377
+ if (outerNodeRefs[0] !== outerNodeRefs[outerNodeRefs.length - 1]) {
378
+ outerNodeRefs.push(outerNodeRefs[0]!)
379
+ }
380
+
381
+ const outerWayId =
382
+ type === "relation"
383
+ ? this.getNextWayId()
384
+ : (featureId ?? this.getNextWayId())
385
+ this.osm.ways.addWay({
386
+ id: outerWayId,
387
+ refs: outerNodeRefs,
388
+ tags: type === "relation" ? { area: "yes" } : { area: "yes", ...tags },
389
+ })
390
+
391
+ if (type === "way") return
392
+
393
+ // Create separate ways for holes
394
+ const holeWayIds: number[] = []
395
+ for (let i = 1; i < coordinates.length; i++) {
396
+ const holeRing = coordinates[i]
397
+ if (!holeRing || holeRing.length < 3) continue
398
+
399
+ const holeNodeRefs: number[] = []
400
+ for (const [lon, lat] of holeRing) {
401
+ if (lon === undefined || lat === undefined) continue
402
+ const nodeId = this.getOrCreateNode(lon, lat)
403
+ holeNodeRefs.push(nodeId)
404
+ }
405
+
406
+ if (holeNodeRefs.length < 3) continue
407
+
408
+ // Ensure the ring is closed
409
+ if (holeNodeRefs[0] !== holeNodeRefs[holeNodeRefs.length - 1]) {
410
+ holeNodeRefs.push(holeNodeRefs[0]!)
411
+ }
412
+
413
+ const holeWayId = this.getNextWayId()
414
+ this.osm.ways.addWay({
415
+ id: holeWayId,
416
+ refs: holeNodeRefs,
417
+ tags: { area: "yes" },
418
+ })
419
+ holeWayIds.push(holeWayId)
420
+ }
421
+
422
+ this.osm.relations.addRelation({
423
+ id: featureId ?? this.getNextRelationId(),
424
+ members: [
425
+ { type: "way", ref: outerWayId, role: "outer" },
426
+ ...holeWayIds.map(
427
+ (id) =>
428
+ ({ type: "way", ref: id, role: "inner" }) as OsmRelationMember,
429
+ ),
430
+ ],
431
+ tags: {
432
+ type: "multipolygon",
433
+ ...tags,
434
+ },
435
+ })
436
+ }
437
+
438
+ private processMultiPolygon(
439
+ geometry: MultiPolygon,
440
+ featureId: number | undefined,
441
+ tags: OsmTags | undefined,
442
+ ) {
443
+ const coordinates = geometry.coordinates
444
+ if (coordinates.length === 0) return
445
+
446
+ const relationMembers: OsmRelationMember[] = []
447
+
448
+ for (const polygon of coordinates) {
449
+ if (polygon.length === 0) continue
450
+
451
+ const outerRing = polygon[0]
452
+ if (!outerRing || outerRing.length < 3) continue
453
+
454
+ // Create nodes for outer ring
455
+ const outerNodeRefs: number[] = []
456
+ for (const [lon, lat] of outerRing) {
457
+ if (lon === undefined || lat === undefined) continue
458
+ const nodeId = this.getOrCreateNode(lon, lat)
459
+ outerNodeRefs.push(nodeId)
460
+ }
461
+
462
+ if (outerNodeRefs.length < 3) continue
463
+
464
+ // Ensure the ring is closed
465
+ if (outerNodeRefs[0] !== outerNodeRefs[outerNodeRefs.length - 1]) {
466
+ outerNodeRefs.push(outerNodeRefs[0]!)
467
+ }
468
+
469
+ const outerWayId = this.getNextWayId()
470
+ this.osm.ways.addWay({
471
+ id: outerWayId,
472
+ refs: outerNodeRefs,
473
+ tags: { area: "yes" },
474
+ })
475
+ relationMembers.push({ type: "way", ref: outerWayId, role: "outer" })
476
+
477
+ // Create separate ways for holes
478
+ for (let i = 1; i < polygon.length; i++) {
479
+ const holeRing = polygon[i]
480
+ if (!holeRing || holeRing.length < 3) continue
481
+
482
+ const holeNodeRefs: number[] = []
483
+ for (const [lon, lat] of holeRing) {
484
+ if (lon === undefined || lat === undefined) continue
485
+ const nodeId = this.getOrCreateNode(lon, lat)
486
+ holeNodeRefs.push(nodeId)
487
+ }
488
+
489
+ if (holeNodeRefs.length < 3) continue
490
+
491
+ // Ensure the ring is closed
492
+ if (holeNodeRefs[0] !== holeNodeRefs[holeNodeRefs.length - 1]) {
493
+ holeNodeRefs.push(holeNodeRefs[0]!)
494
+ }
495
+
496
+ const holeWayId = this.getNextWayId()
497
+ this.osm.ways.addWay({
498
+ id: holeWayId,
499
+ refs: holeNodeRefs,
500
+ tags: { area: "yes" },
501
+ })
502
+ relationMembers.push({ type: "way", ref: holeWayId, role: "inner" })
503
+ }
504
+ }
505
+
506
+ if (relationMembers.length > 0) {
507
+ this.osm.relations.addRelation({
508
+ id: featureId ?? this.getNextRelationId(),
509
+ members: relationMembers,
510
+ tags: { type: "multipolygon", ...tags },
511
+ })
512
+ }
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Parse tags from various formats.
518
+ */
519
+ function parseTags(
520
+ tagsData: Record<string, string | number> | string | null | undefined,
521
+ ): OsmTags | undefined {
522
+ if (!tagsData) return undefined
523
+
524
+ if (typeof tagsData === "string") {
525
+ try {
526
+ const parsed = JSON.parse(tagsData)
527
+ if (typeof parsed === "object" && parsed !== null) {
528
+ return parsed as OsmTags
529
+ }
530
+ } catch {
531
+ return undefined
532
+ }
533
+ }
534
+
535
+ if (typeof tagsData === "object") {
536
+ const tags: OsmTags = {}
537
+ for (const [key, value] of Object.entries(tagsData)) {
538
+ if (typeof value === "string" || typeof value === "number") {
539
+ tags[key] = value
540
+ } else if (value != null) {
541
+ tags[key] = String(value)
542
+ }
543
+ }
544
+ return Object.keys(tags).length > 0 ? tags : undefined
545
+ }
546
+
547
+ return undefined
548
+ }
549
+
550
+ /**
551
+ * Normalize geometry winding order for OSM conventions.
552
+ */
553
+ function normalizeGeometry(geometry: Geometry): Geometry {
554
+ if (geometry.type === "Polygon" || geometry.type === "MultiPolygon") {
555
+ // Use rewind to ensure correct winding order
556
+ const feature = {
557
+ type: "Feature" as const,
558
+ geometry,
559
+ properties: {},
560
+ }
561
+ const rewound = rewindFeature(feature)
562
+ return rewound.geometry ?? geometry
563
+ }
564
+ return geometry
565
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @osmix/geoparquet - Import OSM data from GeoParquet files.
3
+ *
4
+ * Provides import functionality for GeoParquet files including OpenStreetMap US Layercake,
5
+ * converting geometry data to Osmix's in-memory format.
6
+ *
7
+ * Handles geometry mapping:
8
+ * - Point → Node
9
+ * - LineString → Way with nodes
10
+ * - Polygon → Way (simple) or Relation (with holes)
11
+ * - MultiPolygon → Multipolygon relation
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Import GeoParquet data to Osm index
16
+ * import { fromGeoParquet } from "@osmix/geoparquet"
17
+ *
18
+ * const osm = await fromGeoParquet("./data.parquet")
19
+ * const highways = osm.ways.search("highway")
20
+ * ```
21
+ *
22
+ * @module @osmix/geoparquet
23
+ */
24
+
25
+ export * from "./from-geoparquet"
26
+ export * from "./types"
27
+ export { parseWkb } from "./wkb"
package/src/types.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Type definitions for GeoParquet data.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type { GeoBbox2D, OsmTags } from "@osmix/shared/types"
8
+ import type { AsyncBuffer, ParquetReadOptions } from "hyparquet"
9
+
10
+ /**
11
+ * Source types that can be used to read GeoParquet data by hyparquet.
12
+ * - string: File path (Node.js/Bun) or URL (browser)
13
+ * - URL: URL object
14
+ * - ArrayBuffer: Raw parquet data
15
+ * - AsyncBuffer: hyparquet async buffer for streaming
16
+ */
17
+ export type GeoParquetSource = string | URL | ArrayBuffer | AsyncBuffer
18
+
19
+ /**
20
+ * Raw row from GeoParquet file.
21
+ * The geometry field is WKB-encoded.
22
+ */
23
+ export interface GeoParquetRow {
24
+ /** OSM entity type */
25
+ type: "node" | "way" | "relation"
26
+ /** OSM entity ID */
27
+ id: bigint | number
28
+ /** OSM tags as string or key-value pairs */
29
+ tags: OsmTags | string
30
+ /** the xmin, ymin, xmax, and ymax of the element’s geometry */
31
+ bbox: GeoBbox2D
32
+ /** WKB-encoded geometry or GeoJSON */
33
+ geometry: Uint8Array | GeoJSON.Geometry | string
34
+ }
35
+
36
+ /**
37
+ * Options for reading GeoParquet files.
38
+ */
39
+ export interface GeoParquetReadOptions
40
+ extends Omit<ParquetReadOptions, "onComplete" | "file" | "columns"> {
41
+ /** Column name for the entity type (default: "type") */
42
+ typeColumn?: string
43
+ /** Column name for the entity ID (default: "id") */
44
+ idColumn?: string
45
+ /** Column name for the entity tags (default: "tags") */
46
+ tagsColumn?: string
47
+ /** Column name for the entity bbox (default: "bbox") */
48
+ bboxColumn?: string
49
+ /** Column name for the entity geometry (default: "geometry") */
50
+ geometryColumn?: string
51
+ }