How to Map Earthquakes Against Plate Boundaries in Python

On this page

Problem statement

Most earthquakes happen at the edges of tectonic plates, and how much "most" means, which kinds of boundary produce the most and largest earthquakes, and where the exceptions are, are questions a catalogue and a plate boundary model can answer together. The work is a nearest-feature distance on a global scale, where no single map projection preserves distance, and a join of each earthquake to the type of boundary it is nearest to: subduction zone, ocean ridge, transform fault or continental collision.

This page joins 25 years of magnitude 5.5 and larger earthquakes from the USGS catalogue to Peter Bird's PB2002 plate boundary model, measures each event's great-circle distance to the nearest boundary with a three-dimensional k-d tree, and compares boundary types by the number and size of their earthquakes and by how fast the plates on either side move.

Quick answer

Densify the boundaries into points, convert everything to unit vectors on the sphere, and query a k-d tree; the chord distance converts exactly to a great-circle distance:

chord, idx = tree.query(xyz(ev.longitude, ev.latitude))
ev["dist_km"] = 2 * np.arcsin(chord / 2) * 6371.0
ev["cls"] = B.cls.values[idx]
for d in (50, 100, 200, 500):
    print(f"within {d:>3} km of a plate boundary: {(ev.dist_km <= d).mean():.1%}")
within  50 km of a plate boundary: 42.6%
within 100 km of a plate boundary: 68.1%
within 200 km of a plate boundary: 86.6%
within 500 km of a plate boundary: 97.4%

42.6% of the earthquakes are within 50 km of a boundary, 68.1% within 100 km, 86.6% within 200 km and 97.4% within 500 km. Even the remaining 2.6% are not all intraplate, as the example below shows.

World map of PB2002 plate boundaries coloured by type, red subduction zones around the Pacific and Indonesia, blue ocean ridges through the Atlantic, Indian and Pacific oceans, and continental boundaries across Asia, with M5.5 and larger earthquakes from 2000 to 2024 as grey dots concentrated along the subduction zones. World map of PB2002 plate boundaries coloured by type, red subduction zones around the Pacific and Indonesia, blue ocean ridges through the Atlantic, Indian and Pacific oceans, and continental boundaries across Asia, with M5.5 and larger earthquakes from 2000 to 2024 as grey dots concentrated along the subduction zones.
Earthquakes and plate boundaries.

Boundary types, earthquakes per type, plate speed

1. The boundary types

PB2002 divides every boundary into short steps, each classified and carrying the relative velocity of the two plates. Summarise length and velocity by class:

names = {"SUB": "subduction", "OSR": "oceanic spreading ridge", "OTF": "oceanic transform", "OCB": "oceanic convergent",
         "CRB": "continental rift", "CTF": "continental transform", "CCB": "continental convergent"}
S["name"] = S.cls.map(names)
print(S.groupby("name").agg(km=("length", "sum"), steps=("length", "size"), velocity=("vel", "median")).sort_values("km", ascending=False).round(0).to_string())
                              km  steps  velocity
name                                             
oceanic spreading ridge  67335.0   1875      29.0
subduction               51470.0   1129      68.0
oceanic transform        47894.0   1147      31.0
continental rift         27470.0    474      13.0
continental transform    26174.0    457      18.0
continental convergent   23003.0    401      16.0
oceanic convergent       17447.0    341      11.0

Ocean ridges are the longest boundary type, 67,335 km, followed by subduction zones, 51,470 km, and oceanic transforms, 47,894 km. Subduction zones also converge fastest: a median relative velocity of 68 mm a year, against 29 mm for ridges and 11 to 18 mm for the continental boundary types.

2. Earthquakes per boundary type

Assign each earthquake within 200 km of a boundary to the class of its nearest step, and compare counts per 1,000 km of boundary and shares of seismic moment:

near = ev[ev.dist_km <= 200].copy()
near["m0"] = 10 ** (1.5 * near.mag + 9.1)
per = near.groupby("cls").agg(events=("mag", "size"), m0=("m0", "sum")).join(S.groupby("cls").length.sum())
per["per_1000km"] = per.events / per.length * 1000
per["moment_share"] = per.m0 / near.m0.sum()
print(per.assign(name=per.index.map(names)).set_index("name")[["events", "per_1000km", "moment_share"]].sort_values("events", ascending=False).round(3).to_string())
top = near.loc[near.groupby("cls").mag.idxmax()]
print("\nlargest event assigned to each class:")
print(top.assign(name=top.cls.map(names), time=top.time.str[:10])[["name", "time", "mag", "place"]].to_string(index=False))
                         events  per_1000km  moment_share
