GeoQC is a typed Python toolkit for quality control of geospatial data. It detects common geometry defects and provides reusable building blocks for CRS, datum, axis-order, attribute, batch, and reporting workflows.
Release status:
0.2.0(alpha). The top-levelvalidate_geometry()API is ready for evaluation. Advanced APIs may evolve before1.0and changes will be documented in the changelog.
GeoQC exposes one validation engine through several surfaces, so results are identical no matter how you call it:
- Developers install the package (
pip install geoqc) and use the Python API or thegeoqcCLI. - Everyone else runs the bundled web app (
apps/web) in a browser. It uploads datasets to the FastAPI backend, which validates them with the samegeoqcengine — no validation logic is reimplemented in JavaScript. See API and deployment to run the backend and client locally.
Because the web API, CLI, and Python API all call
ShapelyGeometryValidator through the same audit engine, the Website, CLI, and
library always agree on what is valid. See Architecture.
- Validate Shapely geometries for empty or invalid values, self-intersections, ring errors, and duplicate vertices.
- Repair topology with minimal shape change — self-intersections, invalid rings, duplicate vertices, slivers, overlaps, and enclosed gaps — with before/after geometry, a repair report, and preview/apply/undo. See intelligent topology repair.
- Read common vector datasets through GeoPandas and Pyogrio, including Shapefile, GeoJSON, GeoPackage, and FlatGeobuf.
- Analyze and repair road networks, including every
MultiLineStringpart, dangling/broken connections, duplicate segments, unnoded intersections, conservative endpoint snapping, full intersection noding, minimum connector planning for disconnected islands, and source-feature provenance. Thegeoqc repair-roadsCLI writes a new GeoPackage atomically. - Apply declarative Phase-2 topology checks for matching boundaries, connected endpoints, overshoot/undershoot, geometry types, multipart features, spikes, minimum segment/vertex spacing, spatial relations, attribute-controlled overlap, and precision grids.
- Stream GeoPackage, Shapefile, GeoJSON, and GeoParquet audits in bounded Arrow chunks without loading the complete dataset into RAM.
- Audit independent datasets concurrently with memory-aware process scheduling, isolated failures, and deterministic output ordering.
- Inspect CRS consistency, axis order, and possible datum shifts.
- Run typed, extensible quality rules with immutable result models.
- Process bounded batches and generate optional HTML or interactive reports.
- Expose a hardened FastAPI upload endpoint and a responsive React client.
GeoQC can return repaired geometries or write a new output dataset, but it never silently modifies a source dataset. Ambiguous repairs are marked for review and retain before/after geometry plus change metrics.
- Python 3.12 or 3.13
- A platform supported by GeoPandas, Pyogrio, Shapely, and PyProj wheels
Install the core library and CLI from PyPI:
python -m pip install geoqcInstall optional delivery and reporting dependencies only when needed:
python -m pip install "geoqc[api]" # FastAPI and Uvicorn
python -m pip install "geoqc[report]" # Jinja2 and Folium
python -m pip install "geoqc[all]" # All optional featuresTo install a source checkout:
python -m pip install .The repository ships a 120 KB sample, examples/sample_defects.gpkg, with
known defects: a self-intersecting parcel, a 0.05 m sliver, two overlapping
parcels, and a road that stops 0.5 m short of the network. From a source
checkout:
import geoqc
report = geoqc.audit_dataset(
"examples/sample_defects.gpkg",
layer="parcels",
profile="docs/examples/parcel-profile.yaml",
)
print(report.status, report.issue_count)
for issue in report.issues:
print(issue.code, issue.feature_index, issue.message)Expected output includes GEO-SELF_INTERSECTION, TOP-MINIMUM-AREA for the
sliver, and TOP-NO-OVERLAP. Use layer="roads" with
docs/examples/road-network-profile.yaml to see the two ROAD-DEAD_END
findings. examples/make_sample_data.py regenerates the file.
The report always includes dataset metadata and explicit passed, failed,
error, or skipped outcomes. Attribute checks require an AttributeSchema;
unconfigured topology/spatial checks and checks lacking a safe CRS are skipped
with an actionable reason.
For profile-driven rules and issue-layer output, load a reusable profile:
from geoqc import audit_file, load_quality_profile, write_issue_layers
profile = load_quality_profile("parcel-profile.yaml")
result = audit_file("parcels.gpkg", layer="parcels", profile=profile)
print(result.quality_score)
print(result.issues[0].recommendation)
write_issue_layers(result, "parcel-issues.gpkg")Presets are available for parcels, administrative boundaries, roads, and point surveys. Findings include severity, repair risk, source feature indexes, suggested fixes, and issue geometry. See the dataset quality workflow for custom cross-layer rules, safe repair, reports, CRS/unit guards, and CI quality gates.
Validate one Shapely geometry using the stable top-level API:
from shapely.geometry import Polygon
from geoqc import GeometryIssueType, validate_geometry
polygon = Polygon([(0, 0), (2, 2), (0, 2), (2, 0), (0, 0)])
result = validate_geometry(polygon)
print(result.is_valid) # False
for issue in result.issues:
print(issue.issue_type, issue.message)
if result.has_issue(GeometryIssueType.SELF_INTERSECTION):
print("Repair the polygon before using it downstream.")Results are immutable, typed dataclasses. Passing an object that is not a
Shapely geometry raises TypeError so contract errors are detected early.
Ready-made profiles check geometry and topology only. They require no
attribute columns, because each agency uses its own schema. They ship inside the
package, so a pip install has them. List them with geoqc presets and run one
by name:
geoqc check persil.gpkg --preset bidang-tanahThe same files are in docs/examples/ for reference.
| Preset | Layer name | Checks |
|---|---|---|
batas-administrasi |
batas_administrasi |
overlaps, gaps, spikes, slivers |
bidang-tanah |
bidang_tanah |
overlaps, spikes, tiny parcels, near-duplicate vertices |
kawasan-hutan |
kawasan_hutan |
overlaps, spikes, slivers |
jalan |
jalan |
multipart lines, dangles, overshoots/undershoots, short segments |
konsesi-kawasan-hutan |
konsesi + kawasan_hutan |
concessions that intersect kawasan hutan |
Rules apply to the layer named in each file (for a single-layer file, its
file name), so rename layer in a copy if yours differs. Thresholds are in metres and need a projected CRS such as UTM or TM-3.
import geopandas as gpd
import geoqc
result = geoqc.audit_dataset(
"persil.gpkg", layer="bidang_tanah", profile="docs/examples/bidang-tanah.yaml"
)
profile = geoqc.load_quality_profile("docs/examples/konsesi-kawasan-hutan.yaml")
frames = {
"konsesi": gpd.read_file("konsesi.gpkg"),
"kawasan_hutan": gpd.read_file("kawasan_hutan.gpkg"),
}
result = geoqc.audit_layers(frames, profile=profile)The cross-layer rule (must_not_intersect) also reports concessions that only
share an edge with kawasan hutan.
Check a dataset with no options. GeoQC picks a preset from the geometry type,
writes an issue layer and an HTML report to geoqc_output/, and prints a
verdict:
geoqc check data.shp
geoqc fix data.shp fixed.gpkgMore commands:
geoqc --help
geoqc --version
geoqc presets
geoqc audit path/to/folder
geoqc check parcels.gpkg --preset parcel --issues issues.gpkg --report report.html
geoqc repair-roads roads.shp repaired.gpkg --snap-tolerance 0.1Worker count is selected automatically; use --workers N as a safe upper bound.
Each dataset line includes the unified report status and issue count. Folder
discovery, --recursive, multiprocessing, engine selection, and exit codes are
unchanged.
See parallel streaming audits and the
CLI documentation for behavior and limitations.
After installing the api extra, start the local API:
python -m uvicorn geoqc.interfaces.api.main:app --host 127.0.0.1 --port 8000Open http://127.0.0.1:8000/docs for OpenAPI documentation. The
POST /api/geometry/validate endpoint accepts bounded Shapefile component
sets, GeoJSON, GeoPackage, FlatGeobuf, and GeoParquet uploads. Multi-layer
GeoPackages require a layer field.
The browser client is developed separately under apps/web; see its README for
local commands. Do not expose the development server publicly.
KML and GML are intentionally rejected by the public upload endpoint because XML-based formats can introduce external-resource risks. For Internet-facing deployments, place authentication, rate limiting, request-size enforcement, structured access logging, and TLS at a trusted reverse proxy or gateway.
- Documentation index
- Architecture
- CLI reference
- API and deployment
- Developer guide
- FAQ
- Security policy
- Release checklist
Only names exported directly from geoqc are considered stable public API in
the 0.x series. Other modules are advanced APIs and may change with notice in
CHANGELOG.md.
uv sync --all-extras --all-groups
uv run ruff format --check .
uv run ruff check .
uv run mypy src tests
uv run pytest
npm --prefix apps/web ci
npm --prefix apps/web run checkRead CONTRIBUTING.md before opening a pull request.
Copyright (c) 2026 Ananda Gunawan.
GeoQC is open-source software distributed under the MIT License.