Gallery

A year of earthquakes

Every earthquake of magnitude 5 or more in a year, as the US Geological Survey recorded them. Most trace the edges of the tectonic plates, around the Pacific's Ring of Fire especially. Slide through the months, and click a quake for the nearest place to it.

Shallow 400 mi deep

Build it yourself

The page downloads the year's earthquakes straight from the USGS earthquake catalog, which sends GeoJSON that MapLibre can draw as it is, and asks Mapricot's reverse geocoding for the nearest place to a clicked quake. It was tested with MapLibre GL JS 6.15 in Chromium.

What you need

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>A year of earthquakes</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/dist/maplibre-gl.css">
  </head>
  <body style="margin: 0">
    <div style="position: absolute; z-index: 1; top: 10px; left: 10px; padding: 8px; background: white">
      <input id="month" type="range" min="0" max="12" value="0">
      <label for="month" id="label" style="display: inline-block; width: 9rem">The whole year</label>
    </div>
    <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 key = "mapricot_pk_…";
      const now = new Date();
      const months = Array.from({ length: 13 }, (_, i) => Date.UTC(now.getUTCFullYear() - 1, now.getUTCMonth() + i));

      const map = new maplibregl.Map({
        container: "map",
        style: "https://tiles.mapricot.com/styles/light.json",
        center: [150, 10],
        zoom: 1,
      });
      await map.once("load");

      const start = new Date(months[0]).toISOString().slice(0, 10);
      const end = new Date(months[12]).toISOString().slice(0, 10);
      const response = await fetch(
        `https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&minmagnitude=5&starttime=${start}&endtime=${end}`,
      );
      const quakes = await response.json();
      for (const quake of quakes.features) quake.properties.depth = quake.geometry.coordinates[2];

      map.addSource("quakes", {
        type: "geojson",
        data: quakes,
        attribution: '<a href="https://earthquake.usgs.gov/">USGS</a>',
      });
      map.addLayer({
        id: "quakes",
        type: "circle",
        source: "quakes",
        paint: {
          "circle-radius": ["interpolate", ["exponential", 2], ["get", "mag"], 5, 2.5, 6, 5, 7, 10, 8, 20],
          "circle-color": [
            "interpolate",
            ["linear"],
            ["get", "depth"],
            0, "#ffc078",
            70, "#fd7e14",
            300, "#c2410c",
            650, "#7c2d12",
          ],
          "circle-stroke-color": "#ffffff",
          "circle-stroke-width": 1,
          "circle-opacity": 0.85,
        },
      });

      const slider = document.querySelector("#month");
      const label = document.querySelector("#label");
      slider.addEventListener("input", () => {
        const month = Number(slider.value);
        if (month === 0) {
          map.setFilter("quakes", null);
          label.textContent = "The whole year";
          return;
        }
        map.setFilter("quakes", ["all", [">=", ["get", "time"], months[month - 1]], ["<", ["get", "time"], months[month]]]);
        label.textContent = new Date(months[month - 1]).toLocaleDateString("en-US", { month: "long", year: "numeric", timeZone: "UTC" });
      });

      map.on("click", "quakes", async (event) => {
        const [quake] = event.features;
        const [lon, lat] = quake.geometry.coordinates;
        const popup = new maplibregl.Popup()
          .setLngLat([lon, lat])
          .setText(`Magnitude ${quake.properties.mag}`)
          .addTo(map);
        const response = await fetch(`https://api.mapricot.com/v1/reverse?lat=${lat}&lon=${lon}&limit=1`, {
          headers: { Authorization: `Bearer ${key}` },
        });
        if (!response.ok) throw new Error(`Mapricot returned ${response.status}`);
        const { results } = await response.json();
        popup.setText(`Magnitude ${quake.properties.mag}, ${results.length ? `near ${results[0].label}` : "far from any place"}`);
      });
      map.on("mouseenter", "quakes", () => (map.getCanvas().style.cursor = "pointer"));
      map.on("mouseleave", "quakes", () => (map.getCanvas().style.cursor = ""));
    </script>
  </body>
</html>

Put your browser key in place of mapricot_pk_….

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 and the API refuses the key, as Open the page explains. You should see about 2,000 quakes tracing the Pacific's Ring of Fire. Slide through the months, and click a quake.

MapLibre's own time slider example uses the same filter. For points in the thousands at a glance, a heatmap works too, as in Cluster points in MapLibre GL JS.

Data