name                                                     
subduction                 5290     102.779         0.613
oceanic transform          1692      35.328         0.024
oceanic spreading ridge    1051      15.608         0.013
continental rift            793      28.867         0.277
continental convergent      722      31.387         0.023
continental transform       699      26.705         0.030
oceanic convergent          433      24.818         0.020

largest event assigned to each class:
                   name       time  mag                                     place
 continental convergent 2012-10-28  7.8        206 km SW of Prince Rupert, Canada
       continental rift 2004-12-26  9.1 2004 Sumatra - Andaman Islands Earthquake
  continental transform 2000-11-16  8.0               2000 New Ireland Earthquake
     oceanic convergent 2012-04-11  8.2             2012 Wharton Basin Aftershock
oceanic spreading ridge 2016-07-29  7.7    Pagan region, Northern Mariana Islands
      oceanic transform 2000-11-16  7.8     135 km SE of Kokopo, Papua New Guinea
             subduction 2011-03-11  9.1       2011 Great Tohoku Earthquake, Japan

Subduction zones have 5,290 of the earthquakes within 200 km of a boundary, 103 per 1,000 km over the 25 years, three to seven times the rate of any other type; ridges have the lowest rate, 16 per 1,000 km. Subduction also has 61% of the seismic moment. The 27.7% credited to continental rifts is an artefact, and the next table shows why: the largest event assigned to a rift is the 2004 Sumatra–Andaman M9.1, a subduction megathrust earthquake whose epicentre happens to lie nearer a PB2002 step classed as continental rift than to the Sunda trench. The 2012 Wharton Basin aftershock, an intraplate event in the Indian Ocean, is likewise assigned to a convergent boundary. The nearest boundary is not always the one that moved; a count of events is far less sensitive to such cases than a sum of moment dominated by one earthquake.

Cumulative chart of great-circle distance from each earthquake to the nearest plate boundary on a logarithmic axis: 43 percent within 50 kilometres, 68 percent within 100, 87 percent within 200 and 97 percent within 500. Cumulative chart of great-circle distance from each earthquake to the nearest plate boundary on a logarithmic axis: 43 percent within 50 kilometres, 68 percent within 100, 87 percent within 200 and 97 percent within 500.
How close earthquakes are to boundaries.

3. Faster plates, more earthquakes?

Join each event within 200 km to its nearest step's relative velocity, and compare event rates by velocity class:

near["vel"] = B.vel.values[tree.query(xyz(near.longitude, near.latitude))[1]]
S["vbin"] = pd.cut(S.vel, [0, 20, 40, 60, 80, 200])
near["vbin"] = pd.cut(near.vel, [0, 20, 40, 60, 80, 200])
rate = (near.groupby("vbin", observed=True).size() / S.groupby("vbin", observed=True).length.sum() * 1000).round(1)
print("M5.5+ events per 1,000 km of boundary, 2000-2024, by relative velocity (mm/yr):")
print(rate.to_string())
M5.5+ events per 1,000 km of boundary, 2000-2024, by relative velocity (mm/yr):
vbin
(0, 20]       14.4
(20, 40]      29.6
(40, 60]      41.4
(60, 80]      55.0
(80, 200]    130.2

The faster the plates move relative to each other, the more earthquakes per kilometre of boundary: 14.4 per 1,000 km where they move less than 20 mm a year, rising steadily to 55 at 60–80 mm and 130 above 80 mm a year. Velocity and boundary type overlap, because the fastest boundaries are mostly subduction zones.

The largest earthquakes far from any boundary

The data used on this page

Every earthquake of magnitude 5.5 or more worldwide from 2000 to 2024 from the USGS FDSN event service, and the step file of Peter Bird's PB2002 plate boundary model as GeoJSON from Hugo Ahlenius's tectonicplates repository (Open Data Commons Attribution licence). Each step is densified to points no more than 5 km apart.

