@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
package/src/wkb.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WKB (Well-Known Binary) geometry parsing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Browser-compatible WKB parser using DataView instead of Node.js Buffer.
|
|
5
|
+
* Supports standard WKB and EWKB (with SRID) formats.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
Geometry,
|
|
12
|
+
GeometryCollection,
|
|
13
|
+
LineString,
|
|
14
|
+
MultiLineString,
|
|
15
|
+
MultiPoint,
|
|
16
|
+
MultiPolygon,
|
|
17
|
+
Point,
|
|
18
|
+
Polygon,
|
|
19
|
+
Position,
|
|
20
|
+
} from "geojson"
|
|
21
|
+
|
|
22
|
+
/** WKB geometry type codes */
|
|
23
|
+
const WKB_POINT = 1
|
|
24
|
+
const WKB_LINESTRING = 2
|
|
25
|
+
const WKB_POLYGON = 3
|
|
26
|
+
const WKB_MULTIPOINT = 4
|
|
27
|
+
const WKB_MULTILINESTRING = 5
|
|
28
|
+
const WKB_MULTIPOLYGON = 6
|
|
29
|
+
const WKB_GEOMETRYCOLLECTION = 7
|
|
30
|
+
|
|
31
|
+
/** EWKB flags */
|
|
32
|
+
const EWKB_SRID_FLAG = 0x20000000
|
|
33
|
+
const EWKB_Z_FLAG = 0x80000000
|
|
34
|
+
const EWKB_M_FLAG = 0x40000000
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Binary reader using DataView for browser compatibility.
|
|
38
|
+
*/
|
|
39
|
+
class WkbReader {
|
|
40
|
+
private view: DataView
|
|
41
|
+
private offset = 0
|
|
42
|
+
private littleEndian = true
|
|
43
|
+
|
|
44
|
+
constructor(data: Uint8Array) {
|
|
45
|
+
// Create DataView from the Uint8Array's underlying buffer with correct offset
|
|
46
|
+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
readByte(): number {
|
|
50
|
+
const value = this.view.getUint8(this.offset)
|
|
51
|
+
this.offset += 1
|
|
52
|
+
return value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
readUint32(): number {
|
|
56
|
+
const value = this.view.getUint32(this.offset, this.littleEndian)
|
|
57
|
+
this.offset += 4
|
|
58
|
+
return value
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
readDouble(): number {
|
|
62
|
+
const value = this.view.getFloat64(this.offset, this.littleEndian)
|
|
63
|
+
this.offset += 8
|
|
64
|
+
return value
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setLittleEndian(littleEndian: boolean): void {
|
|
68
|
+
this.littleEndian = littleEndian
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse a WKB geometry into a GeoJSON Geometry object.
|
|
74
|
+
*
|
|
75
|
+
* Browser-compatible implementation using DataView.
|
|
76
|
+
* Supports Point, LineString, Polygon, MultiPoint, MultiLineString,
|
|
77
|
+
* MultiPolygon, and GeometryCollection. Also handles EWKB with SRID.
|
|
78
|
+
*
|
|
79
|
+
* @param wkb - WKB-encoded geometry as Uint8Array
|
|
80
|
+
* @returns Parsed GeoJSON Geometry
|
|
81
|
+
* @throws Error if geometry type is unsupported
|
|
82
|
+
*/
|
|
83
|
+
export function parseWkb(wkb: Uint8Array): Geometry {
|
|
84
|
+
const reader = new WkbReader(wkb)
|
|
85
|
+
return parseGeometry(reader)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse a geometry from the reader at current position.
|
|
90
|
+
*/
|
|
91
|
+
function parseGeometry(reader: WkbReader): Geometry {
|
|
92
|
+
// Read byte order
|
|
93
|
+
const byteOrder = reader.readByte()
|
|
94
|
+
reader.setLittleEndian(byteOrder === 1)
|
|
95
|
+
|
|
96
|
+
// Read geometry type (may include EWKB flags)
|
|
97
|
+
let geometryType = reader.readUint32()
|
|
98
|
+
|
|
99
|
+
// Handle EWKB SRID flag
|
|
100
|
+
if (geometryType & EWKB_SRID_FLAG) {
|
|
101
|
+
// Skip SRID (4 bytes)
|
|
102
|
+
reader.readUint32()
|
|
103
|
+
geometryType &= ~EWKB_SRID_FLAG
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check for Z/M flags and mask them out
|
|
107
|
+
const hasZ = (geometryType & EWKB_Z_FLAG) !== 0
|
|
108
|
+
const hasM = (geometryType & EWKB_M_FLAG) !== 0
|
|
109
|
+
geometryType &= 0x0000ffff // Keep only the base type
|
|
110
|
+
|
|
111
|
+
// Determine coordinate dimensions
|
|
112
|
+
const dimensions = 2 + (hasZ ? 1 : 0) + (hasM ? 1 : 0)
|
|
113
|
+
|
|
114
|
+
switch (geometryType) {
|
|
115
|
+
case WKB_POINT:
|
|
116
|
+
return parsePoint(reader, dimensions)
|
|
117
|
+
case WKB_LINESTRING:
|
|
118
|
+
return parseLineString(reader, dimensions)
|
|
119
|
+
case WKB_POLYGON:
|
|
120
|
+
return parsePolygon(reader, dimensions)
|
|
121
|
+
case WKB_MULTIPOINT:
|
|
122
|
+
return parseMultiPoint(reader)
|
|
123
|
+
case WKB_MULTILINESTRING:
|
|
124
|
+
return parseMultiLineString(reader)
|
|
125
|
+
case WKB_MULTIPOLYGON:
|
|
126
|
+
return parseMultiPolygon(reader)
|
|
127
|
+
case WKB_GEOMETRYCOLLECTION:
|
|
128
|
+
return parseGeometryCollection(reader)
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`Unsupported WKB geometry type: ${geometryType}`)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Read a coordinate (lon, lat, and optionally z/m).
|
|
136
|
+
* Only returns [lon, lat] for GeoJSON compatibility.
|
|
137
|
+
*/
|
|
138
|
+
function readCoordinate(reader: WkbReader, dimensions: number): Position {
|
|
139
|
+
const x = reader.readDouble()
|
|
140
|
+
const y = reader.readDouble()
|
|
141
|
+
|
|
142
|
+
// Read and discard extra dimensions (Z, M)
|
|
143
|
+
for (let i = 2; i < dimensions; i++) {
|
|
144
|
+
reader.readDouble()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return [x, y]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Read an array of coordinates.
|
|
152
|
+
*/
|
|
153
|
+
function readCoordinates(reader: WkbReader, dimensions: number): Position[] {
|
|
154
|
+
const count = reader.readUint32()
|
|
155
|
+
const coords: Position[] = []
|
|
156
|
+
for (let i = 0; i < count; i++) {
|
|
157
|
+
coords.push(readCoordinate(reader, dimensions))
|
|
158
|
+
}
|
|
159
|
+
return coords
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parsePoint(reader: WkbReader, dimensions: number): Point {
|
|
163
|
+
const coordinates = readCoordinate(reader, dimensions)
|
|
164
|
+
return { type: "Point", coordinates }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseLineString(reader: WkbReader, dimensions: number): LineString {
|
|
168
|
+
const coordinates = readCoordinates(reader, dimensions)
|
|
169
|
+
return { type: "LineString", coordinates }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parsePolygon(reader: WkbReader, dimensions: number): Polygon {
|
|
173
|
+
const numRings = reader.readUint32()
|
|
174
|
+
const coordinates: Position[][] = []
|
|
175
|
+
for (let i = 0; i < numRings; i++) {
|
|
176
|
+
coordinates.push(readCoordinates(reader, dimensions))
|
|
177
|
+
}
|
|
178
|
+
return { type: "Polygon", coordinates }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function parseMultiPoint(reader: WkbReader): MultiPoint {
|
|
182
|
+
const numPoints = reader.readUint32()
|
|
183
|
+
const coordinates: Position[] = []
|
|
184
|
+
for (let i = 0; i < numPoints; i++) {
|
|
185
|
+
const point = parseGeometry(reader) as Point
|
|
186
|
+
coordinates.push(point.coordinates)
|
|
187
|
+
}
|
|
188
|
+
return { type: "MultiPoint", coordinates }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parseMultiLineString(reader: WkbReader): MultiLineString {
|
|
192
|
+
const numLineStrings = reader.readUint32()
|
|
193
|
+
const coordinates: Position[][] = []
|
|
194
|
+
for (let i = 0; i < numLineStrings; i++) {
|
|
195
|
+
const lineString = parseGeometry(reader) as LineString
|
|
196
|
+
coordinates.push(lineString.coordinates)
|
|
197
|
+
}
|
|
198
|
+
return { type: "MultiLineString", coordinates }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseMultiPolygon(reader: WkbReader): MultiPolygon {
|
|
202
|
+
const numPolygons = reader.readUint32()
|
|
203
|
+
const coordinates: Position[][][] = []
|
|
204
|
+
for (let i = 0; i < numPolygons; i++) {
|
|
205
|
+
const polygon = parseGeometry(reader) as Polygon
|
|
206
|
+
coordinates.push(polygon.coordinates)
|
|
207
|
+
}
|
|
208
|
+
return { type: "MultiPolygon", coordinates }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function parseGeometryCollection(reader: WkbReader): GeometryCollection {
|
|
212
|
+
const numGeometries = reader.readUint32()
|
|
213
|
+
const geometries: Geometry[] = []
|
|
214
|
+
for (let i = 0; i < numGeometries; i++) {
|
|
215
|
+
geometries.push(parseGeometry(reader))
|
|
216
|
+
}
|
|
217
|
+
return { type: "GeometryCollection", geometries }
|
|
218
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Download Monaco highway data from LayerCake (OSM US) using DuckDB
|
|
4
|
+
# Monaco bounding box coordinates from MapTiler:
|
|
5
|
+
# - xmin: 7.409205
|
|
6
|
+
# - xmax: 7.448637
|
|
7
|
+
# - ymin: 43.72335
|
|
8
|
+
# - ymax: 43.75169
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
OUTPUT_DIR="$SCRIPT_DIR/../../../fixtures"
|
|
12
|
+
OUTPUT_FILE="$OUTPUT_DIR/monaco-highways.parquet"
|
|
13
|
+
|
|
14
|
+
echo "Downloading Monaco highway data from LayerCake..."
|
|
15
|
+
echo "Output: $OUTPUT_FILE"
|
|
16
|
+
|
|
17
|
+
duckdb <<EOF
|
|
18
|
+
INSTALL spatial;
|
|
19
|
+
LOAD spatial;
|
|
20
|
+
|
|
21
|
+
COPY (
|
|
22
|
+
FROM 'https://data.openstreetmap.us/layercake/highways.parquet'
|
|
23
|
+
SELECT
|
|
24
|
+
id,
|
|
25
|
+
tags,
|
|
26
|
+
geometry
|
|
27
|
+
WHERE
|
|
28
|
+
bbox.xmin >= 7.409205 AND bbox.xmax <= 7.448637 AND
|
|
29
|
+
bbox.ymin >= 43.72335 AND bbox.ymax <= 43.75169
|
|
30
|
+
) TO '$OUTPUT_FILE' WITH (FORMAT PARQUET);
|
|
31
|
+
EOF
|
|
32
|
+
|
|
33
|
+
if [ -f "$OUTPUT_FILE" ]; then
|
|
34
|
+
echo "Successfully downloaded Monaco highways to $OUTPUT_FILE"
|
|
35
|
+
echo "File size: $(ls -lh "$OUTPUT_FILE" | awk '{print $5}')"
|
|
36
|
+
else
|
|
37
|
+
echo "Error: Failed to download file"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|