GeoJSON Format

GeoJSON is a standard format for encoding geographic data as JSON — defined in RFC 7946 and supported by every major mapping library (Leaflet, Mapbox, Google Maps, Turf.js) and geospatial database (PostgreSQL/PostGIS, MongoDB, BigQuery). A GeoJSON document consists of geometry objects (Point, LineString, Polygon and their Multi- variants), Feature objects (geometry + properties), and FeatureCollection (array of Features). The most common gotcha: GeoJSON coordinates are [longitude, latitude] — the opposite of the [lat, lng] convention most developers expect. This guide covers all 9 geometry types, the Feature and FeatureCollection wrappers, bounding boxes, and how to read and write GeoJSON in JavaScript and Python. See also JSON examples for general JSON format background.

Validate your GeoJSON in Jsonic before using it in your mapping application.

Open JSON Formatter

GeoJSON structure: the nine types

Every GeoJSON object has a "type" field that identifies what it is. There are six geometry types, plus Feature, FeatureCollection, and GeometryCollection. Most real-world GeoJSON is a FeatureCollection containing Feature objects, each wrapping one geometry with a properties object.

typeDescriptionHas geometry?Has properties?
PointSingle locationYesNo
LineStringSequence of points (road, river)YesNo
PolygonClosed ring with optional holesYesNo
MultiPointCollection of pointsYesNo
MultiLineStringCollection of LineStringsYesNo
MultiPolygonCollection of PolygonsYesNo
FeatureAny geometry + arbitrary propertiesYesYes
FeatureCollectionArray of Feature objects
GeometryCollectionMixed geometry typesYesNo

The minimal valid GeoJSON object is a geometry with a "type" and a "coordinates" field. A FeatureCollection has a "features" array instead. GeometryCollection uses a "geometries" array. In practice, prefer FeatureCollection over GeometryCollection — it preserves per-feature metadata via properties.

Coordinates: [longitude, latitude] order

The single most common GeoJSON bug is reversed coordinates. GeoJSON uses [longitude, latitude] — the mathematical [x, y] axis order — per RFC 7946. This is the opposite of the [latitude, longitude] convention used by Google Maps, Leaflet's L.LatLng constructor, and most GPS device output.

// GeoJSON: [longitude, latitude]   ← x first, then y
// Paris: longitude 2.3522, latitude 48.8566
{ "type": "Point", "coordinates": [2.3522, 48.8566] }

// WRONG — this puts Paris in the middle of Antarctica:
{ "type": "Point", "coordinates": [48.8566, 2.3522] }

// Valid coordinate ranges (WGS 84 / EPSG:4326):
//   longitude: -180 to 180   (west–east)
//   latitude:  -90  to 90    (south–north)

// Optional third element: elevation in metres above WGS 84 ellipsoid
// Eiffel Tower (approx ground level):
{ "type": "Point", "coordinates": [2.2945, 48.8584, 33.0] }

RFC 7946 restricts GeoJSON to WGS 84 (EPSG:4326) only — no other coordinate reference system is permitted. If your data uses a projected CRS (e.g., UTM, OSGB36), you must reproject to WGS 84 before creating GeoJSON. The optional "crs" member from the older GeoJSON draft (2008) is explicitly deprecated by RFC 7946.

Point, LineString, and Polygon examples

These three geometry types cover the vast majority of real-world geographic data. Each uses a "coordinates" field whose structure depends on the type.

Point — a single location

{
  "type": "Point",
  "coordinates": [2.3522, 48.8566]
}
// coordinates is a single [longitude, latitude] pair
// Add a third element for elevation: [2.3522, 48.8566, 35.0]

LineString — a path or route

{
  "type": "LineString",
  "coordinates": [
    [-0.1276, 51.5074],   // London
    [2.3522,  48.8566],   // Paris
    [13.4050, 52.5200]    // Berlin
  ]
}
// coordinates is an array of [longitude, latitude] pairs (minimum 2)
// Represents a straight-line path between successive points

Polygon — an area with a boundary

{
  "type": "Polygon",
  "coordinates": [
    [
      [-73.9580, 40.8003],
      [-73.9498, 40.7968],
      [-73.9737, 40.7648],
      [-73.9819, 40.7683],
      [-73.9580, 40.8003]
    ]
  ]
}
// coordinates is an array of LinearRings.
// First ring = exterior boundary (counter-clockwise per RFC 7946).
// Subsequent rings = holes (clockwise).
// IMPORTANT: the closing coordinate MUST equal the first coordinate.

// Polygon with a hole (donut shape):
{
  "type": "Polygon",
  "coordinates": [
    [
      [-73.9580, 40.8003], [-73.9498, 40.7968],
      [-73.9737, 40.7648], [-73.9819, 40.7683],
      [-73.9580, 40.8003]
    ],
    [
      [-73.9650, 40.7900], [-73.9600, 40.7880],
      [-73.9700, 40.7750], [-73.9650, 40.7900]
    ]
  ]
}

