Fixing Earthquake Maps and Statistics Split at the Antimeridian

On this page

Problem statement

Tonga and Fiji are one of the most active earthquake regions on Earth, and they sit astride the 180° meridian. Data in longitudes from -180 to 180 split the region in two: a map centred on the Pacific shows half the earthquakes at the left edge and half at the right, the mean longitude of the region comes out at -72°, off the coast of South America, a bounding box covers the whole globe, and a request to a web service for "170°E to 170°W" is rejected. None of this is a data error; it is a seam in the coordinate system that every global dataset has to cross somewhere.

This page downloads ten years of M5+ earthquakes around Tonga and Fiji from the USGS, shows each symptom of the seam, and fixes them by shifting longitudes to 0–360 for the region, using a circular mean, and asking the service for the box in a form it accepts.

Quick answer

For a region that crosses 180°, work in 0–360 longitudes and convert back only for display or export:

print(f"mean longitude, -180..180: {ev.longitude.mean():.2f}")
lon360 = ev.longitude % 360
print(f"mean longitude, 0..360: {lon360.mean():.2f} = {((lon360.mean() + 180) % 360) - 180:.2f} in -180..180")
print(f"circular mean: {np.degrees(np.arctan2(np.sin(np.radians(ev.longitude)).mean(), np.cos(np.radians(ev.longitude)).mean())):.2f}")
mean longitude, -180..180: -72.06
mean longitude, 0..360: 181.08 = -178.92 in -180..180
circular mean: -178.91

The arithmetic mean of the longitudes as stored is -72.06°, a point off South America, about 100° of longitude from the nearest of the earthquakes. In 0–360 the mean is 181.08°, which is -178.92° in the usual convention, and the circular mean agrees to 0.01°.

Two maps of 1,634 M5 and larger earthquakes around Tonga and Fiji: on a world map in minus 180 to 180 longitudes the region is split between the left and right edges and the mean longitude line falls at minus 72 degrees off South America; on a regional map in 0 to 360 longitudes the earthquakes form one region across the 180 degree line, with deep events in purple west of the shallow Tonga trench events. Two maps of 1,634 M5 and larger earthquakes around Tonga and Fiji: on a world map in minus 180 to 180 longitudes the region is split between the left and right edges and the mean longitude line falls at minus 72 degrees off South America; on a regional map in 0 to 360 longitudes the earthquakes form one region across the 180 degree line, with deep events in purple west of the shallow Tonga trench events.
One region, two conventions.

Queries, bounds and distances across the line

1. The query

The FDSN service requires minlongitude to be less than maxlongitude, so "170 to -170" fails. It accepts longitudes beyond ±180 instead:

bad = requests.get(Q, params=dict(format="geojson", minlongitude=170, maxlongitude=-170, **BASE), timeout=60)
print(f"170 to -170: HTTP {bad.status_code}: {bad.text.splitlines()[2] if bad.status_code != 200 else ''}")
print(f"170 to 190: {len(ev)} events; returned longitudes from {ev.longitude.min():.2f} to {ev.longitude.max():.2f}")
print(f"east of 180 (negative longitudes): {(ev.longitude < 0).sum()}; west of 180: {(ev.longitude > 0).sum()}")
170 to -170: HTTP 400: minlongitude must be less than maxlongitude
170 to 190: 1634 events; returned longitudes from -179.99 to 180.00
east of 180 (negative longitudes): 1149; west of 180: 485

Asking for 170 to -170 returns HTTP 400 with the message "minlongitude must be less than maxlongitude". Asking for 170 to 190 works, and the service returns longitudes normalised to -180..180: 1,149 events east of the line, with negative longitudes, and 485 west of it.

2. Bounds, centroids and hulls

GeoPandas computes bounds and hulls in the coordinates it is given. In -180..180 the region spans the globe:

g = gpd.GeoDataFrame(ev, geometry=gpd.points_from_xy(ev.longitude, ev.latitude), crs=4326)
g360 = gpd.GeoDataFrame(ev, geometry=gpd.points_from_xy(ev.longitude % 360, ev.latitude), crs=4326)
for name, d in (("-180..180", g), ("0..360", g360)):
    b = d.total_bounds
    print(f"{name:>9}: bounds {b[0]:.1f} to {b[2]:.1f} ({b[2] - b[0]:.1f}° wide); hull area {d.union_all().convex_hull.area:,.1f} sq degrees")
