GeoJSON with Python
Python has three tiers of GeoJSON support: the json stdlib for basic parsing, the geojson library for type-safe construction, and geopandas for spatial analysis pipelines.
The right tier depends on what you're doing. Parsing a file and iterating features — stdlib is enough. Constructing GeoJSON programmatically and catching structural mistakes before they reach an API — use the geojson library. Running spatial joins, reprojections, or bulk conversions — reach for geopandas.
Reading a GeoJSON file
Three ways, ordered from lightest dependency to heaviest.
stdlib json — no install required. Returns a plain Python dict. No type checking, no validation.
# stdlib json
import json
with open("features.geojson") as f:
fc = json.load(f)
print(len(fc["features"])) # number of featuresgeojson library — pip install geojson. Returns typed objects (FeatureCollection, Feature, Point, etc.) that inherit from dict. Attribute access works alongside key access.
# geojson library
import geojson
with open("features.geojson") as f:
fc = geojson.load(f)
# fc is a typed FeatureCollection objectgeopandas — pip install geopandas. Returns a GeoDataFrame — a pandas DataFrame with a geometry column. The right tool when you need column operations, spatial queries, or CRS management.
# geopandas
import geopandas as gpd
gdf = gpd.read_file("features.geojson")
print(gdf.shape) # (rows, columns)gpd.read_file accepts local paths, URLs, and .zip archives containing a Shapefile — not just GeoJSON.
Writing a FeatureCollection
# geojson library (recommended — validates output)
import geojson
point = geojson.Point((-0.1276, 51.5074))
feature = geojson.Feature(geometry=point, properties={"name": "London"})
fc = geojson.FeatureCollection([feature])
with open("output.geojson", "w") as f:
geojson.dump(fc, f, indent=2)geojson.Feature raises ValueError if you pass an unrecognised geometry type. That error surfaces immediately in your script — not silently downstream in a map renderer.
# geopandas
gdf.to_file("output.geojson", driver="GeoJSON")gdf.to_file writes the active CRS to the file if one is set. If you've reprojected the data with gdf.to_crs(epsg=4326), the output coordinates will be in WGS 84 — which is what GeoJSON requires.
Filtering features by property
import geojson
with open("features.geojson") as f:
fc = geojson.load(f)
parks = [
feat for feat in fc["features"]
if feat["properties"].get("category") == "park"
]
result = geojson.FeatureCollection(parks)Using .get("category") instead of ["category"] avoids KeyError when the property is absent on some features — a common situation with real-world GeoJSON datasets where properties are inconsistent across features.
For more complex filtering — by geometry type, bounding box, or spatial relationship — geopandas is the right tool:
import geopandas as gpd
gdf = gpd.read_file("features.geojson")
parks = gdf[gdf["category"] == "park"]
parks.to_file("parks.geojson", driver="GeoJSON")Shapely geometry to GeoJSON
from shapely.geometry import Polygon, mapping
import json
poly = Polygon([(-0.1750, 51.5030), (-0.1580, 51.5030),
(-0.1580, 51.5130), (-0.1750, 51.5130)])
geojson_geom = mapping(poly)
print(json.dumps(geojson_geom, indent=2))
# {"type": "Polygon", "coordinates": [...]}shapely.geometry.mapping converts any Shapely geometry to a Python dict that matches the GeoJSON geometry structure. The coordinates are in [longitude, latitude] order — the same order Shapely stores them. Wrap the result in a Feature to attach properties:
import geojson
feature = geojson.Feature(
geometry=geojson_geom,
properties={"name": "Hyde Park boundary"}
)To go the other direction — GeoJSON dict to a Shapely geometry — use shapely.geometry.shape:
from shapely.geometry import shape
shapely_geom = shape(geojson_geom)
print(shapely_geom.area) # in degrees² — reproject first for meaningful areaFolium: render GeoJSON on a map
import folium
import json
m = folium.Map(location=[51.5074, -0.1276], zoom_start=12)
with open("features.geojson") as f:
data = json.load(f)
folium.GeoJson(data, name="features").add_to(m)
m.save("map.html")folium.GeoJson accepts a Python dict, a file path, or a URL. The output is an HTML file with a Leaflet map embedded. Open it in any browser — no server required.
For per-feature styling, pass a style_function:
folium.GeoJson(
data,
style_function=lambda feature: {
"fillColor": "#2d9e6b" if feature["properties"].get("type") == "park" else "#3388ff",
"color": "#333",
"weight": 1,
"fillOpacity": 0.5,
}
).add_to(m)Watch out:
json.dumpson a GeoJSON dict produces valid JSON but doesn't validate the GeoJSON structure. Usegeojson.dumpsfrom thegeojsonlibrary — it raisesValueErroron invalid geometry types or malformed coordinate arrays.
For converting Shapefiles before processing in Python, see Shapefile to GeoJSON. For GeoJSON format reference, see What is GeoJSON?.
For drawing and editing GeoJSON interactively rather than programmatically, Element Editor's playground accepts file upload and exports back to GeoJSON.
Continue reading
- Shapefile to GeoJSON: three ways to convert
How to convert a Shapefile to GeoJSON using QGIS, ogr2ogr, or Python — with notes on what gets preserved and the reproject trap.
- GeoJSON with PostGIS and PostgreSQL
How to store, query, and export GeoJSON in PostgreSQL using PostGIS — ST_AsGeoJSON, ST_GeomFromGeoJSON, and building a FeatureCollection in SQL.
- FoundationsGeoJSON examples
Ready-to-use GeoJSON examples for Point, LineString, Polygon, MultiPolygon, and FeatureCollection — plus where to download free GeoJSON datasets.

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.