TopoJSON — Topological GeoJSON Extension from Mike Bostock for D3.js

2026-05-2710 min read
TopoJSONGeoJSOND3.jsMike Bostockchoroplethtopology

TopoJSON is a topological extension of GeoJSON invented by Mike Bostock (creator of D3.js) in 2012. The idea is simple and elegant: in a typical GeoJSON with administrative boundaries, the same segment of border between two neighbouring regions is stored twice — once in the polygon of region A, again in the polygon of region B. TopoJSON pulls these shared boundaries into an arcs array, and geometries reference them by index. The result is a file that's typically 5–10× smaller than the equivalent GeoJSON without precision loss, and topology (shared edges) becomes explicit — simplifying one arc automatically simplifies all polygons that reference it. That made TopoJSON the de facto standard for choropleth maps in D3.js, electoral maps in The New York Times, static visualizations in Observable and Datawrapper. On osm2cdr.ru topojson_exporter.py takes OSM data for a region and builds the topology via the topojson library (a Python port of Bostock's original implementation), producing a ready-to-use .topojson file with quantization for additional compression.

Map generation of Rome city center

History: Mike Bostock 2012 → D3.js v3/v4/v5 integration

Mike Bostock worked at The New York Times on interactive graphics in 2010–2011 while simultaneously developing D3.js — a data-driven document library that would become the foundation of modern web visualization. While building election maps and choropleths (US maps colored by state metrics), a practical problem emerged: GeoJSON with US state boundaries weighed ~600 KB, with counties — already ~6 MB. For interactive browser rendering in 2012 that was too much.

Analysis showed that 80% of the GeoJSON volume for administrative borders was duplicated coordinates at the seams of neighbouring regions. The California–Nevada border sat fully in the California polygon, and fully in the Nevada polygon — coordinate for coordinate. The fix came from classic computational geometry: represent the geometry as a graph of shared arcs where every edge is stored once and polygons are sequences of arc references.

In August 2012 Bostock published the first version of TopoJSON on GitHub: a spec for the format, plus the topojson Node.js CLI for GeoJSON → TopoJSON conversion, and topojson-client for decoding back to GeoJSON for rendering. Within a year the GitHub repo had 2000+ stars and TopoJSON became part of the D3.js v3 ecosystem.

D3.js v4 (2016) split the functionality into separate modules — d3-geo for projections and path generation, topojson-client for TopoJSON handling. D3.js v5 (2018) and v6 (2020) kept the same integration: import topojson-client, call topojson.feature(topology, topology.objects.states) — get back a GeoJSON FeatureCollection ready for d3.geoPath(). The spec itself hasn't changed materially since 2013 — Bostock intentionally keeps it stable.

Important caveat: TopoJSON has never been and is not an OGC standard. It's a community spec maintained by Mike Bostock and the D3.js community. That's both a strength and a weakness: the format lives where D3 lives — in data journalism, static web visualization, educational projects. In corporate GIS (ArcGIS, ENVI, ERDAS) TopoJSON isn't supported natively — those workflows convert through GeoJSON.

Inside TopoJSON: type "Topology", objects, arcs, transform

A TopoJSON file is valid JSON with a strict structure. Top level is always {"type": "Topology"} followed by three required keys: objects, arcs, transform.

objects is a dictionary of named geometries (similar to FeatureCollection in GeoJSON, but keyed by name). Each object describes geometry via arc references:

{
  "type": "Topology",
  "objects": {
    "states": {
      "type": "GeometryCollection",
      "geometries": [
        {"type": "Polygon", "arcs": [[0, 1, 2]], "properties": {"name": "California"}},
        {"type": "Polygon", "arcs": [[-3, 3, 4]], "properties": {"name": "Nevada"}}
      ]
    }
  },
  "arcs": [...],
  "transform": {...}
}

Note: arc -3 is a negative reference. In TopoJSON ~i (bitwise NOT) means "use arc index i in reverse direction". Arc 2 goes from A to B; -3 (i.e. ~2) goes B to A. That lets two neighbouring polygons reference the same edge geometry without duplication.