-180..180: bounds -180.0 to 180.0 (360.0° wide); hull area 3,925.7 sq degrees
   0..360: bounds 170.0 to 188.8 (18.8° wide); hull area 183.0 sq degrees

In -180..180 the bounding box is the whole width of the globe and the convex hull covers 3,925.7 square degrees, a band around the Earth with a hole where the earthquakes are. In 0–360 the bounds are 170.0° to 188.8°, 18.8° wide, and the hull 183.0 square degrees, about a twentieth.

Width of the bounding box of the Tonga-Fiji earthquakes in the two longitude conventions. bounding box width of one region, degrees of longitudein -180..180360.0° (the globe)in 0..36018.8°090°180°270°360°Convex hull: 3,925.7 against 183.0 square degrees spatialworkflow.io
The bounding box.

3. Distances across the line

Planar distances in degrees across the seam are nonsense; projected distances need a CRS whose central meridian is near 180°. Compare the distance between two events on either side of the line:

a = ev.loc[(ev.longitude > 179).idxmax()]; b = ev.loc[(ev.longitude < -179).idxmax()]
print(f"A at {a.longitude:.2f}, B at {b.longitude:.2f}")
print(f"planar degrees: {np.hypot(a.longitude - b.longitude, a.latitude - b.latitude):.1f}°")
geod = Geod(ellps="WGS84")
print(f"geodesic: {geod.inv(a.longitude, a.latitude, b.longitude, b.latitude)[2] / 1000:.1f} km")
pc = gpd.GeoSeries(gpd.points_from_xy([a.longitude, b.longitude], [a.latitude, b.latitude]), crs=4326).to_crs("+proj=eqc +lon_0=180")
print(f"equidistant cylindrical centred on 180°: {pc.iloc[0].distance(pc.iloc[1]) / 1000:.1f} km")
A at 179.25, B at -179.34
planar degrees: 358.6°
geodesic: 346.6 km
equidistant cylindrical centred on 180°: 351.9 km

Two events 1.4° of longitude apart across the line are 358.6° apart as planar degrees. On the ellipsoid they are 346.6 km apart; they also differ in latitude. A projection centred on 180° gives 351.9 km, 1.5% more, because an equidistant cylindrical projection is exact only along meridians and the equator.

Binning across the antimeridian

The data used on this page

Every earthquake of magnitude 5 or more from 2015 to 2024 between 25° and 10°S and 170°E and 170°W from the USGS FDSN event service, requested as longitudes 170 to 190.

from pathlib import Path

import geopandas as gpd
import numpy as np
import pandas as pd
import requests
from pyproj import Geod

Path("data").mkdir(exist_ok=True)
Q = "https://earthquake.usgs.gov/fdsnws/event/1/query"
BASE = dict(starttime="2015-01-01", endtime="2025-01-01", minmagnitude=5, minlatitude=-25, maxlatitude=-10)
p = Path("data/tonga_fiji_m5_2015_2024.csv")
if not p.exists():
    r = requests.get(Q, params=dict(format="csv", minlongitude=170, maxlongitude=190, orderby="time-asc", **BASE), timeout=300)
    r.raise_for_status()
    p.write_text(r.text)
ev = pd.read_csv(p)
ev = ev[ev["type"] == "earthquake"].reset_index(drop=True)
print(f"{len(ev):,} earthquakes M5+; depths {ev.depth.min():.0f} to {ev.depth.max():.0f} km")
1,634 earthquakes M5+; depths 0 to 671 km

1,634 earthquakes of M5 or more in ten years, from the surface to 671 km deep: the Tonga slab is the most productive source of deep earthquakes on Earth.

Example 1: binning across the line

Counting events in 1° cells with floor(longitude) puts the cells at 179 and -180 at opposite ends of any table or grid. Bin in 0–360 to keep neighbours together:

