concavewrap-geo
Build auditable concave hulls that cover complete polygon areas.
Many concave-hull implementations guarantee that the output contains the input
vertices. For areal data that is not enough: a hull edge can cut through a source
polygon. concavewrap repairs or rejects invalid input, constructs the hull, and
then enforces and verifies coverage of every source polygon.
Quick start
python -m pip install concavewrap-geo
concavewrap build areas.gpkg \
--layer areas \
--ratio 0.25 \
--output hulls.gpkg \
--diagnostics hulls.json
The output is a GeoPackage layer named hulls. The JSON report records input and
output areas, added fill area, the convex-hull area, coverage checks, repaired
features, and any convex fallback.
Reproducible example
Generate and process four synthetic polygon arrangements, including the failure modes that commonly make a single concave-hull setting misleading:
python examples/run_examples.py
| scenario | question answered | expected result |
|---|---|---|
forest-patches |
can irregular areal patches be wrapped without cutting them? | one hull that covers every complete polygon |
large-gap |
should disconnected patches be bridged? | global gives one hull; component gives two |
narrow-neck |
does a thin connecting polygon remain covered? | one valid hull covering the neck and both ends |
ring-with-hole |
should a semantic hole remain open? | allow_holes=True keeps it; the default fills it |
Generated inputs, outputs, and JSON reports are placed under
examples/generated/. To run the standard case through the CLI:
concavewrap build examples/generated/forest-patches.gpkg \
--layer areas \
--ratio 0.25 \
--output examples/generated/forest-hull-cli.gpkg \
--diagnostics examples/generated/forest-hull-cli.json
Expected audit values are printed and asserted by the script, then saved in
summary.json. No external or real-world data is used.
Ratio and policies
--ratio follows GEOS/Shapely semantics:
| value | behavior |
|---|---|
0 |
most concave result available from the input vertices |
0.25 |
useful starting point for irregular polygon collections |
1 |
convex hull |
The ratio is dimensionless. It is not an alpha radius or a distance threshold, so values from alpha-shape tools cannot be copied directly.
| option | choices | meaning |
|---|---|---|
--scope |
global, component |
bridge all polygons in a group, or keep disconnected dissolved components separate |
--group-by FIELD |
any scalar field | build an independent result for each attribute value |
--allow-holes |
flag | retain source holes and permit hull holes; without it all holes are filled |
--invalid |
repair, error |
repair invalid polygonal geometry with GEOS or stop |
--fallback |
convex, error |
use and report the convex hull if the concave operation fails, or stop |
Guarantees
For every output record, concavewrap verifies that:
- the result is valid polygonal geometry;
- the result covers the complete input polygon area, not only its vertices;
- the result does not extend beyond the convex hull, within floating-point tolerance;
- the chosen hole, grouping, component, repair, and fallback policies are recorded;
- input ordering determines stable group and component ordering.
Output fields
| field | meaning |
|---|---|
hull_id |
stable one-based output identifier |
group_value |
text representation of the grouping value, or null |
source_features |
number of input features contributing to the hull |
repaired_features |
number of invalid contributors repaired |
input_area |
dissolved source area in CRS square units |
hull_area |
output hull area |
fill_area |
area added around or between source polygons |
convex_area |
area of the corresponding convex hull |
hull_to_convex_ratio |
output area divided by convex-hull area |
fallback_used |
whether the convex fallback was used |
fallback_reason |
captured failure message, if any |
ratio, allow_holes, scope |
effective construction settings |
covers_input |
final invariant check |
Python API
import geopandas as gpd
from concavewrap import HullOptions, HullResult, build_hulls
areas = gpd.read_file("areas.gpkg", layer="areas")
result: HullResult = build_hulls(
areas,
HullOptions(ratio=0.25, scope="global", allow_holes=False),
)
result.hulls.to_file("hulls.gpkg", layer="hulls", driver="GPKG")
print(result.report())
The stable public interface is build_hulls(...), HullOptions, HullResult,
and HullDiagnostic.
Limits
- Input geometries must be Polygon or MultiPolygon.
- A projected CRS with horizontal units in metres is required so that area audit values are meaningful and comparable.
componentscope dissolves touching and overlapping polygons before identifying disconnected components.- Concave hulls are not unique across algorithms. This package uses the GEOS algorithm exposed by Shapely and records its ratio explicitly.
- A hull is a generalization. Always inspect results for sparse samples, narrow necks, very large gaps, and geometries whose holes have semantic meaning.
Run concavewrap --help for all options. Exit code 0 means success and 2
means invalid input or a processing failure.
The pre-release and launch checks are listed in
docs/release-checklist.md.
Development
python -m pip install -e ".[test]"
python -m ruff check .
python -m ruff format --check .
python -m pytest
Copyright 2026 Alena Nikitina. Licensed under the Apache License 2.0.
Metadata
Release files for concavewrap-geo 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| concavewrap_geo-0.1.0.tar.gz | 20.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| concavewrap_geo-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.7 kB
Release files / concavewrap_geo-0.1.0.tar.gz
| Download URL | concavewrap_geo-0.1.0.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d270b8093589db673426bad076d29edd4cea592645d5762ea5e0c613127ef6bf
|
|
BLAKE2b-256 checksum How to use checksums |
8e9c791054367c5b34933138d81c6773b28998aa7e922e4f6ed921f9b553f801
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / concavewrap_geo-0.1.0-py3-none-any.whl
| Download URL | concavewrap_geo-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fee03b694271ff0a64cc521974735d2f94bcfacaad8706e33ac98758cb9824da
|
|
BLAKE2b-256 checksum How to use checksums |
89356863fc97b9182197531ba0f3bc14277db4914c75a51443f6a0134f4e82a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log