Upload a GeoJSON or a Shapefile and check whether every geometry is valid under the Simple Features model — with the reason for each error and an optional automatic repair you can download. Free, runs entirely in your browser: the file never leaves your computer.
Published
September 1, 2026
Upload a file — .geojson, or a shapefile as a single .zip or as .shp/.dbf/.shx selected together — and find out whether every geometry is valid. If it isn’t, the exact reason; if you want, we’ll fix it and you can download the result.
Runs entirely in your browser (Python via WebAssembly): the file is never uploaded to a server. Want to see the actual code, understand why a geometry can be invalid, or compare against R’s independent implementation? Read the full article.
// The input and the writing logic into the virtual filesystem live in// shared/upload.js (WebGeoDS.Upload) — same helper used across the// blog's articles, not rewritten here.viewof uploadedFiles = WebGeoDS.Upload.createInput()
// Whether Validate has ever completed successfully — gates the Fix// button below (see fixButton): clicking Fix first would hit a// Python NameError (`gdf` doesn't exist yet) instead of a clear// message, since Fix reuses Validate's session state rather than// reading the file itself.mutable validateSucceeded =false
Validate
Click Validate to run the check on the current data — your file uploaded above, or (if nothing’s uploaded) a built-in bowtie-polygon example. No repair yet, just diagnosis: is the geometry valid? If not, why?
The code itself isn’t shown here — it’s a fixed, non-editable check using Shapely/GEOS underneath; see the full article for the actual code, editable and explained line by line.
First run loads the Python engine — expect 30-40 seconds. Instant after that, and again each time you come back to this page.
sharedMapEl = sharedMap.element
// The color is decided by JS, not Python — see geometry-validity.qmd// for the full explanation of this pattern. Checks BOTH possible keys// (valid / valid_after) because the paint chosen at diagnosis time// stays fixed even after Repair replaces the properties.VALIDITY_PAINT = ({"fill-color": ["case", ["any", ["==", ["get","valid"],true], ["==", ["get","valid_after"],true]],"#2ea44f","#e05252" ],"fill-opacity":0.55})
make_valid() rebuilds the geometry into an equivalent, valid form. Requires having run Validate above at least once: it reuses exactly the same data just diagnosed, no new reading of the file.
// Depends on (reads) validateSucceeded, so this cell — and the button// it returns — is recreated the moment Validate first succeeds,// switching from disabled to enabled without a page reload.fixButton = {const button =document.createElement("button"); button.textContent="🔧 Fix"; button.disabled=!validateSucceeded; button.title= validateSucceeded ?"":"Run Validate above first"; button.onclick=async () => { button.disabled=true; button.classList.add("webgeods-btn-loading"); button.textContent="⌛ Fixing...";try {awaitwindow.WebGeoDS.CodeCell.find("geometry-repair-py").run(); } finally { button.disabled=false; button.classList.remove("webgeods-btn-loading"); button.textContent="🔧 Fix"; } };return button;}
Download the current state — fixed if you ran the section above, otherwise diagnosed as-is.
// Not promoted to shared/ (still only one tool using it): a Blob +// a temporary <a download> is enough, no library needed. If a second// tool ends up duplicating this same function, that's the point to// move it into a shared file, not before.downloadGeoJSON = (sourceId, filename) => {const data = sharedMap.getGeoJSON(sourceId);if (!data) return;window.WebGeoDS.track?.("download_clicked", { tool:"geojson-shapefile-validator", sourceId });const blob =newBlob([JSON.stringify(data,null,2)], { type:"application/geo+json" });const url = URL.createObjectURL(blob);const a =document.createElement("a"); a.href= url; a.download= filename; a.click(); URL.revokeObjectURL(url);}
---
Want to understand what makes a geometry invalid, see the actual
code, or compare against R's independent implementation? The
[full article](/posts/geometry-validity.html) uses this same engine
with a gallery of common errors explained one by one.