Converting Between Formats¶
The convert command transforms between GeoParquet and other vector formats with automatic format detection and optimization.
CLI vs Python Behavior
The CLI gpio convert applies Hilbert sorting by default for optimal spatial queries.
The Python gpio.convert() does NOT sort by default - chain .sort_hilbert() explicitly if needed.
Basic Usage¶
gpio convert input.shp output.parquet
Automatically applies:
- ZSTD compression (level 15)
- 100,000 row groups
- Bbox column with proper metadata
- Hilbert spatial ordering
- GeoParquet metadata (version auto-detected — see GeoParquet Version; 1.1 for non-GeoParquet inputs)
import geoparquet_io as gpio
# Convert with Hilbert sorting (recommended)
gpio.convert('input.shp').sort_hilbert().write('output.parquet')
# Or without sorting (faster but less optimal for spatial queries)
gpio.convert('input.shp').write('output.parquet')
Supported Formats¶
Input Formats (to GeoParquet)¶
Auto-detected by file extension:
- Shapefile (.shp)
- GeoJSON (.geojson, .json)
- GeoPackage (.gpkg)
- FlatGeobuf (.fgb)
- File Geodatabase (.gdb)
- CSV/TSV (.csv, .tsv, .txt) - See CSV/TSV Support below
Any format supported by DuckDB's spatial extension (50+ formats) can be read.
Output Formats (from GeoParquet)¶
Auto-detected from output file extension:
- GeoParquet (.parquet) - Optimized cloud-native format
- GeoPackage (.gpkg) - SQLite-based OGC standard
- FlatGeobuf (.fgb) - Cloud-native streaming format
- CSV (.csv) - Tabular with WKT geometry
- Shapefile (.shp) - Legacy ESRI format
- GeoJSON (.geojson, .json) - Web-friendly JSON format
Converting FROM GeoParquet¶
Convert GeoParquet to other formats with automatic format detection:
# Auto-detects format from extension
gpio convert data.parquet output.gpkg # → GeoPackage
gpio convert data.parquet output.fgb # → FlatGeobuf
gpio convert data.parquet output.csv # → CSV with WKT
gpio convert data.parquet output.shp # → Shapefile
gpio convert data.parquet output.geojson # → GeoJSON
# Use explicit subcommand
gpio convert geopackage data.parquet output.gpkg
gpio convert flatgeobuf data.parquet output.fgb
gpio convert csv data.parquet output.csv
gpio convert shapefile data.parquet output.shp
gpio convert geojson data.parquet output.geojson
import geoparquet_io as gpio
# Load and convert
table = gpio.read('data.parquet')
# Auto-detects from extension
table.write('output.gpkg') # → GeoPackage
table.write('output.fgb') # → FlatGeobuf
table.write('output.csv') # → CSV with WKT
table.write('output.shp') # → Shapefile
table.write('output.geojson') # → GeoJSON
# Or use explicit format
table.write('output.dat', format='csv')
Format-Specific Options¶
GeoPackage:
# Custom layer name
gpio convert data.parquet output.gpkg --layer-name buildings
# Overwrite existing
gpio convert data.parquet output.gpkg --overwrite
Shapefile:
# Custom encoding (default: UTF-8)
gpio convert data.parquet output.shp --encoding ISO-8859-1
# Overwrite existing
gpio convert data.parquet output.shp --overwrite
Shapefile Limitations
- Column names truncated to 10 characters
- File size limit of 2GB
- Limited data type support
- Creates multiple files (.shp, .shx, .dbf, .prj)
- Consider using GeoPackage or FlatGeobuf instead
Remote Shapefile Storage
When writing shapefiles to remote storage (S3, GCS, Azure), all sidecar files (.shp, .shx, .dbf, .prj, etc.) are automatically packaged into a single .shp.zip archive before upload. This ensures atomic uploads and avoids incomplete multi-file uploads.
# Local: Creates output.shp, output.shx, output.dbf, etc.
gpio convert data.parquet output.shp
# Remote: Uploads output.shp.zip containing all files
gpio convert data.parquet s3://bucket/output.shp
# → Creates s3://bucket/output.shp.zip
CSV:
# Include WKT geometry (default)
gpio convert data.parquet output.csv
# Exclude geometry
gpio convert data.parquet output.csv --no-wkt
# Exclude bbox column
gpio convert data.parquet output.csv --no-bbox
GeoJSON:
# Custom precision (default: 7)
gpio convert data.parquet output.geojson --precision 5
# Pretty-print JSON
gpio convert data.parquet output.geojson --pretty
# Include bbox for each feature
gpio convert data.parquet output.geojson --write-bbox
# Use specific field as feature ID
gpio convert data.parquet with-ids.geojson --id-field osm_id
Cloud Output Support¶
All formats support cloud destinations via upload:
# Write local then upload
gpio convert data.parquet local.gpkg
gpio publish upload local.gpkg s3://bucket/output.gpkg
import geoparquet_io as gpio
# Write locally first
table = gpio.read('data.parquet')
table.write('local.gpkg')
# Upload to cloud
gpio.upload('local.gpkg', 's3://bucket/output.gpkg')
Multi-Layer Formats¶
GeoPackage and FileGDB files can contain multiple layers. By default, the first layer is read. Use --layer to select a specific layer.
# Read specific layer from GeoPackage
gpio convert geoparquet multilayer.gpkg buildings.parquet --layer buildings
# Read specific layer from FileGDB
gpio convert geoparquet data.gdb roads.parquet --layer roads
# Without --layer, reads the first/default layer
gpio convert geoparquet data.gpkg output.parquet
import geoparquet_io as gpio
# Read specific layer
gpio.convert('multilayer.gpkg', layer='buildings').write('buildings.parquet')
gpio.convert('multilayer.gpkg', layer='roads').write('roads.parquet')
# Read first layer (default)
gpio.convert('multilayer.gpkg').write('output.parquet')
Invalid Layer Names
Due to an upstream bug in DuckDB's spatial extension, specifying a non-existent layer name may cause a crash instead of raising an error. Ensure layer names are valid before conversion. You can inspect available layers using tools like ogrinfo:
ogrinfo multilayer.gpkg
GeoParquet-to-GeoParquet Conversion¶
When converting between GeoParquet files, special handling is applied to preserve GeoParquet-specific features.
Multiple Geometry Columns¶
GeoParquet files can have multiple geometry columns (e.g., geometry for point locations and boundary for polygon boundaries). When converting, all geometry columns are preserved with their:
- Original column names
- CRS (coordinate reference system)
- Encoding (WKB)
- Geometry types
# Both geometry columns preserved
gpio convert input_multi_geom.parquet output.parquet
import geoparquet_io as gpio
# Both geometry columns preserved automatically
gpio.convert('input_multi_geom.parquet').write('output.parquet')
# With Hilbert sorting (uses primary geometry column)
gpio.convert('input_multi_geom.parquet').sort_hilbert().write('output.parquet')
Bbox and Hilbert Ordering
Bbox computation and Hilbert spatial ordering use the primary geometry column only. Secondary geometry columns are preserved but do not influence spatial indexing.
When the covering metadata is written
A covering entry tells readers that a bbox column's values bound the
geometry, so engines prune on it. gpio convert declares one only when it
can stand behind the claim:
- it computed the bbox column itself, from the geometry, during this convert
- the input's own metadata already declared a covering for the column being preserved
- the output carries a conventional
bboxstruct column (the shape every GeoParquet 1.0 writer emitted, beforecoveringexisted) — this is what makes a 1.0 → 1.1 upgrade declare it
A preserved column with any other name — bounds, parcel_extent,
tile_bounds — is left undeclared, because its name is not evidence that
its values bound the geometry, and a wrong covering makes readers skip rows
that genuinely match. Declare such a column deliberately with
gpio add bbox-metadata; gpio check bbox will point this out.
Custom Geometry Column Names¶
GeoParquet files can use non-standard geometry column names (e.g., the_geom, my_geometry). These names are preserved during conversion:
# Input has primary_column: "the_geom"
# Output preserves "the_geom" (not renamed to "geometry")
gpio convert input.parquet output.parquet
import geoparquet_io as gpio
# Input has primary_column: "the_geom"
# Output preserves "the_geom" (not renamed to "geometry")
gpio.convert('input.parquet').write('output.parquet')
# Custom geometry column names are automatically detected
# For files without GeoParquet metadata, specify the column:
gpio.convert('input.parquet', geometry_column='the_geom').write('output.parquet')
Edges Metadata Preservation¶
Non-planar edges metadata ("edges": "spherical", e.g. from BigQuery
GEOGRAPHY extracts) survives every rewrite: convert, extract, sort,
convert reproject, and partition all carry it through to the output —
including remote (S3/GCS/Azure) outputs.
# Input with edges: "spherical" keeps it in the output
gpio convert geography.parquet output.parquet
gpio sort hilbert geography.parquet sorted.parquet
import geoparquet_io as gpio
# edges metadata is preserved through the pipeline
gpio.read('geography.parquet').sort_hilbert().write('output.parquet')
When a GeoParquet 2.0 input declares an ellipsoidal edges algorithm
(vincenty, karney, andoyer, thomas) and the output is 1.x, the
algorithm is mapped to "spherical" — the only non-planar value 1.x supports
— with a warning. 2.0 outputs keep the algorithm verbatim.
Z and M Dimensions¶
Geometries with Z (elevation) and/or M (measure) values are preserved, and the
written geometry_types metadata carries the spec's dimension suffixes
("Point Z", "LineString ZM"). gpio check spec validates these suffixes
against the actual coordinate dimensions in both directions.
Remote Files¶
Read from cloud storage or HTTPS:
# Convert remote file
gpio convert https://example.com/data.geojson local.parquet
# Convert from S3
gpio convert s3://bucket/input.parquet local-optimized.parquet
# Convert remote to local format
gpio convert s3://bucket/data.parquet local.gpkg
See Remote Files Guide for authentication setup.
Options¶
Geometry Repair¶
Invalid geometry (self-intersections, unclosed rings — common in government and
municipal data) is repaired automatically with ST_MakeValid and a warning
reports how many features were fixed.
Repair is on by default across the pipeline (convert, extract wfs,
extract arcgis, extract bigquery, extract carto, extract geoparquet,
convert geojson, and pmtiles create). It prevents downstream failures such
as tippecanoe TopologyException crashes during PMTiles generation.
To preserve invalid geometry exactly (e.g. for round-tripping), opt out — gpio still counts and warns about the invalid features it left untouched.
# Default: repairs and warns ("Repaired 3 invalid geometries")
gpio convert cordoba.gpkg output.parquet
# Opt out: preserves invalid geometry, still warns
# ("Left unrepaired 3 invalid geometries (--no-repair-geometry)")
gpio convert cordoba.gpkg output.parquet --no-repair-geometry
import geoparquet_io as gpio
# Default: repairs invalid geometry
gpio.convert("cordoba.gpkg").write("output.parquet")
# Opt out: preserve invalid geometry exactly
gpio.convert("cordoba.gpkg", repair_geometry=False).write("output.parquet")
Only invalid geometry is touched: valid geometry is byte-identical, NULL is
preserved, and bounding boxes stay correct (ST_MakeValid never expands an
envelope).
Curved Geometries¶
GeoParquet cannot represent curved geometry types (CircularString,
CompoundCurve, CurvePolygon, MultiCurve, MultiSurface — common in GeoPackage
and FileGDB exports from CAD or ArcGIS), so by default gpio strokes arcs into
line segments on read, the same operation as ogr2ogr -nlt CONVERT_TO_LINEAR
or PostGIS ST_CurveToLine. A warning reports how many features were
linearized.
Note this alters the geometry: arcs become chains of straight segments,
sampled every 4° of arc by default (GDAL's default; ~0.08% area error on a
full circle). Earlier gpio versions failed on curved input — opt out with
--no-linearize-curves to keep that strict behavior.
# Default: linearizes and warns ("Linearized 66 curved geometries ...")
gpio convert curved.gpkg output.parquet
# Denser sampling: 1 degree per segment
gpio convert curved.gpkg output.parquet --max-angle-deg 1
# Opt out: curved input fails with an actionable error
gpio convert curved.gpkg output.parquet --no-linearize-curves
import geoparquet_io as gpio
# Default: linearizes curved geometries
gpio.convert("curved.gpkg").write("output.parquet")
# Denser sampling: 1 degree per segment
gpio.convert("curved.gpkg", max_angle_deg=1.0).write("output.parquet")
# Opt out: raise on curved input instead
gpio.convert("curved.gpkg", linearize_curves=False)
Linear geometries pass through untouched, NULL is preserved, and empty curves become their empty linear counterpart. The surface family (PolyhedralSurface, TIN, Triangle) is not linearized and still raises an error.
Curved input is spotted either by a header scan of local GeoPackages or on the
first pass that parses geometry. --skip-hilbert removes that pass, so a
curved source which is not a local .gpkg (FileGDB, a GeoPackage on S3) still
errors under --skip-hilbert — drop the flag, or linearize with ogr2ogr
first.
Skip Hilbert Ordering¶
For faster conversion when spatial ordering isn't critical:
gpio convert large.gpkg output.parquet --skip-hilbert
Trade-off: Faster conversion but less optimal for spatial queries.
Custom Compression¶
Control compression type and level:
# GZIP compression
gpio convert input.shp output.parquet --compression GZIP --compression-level 6
# Uncompressed (not recommended)
gpio convert input.geojson output.parquet --compression UNCOMPRESSED
Available compression types:
- ZSTD (default, level 15) - Best compression + speed balance
- GZIP (level 1-9) - Wide compatibility
- BROTLI (level 1-11) - High compression
- LZ4 - Fastest decompression
- SNAPPY - Fast compression
- UNCOMPRESSED - No compression
GeoParquet Version¶
Control the GeoParquet encoding version written to output.
Auto-detection (default): when --geoparquet-version is not specified, the
output version is resolved from the input:
- GeoParquet 2.0 input → stays 2.0 (no silent downgrade to 1.1)
- Bare native geo types (Parquet
GEOMETRY/GEOGRAPHYcolumns withoutgeometadata) → upgraded to 2.0 - GeoParquet 1.x input → written as 1.1
- Non-GeoParquet input (Shapefile, GeoJSON, ...) → written as 1.1
Auto-detection applies to both gpio convert and gpio convert reproject. An
explicit --geoparquet-version always wins. The Python API resolves the
version the same way, so gpio.read('native.parquet').write('out.parquet')
writes true 2.0 native output just like the CLI.
# Auto mode: a 2.0 input stays 2.0
gpio convert input_v2.parquet output.parquet
# Auto mode: reproject preserves the input version too
gpio convert reproject input_v2.parquet output.parquet --dst-crs EPSG:3857
# Explicit version always wins
gpio convert input_v2.parquet output.parquet --geoparquet-version 1.1
import geoparquet_io as gpio
# Auto mode: a 2.0 or native-geo input stays/becomes 2.0
gpio.read('input_v2.parquet').write('output.parquet')
# Explicit version always wins
gpio.read('input_v2.parquet').write(
'output.parquet', geoparquet_version='1.1'
)
Explicit versions:
# GeoParquet 1.1 with native GeoArrow nested-coordinate encoding
# (no bbox column; incompatible mixed-geometry columns fall back to WKB)
gpio convert input.geojson output.parquet --geoparquet-version 1.1-geoarrow
# GeoParquet 1.0 with WKB encoding
gpio convert input.shp output.parquet --geoparquet-version 1.0
# GeoParquet 1.1 with WKB encoding (default)
gpio convert input.shp output.parquet --geoparquet-version 1.1
import geoparquet_io as gpio
# GeoParquet 1.1 with native GeoArrow nested-coordinate encoding
# Converts geometry from any input (GeoJSON, Shapefile, GeoPackage, CSV, WKB GeoParquet)
# to native GeoArrow types. No bbox column is added.
# Columns mixing incompatible geometry types fall back to WKB.
gpio.convert('input.geojson').write(
'output.parquet', geoparquet_version='1.1-geoarrow'
)
# GeoParquet 1.0 with WKB encoding
gpio.convert('input.shp').write(
'output.parquet', geoparquet_version='1.0'
)
Available versions:
- 1.0 — GeoParquet 1.0 with WKB encoding
- 1.1 — GeoParquet 1.1 with WKB encoding (default for 1.x and non-GeoParquet inputs)
- 1.1-geoarrow — GeoParquet 1.1 with native GeoArrow (nested-coordinate) encoding; no bbox
column; compatible geometry type mixes are promoted (e.g. Polygon + MultiPolygon →
MultiPolygon); incompatible mixes fall back to WKB
- 2.0 — GeoParquet 2.0 with native Parquet geo types
- parquet-geo-only — Native Parquet geo types without GeoParquet metadata
Verbose Output¶
Track progress and see detailed information:
gpio convert input.gpkg output.parquet --verbose
Shows: - Geometry column detection - Dataset bounds calculation - Bbox column creation - Hilbert ordering progress - File size and validation
Examples¶
Basic Shapefile Conversion¶
gpio convert buildings.shp buildings.parquet
Output:
Converting buildings.shp...
Done in 2.3s
Output: buildings.parquet (4.2 MB)
✓ Output passes GeoParquet validation
import geoparquet_io as gpio
gpio.convert('buildings.shp').sort_hilbert().write('buildings.parquet')
Large Dataset Without Hilbert¶
gpio convert large_dataset.gpkg output.parquet --skip-hilbert
import geoparquet_io as gpio
# Python doesn't sort by default, so just skip sort_hilbert()
gpio.convert('large_dataset.gpkg').write('output.parquet')
Skips Hilbert ordering for faster processing on large files.
Custom Compression Settings¶
gpio convert roads.geojson roads.parquet \
--compression ZSTD \
--compression-level 22 \
--verbose
Maximum ZSTD compression with progress tracking.
Convert and Inspect¶
# Convert
gpio convert input.shp output.parquet
# Verify
gpio inspect output.parquet
# Validate
gpio check all output.parquet
CSV/TSV Support¶
Auto-detects geometry columns. WKT columns (wkt, geometry, geom) checked first, then lat/lon pairs (lat/lon, latitude/longitude).
# Auto-detect WKT or lat/lon
gpio convert points.csv points.parquet
# Explicit columns
gpio convert data.csv out.parquet --wkt-column geom_wkt
gpio convert data.csv out.parquet --lat-column lat --lon-column lng
# Custom delimiter
gpio convert data.txt out.parquet --delimiter "|"
CRS and Validation¶
Default: WGS84 (EPSG:4326). Override with --crs for WKT data:
gpio convert projected.csv out.parquet --crs EPSG:3857
Validates lat/lon ranges (-90 to 90, -180 to 180). Warns on large coordinates suggesting projected CRS.
Invalid Geometries¶
Fails on invalid WKT by default. Skip the unparsable rows with
--skip-invalid:
gpio convert messy.csv out.parquet --skip-invalid
A row whose geometry column is empty is not invalid — it is a row without geometry. Those rows are kept either way, with NULL geometry, so their attributes survive the conversion; they sort after the ordered rows, as they do for every other input format.
Skips invalid rows, disables Hilbert ordering. Mixed geometry types supported.
Delimiters¶
Auto-detects comma and tab. Override with --delimiter for semicolon, pipe, or any single character.
gpio convert data.csv out.parquet --delimiter ";"
Performance¶
The convert command uses DuckDB's spatial extension - the fastest option for GeoParquet conversion, especially for large files.
Benchmarks on representative datasets:
| Dataset | Size | Features | DuckDB | PyOGRIO | ogr2ogr | Fiona |
|---|---|---|---|---|---|---|
| GAUL L2 Shapefile | 739 MB | 45k | 4.6s | 5.9s | 4.1s | 187s |
| Argentina Roads | 1.1 GB | 3.5M | 30s | 66s | 117s | 349s |
DuckDB also uses significantly less memory than alternatives (near-zero vs 600MB-2GB for GeoPandas).
To run your own benchmarks:
gpio benchmark compare input.geojson --iterations 3
See gpio benchmark for details.
See Also¶
- CLI Reference: convert
- benchmark command - Compare conversion performance
- add command - Add indices to existing GeoParquet
- sort command - Sort existing GeoParquet spatially
- check command - Validate and fix GeoParquet files