arcs is the array of all unique polylines. Each arc is an array of coordinates. The main TopoJSON win: one shared boundary between two regions is stored here exactly once.

transform has two fields: scale and translate. It defines quantization — a linear transform mapping float coordinates to integers for compression. Instead of storing [-118.2437, 34.0522] (24 JSON bytes), TopoJSON stores [1247, 8932] (10 bytes), and the client decodes via x_real = x_int * scale[0] + translate[0]. A quantization factor of 1e5 gives ~1 meter precision in WGS 84 — plenty for any web visualization.

Coordinates inside arcs are also delta-encoded: first point is absolute, every following point is the delta from the previous. Adjacent points in a polyline are usually close, so deltas are small numbers that compress even better under gzip on the HTTP layer. The combo "quantization + delta + gzip" typically shrinks files 7–12× versus equivalent GeoJSON.

Use cases: D3.js choropleth, electoral maps, Observable, web dashboards

D3.js choropleth maps. The main use case. Standard workflow: a static us-states.topojson (~70 KB for all 50 states) sits on a CDN, JS code fetches it once, topojson.feature() turns it into GeoJSON, that feeds into d3.geoPath() with a d3.geoAlbersUsa() projection, and SVG polygons get rendered with a color scale driven by a data metric (population, unemployment, election results). Whole stack loads in <100 ms, rendering stays smooth even on mobile.

Electoral maps. The New York Times, The Washington Post, Bloomberg used TopoJSON for interactive election dashboards from 2012 to 2020. At county level (3000+ polygons) GeoJSON would weigh 15 MB, TopoJSON — 1.5–2 MB. The difference between "instant load" and "frozen on mobile". Many of these interactives live in public NYT / Pudding / FiveThirtyEight GitHub repos.

