Learn

GeoJSON polygon

A GeoJSON Polygon is a closed shape represented as an array of linear rings. The first ring is the outer boundary; any additional rings are holes cut out of it.

TL;DR
  • coordinates is an array of rings — the first ring is the outer boundary, additional rings are holes
  • Outer ring must wind counter-clockwise; holes must wind clockwise (RFC 7946)
  • Each ring must close — the first and last position must be identical
  • Common mistakes: wrong winding order, open rings, [lat, lng] instead of [lng, lat]

Polygons are the GeoJSON geometry type you'll use for areas: city boundaries, delivery zones, building footprints, park outlines. Understanding the coordinate structure — and the three rules that trip up almost everyone — is the difference between geometry that validates and geometry that silently breaks rendering pipelines.

Coordinate structure

A Polygon's coordinates field is an array of rings. Each ring is an array of positions. Each position is a [longitude, latitude] pair. That gives you three levels of nesting:

coordinates     → array of rings
  ring          → array of positions  
    position    → [longitude, latitude]

Here's a minimal valid Polygon — a small rectangle near the Eiffel Tower in Paris:

{
  "type": "Polygon",
  "coordinates": [
    [
      [2.2945, 48.8584],
      [2.2950, 48.8584],
      [2.2950, 48.8590],
      [2.2945, 48.8590],
      [2.2945, 48.8584]
    ]
  ]
}

The outer array holds rings. This Polygon has one ring — the outer boundary. That ring has five positions. The first and last position are identical ([2.2945, 48.8584]), which closes the ring.

To turn this into a Feature with properties, wrap it:

{
  "type": "Feature",
  "properties": {
    "name": "Eiffel Tower plaza",
    "category": "landmark"
  },
  "geometry": {
    "type": "Polygon",
    "coordinates": [
      [
        [2.2945, 48.8584],
        [2.2950, 48.8584],
        [2.2950, 48.8590],
        [2.2945, 48.8590],
        [2.2945, 48.8584]
      ]
    ]
  }
}

The outer ring

RFC 7946 specifies that the outer ring of a Polygon must use counter-clockwise winding order when viewed from above (from the north pole looking down). Counter-clockwise means the interior of the polygon is always on the left as you traverse the ring.

Why this matters: PostGIS rejects polygons that violate winding order when you use ST_GeomFromGeoJSON with strict settings. Mapbox GL JS enforces it for fill layers — an incorrectly wound polygon may render as the entire map area filled, with your intended shape cut out. Libraries that tolerate either direction (Leaflet, for example) mask the problem locally, so it surfaces only when you push to a database or a different renderer.

The five-position ring above traces counter-clockwise: start at the bottom-left, move right along the bottom, up the right side, left along the top, and return to the start. That's the correct order.

A quick check: stand at the first position, face the second. If the polygon's interior is on your left, the winding is counter-clockwise (correct for the outer ring).

Holes: inner rings

A hole is a second (or third, or fourth) ring in the coordinates array. Inner rings use clockwise winding order — the opposite of the outer ring.

Here's a park boundary with a lake cut out of it. The outer ring traces the park perimeter; the inner ring traces the lake:

{
  "type": "Polygon",
  "coordinates": [
    [
      [-0.1750, 51.5030],
      [-0.1580, 51.5030],
      [-0.1580, 51.5130],
      [-0.1750, 51.5130],
      [-0.1750, 51.5030]
    ],
    [
      [-0.1700, 51.5060],
      [-0.1700, 51.5100],
      [-0.1640, 51.5100],
      [-0.1640, 51.5060],
      [-0.1700, 51.5060]
    ]
  ]
}

The first ring (outer boundary) goes: bottom-left → bottom-right → top-right → top-left → close. That's counter-clockwise.

The second ring (the lake hole) goes: top-left → bottom-left → bottom-right → top-right → close. That's clockwise.

Both rings close (first position === last position). The hole must be entirely inside the outer ring — a hole that extends outside the boundary is invalid.

You can have multiple holes in one Polygon — add a third, fourth, or fifth ring to coordinates. Each additional ring must use clockwise winding and must not overlap with other inner rings.

Coordinate precision

GeoJSON coordinates are decimal degrees. Precision in degrees translates to distance on the ground, with the relationship varying by latitude. These figures are approximate values at the equator, where one degree spans the most distance:

Decimal placesPrecision (at equator)Use case
1~11 kmCountry-level visualization
2~1.1 kmCity-level visualization
3~111 mDistrict-level data
4~11 mBuilding-level precision
5~1.1 mStreet-level precision
6~0.11 m (11 cm)Web mapping standard
8~1.1 mmGPS surveying

Rule of thumb: 6 decimal places for web mapping; 5 if file size matters. A Polygon with 1,000 vertices at 6 decimal places is ~30 KB — reasonable for network transfer. At 15 decimal places (the float64 maximum), that same file is ~50 KB with no usable added precision.

Truncating coordinate precision is a common, safe optimization: round each coordinate to 6 decimal places before storing or serving.

Common mistakes

First and last position must be identical