cells = ev.assign(lon_cell=np.floor(ev.longitude % 360), lat_cell=np.floor(ev.latitude)).groupby(["lat_cell", "lon_cell"]).size()
top = cells.nlargest(3)
print("busiest 1° cells (0..360 longitude):", "; ".join(f"{la:.0f}, {lo:.0f} ({n})" for (la, lo), n in top.items()))
cols = np.floor(ev.longitude % 360).value_counts().sort_index()
print(f"events per column of 1°, 175 to 184 (0..360): {cols.loc[175:184].astype(int).to_dict()}")
busiest 1° cells (0..360 longitude): -16, 186 (82); -24, 171 (75); -23, 171 (72)
events per column of 1°, 175 to 184 (0..360): {175.0: 13, 176.0: 27, 177.0: 13, 178.0: 16, 179.0: 40, 180.0: 93, 181.0: 144, 182.0: 103, 183.0: 89, 184.0: 155}

The busiest 1° cells are at 16°S, 186° (174°W) in northern Tonga and at 23–24°S, 171°E, near the southern end of the Vanuatu arc. Across the line the counts run smoothly, 40 events in the column at 179° and 93 at 180°; in -180..180 those two columns would be at opposite ends of the table.

Two bar charts of events per one-degree longitude column: sorted in minus 180 to 180, the columns jump from minus 171 to 170 in the middle so neighbours across 180 degrees sit at opposite ends; in 0 to 360 the columns run continuously from 170 to 188, peaking at 186. Two bar charts of events per one-degree longitude column: sorted in minus 180 to 180, the columns jump from minus 171 to 170 in the middle so neighbours across 180 degrees sit at opposite ends; in 0 to 360 the columns run continuously from 170 to 188, peaking at 186.
Binning across the line.

Longitude is an angle, and angles wrap

What breaks at the antimeridian and what does not. at the 180° seambreaks in -180..180mean longitude: -72°bounds and hulls span the globeplanar distance 358.6°binning splits neighboursworks anywherecircular mean: -178.91°0..360 for a region near 180°geodesic: 346.6 kmunit vectors on the sphere1,634 M5+ earthquakes, Tonga and Fiji, 2015-2024 spatialworkflow.io
At the 180 degree seam.

Longitude is an angle, and angles wrap: 179.9° and -179.9° are 0.2° apart, not 359.8°. Any method that treats longitude as an ordinary number, means, bounds, hulls, sorting, binning, planar distance, breaks where the numbers jump. Most global datasets put the jump at 180° because few people live there, but the Pacific is where many earthquakes, cyclones and ships are. The fix is to move the jump away from the data: for a region straddling 180°, shift to 0–360, where the region is continuous and the jump sits at the Greenwich meridian.

For statistics on angles over a wide range, a circular mean, the direction of the average unit vector, is correct at any longitude. For distances, measure on the ellipsoid with a geodesic or use a projection centred near the region. For maps, set the projection's central meridian near 180° rather than shifting the data, and for exported files remember that GeoJSON expects -180 to 180, with geometries that cross the line split in two.

GeoJSON rules, Shapely and plotting

Checks for data near the 180° meridian. before working near 180°query with 170 to 190170 to -170 gives HTTP 400shift to 0..360 for the regionlon % 360circular mean for anglesnot the arithmetic meangeodesic distancespyproj.Geod handles the seamsplit lines for GeoJSONRFC 7946USGS FDSN event service spatialworkflow.io
Before working near 180 degrees.
  • GeoJSON: RFC 7946 asks that geometries crossing the antimeridian be split; points are fine, lines and polygons are not.
  • Shapely: shift to 0–360 is safe for points; for lines crossing 180° in -180..180, shift every vertex and check none jumps.
  • Web services: other APIs handle crossing boxes differently; some accept min > max, some want two requests.
  • Plotting: matplotlib with a 0–360 x-axis keeps the region together; label ticks back to -180..180 if readers expect it.

FAQ

How do I query USGS earthquakes across the 180° meridian?

Use longitudes beyond 180: minlongitude=170&maxlongitude=190 works, while minlongitude=170&maxlongitude=-170 is rejected because the minimum must be less than the maximum.

Why is my mean longitude wrong in the Pacific?

Points either side of 180° have longitudes near +180 and -180, which average to a value far from the region, -72° for Tonga and Fiji. Shift to 0–360 or use a circular mean.

How do I plot data that crosses the antimeridian?

Convert longitudes to 0–360 for a region near 180°, or use a map projection with its central meridian at 180°.

Do geodesic distances have this problem?

No: pyproj.Geod works on the ellipsoid and handles the antimeridian correctly.