Basin GeoParquet Export
pourpoint exports delineated basins to GeoParquet, the OGC standard that stores vector geometry inside Parquet files so tools like GeoPandas, DuckDB spatial, and QGIS read them natively. Reach for it when you want to delineate a catalog of basins once and query the outlines many times.
This page describes the columns, geometry encoding, and basin-identity rules that
BasinGeoParquetWriter writes, so you can consume the output with any
GeoParquet-aware reader.
Purpose
The export supports the "delineate once, extract many" workflow: produce one dataset-level basin outlines file that downstream tools that partition by basin can query by basin identity and source-fabric delineation.
Each row represents one (basin_id, delineation) pair. Reusing a basin_id with different delineation labels is valid and represents the same caller-owned basin catalog entry delineated from different fabric data or methods.
Required Schema
| Column | Arrow type | Nullable | Description |
|---|---|---|---|
basin_id |
Utf8 |
No | Caller-owned basin identity, unique per physical basin within a run and safe as a Hive-style key=value folder/URL path segment. |
delineation |
Utf8 |
No | Source fabric data and method label, formatted by default as {fabric_name}/{fabric_version}/{method}. |
geometry |
Binary |
No | OGC WKB MultiPolygon, 2D, EPSG:4326. |
outlet_lon |
Float64 |
No | Resolved outlet longitude in EPSG:4326. |
outlet_lat |
Float64 |
No | Resolved outlet latitude in EPSG:4326. |
area_km2 |
Float64 |
No | Final geodesic drainage area from the dissolved watershed. |
bbox_minx |
Float32 |
No | Geometry bounding box west, rounded outward. |
bbox_miny |
Float32 |
No | Geometry bounding box south, rounded outward. |
bbox_maxx |
Float32 |
No | Geometry bounding box east, rounded outward. |
bbox_maxy |
Float32 |
No | Geometry bounding box north, rounded outward. |
The file must not store hilbert_index; Hilbert ordering is only an export sort key.
Optional Provenance
The default documented profile includes these nullable provenance columns:
| Column | Arrow type | Nullable | Description |
|---|---|---|---|
resolution_method |
Utf8 |
Yes | Exported display/debug form of outlet resolution. |
refinement_status |
Utf8 |
Yes | One of applied, best_effort_skipped, or disabled. |
upstream_unit_ids |
List<Int64> |
Yes | Dataset-local HFX unit IDs included in traversal, including the terminal. |
adapter_version |
Utf8 |
Yes | Adapter/tooling version from the HFX manifest. This is provenance only. |
A minimal profile may omit all optional provenance columns.
Geometry And CRS
geometry is 2D OGC WKB MultiPolygon in EPSG:4326. The Parquet file must carry GeoParquet geo footer metadata in file-level key-value metadata, not only Arrow schema metadata.
The geo JSON declares:
version:1.1.0primary_column:geometrycolumns.geometry.encoding:WKBcolumns.geometry.geometry_types:["MultiPolygon"]columns.geometry.crs: EPSG:4326 PROJJSON withid.authority = "EPSG"andid.code = 4326columns.geometry.bbox: dataset-level[minx, miny, maxx, maxy]as f64 values covering all row geometries
The export omits orientation. It also does not declare GeoParquet covering.bbox; the flat bbox columns are ordinary attributes with Parquet statistics.
Basin Identity
basin_id is parsed at the boundary with an allowlist:
- bytes length: 1 through 128
- regex:
^[A-Za-z0-9._-]+$ - reject
.and.. - reject Windows device names case-insensitively:
CON,PRN,AUX,NUL,COM1throughCOM9, andLPT1throughLPT9 - reject trailing
.or trailing space - reject
=by the regex, so an ID stays safe as a Hive-stylekey=valuefolder or URL path segment
The caller owns basin_id assignment. A narrow single-fabric default may use DelineationResult::terminal_unit_id() formatted as decimal text, but negative terminal unit IDs are rejected instead of emitting a leading -.
Duplicate (basin_id, delineation) rows are an error. If default terminal-unit IDs collide, the writer must report a specific default-ID collision that names both originating outlets and asks the caller to supply explicit basin_id values.
Delineation Label
delineation identifies source fabric data and export method. The default label is:
The fabric_version component comes from Manifest::fabric_version(), not adapter_version. If fabric_version() is absent, the default label constructor errors and the caller must provide an explicit DelineationLabel or explicit fabric data version.
adapter_version may appear in the optional provenance column, but it never drives delineation. The label is a column value, not a filesystem path segment, so it is not validated with the basin_id path allowlist.
Bbox Columns
Per-row bbox columns are computed from the dissolved MultiPolygon<f64> and converted to Float32 with outward rounding:
bbox_minxandbbox_minyround toward negative infinity at f32 precisionbbox_maxxandbbox_maxyround toward positive infinity at f32 precision
The dataset-level GeoParquet bbox remains f64 and must cover the true row bboxes.
Hilbert Ordering
HFX does not define Hilbert curve parameters for this export. Pourpoint therefore owns these deterministic parameters:
- fixed global EPSG:4326 extent:
[-180.0, -90.0, 180.0, 90.0] - input point: centroid of the dissolved
MultiPolygon - bit depth: 16 bits per axis
- axis mapping: longitude maps to x, latitude maps to y
- normalization: clamp finite lon/lat to the global extent before quantization
- quantization: map minimum extent to
0, maximum extent to(2^16 - 1) - sort key:
HilbertIndex(u32) - tie-breaks:
(hilbert_index ASC, basin_id ASC, delineation ASC)
Because ordering uses a fixed global extent, adding unrelated rows does not renormalize existing row keys.
Row Groups
For fewer than 4,096 rows, write exactly one row group. For 4,096 or more rows:
- compute
group_count = ceil(row_count / 8192) - distribute rows evenly across groups
- give the first
remaindergroups one extra row - ensure every group, including the final group, has 4,096 through 8,192 rows
Writers must enable bbox column statistics so row-group pruning can use the outward-rounded bbox attributes.
Minimal Example
| basin_id | delineation | geometry | outlet_lon | outlet_lat | area_km2 | bbox_minx | bbox_miny | bbox_maxx | bbox_maxy | adapter_version |
|---|---|---|---|---|---|---|---|---|---|---|
rhine-basel |
grit/2.0.0/d8-best-effort |
WKB MultiPolygon | 7.589 | 47.5596 | 365000.0 | 5.80 | 45.80 | 10.60 | 48.90 | hfx-grit-adapter/1.4.0 |
rhine-basel |
grit/2.0.0/no-refine |
WKB MultiPolygon | 7.589 | 47.5596 | 364200.0 | 5.81 | 45.81 | 10.58 | 48.88 | hfx-grit-adapter/1.4.0 |
The repeated basin_id is valid because the delineation values are distinct.
For a locally compiled MERIT HFX dataset (built with the HFX merit-v2
reference adapter), overlapping Pfaf D8 rasters can make refinement
ambiguous; runnable MERIT export examples should use an explicit no-refine
method label when refinement is disabled for that reason.
Reader Smoke
The committed fixture at crates/core/tests/fixtures/export/basin-geoparquet-golden.parquet was smoke-loaded with GeoPandas and PyOGRIO available in the local Python environment:
python3 - <<'PY'
import geopandas as gpd
frame = gpd.read_parquet("crates/core/tests/fixtures/export/basin-geoparquet-golden.parquet")
assert len(frame) == 3
assert frame.crs.to_epsg() == 4326
assert frame.geometry.geom_type.tolist() == ["MultiPolygon"] * 3
assert frame["basin_id"].tolist() == ["basin-center", "basin-west", "basin-east"]
PY
CLI Emission
The current pourpoint delineate CLI supports single-outlet flags and an --outlets
CSV for GeoJSON output; --format currently accepts only geojson. It does not
define a basin GeoParquet export catalog shape. A CLI wrapper should be added
only after that catalog contract is settled, so it can parse all BasinId
values before delineation and call the core writer without inventing another
input format.