The winding order rule (exterior ring counter-clockwise, holes clockwise) is required by RFC 7946 but not enforced by all parsers. Violating it can cause rendering issues in some libraries. Use a validator like geojsonhint to catch winding order errors automatically.

Feature and FeatureCollection

Bare geometry objects carry no metadata. A Feature wraps any geometry and adds a "properties" object — the standard place for names, IDs, styling hints, population figures, or any other per-feature data. A FeatureCollection is the standard container for sharing geographic datasets; it is what you get from most GeoJSON APIs and what Leaflet, Mapbox, and Turf.js expect by default.

// A single Feature
{
  "type": "Feature",
  "id": "city-paris",
  "geometry": {
    "type": "Point",
    "coordinates": [2.3522, 48.8566]
  },
  "properties": {
    "name": "Paris",
    "country": "France",
    "population": 2161000,
    "capital": true
  }
}

// "id" is optional; can be a string or number
// "properties" can be null or any JSON object
// "geometry" can be null for a feature with no location
// FeatureCollection — three cities as Point Features
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": 1,
      "geometry": { "type": "Point", "coordinates": [2.3522, 48.8566] },
      "properties": { "name": "Paris", "country": "France" }
    },
    {
      "type": "Feature",
      "id": 2,
      "geometry": { "type": "Point", "coordinates": [-0.1276, 51.5074] },
      "properties": { "name": "London", "country": "UK" }
    },
    {
      "type": "Feature",
      "id": 3,
      "geometry": { "type": "Point", "coordinates": [13.4050, 52.5200] },
      "properties": { "name": "Berlin", "country": "Germany" }
    }
  ]
}

// Accessing features in JavaScript:
const cities = featureCollection.features;
cities.forEach(f => console.log(f.properties.name, f.geometry.coordinates));

The "features" array may be empty ([]) but must be present. The FeatureCollection itself has no "properties" field — add metadata as top-level members if needed (RFC 7946 permits foreign members).

Read and write GeoJSON in JavaScript and Python

GeoJSON is plain JSON, so reading and writing it requires only the standard JSON library. Mapping and analysis libraries add higher-level helpers on top. For guidance on parsing JSON in Python in general, see our dedicated guide.

JavaScript: read and use GeoJSON

// GeoJSON is just regular JSON — parse it the usual way
const raw = '{"type":"FeatureCollection","features":[...]}';
const geojson = JSON.parse(raw);

// Fetch from a URL
const geojson = await fetch('data.geojson').then(r => r.json());

// Leaflet — render all features on a map
L.geoJSON(geojson).addTo(map);

// Leaflet with custom styling and popups
L.geoJSON(geojson, {
  style: feature => ({ color: '#3388ff', weight: 2 }),
  onEachFeature: (feature, layer) => {
    if (feature.properties?.name) {
      layer.bindPopup(feature.properties.name);
    }
  },
}).addTo(map);

// Mapbox GL JS — add as a source
map.addSource('cities', { type: 'geojson', data: geojson });
map.addLayer({
  id: 'city-points',
  type: 'circle',
  source: 'cities',
  paint: { 'circle-radius': 6, 'circle-color': '#B42222' },
});

// Turf.js — spatial analysis
import * as turf from '@turf/turf';
const centroid = turf.centroid(geojson);          // center of FeatureCollection
const buffered = turf.buffer(geojson, 10, { units: 'kilometers' });
const area     = turf.area(polygonFeature);        // area in square metres

Python: read and write GeoJSON

import json

# Read GeoJSON — it is plain JSON
with open('data.geojson') as f:
    data = json.load(f)

# Iterate over features
for feature in data['features']:
    name = feature['properties'].get('name')
    coords = feature['geometry']['coordinates']
    print(f"{name}: {coords}")

# Write GeoJSON back to disk
geojson_dict = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [2.3522, 48.8566]},
            "properties": {"name": "Paris"}
        }
    ]
}
with open('output.geojson', 'w') as f:
    json.dump(geojson_dict, f, indent=2)

# shapely — geometric operations
from shapely.geometry import shape, mapping

geom = shape(feature['geometry'])          # GeoJSON dict → Shapely geometry
print(geom.area)                           # area in CRS units (degrees for WGS 84)
buffered = geom.buffer(0.01)              # buffer by ~1 km at mid-latitudes
back_to_geojson = mapping(buffered)       # Shapely → GeoJSON dict

# geopandas — full GIS workflow
import geopandas as gpd

gdf = gpd.read_file('data.geojson')       # read GeoJSON into a GeoDataFrame
print(gdf.crs)                            # EPSG:4326
gdf_projected = gdf.to_crs('EPSG:3857')  # reproject to Web Mercator
gdf.to_file('output.geojson', driver='GeoJSON')  # write back

Bounding box and GeometryCollection

RFC 7946 defines two additional optional features: the "bbox" member for spatial extent, and GeometryCollection for grouping mixed geometry types.

Bounding box (bbox)

// bbox format: [west, south, east, north]
//               [min-longitude, min-latitude, max-longitude, max-latitude]

