What this is about
I run randomly.online, a set of free browser tools that all share one rule: your data never leaves your machine. No uploads. No accounts. Nothing round-trips to a server. One of those tools is a world clock, where you pin the cities your schedule depends on and watch them all tick at once.
That page needed a map. Building it forced a decision I had not expected to make, about whose version of the world's borders I was going to publish.
The shipped map. Look closely: there is not a single line between countries.
The short version: Natural Earth's countries layer encodes disputed boundaries as de facto control, so drawing it publishes a position on Kashmir, Crimea, Western Sahara, Taiwan and several others without anyone choosing to. I switched the build to Natural Earth's land layer, which is the same coastlines with internal boundaries dissolved away. The disputed lines are gone. They are not in the shipped file to be drawn. That change also cut the geometry from 77.6K to 35.3K raw, and 30K to 14K gzipped, because internal border arcs were 36% of the data.
What the map actually does
Worth setting the scene, because the constraints drove everything after.
It renders to a <canvas> from three JSON files served off my own domain. You can zoom to 14x. There are 1,400 cities from GeoNames, a day/night terminator that moves in real time, and an optional layer shading UTC offset regions. Click a pin and you get a card with that city's local time, its offset, and whether it is currently day or night there.
No tile server, ever. That is the whole reason the geometry is self-hosted rather than pulled from Mapbox or Google Maps, and it is why file size mattered enough to measure. A default page load is 52K gzipped for the entire map.
Why are country borders a problem?
Natural Earth is public-domain vector map data. It is the obvious start. Its countries layer depicts de facto control — project who actually administers a, territory rather than who claims. it The says so openly and disclaims any authority on the question.
Draw those outlines and you have drawn the Kashmir Line of Control rather than India's claimed border. You have also drawn Crimea, Western Sahara, the Israel/Palestine line, Kosovo, Northern Cyprus, and Taiwan as a separate outline.
I chose none of that. It arrived with the file.

I am based in India, where the depiction of national boundaries on published maps is regulated and getting it wrong is not hypothetical. That is my sharp edge. But the general problem is bigger than my jurisdiction. I had shipped a political position inside a clock widget, on seven or eight live disputes at once, because I picked the obvious layer from the obvious dataset and did not look closely at what it drew.
The fix was one line. buildLand() now reads topology.objects.land instead of topology.objects.countries.
I could have kept the country geometry and simply not stroked the internal lines. I deliberately did not. Hiding a line leaves it in the file, one CSS change away from coming back on some future afternoon when I have forgotten why it was hidden.
How much smaller did it get?
| Before | After | Change | |
|---|---|---|---|
| Land geometry, raw | 77.6K | 35.3K | −54% |
| Land geometry, gzipped | 30K | 14K | −53% |
Internal borders were 36% of the geometry: 327 arcs and 2,976 points that existed only to draw lines between neighbours. A coastline borders the sea. Borders are shared between two countries, and the dataset stores them as their own arcs.
So the version that sidesteps the legal problem is also the version that loads faster. That was luck, not foresight. The rest of the size win is encoding: rings are quantised to 0.01 degrees and delta-encoded as integers, which saves more than any simplification pass I tried.
What is the antimeridian bug?
Once the borders were gone I noticed something I had been looking at for days without registering it — a land-coloured stripe running the full width of the map, north of Siberia. I had filed it mentally as a rendering artifact. It was a country.

Four polygons in Natural Earth cross the 180th meridian: the Chukotka lobe of Russia, Wrangel Island, eastern Fiji, and Antarctica's seam. When a polygon crosses that line, its longitude jumps by 360 degrees between two adjacent points — from +179 to −179. On an equirectangular projection, the renderer connects those two points with a straight line, and that line runs across every longitude on Earth. The renderer obeyed.
Wrangel Island is ten points. Ten points painted a bar across the entire Arctic.
The fix runs at build time. splitAtAntimeridian() unwraps each ring so its longitudes run continuously past 180, then uses Sutherland-Hodgman clipping to cut a copy into every 360-degree window the ring touches. Russia went from 12 rings to 14. Fiji went 2 to 3. Total cost across the whole file: 36 bytes.

One ordering detail cost me an hour. The split has to happen before simplification. A line-simplification pass measures perpendicular distance between points to decide what to discard, and an unsplit ring contains a 360-degree jump that the simplifier reads as an enormous, obviously important feature worth preserving at all costs. Split first. Then simplify.
Why were clicks near cities disappearing?
The map has about 40 invisible 22-pixel hit targets sitting over city pins. Clicking one usually did nothing at all, and the console stayed silent. Two bugs were stacked.
The first was mine. My pan-versus-click threshold was Math.hypot(dx, dy) > 4, meaning any pointer movement over four pixels counted as a drag rather than a click. No real hand holds four pixels between press and release. Ordinary clicks were being classified as tiny pans and discarded with no feedback. The threshold is now 10 pixels for a mouse and 16 for touch.
The second one is worth knowing if you write pointer handlers yourself. My endPointer skipped any release whose target was a pin button, on the reasonable-sounding theory that the button would receive its own click event. It does not.
The browser dispatches
clickto the common ancestor of thepointerdownandpointerup
targets.
Press on open canvas, release on a pin, and the click fires on the stage — never on the button. My handler ignored it because the release looked like it belonged to the button. The button never heard. Since the pins blanket every populated region, that was most clicks anywhere near a city.
What I deliberately left alone
Two political residues are still in the shipped product, and both are choices rather than things I missed.
The optional UTC bands layer. These stripes do follow political borders, because time zones are political.
The bands layer follows political boundaries, because Natural Earth's time zone polygons do. It is off by default. It lazy-loads too, so the 21K it costs is only paid by people who switch it on. A time zone boundary reads as a claim about clocks rather than about sovereignty, and I decided that was a different kind of line.
City detail cards also still name the country a city sits in, straight from GeoNames. Sevastopol comes through as UA. Eight cities come through as TW. Naming the country a city is in is ordinary, the source is cited on the page, and stripping country names to dodge the question would have made the tool worse at its actual job.
I refused one shortcut. Time zone shading genuinely requires political boundaries, because time zones are political artifacts. The tempting alternative is 15-degree meridian stripes, which are clean, symmetric and wrong. China spans five of them and runs a single offset across all five. A world clock drawing those stripes would be lying about the largest single population it covers.
The regression test now asserts that the countries key is absent from the built payload and that a bordersNote field is present. If some future version of me reinstates per-country geometry, the build fails loudly instead of quietly redrawing Kashmir.
The map runs on the world clock at randomly.online. Map data: Natural Earth (public domain). City coordinates: GeoNames (CC BY 4.0). 1,400 cities, rendered in your browser, no tile server contacted.



Top comments (0)