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.
{ "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:
| Type | Shape | Coordinates structure |
|---|---|---|
Point | A single location | [lng, lat] |
LineString | A path (route, road) | array of [lng, lat] points |
Polygon | An area (zone, boundary) | array of rings, each an array of points |
MultiPoint / MultiLineString / MultiPolygon | Multiple disconnected shapes of the above | array of the single-type structure |
A LineString for a delivery route:
{
"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:
{
"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.
{
"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:
{
"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:
// Leaflet
L.geoJSON(geojsonData, {
onEachFeature: (feature, layer) => {
layer.bindPopup(feature.properties.name);
},
}).addTo(map);// 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:
-- 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:
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' ownLatLngobjects) 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.