The short version

Quality control for a spatial data product means turning every assumption a user will make about the data into an automated check that runs on every update. At minimum that is geometry validity, CRS conformance, topology, attribute completeness and domain conformance, and metadata presence. A "QC step" that is one analyst eyeballing the map before delivery is not QC — it does not scale, it is not repeatable, and it catches only what happens to be on screen.

A spatial data product (a maintained GeoPackage, a PostGIS-backed API, a vector-tile service) differs from a one-off map in that it is consumed programmatically and updated repeatedly. That changes the failure model: a single invalid polygon that a person would ignore can break an ST_Intersects query, throw an exception in a tile renderer, or silently drop features from an API response. QC is the gate that keeps each update from regressing the product.

The dimensions of spatial data quality

The ISO 19157 family formalises geographic data quality into elements that map cleanly onto practical checks:

  • Geometric/positional accuracy — do features sit where they claim to, in the declared CRS?
  • Logical consistency — geometry validity, topological consistency, format conformance, and domain conformance of attribute values.
  • Completeness — commission (extra features) and omission (missing features) against the intended coverage.
  • Temporal quality — is the dataset current, and is the timestamp recorded?
  • Thematic accuracy — are classifications and attribute values correct?

You do not need the full ISO machinery, but using these categories stops QC from being an ad hoc list and ensures each class of error has at least one test.

Geometry validity

Invalid geometry is the most common and most damaging defect because it propagates silently. Self-intersecting ("bowtie") polygons, rings wound the wrong way, duplicate vertices, and null geometries each break a different downstream operation.

In PostGIS, find and explain problems:

SELECT id, ST_IsValidReason(geom)
FROM   parcels
WHERE  NOT ST_IsValid(geom);

Repair with ST_MakeValid, but inspect the result — ST_MakeValid can split a self-intersecting polygon into a multipolygon, which may or may not be what the data should represent:

UPDATE parcels
SET    geom = ST_MakeValid(geom)
WHERE  NOT ST_IsValid(geom);

With GDAL, validate and fix during conversion:

ogr2ogr -f GPKG out.gpkg in.gpkg -makevalid

In QGIS the equivalent is Vector > Geometry Tools > Check Validity and Fix Geometries.

CRS conformance

Every layer in the product must carry the CRS it claims, and reprojected data must have been transformed, not relabelled. A frequent defect is geometry stored with SRID 0 (undeclared) or mislabelled via ST_SetSRID on data that was never transformed.

-- find geometries with the wrong or missing SRID
SELECT DISTINCT ST_SRID(geom) FROM parcels;

If the product's contract is EPSG:3857 tiles built from EPSG:25831 source, the check is: source SRID equals 25831, and the published geometry equals ST_Transform(geom, 3857). Make the contract explicit and test it.

Topology

Topology rules depend on the data model. For a parcel fabric, polygons must not overlap and should not leave slivers; for a stream network, lines must connect at nodes without dangles. PostGIS expresses these as set queries:

-- polygon overlaps (should return zero rows)
SELECT a.id, b.id
FROM   parcels a JOIN parcels b
       ON a.id < b.id AND ST_Overlaps(a.geom, b.geom);

In QGIS, the Topology Checker plugin runs the same rule set interactively, which is useful for triage, but the SQL version is what you put in the pipeline.

Attribute and domain conformance

Geometry can be perfect while attributes are useless. Check for nulls in required fields, values outside allowed domains, and broken referential links:

-- required field completeness
SELECT count(*) AS missing_unit
FROM   contacts WHERE unit_code IS NULL;

-- domain conformance
SELECT id, confidence FROM faults
WHERE  confidence NOT IN ('certain','approximate','inferred');

Domain lists, numeric ranges, and code tables should be defined once and enforced both at write time (constraints or CHECK) and at QC time.

Metadata that makes the product trustworthy

A dataset users can rely on carries, at the file or service level: the CRS and units, lineage (source datasets and the processing steps applied), a temporal extent and update date, an accuracy/completeness statement, and licensing/provenance. For GeoPackage, store this in the gpkg_metadata tables or a sidecar; for an API, expose it through the collection description. The OGC API - Features specification gives each collection a metadata document precisely so consumers can discover CRS, extent, and links before querying.

Automating it: a QC gate

The point of writing checks as queries is that they become a gate. A practical pattern is a single script that runs every assertion and exits non-zero if any returns offending rows:

-- each block: name, count of violations
SELECT 'invalid_geom' AS check, count(*) FROM parcels WHERE NOT ST_IsValid(geom)
UNION ALL
SELECT 'wrong_srid', count(*) FROM parcels WHERE ST_SRID(geom) <> 25831
UNION ALL
SELECT 'null_unit', count(*) FROM contacts WHERE unit_code IS NULL;

Run it in CI on every data update; block publishing if any count is non-zero; store the results so a regression (a check that used to pass and now fails) is immediately visible. This is the difference between a product that degrades quietly and one whose quality is observable.

Worked example: a parcels API update

A monthly parcel refresh fed a vector-tile API. After one update, tiles in one district rendered blank. The cause: an upstream edit introduced 14 self-intersecting polygons; the tile builder skipped them, and no one looked at that district. The fix was procedural, not heroic: we added ST_IsValid and overlap checks to the ingest pipeline. The next bad batch failed the gate before publishing, with a list of 14 offending IDs sent back to the data provider. Mean time to a clean dataset dropped from "whenever a user complained" to the same day.

Bathyl perspective

We design spatial products so quality is a property of the pipeline, not of the person on shift. Every Bathyl data product ships with its validation queries, its CRS contract, and its lineage, so a client can re-run the checks themselves and see exactly what "good" means for that dataset. A product you cannot test is a product you cannot trust to update.

Related reading

Sources