GeoJSON File Size Optimization
Reduce GeoJSON file size by 50-90% using simplification precision reduction minification and format alternatives.
Why GeoJSON Files Get Large
GeoJSON is a human-readable format built on JSON, which means it carries significant overhead. Every coordinate pair is stored as a text array, every property as a key-value string, and every geometry type as a named object. A single polygon with 500 vertices representing a city district can easily reach 50KB before you've added a single property.
Three factors dominate GeoJSON file size. First, coordinate precision: floating-point numbers exported from GIS tools like QGIS or PostGIS default to 15 decimal places, the full precision of a 64-bit float. The longitude of the Empire State Building at full precision is -73.985700000000001 — those trailing zeros contribute bytes without adding any geographic meaning. Second, geometry complexity: raw administrative boundaries from sources like Natural Earth or OpenStreetMap contain hundreds of thousands of vertices capturing every coastal inlet and river meander, far more detail than most web maps require. Third, verbose structure: GeoJSON repeats property keys for every single feature. A dataset of 10,000 points each with 8 properties repeats those 8 key strings 10,000 times.
A typical US county boundary file from the Census Bureau runs 13MB. After the four optimization steps below, the same data renders identically on a web map at 0.9MB — a 93% reduction with no visible quality loss at zoom levels below 14.
Step 1: Reduce Coordinate Precision
Coordinate precision is the fastest win. Geographic coordinates only need as many decimal places as your use case requires. At 6 decimal places, you have ~11cm accuracy — more than sufficient for street-level mapping, navigation, and most web applications. Raw GIS exports use 15 decimal places, which encodes sub-atomic precision that serves no practical purpose and inflates every coordinate pair by roughly 9 characters.
Here is the same coordinate at 15 decimals versus 6:
json// Before: 15 decimal places (raw GIS export)
[-73.985700000000001, 40.748400000000003]
// After: 6 decimal places (~11cm accuracy)
[-73.9857, 40.7484]That single coordinate pair shrinks from 42 characters to 20 — a 52% reduction. Multiply that across a polygon with 2,000 vertices and you've saved roughly 44KB from coordinates alone.
The right precision depends entirely on what you're mapping. Use this reference to choose:
| Decimal Places | Accuracy | Example Use Case |
|---|---|---|
| 15 | Sub-atomic (~0.1nm) | Raw float64 export, scientific measurement |
| 6 | ~11 cm | Street-level mapping, GPS navigation, address geocoding |
| 5 | ~1.1 m | City features, parks, building footprints |
| 4 | ~11 m | Country outlines, regional boundaries, choropleth maps |
For a national boundaries dataset rendered at country zoom levels, 4 decimal places is entirely sufficient. Dropping from 15 to 4 decimals cuts coordinate character count by roughly 70%. Use the coordinate precision tool to round your coordinates to the appropriate level without touching geometry structure.
One caveat: always reduce precision as a final step after any spatial operations. Running a buffer or intersection on already-rounded coordinates can introduce geometric errors at shared boundaries.
Step 2: Simplify Geometries
Geometry simplification removes vertices that contribute negligible visual detail at your target zoom level. The standard algorithm is Douglas-Peucker, which recursively removes points that fall within a specified tolerance distance from the line between their neighbors. Turf.js, Mapshaper, and most GIS tools implement this algorithm.
The tolerance value is expressed in the same units as your coordinates — degrees for WGS84, meters for projected CRS. Common tolerance values for web mapping:
json// Tolerance guide for WGS84 coordinates (degrees)
// 0.0001 → ~11m — suitable for detailed city features
// 0.001 → ~111m — suitable for regional features
// 0.01 → ~1.1km — suitable for country-level outlines
// 0.1 → ~11km — suitable for continental outlinesA real example: the Golden Gate Bridge's surrounding park boundary from OpenStreetMap contains 8,400 vertices. At tolerance 0.0001 (11m), simplification reduces it to 1,200 vertices with no visible change on a web map at zoom 14 or below. At tolerance 0.001 (111m), it drops to 340 vertices — appropriate for zoom levels 10 and below. That's a 96% vertex reduction with the geometry still clearly recognizable.
Douglas-Peucker preserves topology within a single geometry but does not guarantee shared borders between adjacent polygons remain aligned after simplification. If you're simplifying administrative boundaries that must share edges (states, countries), use Visvalingam-Whyatt or topology-aware simplification via Mapshaper with -simplify keep-shapes.
Use the GeoJSON simplifier to apply Douglas-Peucker simplification with a live preview before committing to a tolerance value. Preview at multiple zoom levels — a tolerance that looks fine at zoom 8 may produce ugly artifacts at zoom 12.
Vertex counts before and after simplification at different tolerances for a US state outline (California, ~45,000 vertices raw):
json// California boundary vertex counts by tolerance
// Raw export: 45,231 vertices (full OSM detail)
// Tolerance 0.0001: 12,400 vertices (street-level zoom)
// Tolerance 0.001: 2,840 vertices (city zoom)
// Tolerance 0.01: 580 vertices (state zoom)
// Tolerance 0.1: 90 vertices (country zoom)Step 3: Minify
GeoJSON formatted for human readability includes newlines, indentation, and spaces after colons and commas. A formatted file with 2-space indentation adds 3–8 bytes per line. For a FeatureCollection with 5,000 features, that's easily 50,000 lines of whitespace overhead.
Minification removes all unnecessary whitespace while preserving the data exactly. Here is a formatted versus minified feature:
json// Formatted: 187 characters
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-73.9857, 40.7484]
},
"properties": {
"name": "Empire State Building"
}
}
// Minified: 98 characters (48% smaller)
{"type":"Feature","geometry":{"type":"Point","coordinates":[-73.9857,40.7484]},"properties":{"name":"Empire State Building"}}Typical minification savings on real datasets run 30–45% depending on how deeply nested and well-indented the source file is. A file formatted with 4-space indentation will see larger gains than one using 2-space. Files generated by Python's json.dumps(indent=4) routinely see 40%+ savings from minification alone.
Use the GeoJSON minifier to strip whitespace in one click. If you're serving GeoJSON over HTTP, also enable gzip or Brotli compression at the server level — minified JSON compresses roughly 15% better than formatted JSON because repeated structural characters compress more efficiently without interleaved whitespace.
Step 4: Remove Unnecessary Properties
GeoJSON exports from databases and GIS tools frequently include every column in the source table. A shapefile of US roads exported from a PostGIS database might carry 40 properties per feature: OBJECTID, created_at, updated_at, source_agency, data_quality_flag, and 35 others that your web map never reads. If you only need name and road_class, stripping the other 38 properties eliminates the bulk of the feature data.
Property overhead scales with feature count. A dataset of 20,000 road segments each carrying 40 properties where each key averages 15 characters and each value averages 10 characters amounts to roughly 20MB of property data alone. Retaining only 2 properties reduces that to 1MB — a 95% reduction in property data.
json// Before: 40 properties per feature
{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[...]] },
"properties": {
"OBJECTID": 14523,
"name": "Market Street",
"road_class": "primary",
"created_at": "2019-03-14T08:22:11Z",
"updated_at": "2023-11-02T16:45:00Z",
"source_agency": "SF_DPW",
"data_quality": "verified",
"tiger_id": "110454892",
"... 32 more properties": "..."
}
}
// After: 2 properties retained
{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[...]] },
"properties": {
"name": "Market Street",
"road_class": "primary"
}
}Before stripping properties, audit your rendering code and any popups or tooltips to confirm which properties are actually consumed. Remove property stripping from your pipeline if you're passing the file to other tools that may depend on those fields.
Benchmark: A Real Dataset Through Each Step
This benchmark uses a US county boundaries dataset (3,233 counties, polygon geometries, 12 properties per feature) exported from the Census Bureau TIGER/Line shapefiles at full precision. Each step is applied cumulatively.
| Step | File Size | Reduction from Previous |
|---|---|---|
| Original (raw export) | 4.2 MB | — |
| Precision reduced to 6 decimals | 2.8 MB | 33% |
| Simplified (tolerance 0.001) | 1.1 MB | 61% |
| Minified | 0.8 MB | 27% |
| Properties stripped to 2 fields | 0.5 MB | 38% |
| Combined (all steps) | 0.5 MB | 88% from original |
The combined result renders identically to the original at zoom levels 4–10 (national to state zoom). The 0.5MB file loads in under 200ms on a 25 Mbps connection versus over 1.6 seconds for the original 4.2MB file — well within the 1-second interaction budget for perceived performance.
Note that simplification provides the largest single reduction (61%) for polygon datasets with complex boundaries. For point datasets with many properties, property stripping will dominate. Always profile your specific dataset rather than assuming a fixed order of impact.
When to Switch Formats
Optimization can take you only so far. Some datasets have structural characteristics that GeoJSON cannot efficiently represent, regardless of how aggressively you optimize.
TopoJSON for Shared Borders
TopoJSON encodes topology explicitly: shared borders between adjacent polygons are stored once as arcs, not duplicated per feature. For administrative boundary datasets (countries, states, counties), this eliminates the most redundant data. A world countries GeoJSON at 244KB typically converts to TopoJSON at 97KB — a 60% reduction with no precision loss and perfect topological consistency. The tradeoff is that TopoJSON requires a client-side library to decode. See GeoJSON vs TopoJSON for a full comparison.
Vector Tiles for Large Datasets
When your dataset exceeds ~5MB or contains more than 50,000 features, single-file GeoJSON is the wrong format for web delivery regardless of optimization. Vector tiles (MVT/PBF format) split data into a pyramid of tiles keyed by zoom/x/y, delivering only the features visible in the current viewport at the appropriate detail level. A 500MB road network that would be impossible to load as GeoJSON serves seamlessly as vector tiles because a typical viewport at zoom 12 fetches fewer than 20 tiles totaling under 2MB. Generate tiles with tippecanoe or serve dynamically with PostGIS and pg_tileserv.
FlatGeobuf for Random Access
FlatGeobuf is a binary format with a spatial index that supports HTTP range requests, meaning a client can fetch only the features intersecting the current viewport without downloading the entire file. A 200MB FlatGeobuf file of global building footprints allows a web client to retrieve the ~2,000 buildings visible in a city viewport in a single range request of roughly 300KB. GeoJSON has no equivalent capability — the client must download the entire file or you must implement server-side filtering.
Choose GeoJSON when your optimized file size is under 2MB, your audience expects interoperable data exchange, or you're debugging spatial data and need human-readable output. Switch formats when those conditions no longer hold.