Gallery

City lights globe

More than 7,000 cities and towns, each glowing in proportion to its population, on a globe that turns until you grab it. The bright clusters are the world's great urban regions: the Ganges plain, eastern China, Java, the Nile delta, and the corridor from Boston to Washington.

Build it yourself

The style is a few layers of its own in MapLibre's globe projection: dark land and water from the map tiles, then Natural Earth's populated places drawn twice, once as wide, blurred circles for the glow and once as small, bright ones for the cities. It was tested with MapLibre GL JS 6.15 in Chromium.

What you need

Nothing but a web server to serve the page: the map tiles and Natural Earth's data need no key.

Make the page

Make a folder, and in it index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>City lights globe</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/dist/maplibre-gl.css">
  </head>
  <body style="margin: 0; background: #05070d">
    <div id="map" style="height: 100vh"></div>
    <script type="module">
      import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/+esm";

      const cities =
        "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_10m_populated_places_simple.geojson";
      const size = (small, large) => ["interpolate", ["linear"], ["sqrt", ["get", "pop_max"]], 0, small, 6000, large];

      const map = new maplibregl.Map({
        container: "map",
        style: {
          version: 8,
          projection: { type: "globe" },
          sky: { "atmosphere-blend": 0.6, "sky-color": "#0b1020", "horizon-color": "#1c2541", "fog-color": "#0b1020" },
          sources: {
            protomaps: { type: "vector", url: "https://tiles.mapricot.com/planet.json" },
            cities: { type: "geojson", data: cities, attribution: '<a href="https://www.naturalearthdata.com/">Natural Earth</a>' },
          },
          layers: [
            { id: "background", type: "background", paint: { "background-color": "#0b1020" } },
            { id: "earth", type: "fill", source: "protomaps", "source-layer": "earth", paint: { "fill-color": "#1a2036" } },
            {
              id: "water",
              type: "fill",
              source: "protomaps",
              "source-layer": "water",
              filter: ["==", ["geometry-type"], "Polygon"],
              paint: { "fill-color": "#0b1020" },
            },
            {
              id: "glow",
              type: "circle",
              source: "cities",
              paint: {
                "circle-radius": ["interpolate", ["linear"], ["zoom"], 1, size(1.5, 18), 6, size(5, 60)],
                "circle-color": "#ff922b",
                "circle-blur": 1,
                "circle-opacity": 0.45,
              },
            },
            {
              id: "lights",
              type: "circle",
              source: "cities",
              paint: {
                "circle-radius": ["interpolate", ["linear"], ["zoom"], 1, size(0.5, 3), 6, size(1.5, 9)],
                "circle-color": "#fff4e6",
              },
            },
          ],
        },
        center: [60, 25],
        zoom: 2,
      });

      let turning = !matchMedia("(prefers-reduced-motion: reduce)").matches;
      map.on("mousedown", () => (turning = false));
      map.on("touchstart", () => (turning = false));
      map.on("wheel", () => (turning = false));
      function turn() {
        if (!turning) return;
        const center = map.getCenter();
        map.setCenter([center.lng - 0.04, center.lat]);
        requestAnimationFrame(turn);
      }
      map.once("load", turn);
    </script>
  </body>
</html>

Open it

Serve the folder with npx serve or python3 -m http.server, and open the address it prints. Opened straight from the file, the map stays blank, as Open the page explains. You should see the globe from above India, glowing with cities and turning slowly.

Data