A dark, full-bleed 3D globe that becomes a street map as you zoom in, rendering the world's businesses on top of live, free, keyless data.
A dark, full-bleed 3D globe that becomes a street map as you zoom in, rendering the world's businesses on top of live, free, keyless data. Spin the globe to see major companies; dive into any city to see real local businesses with logos and names; watch regions shade themselves by business density.
Every data source is free, live, and requires no API key or token.
npm install
npm run dev # β http://localhost:5173npm run build # static bundle in dist/ (Netlify / Vercel / GitHub Pages ready)
npm run preview # serve the production build locally
npm run test # unit tests for the pure data transforms
npm run smoke # live check that Wikidata + Overpass are reachableNo .env, no tokens, nothing to sign up for.
TERRAFIRM is a static SPA β npm run build emits a dist/ folder of plain
files that any static host or CDN serves. There's no server, no database, nothing
to keep running, so it scales to as many visitors as your CDN handles (see
Scaling below).
| Host | How |
|---|---|
| Vercel | Import the repo β vercel.json sets the build, SPA routing, and cache headers. Or npx vercel --prod. |
| Netlify | Import the repo β netlify.toml does the same. Or npx netlify deploy --prod. |
| Cloudflare Pages | Build command npm run build, output dir dist. public/_redirects handles SPA routing. |
| GitHub Pages | npm run build then publish dist/. For a project page (user.github.io/repo), set base: '/repo/' in vite.config.ts first. |
The included configs set two things every host needs for an SPA:
- SPA fallback β every route serves
index.html(the app is client-routed). - Cache headers β hashed
assets/*are immutable (cached a year);companies.seed.jsonisstale-while-revalidateso revisits are instant.
public/og.png (the link-preview card) is generated from scripts/og.svg:
npm run og # re-render public/og.png after editing scripts/og.svgWhen you point a real domain at the deploy, set the absolute og:url / og:image
and canonical URLs in index.html (there's a commented block showing exactly which).
Because the app is static, the site itself never "hangs" under load β the CDN serves the same cached files to everyone. The only shared resources are the public data APIs, and those are hit directly from each visitor's browser (their own IP), not from one server, so there's no single bottleneck that 100 simultaneous users could overwhelm:
- Companies come from the bundled
companies.seed.jsonsnapshot β zero API calls on load, so the company layer is instant no matter how busy Wikidata is. - Street businesses come from Overpass, which rate-limits per IP. The app fails
over across four mirrors, backs off with jitter on
429/504, caches every cell for 15 min, and only fetches at zoom β₯ 13 β so an individual user degrades gracefully rather than the site going down.
For storefront-scale coverage without depending on Overpass at all, host the Overture Places PMTiles file as a static asset on your own CDN (details below) β that removes the last shared third-party dependency entirely.
TERRAFIRM renders three altitude tiers, driven entirely by the map's zoom:
| Tier | Zoom | What you see | Data source |
|---|---|---|---|
| ORBIT | 0β8 | Rotating globe, country tint, the world's biggest companies as logo markers (more appear as you zoom) | Wikidata + Natural Earth |
| REGIONAL | 9β12 | H3 hexagon shading β a unique colour per area, opacity scaled to business density | H3 over accumulated points |
| STREET | β₯13 | Every named local business, clustered, with logo/monogram + name | Overpass (live OSM) |
- Basemap: OpenFreeMap
darkstyle, recoloured after load to near-black water (#05090F) with amber/cyan accents. - Globe β map: MapLibre GL JS v5 with
projection: globe. The globe flattens into a normal pan/zoom street map automatically as you zoom in. - Markers: HTML markers, only created for what's on screen. Logos lazy-load and fall back to a generated monogram so an image is never broken. Businesses are clustered client-side with supercluster; cluster bubbles zoom in on click.
- Region colour: hue is a deterministic hash of the H3 cell id (every area gets its own colour); opacity encodes density. Below zoom 9 it swaps to a per-country tint.
The bottom status bar shows a LIVE indicator:
scanning(amber pulse) β fetching the visible cells from Overpasslive(cyan) β current viewport is cached/fresh, with the last sync timeunreachable(red dot) β Overpass is busy; the app backs off and retries- The β³ refresh button force-expires the visible cells and re-fetches.
Overpass and Nominatim are shared community infrastructure. TERRAFIRM is built to be a good citizen:
- Overpass is only queried at STREET zoom (β₯ 13) β small z13 cells that return
fast β after the map has been idle 600 ms (
moveend+ debounce). Big regional queries are deliberately avoided: over a shared mirror they queue for 30β60s and504, so the REGIONAL density hexes are fed from the bundled company HQs + accumulated street businesses instead (zero network, never empty). Only uncached cells are fetched, each cached in IndexedDB with a 15-minute TTL. Queries are capped (out center 400,[timeout:20]), in-flight requests are aborted on view change, and the client backs off with jitter on429/504and fails over across four mirror endpoints (started at random so visitors don't all hit the same one). - Global business search β typing any business name queries Nominatim, which
indexes every named POI and address in OSM worldwide. A POI hit becomes a real
marker + detail panel (website, phone, hours, address pulled from OSM
extratags), so the tiniest shop is locatable by name even if it isn't in the current viewport. - Wikidata is queried once on startup (top ~2,000 companies by sitelink count) and cached in IndexedDB for 24 h, so revisits start instantly. The SPARQL ranks companies in a cheap bounded subquery before hydrating details, keeping it under the WDQS 60 s budget.
- Nominatim searches are debounced to β€ 1 req/sec with an identifying
emailparam, per its usage policy.
All caching lives in src/data/cache.ts (a tiny dependency-free IndexedDB k/v store
with TTL and an in-memory fast path; it degrades to memory-only in private mode).
Legally required attribution (Β© OpenStreetMap contributors Β· Wikidata) is always
visible in the footer.
src/
config.ts all tunables: TTLs, zoom thresholds, marker budgets, endpoints
types.ts Entity / Company / Business β source-agnostic shapes
data/
cache.ts IndexedDB k/v store with TTL
wikidata.ts SPARQL query + bindings β Company[] (pure transform)
overpass.ts slippy-tile maths + query + JSON β Business[] (pure transform)
nominatim.ts geocoding search
countries.ts world borders + deterministic per-country hue
logos.ts favicon / Commons / monogram resolution + LRU
transforms.test.ts unit tests for the pure transforms
map/
MapView.tsx orchestrates the map, fetching, clustering, rendering
style.ts dark-theme recolouring of the OpenFreeMap style
layers.ts country + H3 shading sources/layers
h3.ts businesses β H3 hex GeoJSON (colour + density)
markers.ts diffing HTML-marker manager (logos, monograms, clusters)
cluster.ts supercluster wrapper for businesses
camera.ts globe auto-spin + fly-to (respects reduced-motion)
mapBus.ts lets search/panel drive the camera
state/store.ts zustand store
ui/ SearchBar Β· StatusBar Β· AltitudeRail Β· Panel Β· Toasts
The fetchers are typed, support AbortController, retry/back off, and keep their
JSONβdomain transforms as pure, unit-testable functions.
The Business type is deliberately source-agnostic, so a new adapter can populate
it identically to the Overpass one. The intended upgrade is the
Overture Maps Foundation Places dataset β 60M+ free
business POIs, downloadable as GeoParquet / PMTiles β for full offline-scale coverage:
- Convert Overture Places to PMTiles and host the file (static, no server).
- Add it as a MapLibre vector source and read features from the visible tiles.
- Map each feature to
Businessin a newsrc/data/overture.tsadapter β the rest of the app (clustering, panel, markers, H3 shading) is unchanged.
Other drop-in sources that fit the same Business/Company shape:
- People Data Labs Free Company Dataset (~22M companies: name, domain, industry, size, locality) β great for a richer company baseline; import into Postgres/Supabase and expose a bbox endpoint. Note it's company/locality-level, not per-storefront lat/lng, so it complements (not replaces) the OSM/Overture POI layer.
- Public registries β UK Companies House, US Data.gov / city open-data portals, Australia ABR β for authoritative legal company data per country.
- Keyboard accessible (search has full arrow/enter/escape navigation; markers and controls are focusable buttons).
prefers-reduced-motionis respected β the globe doesn't auto-spin and fly-tos are instant.- Responsive: the panel goes full-width and the altitude rail hides on small screens; touch pan/pinch work out of the box.
- Map data Β© OpenStreetMap contributors
- Company data from Wikidata (CC0)
- Base tiles by OpenFreeMap
- Country borders from Natural Earth
Contributions are welcome and appreciated! Whether it is a bug fix, a new data source, or a documentation improvement, please read the Contributing Guide and the Code of Conduct before you start.
Good places to begin:
- Browse the open issues, especially those labelled
good first issueandhelp wanted. - Open an issue to report a bug or propose a feature.
See CHANGELOG.md for a list of notable changes to the project.
Distributed under the MIT License. See LICENSE.md for more information.
- OpenStreetMap contributors for the map data.
- Wikidata for the company data.
- OpenFreeMap for the base tiles.
- Natural Earth for country borders.
- MapLibre GL JS, H3, and supercluster for the rendering stack.