GeoJSON guide
Why is my GeoJSON polygon invalid?
If a tool just told you “Polygons and MultiPolygons should follow the right-hand rule,” your ring is wound the wrong way — it’s not corrupt, just clockwise where RFC 7946 wants counterclockwise. This page explains the rule, shows how the shoelace formula detects it, how to rewind a ring, and the other reasons a polygon gets flagged invalid — each with the exact message our validator prints. Everything runs in your browser; nothing you paste is uploaded.
What the right-hand rule actually says
RFC 7946, the GeoJSON standard, pins down winding order for polygons:
- Exterior rings are counterclockwise (CCW).
- Interior rings — holes — are clockwise (CW).
The mnemonic is the “right-hand rule”: walk the boundary in the order the points are listed, and the polygon’s interior is always on your left. The spec chose a fixed convention because a bare list of coordinates is otherwise ambiguous on a sphere. The same four corners, read counterclockwise, describe a one-degree square; read clockwise, a spherical-geometry engine can legitimately interpret them as the entire Earth minus that square. Winding order is the only thing that disambiguates the small inside from the enormous outside.
One nuance worth knowing: RFC 7946 phrases this as a SHOULD, not a MUST. Conforming parsers are required to accept rings in either direction, and producers should emit the right-hand order. That’s why a “wrong” winding is a warning here, not a hard error — but as the next section shows, plenty of downstream systems treat it as fatal.
Why most renderers tolerate it — but some pipelines break
Draw a clockwise polygon in Leaflet, MapLibre’s default fill, or almost any GeoJSON preview and it looks perfect: those renderers treat a polygon as a flat filled shape and don’t care which way you walked the boundary. That tolerance is exactly why bad winding survives so long undetected — until it reaches a system that reads winding as meaning:
- Vector-tile encoders
- Mapbox Vector Tile geometry uses fill winding to distinguish exterior boundaries from holes. Feed a tile encoder a clockwise exterior ring and the fill can invert — the inside renders empty and the outside fills — or the feature drops entirely.
- Spherical-geometry libraries
-
Turf’s boolean operations (
intersect,union,difference) and Google’s S2 region coverer compute on the sphere, where a ring’s direction sets which side is interior. A clockwise exterior becomes “all of the globe except this patch,” so an area query returns the complement of what you meant. - Geography-typed DB columns
-
PostGIS
geographyand SQL Servergeographyvalidate winding on insert. Depending on the engine and version they either reject the row outright or silently store the complementary polygon — a bug you won’t notice until a containment query returns the whole world. - Strict RFC 7946 validators
- Linters and schema checkers built to the spec’s producer guidance emit the very string that brought you here: “Polygons and MultiPolygons should follow the right-hand rule.”
How detection works: the shoelace formula
Winding direction falls straight out of a ring’s signed area, computed with the
shoelace (surveyor’s) formula. For a ring of points
(x₀,y₀), (x₁,y₁), … — remember GeoJSON order is [longitude, latitude],
so x = lon and y = lat:
2A = Σ (xᵢ · yᵢ₊₁ − xᵢ₊₁ · yᵢ)
A positive signed area means counterclockwise; negative means
clockwise. Here’s the correct (CCW) square worked out term by term — the exterior ring is
[100,0] → [101,0] → [101,1] → [100,1] → [100,0]:
| Edge (xᵢ,yᵢ) → (xᵢ₊₁,yᵢ₊₁) | xᵢ · yᵢ₊₁ | xᵢ₊₁ · yᵢ | Term |
|---|---|---|---|
| (100,0) → (101,0) | 100·0 = 0 | 101·0 = 0 | 0 |
| (101,0) → (101,1) | 101·1 = 101 | 101·0 = 0 | +101 |
| (101,1) → (100,1) | 101·1 = 101 | 100·1 = 100 | +1 |
| (100,1) → (100,0) | 100·0 = 0 | 100·1 = 100 | −100 |
| Sum = 2A | +2 | ||
So 2A = +2, A = +1. The sign is positive → counterclockwise
→ a valid RFC 7946 exterior ring. Reverse the point order and every term flips sign, giving
2A = −2, A = −1 → clockwise → the winding-order
warning. That single sign is the whole test.
How to fix it: rewind
The fix is simply to reverse the ring’s point order when the sign is wrong — that’s what “rewind” means. Below is the very same square in both directions. The signed area confirms the label, so you can trust which is which:
{
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
]
]
} {
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[100.0, 1.0],
[101.0, 1.0],
[101.0, 0.0],
[100.0, 0.0]
]
]
}
Programmatically, the incumbent tool is
@mapbox/geojson-rewind:
rewind(geojson) returns the geometry with RFC-correct winding.
@turf/rewind does the same and takes a { reverse: true } option
for the opposite convention. Both simply compute the shoelace sign per ring and flip the ones
that are backwards. On this site you can skip the code entirely — paste the polygon into the
inspector and click the one-click rewind fix next to the warning.
See it in the inspector
The polygon below is wound clockwise on purpose — the reversed version of the square above. Open it in the inspector and you’ll see the winding-order warning with a one-click rewind, or copy it and paste it yourself.
Open this broken polygon in the Inspector →
{
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[100.0, 1.0],
[101.0, 1.0],
[101.0, 0.0],
[100.0, 0.0]
]
]
} Other reasons your polygon is invalid
Winding order is a warning, not the only thing that gets a polygon rejected. These are the other
problems our validator catches. Each row shows a minimal broken ring, the stable issue
code, and the exact message the validator prints — copied verbatim from its
output, not paraphrased.
- Unclosed ring
ring-not-closederror -
[[100,0],[101,0],[101,1],[100,1]]The last point does not repeat the first, so the ring never closes.
Ring not closed — ring 1 (exterior) starts at (100, 0) but ends at (100, 1). Close it by repeating the first point at the end.
- Self-intersection
self-intersectionerror -
[[100,0],[101,1],[101,0],[100,1],[100,0]]A “bow-tie” ring whose edges cross — the exterior boundary is not simple.
Self-intersection — the polygon crosses itself at (100.5, 0.5). This is an invalid ring/line; most tools reject it.
- Duplicate consecutive vertices
duplicate-consecutive-pointswarning -
[[100,0],[101,0],[101,1],[101,1],[100,1],[100,0]]A point repeated back-to-back makes a zero-length edge — so the validator also reports a self-intersection at that vertex.
Duplicate points — ring 1 (exterior) repeats 1 consecutive point (e.g. (101, 1)). Remove the repeats.
- Out-of-range coordinates
latitude-out-of-rangeerror -
[[100,0],[101,0],[101,95],[100,95],[100,0]]Latitude 95° is impossible (valid range is −90…90). Longitude has its own code, longitude-out-of-range.
2 coordinates have latitude outside [-90, 90].
The winding-order case shown earlier carries the code winding-order with the message:
“Winding order — ring 1 (exterior) is clockwise, but RFC 7946 wants exterior rings
counterclockwise. Most renderers tolerate it; rewind to comply.”
Sources and further reading
The winding rule is quoted from the GeoJSON standard; the other failure modes are checked against OGC simple-feature validity and this site's fixture-backed validator.
- RFC 7946 — The GeoJSON Format — polygon ring winding, positions and interoperability requirements
- OGC Simple Feature Access — Common Architecture — the underlying simple-feature geometry validity model
- PostGIS ST_IsValid — standards-based geometry validity checks and notices