jsongeojsonmapsgis

GeoJSON Explained: The JSON Format Behind Every Map

·10 min read·JSON Concepts

Every map you've ever coded against speaks JSON

If you've drawn a shape on a map, stored a delivery zone, or queried "restaurants within 2km," you've touched GeoJSON — an IETF standard (RFC 7946) for encoding geographic data as plain JSON. Mapbox, Leaflet, Google Maps' data layers, PostGIS, MongoDB's geospatial queries, and most government open-data portals all speak it natively. It's a small spec — a handful of geometry types and one wrapper object — and once you've seen the shapes, every mapping API stops being a black box.

The core idea: geometry is just coordinates

A GeoJSON geometry is a type plus a coordinates array. Coordinates are always [longitude, latitude]not lat/lng, which is the single most common bug developers hit when they wire GeoJSON into a UI expecting the opposite order.

json
{ "type": "Point", "coordinates": [72.5714, 23.0225] }

That's a single point (Ahmedabad, India) — longitude first, then latitude.

The geometry types

GeoJSON defines seven geometry types, but in practice you'll use four constantly:

TypeShapeCoordinates structure
PointA single location[lng, lat]
LineStringA path (route, road)array of [lng, lat] points
PolygonAn area (zone, boundary)array of rings, each an array of points
MultiPoint / MultiLineString / MultiPolygonMultiple disconnected shapes of the abovearray of the single-type structure

A LineString for a delivery route:

json
{
  "type": "LineString",
  "coordinates": [
    [72.5714, 23.0225],
    [72.5800, 23.0300],
    [72.5900, 23.0400]
  ]
}

A Polygon for a service area — note the ring must close (first and last point identical), and a polygon with a hole has a second inner ring:

json
{
  "type": "Polygon",
  "coordinates": [
    [
      [72.55, 23.00], [72.60, 23.00], [72.60, 23.05], [72.55, 23.05], [72.55, 23.00]
    ]
  ]
}

Feature and FeatureCollection: attaching data to geometry

A bare geometry has no attributes — no name, no ID, no metadata. That's what Feature is for: it wraps a geometry with a properties object of arbitrary JSON.

json
{
  "type": "Feature",
  "geometry": { "type": "Point", "coordinates": [72.5714, 23.0225] },
  "properties": { "name": "Ahmedabad Warehouse", "capacity": 500, "active": true }
}

Most real-world data is a FeatureCollection — an array of Features, which is what you'll get back from almost any mapping API or GeoJSON export:

json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [72.5714, 23.0225] },
      "properties": { "name": "Warehouse A" }
    },
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [77.2090, 28.6139] },
      "properties": { "name": "Warehouse B" }
    }
  ]
}

Rendering GeoJSON in the browser

Leaflet and Mapbox GL both accept a FeatureCollection directly:

javascript
// Leaflet
L.geoJSON(geojsonData, {
  onEachFeature: (feature, layer) => {
    layer.bindPopup(feature.properties.name);
  },
}).addTo(map);
javascript
// Mapbox GL JS
map.addSource("warehouses", { type: "geojson", data: geojsonData });
map.addLayer({ id: "warehouses-layer", type: "circle", source: "warehouses" });

Neither library needs a special parser — GeoJSON is just JSON, so fetch(...).then(r => r.json()) is the entire ingestion step.

Querying GeoJSON in a database

PostGIS (PostgreSQL's spatial extension) converts GeoJSON to its native geometry type and back:

sql
-- Store a point from GeoJSON
INSERT INTO locations (name, geom)
VALUES ('Warehouse A', ST_GeomFromGeoJSON('{"type":"Point","coordinates":[72.5714,23.0225]}'));

-- Export a row back to GeoJSON
SELECT ST_AsGeoJSON(geom) FROM locations WHERE name = 'Warehouse A';

-- Find everything within 2km
SELECT name FROM locations
WHERE ST_DWithin(geom::geography, ST_MakePoint(72.5714, 23.0225)::geography, 2000);

MongoDB stores GeoJSON directly on a document and indexes it with 2dsphere:

javascript
db.locations.createIndex({ geometry: "2dsphere" });

db.locations.insertOne({
  name: "Warehouse A",
  geometry: { type: "Point", coordinates: [72.5714, 23.0225] },
});

db.locations.find({
  geometry: { $near: { $geometry: { type: "Point", coordinates: [72.57, 23.02] }, $maxDistance: 2000 } },
});

The longitude/latitude bug, and other gotchas

  • Coordinate order. GeoJSON is always [longitude, latitude]. Most humans think and speak "lat, lng" — double-check every boundary where data crosses between GeoJSON and a UI or a different library, since some (like Google Maps' own LatLng objects) use the opposite order.
  • Polygon winding and closure. The first and last coordinate of a ring must be identical (the ring closes itself). RFC 7946 also recommends exterior rings wind counter-clockwise and holes wind clockwise, though many real-world producers ignore this and most consumers tolerate it.
  • Precision. GeoJSON coordinates are plain JSON numbers — six decimal places is roughly 10cm of precision, which is enough for almost everything; don't ship 15 decimal places of floating-point noise.
  • Antimeridian and poles. Shapes crossing the 180° line or wrapping a pole need special handling that RFC 7946 covers but most simple tools don't implement — a known edge case for global datasets.

Frequently asked questions

Always [longitude, latitude] — the opposite of how most people say it aloud. This single detail causes more GeoJSON bugs than anything else in the spec.

A geometry (Point, Polygon, etc.) is just shape data. A Feature wraps a geometry together with a properties object so you can attach a name, ID, or any other JSON metadata to that shape.

Yes — paste it into JSONKit's JSON Validator to catch syntax errors, or the JSON Formatter to inspect a large FeatureCollection's structure before writing map code against it.

No — only points, lines and polygons (straight-line segments). A "circle" is conventionally approximated as a many-sided polygon by the producing tool.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.