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.
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
- A browser key with the Places product, made as Getting started shows. Add
http://localhost:*to its allowed origins, so it works from your computer while you build. - The map tiles and the USGS catalog 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>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_….
monthsholds the start of each of the last twelve months, and the start of this one, as times in milliseconds, like the quakes'timeproperty.- USGS puts each quake's depth in its coordinates, as a third number, where a style can't read it. The loop copies it into the quake's properties.
- The circle's area doubles with each step of magnitude, and its color darkens with depth, from shallow quakes in light orange to ones 400 miles down in brown.
- The slider sets a filter on
time, so changing months redraws without loading anything again. The label has a fixed width, so the slider doesn't move as month names change length. - Many quakes are far out at sea, where reverse geocoding finds no place.
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
- Earthquakes: USGS Earthquake Hazards Program (public domain)
- Nearest place: Mapricot's reverse geocoding, from OpenStreetMap, OpenAddresses, Who's On First, and GeoNames
- Map: OpenStreetMap contributors (ODbL), in Mapricot's map tiles