spatial-data-qa
Skill2 starsChanged 4 days agoApache-2.0View on GitHub
Quality-checking spatial data before it is used or delivered, covering invalid geometry (self-intersections, ring orientation), null and empty shapes, duplicates, mixed geometry types, slivers, gaps and overlaps, topology, schema and domain checks, and CRS sanity, and what to fix, flag or send to the data owner. Use it when asked to "QA this layer", "validate geometry", "check for duplicates", "fix invalid polygons", "is this data clean", or before handing any result to someone else.
In this repository15 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- arcpy-scriptingSkill
- crs-and-datumsSkill
- enterprise-geodatabasesSkill
- imagery-and-stacSkill
- lidar-with-pdalSkill
- linear-referencingSkill
- maps-and-deliverablesSkill
- open-data-sourcingSkill
- raster-and-terrainSkill
- sop-conversionSkill
- spatial-joins-and-keysSkill
- spatial-sqlSkill
- working-with-columbusSkill
- writing-clearly-and-conciselySkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 16
Every row that goes in is accounted for in what comes out: kept, fixed (with a record of what changed), flagged, or excluded with a count …
- Distinctive: line 45
Check before counting or summing anything.
- Very specific: line 145
No params. The result carries geometry.repaired ("Repaired N invalid geometries", warn when N > 0) and the usual output checks.
- Very specific: line 177
Run verify on every layer before you hand it to anyone, and include its checks in the report.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Spatial data QA
QA is finding what is wrong, saying so with numbers, and deciding for each finding whether to fix it, flag it or ask the data owner. It is never quietly deleting the rows that look bad.
General practice (with or without Columbus)
The rule: never silently drop rows
Every row that goes in is accounted for in what comes out: kept, fixed (with a record of what changed), flagged, or excluded with a count and a reason the person agreed to. If you filtered anything, say how many rows and why.
What to check, in order
- CRS sanity. A CRS is recorded; the coordinates fall inside its area of use; the units are what you expect; X and Y are not swapped. (See the crs-and-datums skill.) Everything below is meaningless if the layer is in the wrong place.
- Null and empty geometry. Null (no geometry at all) and empty (a geometry with no coordinates) are different. Count both. They usually mean a failed geocode, a record with no location yet, or a broken export.
- Validity. Count invalid geometries and the reasons:
- self-intersection (a "bowtie" polygon, a ring that crosses itself);
- ring self-intersection or a hole that touches the shell along a line;
- hole outside its shell, or nested shells in a MultiPolygon;
- unclosed rings or too few points (from bad exports);
- duplicate consecutive vertices and spikes (often valid, but noisy).
- Ring orientation. OGC/GEOS validity ignores it, but consumers do not agree: shapefiles expect clockwise outer rings, GeoJSON (RFC 7946) counter-clockwise. A reversed ring can make a polygon draw or compute as its complement in some tools. Fix orientation on export for the target format; it is not a data error by itself.
- Geometry types. One layer, one geometry family. Polygon and MultiPolygon together are fine for most tools; points mixed with polygons are not, and shapefiles allow only one type.
- Duplicates.
- Exact geometry duplicates: often a double load or an append run twice.
- Same id, different geometry: versioning or an edit conflict.
- Near-duplicates: the same feature digitized twice, offset by a little. Check before counting or summing anything.
- Coverage topology (for layers that should tile an area: parcels, counties,
districts, zoning):
- overlaps: two polygons claim the same ground;
- gaps: ground claimed by nobody;
- slivers: very thin polygons along shared edges, usually from overlaying layers digitized separately. A thinness ratio 4πA/P² near 0 (a circle is 1) and a tiny area together are a good sign of one.
- Network topology (roads, pipes, streams): dangles (line ends that should connect), undershoots and overshoots at intersections, lines that cross without a node, wrong flow direction, and on a linear referencing system: measure gaps, overlaps, reversed measures and events off the route's measure range.
- Schema. The expected fields exist with the expected types; ids are unique and not null; required fields are filled.
- Domains and ranges. Coded values are in the domain (functional class 1 to 7, status
AorR); numbers are in range (lanes 1 to 12, speed limits multiples of 5); dates are not in the future; cross-field rules hold (from-measure less than to-measure).
Fix, flag or ask
| Finding | Usually | Why |
| Missing or wrong CRS | Ask the owner | A guessed CRS is a silent error |
| Swapped X/Y | Fix (swap), say so | Unambiguous when the extent proves it |
| Invalid geometry (self-intersection) | Fix with make-valid, then re-check | Required before overlay, clip or dissolve |
| Invalid geometry that changes a lot when repaired | Flag, ask the owner | The repair may not be what was meant |
| Ring orientation | Fix on export | Format convention, not a data error |
| Null or empty geometry | Flag, keep the row | It may be a real record without a location yet |
| Exact duplicates | Flag; remove only if the owner confirms a double load | Removing the wrong copy loses attributes |
| Mixed geometry types | Split by type | Formats and tools need one type |
| Slivers from your own overlay | Fix (merge into neighbour, or tolerance) | Your process made them |
| Slivers, gaps or overlaps in source data | Flag, ask the owner | Only the owner knows the true boundary |
| Domain violations | Flag, ask the owner | A code you do not know is not a code you can fix |
| Schema missing a required field | Stop and ask | The data is not what the task assumed |
make-valid caveats
Repairing can change a feature's type and shape: a bowtie polygon becomes a MultiPolygon of two triangles, a collapsed polygon becomes a line or a GeometryCollection, and an invalid-and-repaired layer can become mixed-type. After repair, compare the feature count, the geometry types and the total area or length before and after, and re-run the checks.
A QA report
Layer: <name> (<rows> rows, <geometry type>, <CRS>)
CRS: ok / problem
Geometry: <n> null, <n> empty, <n> invalid (<reasons>), <n> duplicate, types: <list>
Topology: <overlaps, gaps, slivers, dangles> or "not checked"
Schema and domains: <findings> or "not checked"
Actions: fixed <what> (<n> features); flagged <what>; asked the owner about <what>
Rows in: <n>; rows out: <n>; difference explained: <yes/how>Say plainly what you did not check. "Not checked" is honest; silence implies "fine".
If the Columbus tools are available
verify
verify {"layer": "ws://<session>/results/roads-<id>.gpkg"}
verify {"layer": "austin:roads"}
verify {"layer": {"connection": "austin", "dataset": "roads", "where": "district = 14"}}layer is a workspace handle or a dataset ("<connection>:<dataset>", or the object form
with where). A dataset bigger than the read limit is refused; narrow it with where.
The result is the layer summary (rows, geometry_types, crs, extent, fields); the
findings are in checks:
| Check | Severity | Message (example) and action |
crs.present | fail | "The layer has no coordinate reference system". Stop; ask for the source CRS. |
crs.info | info | "The layer is in EPSG:3081 (...), units metre". |
crs.area_of_use | fail | Labelled geographic but coordinates cannot be lon/lat. Stop; the fix says swap X/Y or assign the right CRS. |
crs.area_of_use | warn | Coordinates outside the CRS's area of use. Report; likely a wrong label. |
geometry.valid | warn | "3 of 412 geometries are invalid", detail.examples such as Self-intersection[1216724.3 902699.8]. Repair with make_valid. |
geometry.empty | info | "N rows have no geometry and M have an empty one". Report; keep the rows. |
geometry.mixed_types | warn | "The layer mixes geometry types: LineString, Point". Split before export. Multi and single types count as one family. |
geometry.duplicates | warn | "N geometries are exact duplicates of another row's". Check for a double load. |
input.truncated | warn | The layer was cut off at a row limit when made; results cover part of the data. |
geometry.duplicates comes only from verify. The other geometry and CRS checks are also
attached to query_features and vector_op results, and every vector_op reports
geometry.valid for its inputs.
Severities
info: note it.warn: report it with the number and the fix. Do not call the layer "clean".fail: stop. Tell the person what failed, why, and the fix. Do not process further or hand the result on. The server treats fail as blocking for exports and edits.
Repair invalid geometry
vector_op {"op": "make_valid", "inputs": {"layer": "ws://<session>/results/parcels-<id>.gpkg"}}No params. The result carries geometry.repaired ("Repaired N invalid geometries", warn
when N > 0) and the usual output checks. Then:
verifythe new handle.- Compare
summary.rowsandsummary.geometry_typesbefore and after. A newgeometry.mixed_typeswarning means some repairs changed type. - Report how many were repaired, and keep the original handle for the owner.
Other checks you will meet while processing
dissolve.null_groups(warn): features with an emptybyvalue were dissolved together into one group. Fill or filter them first if they should not be.extent.plausible(warn): the output overlaps no input's extent; check CRSs and units.filter.zero_rows,filter.all_rows,query.read_limit(warn) from queries.join.unmatched,join.many_to_manyfrom joins (see the spatial-joins-and-keys skill).
Not in Columbus yet
Columbus does not yet check coverage topology (overlaps, gaps, slivers), network topology, tolerances, or domains and field rules. Do those checks yourself and label them as yours:
- On PostGIS,
sql_selectcan call PostGIS functions, for exampleSELECT COUNT(*) FROM parcels WHERE NOT ST_IsValid(geom), orSELECT id, ST_Area(geom) FROM parcels WHERE ST_Area(geom) < 1(in the layer's units). - Domain checks with
sql_select:SELECT FUNC_CLASS, COUNT(*) FROM ROADS.ROADWAY_INVENTORY GROUP BY FUNC_CLASS, then compare with the domain fromcontext_get. - Engine plugins can add checks through
run_tool; for example the samplecontoso.lrs-check.check_eventsfinds LRS measure gaps, overlaps, reversed and out-of-range measures and events on unknown routes. A plugin's declared checks that Columbus has not run yet show asverify.pending(info).
Run verify on every layer before you hand it to anyone, and include its checks in the
report.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