from pathlib import Path

import numpy as np
import pandas as pd
import requests
import json
from scipy.spatial import cKDTree

Path("data").mkdir(exist_ok=True)
def cached(path, url, **params):
    p = Path(path)
    if not p.exists():
        r = requests.get(url, params=params, timeout=600)
        r.raise_for_status()
        p.write_bytes(r.content)
    return p
steps = json.loads(cached("data/PB2002_steps.json", "https://raw.githubusercontent.com/fraxen/tectonicplates/master/GeoJSON/PB2002_steps.json").read_text())
S = pd.DataFrame([{"cls": f["properties"]["STEPCLASS"], "length": f["properties"]["STEPLENGTH"], "vel": f["properties"]["VELOCITYLE"],
                   "lon0": f["properties"]["STARTLONG"], "lat0": f["properties"]["STARTLAT"],
                   "lon1": f["properties"]["FINALLONG"], "lat1": f["properties"]["FINALLAT"]} for f in steps["features"]])
def xyz(lon, lat):
    lo, la = np.radians(np.asarray(lon, float)), np.radians(np.asarray(lat, float))
    return np.column_stack([np.cos(la) * np.cos(lo), np.cos(la) * np.sin(lo), np.sin(la)])
pts = []
for i, r in S.iterrows():
    a, b = xyz([r.lon0], [r.lat0])[0], xyz([r.lon1], [r.lat1])[0]
    n = max(2, int(np.ceil(r.length / 5)) + 1)
    seg = np.outer(1 - np.linspace(0, 1, n), a) + np.outer(np.linspace(0, 1, n), b)
    pts.append(np.column_stack([seg / np.linalg.norm(seg, axis=1, keepdims=True), np.full(n, i)]))
P = np.vstack(pts)
B = S.iloc[P[:, 3].astype(int)].reset_index(drop=True)
tree = cKDTree(P[:, :3])
ev = pd.read_csv(cached("data/global_m55_2000_2024.csv", "https://earthquake.usgs.gov/fdsnws/event/1/query", format="csv",
                        starttime="2000-01-01", endtime="2025-01-01", minmagnitude=5.5, orderby="time-asc"))
ev = ev[ev["type"] == "earthquake"].reset_index(drop=True)
print(f"{len(S):,} boundary steps, {S.length.sum():,.0f} km, densified to {len(P):,} points; {len(ev):,} earthquakes M5.5+")
5,824 boundary steps, 260,795 km, densified to 60,805 points; 12,334 earthquakes M5.5+

PB2002's 5,824 steps add up to 260,795 km of plate boundary, densified to 60,805 points. The catalogue has 12,334 earthquakes of M5.5 or more from 2000 to 2024.

Example 1: the largest earthquakes far from any boundary

List the largest earthquakes more than 500 km from the nearest PB2002 boundary:

far = ev[ev.dist_km > 500].nlargest(6, "mag")
print(far[["time", "mag", "depth", "dist_km", "place"]].assign(time=lambda d: d.time.str[:10], dist_km=lambda d: d.dist_km.round(0)).to_string(index=False))
print(f"events more than 500 km from a boundary: {(ev.dist_km > 500).sum()} of {len(ev):,}; of those deeper than 300 km: {(ev[ev.dist_km > 500].depth > 300).sum()}")
      time  mag  depth  dist_km                           place
2013-05-24  8.3 598.10    594.0  2013 Sea of Okhotsk Earthquake
2019-05-26  8.0 122.57    587.0       78 km NE of Navarro, Peru
2000-06-18  7.9  10.00    915.0              South Indian Ocean
2001-11-14  7.8  10.00    979.0         Southern Qinghai, China
2016-03-02  7.8  24.00    548.0 southwest of Sumatra, Indonesia
2008-07-05  7.7 632.80    572.0                  Sea of Okhotsk
events more than 500 km from a boundary: 322 of 12,334; of those deeper than 300 km: 59