A ring is closed by repeating the first position at the end. This is not optional — an open ring is invalid GeoJSON.

Invalid:

{
  "type": "Polygon",
  "coordinates": [
    [
      [2.2945, 48.8584],
      [2.2950, 48.8584],
      [2.2950, 48.8590],
      [2.2945, 48.8590]
    ]
  ]
}

The last position is [2.2945, 48.8590], which differs from the first ([2.2945, 48.8584]). This ring is open. Some parsers silently accept it and close the ring themselves; others reject it. The RFC is clear: the ring must be closed.

Fix: add the first position as the last element:

[
  [2.2945, 48.8584],
  [2.2950, 48.8584],
  [2.2950, 48.8590],
  [2.2945, 48.8590],
  [2.2945, 48.8584]
]

Wrong winding order

A polygon with the correct coordinates but wrong winding order looks valid in most viewers. PostGIS and Mapbox GL JS enforce RFC 7946 winding strictly.

Wrong (clockwise outer ring):

[
  [2.2945, 48.8584],
  [2.2945, 48.8590],
  [2.2950, 48.8590],
  [2.2950, 48.8584],
  [2.2945, 48.8584]
]

Fix: reverse the ring array. In JavaScript:

const corrected = ring.slice().reverse()

Or use Turf.js: import { rewind } from '@turf/rewind' — rewind(feature, { reverse: true }) fixes winding on all rings in a Feature or FeatureCollection.

Coordinates in [lat, lng] order

GeoJSON coordinate order is [longitude, latitude]. Swapping to [latitude, longitude] is the most common mistake and the hardest to notice — the polygon renders with valid-looking coordinates, but in the wrong location.

Example: London at [-0.1276, 51.5074]. Swap to [51.5074, -0.1276] and you get a point in the Atlantic Ocean, longitude 51°, latitude -0.13° south of the equator. It's a valid coordinate — just not London.

There's no runtime error. The geometry validates. The map just shows shapes in the wrong place.

Check: for Europe and the UK, longitude is negative (west) and latitude is positive (north). If your longitudes are around 51 and your latitudes are around -0.1, they're swapped.

Self-intersecting rings

A self-intersecting ring crosses itself — like a figure-8 drawn as a single loop. The resulting shape has no well-defined interior. Different tools interpret it differently, and none of them interpret it correctly for your data.

A self-intersecting polygon cannot be stored in PostGIS's geometry type without using ST_MakeValid, which may split it into multiple geometries. Mapbox GL JS renders the intersection region unpredictably.

Fix: split the ring into two separate, non-intersecting rings. If they represent separate areas, use two Polygon Features in a FeatureCollection. If they represent one feature with two parts, use a MultiPolygon.

When to use MultiPolygon

Use Polygon when your feature is a single connected area. Use MultiPolygon when the same real-world entity consists of multiple disconnected areas — a country with offshore islands, a delivery zone split by a highway, an administrative district with an exclave. For the full MultiPolygon reference, see GeoJSON MultiPolygon.

Frequently asked questions

How do you write a GeoJSON polygon?
A GeoJSON Polygon has a 'type' of 'Polygon' and a 'coordinates' array containing one or more rings. Each ring is an array of [longitude, latitude] positions. The first and last position in each ring must be identical to close the ring. The outer ring should use counter-clockwise winding order per RFC 7946.
How do you add a hole to a GeoJSON polygon?
Add a second ring to the 'coordinates' array. The first ring is the outer boundary (counter-clockwise winding). Each additional ring is a hole (clockwise winding). The hole must be fully inside the outer ring.
What is the winding order in GeoJSON?
RFC 7946 specifies counter-clockwise winding for the outer ring of a Polygon, and clockwise winding for any inner rings (holes). Winding order describes the direction coordinates are listed when traversed: counter-clockwise means positions go in the mathematical positive direction (left-hand turn rule).
What is the difference between Polygon and MultiPolygon in GeoJSON?
A Polygon is a single closed shape. A MultiPolygon is an array of Polygons used when one real-world feature consists of multiple disconnected areas — for example, a country with islands, or a delivery zone split across a river.
How many decimal places should GeoJSON coordinates have?
Six decimal places gives ~11 cm precision at the equator, which is sufficient for most web mapping. Use five decimal places (~1.1 m) to reduce file size when high precision isn't needed. Eight decimal places (~1.1 mm) is appropriate only for survey-grade data.
What causes an invalid GeoJSON polygon?
The most common causes are: (1) the first and last positions in a ring are not identical; (2) a self-intersecting ring (the ring crosses itself); (3) an inner ring that extends outside the outer ring; (4) fewer than four positions in a ring (three unique points plus the closing repeat are the minimum for a triangle).

If you need to draw and edit polygons interactively in a React application, Element Editor provides polygon drawing, vertex editing, boolean operations (union, difference, intersection), and hole cutting — open source under MIT.

Continue reading

Mike Acler

Mike Acler

Design engineer and creator of Element Editor, an open-source map drawing and GeoJSON editing library for React. Writes about web cartography and GIS tooling.