Module 6: Spatial Data Formats

6.2 GeoJSON

The web-native vector format — its structure, strengths, limits, and best practices.

Lesson 28 of 100·16 min read

Key takeaways

  • GeoJSON is a plain-text JSON format for exchanging simple features on the web.
  • RFC 7946 mandates EPSG:4326 axis order (lon, lat) — no other CRSs.
  • Great for small-to-medium datasets and APIs; inefficient at scale — use FlatGeobuf or GeoParquet for big data.

Introduction

GeoJSON is the format of the web. It's human-readable, diff-friendly, universally supported, and trivially embeddable. Every web map library (Leaflet, MapLibre, OpenLayers, Mapbox) consumes it natively. This lesson covers the structure, the RFC 7946 rules, and where GeoJSON shines or falls short.

The structure

A GeoJSON document is a JSON object with a type. Top-level types:

  • FeatureCollection — an array of Features.
  • Feature — a geometry + properties.
  • Geometry — Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection.

Minimal example:

JSON
1{
2  "type": "FeatureCollection",
3  "features": [
4    {
5      "type": "Feature",
6      "properties": { "name": "Atlas HQ" },
7      "geometry": {
8        "type": "Point",
9        "coordinates": [12.5683, 55.6761]
10      }
11    }
12  ]
13}

The rules (RFC 7946, 2016)

GeoJSON was standardised by IETF as RFC 7946. Key constraints:

  • CRS must be WGS 84 (EPSG:4326). No other CRS allowed. Older GeoJSON files with crs members should be rejected or reprojected.
  • Axis order is (longitude, latitude). Many bugs stem from flipping this.
  • Right-hand rule for polygons — exterior rings counter-clockwise, interior rings clockwise.
  • Antimeridian crossing — split into two polygons at ±180°.

Tools like geojsonhint validate against RFC 7946.

Strengths

  • Plain text — inspect in any editor, diff in git.
  • Web native — every JavaScript map library loads it directly.
  • Universal — every GIS tool reads and writes it.
  • Simple — no binary parsing.
  • Extendableproperties can hold any JSON payload.

Weaknesses

  • Text is bulky — each coordinate is 10–20 bytes. Large files balloon.
  • Parsing is slow — JSON parsers allocate many objects.
  • Requires full load — you can't stream parts efficiently.
  • EPSG:4326 only — if you need native projected storage, GeoJSON isn't it.

For datasets over ~100 MB, consider FlatGeobuf or GeoParquet.

Compact variants

  • TopoJSON — extends GeoJSON with shared arcs between polygons; compresses well for topologically related features (countries, states).
  • newline-delimited GeoJSON (.ndjson, .geojsonl) — one feature per line; streamable.

Working with GeoJSON in code

Python:

Python
import geopandas as gpd

JavaScript:

JavaScript
1fetch('/data/parks.geojson')
2  .then(r => r.json())
3  .then(data => L.geoJSON(data).addTo(map));

Bash:

Shell
1jq '.features | length' parks.geojson        # count features
2jq '.features[0].properties' parks.geojson   # inspect first feature

Rounding coordinates

Default writers output up to 15 decimal places. For a 1-m-accurate dataset that's overkill. Round to 6 decimals (sub-metre precision) to halve file size:

Python
1gdf.to_file('parks.geojson', driver='GeoJSON',
2            COORDINATE_PRECISION=6)

Validation checklist

Before publishing a GeoJSON:

  • Is the CRS EPSG:4326? If not, reproject.
  • Are coordinates in (lon, lat) order?
  • Are polygons closed (first == last vertex)?
  • Are rings oriented correctly (exterior CCW)?
  • Does it parse with geojsonhint / gdalinfo?

A quick practical check is to upload the GeoJSON to Atlas or another browser map and inspect the result visually. If points appear in the ocean, polygons draw inside-out, or attributes look wrong in popups, the issue is often axis order, CRS, ring orientation, or unexpected property names.

When to not use GeoJSON

  • Very large datasets (>100 MB). Use FlatGeobuf, GeoParquet, or GeoPackage.
  • Need for native projected storage. Use GeoPackage or Shapefile with a .prj.
  • Need for topology or multi-layer files. Use GeoPackage.
  • Need for fast random access over the wire. Use FlatGeobuf with range requests.

Self-check exercises

1. What CRS is a GeoJSON file in? Always?

Per RFC 7946, always EPSG:4326 (WGS 84 geographic) with (longitude, latitude) axis order. Older GeoJSON files sometimes specified a crs member, but the modern spec forbids it. If you receive a non-4326 GeoJSON, reproject before using.

2. Your GeoJSON is 80 MB. How would you reduce file size while preserving accuracy?

(1) Round coordinates to an appropriate precision (6 decimals ~= 11 cm). (2) Drop unnecessary properties. (3) Serve compressed (gzip / brotli) — typically 3-5× smaller. (4) Convert to TopoJSON if features share boundaries. (5) Move to FlatGeobuf or GeoParquet for >100 MB.

3. Your polygon GeoJSON looks inside-out on a Leaflet map. What's likely wrong?

Ring orientation. RFC 7946 mandates counter-clockwise exterior rings. Some writers produce clockwise rings, which most renderers interpret as holes. Use a validator or re-orient rings with shapely.geometry.polygon.orient(poly, sign=1.0).

Summary

  • GeoJSON is the web's default vector format — text-based, JSON.
  • RFC 7946 pins GeoJSON to WGS84 in (lon, lat) with right-hand ring orientation.
  • Excellent below ~100 MB; replaced by binary formats above.
  • Validate before publishing: precision, ring orientation, closure, CRS.

Further reading

  • IETF RFC 7946 — The GeoJSON Format.
  • geojson.io — free GeoJSON editor in the browser.
  • turf.js — JavaScript library of GeoJSON operations.
  • GDAL GeoJSON driver documentation.