The six largest earthquakes more than 500 km from a boundary are a mixed group. The 2013 and 2008 Sea of Okhotsk earthquakes, about 600 km deep, and the 2019 Peru earthquake, 123 km deep, are inside subducted slabs that have travelled hundreds of kilometres from the trench where they started down: far from the surface trace of the boundary, but part of it. The 2000 South Indian Ocean and 2016 south-west Sumatra earthquakes are genuinely intraplate, in the deforming Indian Ocean plate, and the 2001 Kunlun earthquake in Qinghai is in the wide Asian collision zone that a single boundary line does not capture. Of all 322 events more than 500 km from a boundary, 59 are deeper than 300 km.

M5.5 and larger earthquakes per 1,000 km of plate boundary, 2000-2024, by relative plate velocity. M5.5+ per 1,000 km of boundary, by relative velocityup to 20 mm/yr14.420 to 40 mm/yr29.640 to 60 mm/yr41.460 to 80 mm/yr55.0over 80 mm/yr130.203570105140Events within 200 km, joined to the nearest PB2002 step spatialworkflow.io
Faster plates, more earthquakes.

Plates deform at their edges

Measuring global distances from earthquakes to plate boundaries. distance on the sphere5,824 steps260,795 kmdensifyevery 5 km: 60,805pointsunit vectorsno seam at 180Β°k-d treenearest pointchord to arc2 asin(c/2) x 6,371kmDensifying keeps the error under 2.5 km spatialworkflow.io
Distance on the sphere.

Plate tectonics explains most of the world's seismicity: the plates are rigid in their interiors and deform at their edges, where they collide, separate or slide past one another, and earthquakes are how most of that deformation happens in the brittle crust. Different boundaries do it differently. Subduction zones, where one plate dives beneath another, produce the largest earthquakes because their faults are wide, shallowly dipping and locked over large areas. Ocean ridges spread apart with many small, shallow earthquakes; oceanic transforms produce moderate strike-slip events; and continental collisions spread deformation over wide zones, which is why a distance to a single line underestimates their reach.

Measuring distance on a global scale needs care. No map projection keeps distances correct everywhere, so the distance is computed on the sphere: points as unit vectors, a k-d tree for the nearest one, and the chord converted to an arc. Densifying the boundaries matters because the distance to the nearest vertex of a long segment can be much larger than the distance to the segment itself; at 5 km spacing the error is at most 2.5 km. And a boundary model is a simplification: PB2002 draws diffuse zones such as the Himalaya and the Basin and Range as lines, with "orogens" marked separately.

Spheres, the antimeridian and aftershocks

Checks before joining earthquakes to plate boundaries. before joining to plate boundariesdistance on the sphereno projection is right globallydensify the linesvertex distance overstatesnearest is not causativeSumatra 2004 lands on a rift stepdeep events are far at the surface59 of 322 beyond 500 kmcounts, not momentone M9.1 swamps a classUSGS ComCat M5.5+, 2000-2024; PB2002 steps spatialworkflow.io
Before joining to plate boundaries.
  • Spherical, not ellipsoidal: a sphere of radius 6,371 km is accurate to about 0.5% for distances; use pyproj's geodesic for higher precision.
  • The antimeridian: unit vectors have no seam at Β±180Β°; projected or planar methods do, see the antimeridian fix.
  • Aftershocks: large sequences such as Sumatra 2004 and Tohoku 2011 add thousands of events to one boundary type; decluster for rates.
  • Magnitude to moment: the moment from catalogue magnitudes, M0 = 10^(1.5M + 9.1), is approximate for magnitude types other than Mw.

FAQ

What share of earthquakes happen at plate boundaries?

For M5.5+ earthquakes from 2000 to 2024 in the USGS catalogue, 42.6% are within 50 km of a PB2002 boundary, 86.6% within 200 km and 97.4% within 500 km.

Where can I get plate boundary data?

Peter Bird's PB2002 model is available as shapefiles and GeoJSON in the tectonicplates repository on GitHub, under an open attribution licence.

How do I compute distances on a global scale in Python?

Convert longitude and latitude to unit vectors, query a scipy.spatial.cKDTree, and convert the chord length c to an arc with 2Β·arcsin(c/2)Β·R.

Which plate boundaries have the biggest earthquakes?

Subduction zones: in this catalogue at least 61% of the moment released within 200 km of a boundary, and more once the 2004 Sumatra earthquake, which a nearest-step join assigns elsewhere, is counted with them.