Raster to Vector¶
gpio process polygonize and gpio process contour turn rasters into
GeoParquet, backed by contourrs
— a Rust raster-tracing library whose Arrow output flows straight into
gpio's write pipeline:
- polygonize traces a categorical raster (land cover, segmentation masks, classification output) into one polygon feature per contiguous class region;
- contour extracts filled contour bands from a continuous raster
(elevation, temperature), one polygon per band with
min/maxbreak attributes.
The implementation lives in
core/process/raster/
(reader.py,
polygonize.py,
contour.py,
output.py),
with the lazy dependency shims in
core/optional_deps.py.
Installation¶
Raster support is an optional extra (gpio's first and only raster surface):
pip install 'geoparquet-io[raster]'
# or, for a uv tool install:
uv tool install geoparquet-io --with contourrs --with rasterio
Python 3.12+
contourrs ships wheels for CPython 3.12–3.14 only. On Python
3.10/3.11 the extra installs rasterio but skips contourrs, and the
commands explain the floor at runtime. contourrs is Apache-2.0;
rasterio reads the GeoTIFF input and supplies the transform, nodata and
CRS, which land in the output's geo metadata as PROJJSON.
Polygonize¶
# Every class becomes polygons; nodata is honored automatically
gpio process polygonize landcover.tif landcover.parquet
# Keep only classes 1 and 3, name the attribute column
gpio process polygonize mask.tif buildings.parquet \
--values 1,3 --value-column class_id
import geoparquet_io as gpio
from geoparquet_io.api import ops
# Table constructor, chainable
gpio.Table.polygonize('landcover.tif').write('landcover.parquet')
# Pure function returning a PyArrow table
table = ops.polygonize('mask.tif', values=[1.0], value_column='class_id')
| Option | Default | Meaning |
|---|---|---|
--band |
1 | Raster band to trace |
--values |
all | Comma-separated class values to keep |
--value-column |
value |
Name of the class attribute column |
--nodata |
file's tag | Nodata value to exclude. An explicit value replaces the tag: pixels equal to a wrong tag become data again |
--no-mask |
off | Ignore the raster's mask/nodata entirely |
The traced polygons come out with counterclockwise exterior rings, as the GeoParquet spec recommends, and round-trip exactly: rasterizing the output reproduces the input array pixel for pixel.
Contour¶
# Bands every 100 units, spanning the raster's value range
gpio process contour dem.tif contours.parquet --interval 100
# Explicit break values
gpio process contour dem.tif contours.parquet --levels 0,250,500,1000
import geoparquet_io as gpio
from geoparquet_io.api import ops
gpio.Table.contour('dem.tif', interval=100).write('contours.parquet')
table = ops.contour('dem.tif', levels=[0.0, 250.0, 500.0, 1000.0])
| Option | Default | Meaning |
|---|---|---|
--interval N |
— | Breaks every N units from --base (spans the band's range) |
--levels a,b,c |
— | Explicit break values (mutually exclusive with --interval) |
--base |
0.0 | Offset for --interval |
--band |
1 | Raster band to contour |
--nodata |
file's tag | Nodata value to exclude. An explicit value replaces the tag |
--no-mask |
off | Ignore the raster's mask/nodata entirely |
--min-column / --max-column |
min / max |
Names for each band's break attributes |
Each output feature is one band polygon attributed with its [min, max)
break values; bands never overlap and together tile the valid data area.
Contours are interpolated between grid nodes (marching squares), so a
band footprint reaches the outermost pixel centers, not the outer pixel
edges.
Output metadata¶
Both commands write through gpio's normal pipeline: the raster's CRS
becomes the geo block's PROJJSON crs, geometry_types and bbox are
computed from the actual output, and row groups, compression and
GeoParquet version behave exactly as in every other gpio write (all the
usual --compression, --row-group-size, --version options apply).