// Continental USA bounding box
{
  "type": "FeatureCollection",
  "bbox": [-124.73, 24.96, -66.97, 49.38],
  "features": [ /* ... */ ]
}

// bbox can appear on any GeoJSON object (geometry, Feature, FeatureCollection)
{
  "type": "Feature",
  "bbox": [-73.9819, 40.7648, -73.9498, 40.8003],
  "geometry": { /* Central Park polygon */ },
  "properties": { "name": "Central Park" }
}

// bbox for 3D data includes elevation: [west, south, min-elev, east, north, max-elev]
{
  "type": "FeatureCollection",
  "bbox": [-124.73, 24.96, 0, -66.97, 49.38, 4418],
  "features": []
}

GeometryCollection

// GeometryCollection groups heterogeneous geometries
{
  "type": "GeometryCollection",
  "geometries": [
    { "type": "Point",      "coordinates": [2.3522, 48.8566] },
    { "type": "LineString", "coordinates": [[2.3522, 48.8566], [13.4050, 52.5200]] }
  ]
}

// PREFER FeatureCollection over GeometryCollection in most cases:
// GeometryCollection has no "properties" field per geometry — you can't attach
// metadata. Wrapping each geometry in a Feature with properties is better practice.

The anti-meridian(the 180°/-180° longitude line) is a common edge case. RFC 7946 requires that geometries crossing the anti-meridian be split into two separate geometries. Do not use a bounding box with west > east to represent a region that wraps around — split it instead. Libraries like @mapbox/geojson-rewind and turf.booleanCrosses can help detect and fix anti-meridian issues. To store GeoJSON in PostgreSQL/PostGIS, use the ST_GeomFromGeoJSON() function.

Frequently asked questions

What is GeoJSON?

GeoJSON is a standard format (RFC 7946) for encoding geographic data as JSON. It represents locations, paths, and areas using geometry objects: Point (single location), LineString (path or road), Polygon (area with boundary), and their Multi- variants. Feature wraps geometry with a properties object for metadata. FeatureCollection groups multiple Features. GeoJSON is supported natively by every major mapping library (Leaflet, Mapbox, Google Maps API) and geospatial database (PostGIS, MongoDB, BigQuery).

What is the coordinate order in GeoJSON?

GeoJSON uses [longitude, latitude] order — the mathematical [x, y] convention. This is the opposite of the [latitude, longitude] order used by Google Maps, Leaflet's L.LatLng constructor, and most GPS coordinates. Swapping these is the most common GeoJSON bug. For example, Paris is [2.3522, 48.8566] in GeoJSON (longitude first), not [48.8566, 2.3522]. GeoJSON only supports WGS 84 coordinates (EPSG:4326), with longitude between -180 and 180 and latitude between -90 and 90.

What is the difference between a Feature and a Geometry in GeoJSON?

A Geometry (Point, LineString, Polygon, etc.) represents a shape with coordinates only — no metadata. A Feature wraps a geometry and adds a properties object for arbitrary metadata (name, id, color, population, etc.). A FeatureCollection is an array of Features and is the standard container for sharing geographic datasets. In practice, you almost always work with Features and FeatureCollections, not bare geometries.

How do I validate GeoJSON?

Use geojson.io (paste or draw, it validates in real time) or the geojsonhint library (npm install geojsonhint): geojsonhint.hint(geojsonObject)returns an array of error messages. Common validation errors: coordinates out of range (longitude > 180), polygons not closed (last coordinate ≠ first), wrong winding order (exterior ring must be counter-clockwise per RFC 7946), and Feature without geometry (must be null, not omitted). You can also paste your GeoJSON into our JSON formatter and validator to catch syntax errors before any library-level validation.

How do I read GeoJSON in Python?

GeoJSON files are plain JSON — use json.load(): import json; with open('data.geojson') as f: data = json.load(f). For geographic operations, use shapely: from shapely.geometry import shape; geom = shape(feature['geometry']). For full GIS capabilities (projections, spatial joins, area calculations), use geopandas: import geopandas as gpd; gdf = gpd.read_file('data.geojson'). geopandas reads GeoJSON, Shapefile, and many other formats, and provides pandas-like DataFrame operations. See our full guide on parsing JSON in Python for more.

What is the difference between GeoJSON and TopoJSON?

GeoJSON stores each geometry independently, so shared borders (like state boundaries) are stored twice — once in each polygon. TopoJSON is an extension that stores the topology (connections between geometries) separately, eliminating duplication. A TopoJSON file for US states is typically 5–10× smaller than the equivalent GeoJSON. TopoJSON requires a library to decode (d3.js includes topojson-client), while GeoJSON is plain JSON parseable with any standard JSON library. Use GeoJSON for general use and API interop; use TopoJSON for complex polygon datasets where file size matters.

Validate your GeoJSON

Paste your GeoJSON into Jsonic to catch syntax errors and malformed coordinates before using it in Leaflet, Mapbox, or PostGIS.

Open JSON Formatter