Skip to content

Repository files navigation

retromap

A fantasy-style retro world map rendered with OpenLayers. Ships as a fully static site: no backend, no API calls at runtime — all the geographic data (terrain, cities, volcanoes, seamounts, megalithic monuments, several thousand historical events) is pre-baked at build time and bundled with the page.

Project layout

build-data.mjs    Downloads upstream sources and emits data/*
data/             Generated by build-data.mjs, committed to the repo
static/           Cursors, fonts, favicon
index.html
main.js
cities-worker.js  Worker for the 200k-city bbox scan / search palette
points-worker.js  Generic worker — volcanoes / seamounts / megaliths / facts

Local development

npm install
npm run dev              # http://localhost:9191

data/background.pmtiles and data/cities.bin are committed, so cloning the repo is enough to run the site. Regenerate them whenever you want to pick up fresh upstream data — this needs tippecanoe on PATH:

brew install tippecanoe   # or apt-get install tippecanoe on Ubuntu
npm run build-data        # ~30 MB download, cached in .data-cache/
git add data && git commit -m "refresh data"

CI does not need tippecanoe — .github/workflows/deploy.yml consumes the committed data/background.pmtiles directly.

build-data produces several files in data/:

  • background.pmtiles — NaturalEarth layers (land, lakes, rivers, 11 stepped bathymetry bands, glaciers, marine labels, parks, mountain-range polygons) baked into a PMTiles vector-tile archive by tippecanoe. Per-class MVT layers, per-zoom simplification, per-feature tippecanoe.minzoom baked in (e.g. low-rank rivers, regional river_detail, and the abyssal 10 km bathymetry are physically omitted from low-zoom tiles). The browser fetches tiles via HTTP byte-range requests from a single archive — no per-tile DNS, no runtime reprojection, no multi-megabyte JSON.parse on the main thread.
  • cities.bin — every GeoNames city ≥500 inhabitants, packed binary, pre-sorted by a score that boosts capitals (+1B) and district capitals (+100M) over raw population. Coordinates are pre-projected to EPSG:3857 metres so neither the worker bbox scan nor the main-thread feature construction has to call fromLonLat. Layout: u32 count, f32[count] xs, f32[count] ys, u32[count] populations, u8[count*2] country codes, name+search offset/byte sections.
  • volcanoes.bin, seamounts.bin, megaliths.bin, facts.bin — packed point datasets in a shared layout consumed by points-worker.js. Sources: Smithsonian Global Volcanism Program (~1500 Holocene volcanoes), and Wikidata SPARQL queries (seamounts ≥2 sitelinks under Q207326; dolmens, menhirs, stone circles, passage tombs, megalithic tombs, barrows; and historical events with both coordinates and a point in time — battles, sieges, treaties, shipwrecks, expeditions, eruptions, disasters, …). Each feature carries a per-feature minZoom so the LOD curve is baked into the data rather than computed at render time.

Re-run npm run build-data whenever you want to refresh the upstream data; the .data-cache/ directory makes repeat runs free.

How the bbox lookup works

The original backend used a geohash index to return the top-N highest-scored cities inside a viewport. The static version pushes that work into a Web Worker (cities-worker.js):

  1. Main thread fetches cities.bin as an ArrayBuffer and postMessages it to the worker with the transferable list — zero-copy handoff, no parse, no main-thread jank.
  2. Worker maps Float32Array / Uint32Array views over the buffer.
  3. On every moveend, main posts a bbox query; worker scans the cities in score order and returns the first 100 inside the viewport.

Because the array is globally sorted by score, the loop terminates within the first few hundred entries for any populated viewport, and is bounded by a ~2 ms full-array sweep in the worst case (sparse viewport, empty ocean). Stale query results are dropped on the main thread by tracking the latest query id.

Building for production

npm run build            # outputs to dist/

Parcel builds with --public-url ./ so the bundle works under any subpath, including the /<repo>/ path used by GitHub Pages.

Deployment

.github/workflows/deploy.yml runs npm run build on every push to master and publishes dist/ to GitHub Pages. It uses the data/ files committed in the repo — CI does no data download.

To enable: in the repo's Settings → Pages, set "Build and deployment" source to GitHub Actions.

Data sources

About

📜 Modern cartography with an old-world soul

Resources

Stars

Watchers

Forks

Contributors

Languages