Getting Started¶
Installation¶
Pre-built wheels are available for Linux, macOS, and Windows on Python 3.12+.
Development setup¶
git clone https://github.com/taylor-geospatial/contourrs.git
cd contourrs
uv sync --extra dev
uv run maturin develop --release
Run tests:
Pre-commit hooks:
Rust dependencies shared by both crates and their lint settings live in the root
Cargo.toml. The checks apply selected
BeautifulRust practices,
including documented public APIs, explicit unsafe invariants, checked conversions,
and no unexplained production unwrap() calls. Clippy covers all targets and
features; warnings become errors in pre-commit and CI.
For Rust API changes, also check the default feature set, release-mode bounds, and generated documentation:
cargo test -p contourrs --no-default-features
cargo test -p contourrs --release --test raster_access
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps
First usage¶
Polygonize a categorical raster¶
import numpy as np
from contourrs import shapes
raster = np.array(
[
[1, 1, 2],
[1, 2, 2],
[3, 3, 3],
],
dtype=np.uint8,
)
for geojson, value in shapes(raster, connectivity=4):
print(f"value={value}, type={geojson['type']}")
Each geojson is a standard GeoJSON Polygon geometry dict. The value is the pixel value for that region.
Contour a continuous raster¶
import numpy as np
from contourrs import contours
dem = np.random.default_rng(42).random((256, 256)).astype(np.float32)
for geojson, value in contours(dem, thresholds=[0.25, 0.5, 0.75]):
print(f"band={value}, rings={len(geojson['coordinates'])}")
Thresholds define the break values between bands. Each band covers the interval [lo, hi) between consecutive thresholds.
Arrow output (zero-copy)¶
For best performance and GeoParquet export, use the Arrow variants:
from contourrs import shapes_arrow, contours_arrow
table = shapes_arrow(raster, connectivity=4)
# table.schema: geometry (binary/WKB), value (float64)
import pyarrow.parquet as pq
pq.write_table(table, "output.parquet")
Excluding nodata values¶
If your array uses a sentinel nodata value, pass it directly:
Using an Affine transform¶
You can pass an affine.Affine directly without converting it to a tuple:
from affine import Affine
from contourrs import shapes
transform = Affine(10.0, 0.0, 500000.0, 0.0, -10.0, 4500000.0)
polygons = shapes(raster, transform=transform)
Building the docs¶
Install the docs dependencies with uv sync --extra docs, then run bash scripts/build_tutorials.sh and uv run --extra docs zensical build --clean.
Run make docs to preview the generated site.
The build executes the quickstart, DEM, and CDL notebooks and converts the TorchGeo notebook without running inference.
Cells tagged skip_ci are omitted from executed tutorials.