We tore out the maps API and drew the world map ourselves
CouchColumbus was originally built on top of the Google Maps JavaScript API. It now draws the map itself. Here is what we tore out, what replaced it, and why.
Why we tore it out
Displaying a map once through the Maps JavaScript API is a billable event on every page load. There is a monthly free allowance and a personal project rarely exceeds it, but the problem was not the ceiling — it was the structure. What the map does in this service did not justify that much weight.
This was all we needed:
- Fill the world's landmasses in a dark colour
- Draw thin country borders
- Colour in only the countries visited
- Lay a route curve and an aeroplane shape on top
No roads, no satellite imagery, no live traffic, no shop details when you zoom in. And yet for that we were downloading hundreds of kilobytes of SDK, requesting tiles, and ticking a billing counter on every page load. On top of which, calling the geometry library for great circle distances meant pulling in the whole of Maps JS every time an itinerary was built.
What replaced it
Shapes come from Natural Earth. It is public domain and published in TopoJSON-compressed form. This project uses two files.
| File | Original | gzip |
|---|---|---|
countries-110m.json (low resolution) |
105KB | 38KB |
countries-50m.json (high resolution) |
739KB | 225KB |
We draw the low-resolution one first so the map appears immediately, then fetch the high-resolution one in the background and swap it in quietly. Fetch only the high-resolution file from the start and a slow connection leaves a blank screen for a long while.
Projection and path computation are handled by d3-geo. It moves latitude and longitude to
screen coordinates with the equirectangular projection, and geoPath draws straight onto the
canvas. Great circle distances and interpolation come from the same library — Maps JS now loads only
when searching for a place that is not in the built-in dictionary.
Drawing is canvas 2D. Putting several hundred country polygons into the DOM as SVG means
recomputing them on every animation frame. A canvas draws everything at once and clears it at once.
For high-resolution screens we multiply by devicePixelRatio but cap it at 2. Drawing at 3× raises
the pixel count 2.25 times for a difference the eye cannot see.
What tripped us up along the way
Do not use mesh to fill the land. TopoJSON offers both merge and mesh, and mesh returns
lines (a MultiLineString). At a glance the country shapes all appear, so it looks correct — but
fill those lines and you get wedge-shaped smears cutting across the continents. Filling requires
merge, which fuses the countries into a single surface. mesh is for drawing borders only.
The equirectangular projection stretches high latitudes horizontally. The stretch factor is the reciprocal of the cosine of the latitude.
| Latitude | Horizontal stretch |
|---|---|
| 0° (equator) | 1.00× |
| 37.57° (Seoul) | 1.26× |
| 60° | 2.00× |
| 80° | 5.76× |
Which is why the great circle from Seoul to Europe looks like it swells upward on the map. The line is not curved; the map has pulled that region apart.
What still costs money
The map itself is free now. The only paid piece left is destination search, and even that only reaches an external API after searching a 366-city built-in dictionary and a 34,000-city static index first. Most searches never get that far.
None of this is to say maps APIs are bad. If you need directions or live traffic there is no reason to build it yourself. But if what you need is "continent shapes on a dark background," public domain geometry and twenty-odd lines of canvas code will do.