Observable notebooks. Observable (Mike Bostock's platform after leaving NYT, founded 2017) is a reactive notebook environment where TopoJSON is the default format for geographic examples. Standard Observable library includes FileAttachment("file.topojson").json() and out-of-the-box D3 integration. Most "how to make a choropleth" tutorials in the 2020s are written via Observable + TopoJSON.

Datawrapper and Flourish. Two no-code chart creation web tools heavily used by newsrooms (BBC, Le Monde, Reuters). Both accept TopoJSON for custom geographic maps — a journalist just uploads the file and binds a data column.

Static dashboards without a server. If you have a district / city map with admin subdivisions and want to show timeseries metrics on it — TopoJSON + D3.js + GitHub Pages gives you zero-server hosting with fast rendering. Popular pattern in data journalism and civic tech.

Strengths and weaknesses

Strengths. Size: 5–10× smaller than equivalent GeoJSON for admin boundaries via shared arcs + quantization + delta encoding. Explicit topology — topojson.simplify() preserves shared boundaries (Nevada and California stay neighbours with no gap after simplification). A single decoder topojson-client (~3 KB minified) turns TopoJSON back into GeoJSON, then it's the standard D3/Leaflet/MapLibre stack. All GeoJSON features supported: properties, MultiPolygon, GeometryCollection. Stable spec — unchanged since 2013.

Weaknesses. Not an OGC standard — corporate GIS (ArcGIS, ENVI) doesn't read it directly. Doesn't help for data without shared topology (Points, isolated POIs — TopoJSON will be the same size as GeoJSON or larger due to structural overhead). Quantization adds controlled precision loss (a non-issue for web viz, but worth knowing). Needs a pre-processing step (topojson-server for converting from GeoJSON). No native support in QGIS for editing — read works via GDAL/OGR (supported since 2014), write only to GeoJSON.

TopoJSON vs GeoJSON vs Vector Tiles vs SVG

When to pick which format for web cartography:

  • GeoJSON — universal, simple, but verbose. Best choice for interactive web with few features (<5000) and no topology requirements. See GeoJSON details.
  • TopoJSON — for admin boundaries with shared topology, static choropleth maps, electoral maps. 5–10× smaller than GeoJSON.
  • Vector Tiles (MVT/PMTiles) — for basemaps with millions of features, roads, buildings. Lazy z/x/y tile loading. Not for choropleth, for basemap.
  • SVG (inline) — for final output (D3 renders TopoJSON to SVG). Not for transport.
  • Shapefile — for desktop GIS workflows. Not for web. See the Shapefile guide.

For choropleth admin maps — TopoJSON wins. For anything else on the web — usually GeoJSON or Vector Tiles.

Workflow in OSM2CDR

Our topojson_exporter.py is a simple single-stage pipeline:

Step 1. Pull geometries from PostGIS via layer_builder.py with the WKB fast-path (10–50× faster than Overpass).

Step 2. Build a FeatureCollection in memory, pass it to the Python topojson library (a port of Bostock's original implementation). It builds a graph of shared boundaries through geometric matching, generates the arcs array, applies quantization (default 1e5 — ~1 meter precision in WGS 84), and delta-encodes coordinates.

Step 3. Serialize to JSON. Optional pretty=False flag for production (single-line JSON gzips better).

API call:

curl -X POST https://osm2cdr.ru/api/render \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"formats": ["topojson"],
       "bbox": {"min_lon": 30.30, "min_lat": 59.93, "max_lon": 30.32, "max_lat": 59.94},
       "config": {"layers": ["buildings", "roads"]}}'

The response carries a task_id: check readiness at /api/status/{task_id} and take the file from /api/download/{task_id}/topojson.

Frontend D3.js consumption:

import * as topojson from 'topojson-client';
import * as d3 from 'd3';

const topology = await d3.json('/data/nevsky.topojson');
const features = topojson.feature(topology, topology.objects.buildings);
d3.select('svg').selectAll('path')
  .data(features.features)
  .join('path')
  .attr('d', d3.geoPath());

Map generation of Dubai city center

FAQ

How does TopoJSON differ from GeoJSON? TopoJSON is an extension of GeoJSON with topological encoding: shared boundaries are stored once in an arcs array, geometries reference them by index. Plus quantization (integer coordinates) and delta encoding. Result — file 5–10× smaller than equivalent GeoJSON without precision loss.

Can QGIS open TopoJSON? Yes, since QGIS 2.10 (2014) via the GDAL/OGR driver. Layer → Add Vector Layer → pick .topojson. Reading works transparently; writing from QGIS only exports to GeoJSON — to get back to TopoJSON, run the topojson-server CLI (Node.js) or the Python topojson library.

Is TopoJSON suitable for POIs and point data? No. TopoJSON wins on shared boundaries, and points have no shared edges. For POIs and points use plain GeoJSON — it'll be the same size or smaller.

Why isn't TopoJSON an OGC standard? Mike Bostock and the D3.js community deliberately keep it as a community spec without going through OGC. That lets the spec stay simple and iterate quickly. The price — no support in corporate GIS, but for the target niche (data journalism, web viz) that's not critical.

Which libraries work with TopoJSON? Node.js: topojson-server (encoding), topojson-client (decoding). Python: topojson (port by mattijn). JavaScript for rendering: D3.js + topojson-client. CLI: topojson (Node.js, Bostock's original), Mapshaper (Matthew Bloch, conversion and simplification).

Conclusion

TopoJSON is a specialized format with a narrow but important niche: administrative boundaries for web choropleth and electoral maps in the D3.js / Observable stack. If your task is showing countries / regions / counties with a color scale on an interactive web map — TopoJSON gives you a file 5–10× more compact than GeoJSON with no perceived quality loss. If the task is something else (POIs, basemap, GIS workflow) — pick GeoJSON, Vector Tiles, or Shapefile accordingly.

Map generation of Krasnodar city center

Ready to try? Open the Format Wizard at osm2cdr.ru, draw a bbox of any area, and you'll have a TopoJSON file in a minute — ready for drop-in use in D3.js.

Sources

← All articles