How to Find the Midpoint and Great-Circle Path Between Two Coordinates in Python

On this page

Problem statement

Averaging two latitudes and two longitudes gives a midpoint that is wrong for anything but short distances, and a straight line drawn between two airports on a map is not the route a plane flies. The shortest path between two points on the Earth is a great circle, which on most maps curves towards the pole, can pass far north of both endpoints, and breaks into two pieces when it crosses the 180° meridian. Drawing flight routes, migration paths or cable lines correctly needs the true midpoint, points along the great circle, and care at the date line.

This page computes the midpoint of real routes between OurAirports airports with the spherical formula and on the WGS84 ellipsoid, compares them with the naive average, generates points along great circles with pyproj.Geod.npts, finds how far north a route goes, and splits paths at the antimeridian so they draw correctly.

Quick answer

Use Geod.npts for evenly spaced points along the geodesic; the midpoint is the point at half the distance:

def midpoint(a, b):
    az, _, dist = geod.inv(a.longitude_deg, a.latitude_deg, b.longitude_deg, b.latitude_deg)
    lon, lat, _ = geod.fwd(a.longitude_deg, a.latitude_deg, az, dist / 2)
    return lat, lon

jfk, nrt = AP.loc["JFK"], AP.loc["NRT"]
lat, lon = midpoint(jfk, nrt)
print(f"JFK-NRT midpoint: {lat:.2f}, {lon:.2f}; naive average {(jfk.latitude_deg + nrt.latitude_deg) / 2:.2f}, {(jfk.longitude_deg + nrt.longitude_deg) / 2:.2f}")
JFK-NRT midpoint: 69.48, -152.89; naive average 38.20, 33.30

The midpoint of New York JFK to Tokyo Narita lies at 69.48°N, 152.89°W, over northern Alaska. The average of the coordinates, 38.20°N, 33.30°E, is in central Turkey: averaging longitudes either side of 180° sends the midpoint the wrong way round the world.

World map of three routes as great circles and as straight lines in degrees, with true midpoints as circles and averages of the coordinates as crosses: for JFK to Narita the true midpoint is over northern Alaska while the average falls in central Turkey. World map of three routes as great circles and as straight lines in degrees, with true midpoints as circles and averages of the coordinates as crosses: for JFK to Narita the true midpoint is over northern Alaska while the average falls in central Turkey.
Great circles and their midpoints.

The naive average's error, points along the path, the antimeridian

1. How wrong is the naive average?

Measure the distance from the naive midpoint to the true one for routes of different lengths and directions:

for a, z in (("LHR", "CDG"), ("LHR", "JFK"), ("FRA", "SIN"), ("JFK", "NRT"), ("LAX", "SYD")):
    A, Z = AP.loc[a], AP.loc[z]
    lat, lon = midpoint(A, Z)
    nlat, nlon = (A.latitude_deg + Z.latitude_deg) / 2, (A.longitude_deg + Z.longitude_deg) / 2
    off = geod.inv(lon, lat, nlon, nlat)[2] / 1000
    route = geod.inv(A.longitude_deg, A.latitude_deg, Z.longitude_deg, Z.latitude_deg)[2] / 1000
    print(f"{a}-{z}: route {route:7,.0f} km; naive midpoint {off:7,.0f} km from the true one")
LHR-CDG: route     348 km; naive midpoint       3 km from the true one
LHR-JFK: route   5,555 km; naive midpoint     752 km from the true one
FRA-SIN: route  10,284 km; naive midpoint   1,644 km from the true one
JFK-NRT: route  10,854 km; naive midpoint   8,052 km from the true one
LAX-SYD: route  12,051 km; naive midpoint  20,003 km from the true one

For London to Paris the naive midpoint is 3 km out, close enough. For London to New York it is 752 km out and for Frankfurt to Singapore 1,644 km. For JFK to Narita, which crosses the antimeridian, it is 8,052 km out, and for Los Angeles to Sydney 20,003 km, nearly the far side of the Earth.

2. Points along the path

Geod.npts returns points spaced evenly along the geodesic, excluding the endpoints. Find the northernmost point of a route:

pts = np.array(geod.npts(jfk.longitude_deg, jfk.latitude_deg, nrt.longitude_deg, nrt.latitude_deg, 500))
top = pts[pts[:, 1].argmax()]
print(f"JFK-NRT: {len(pts)} intermediate points; northernmost {top[1]:.2f}°N at {top[0]:.2f}°")
print(f"endpoints at {jfk.latitude_deg:.2f}°N and {nrt.latitude_deg:.2f}°N")
JFK-NRT: 500 intermediate points; northernmost 69.65°N at -145.40°
endpoints at 40.64°N and 35.77°N

The great circle from JFK, at 40.64°N, to Narita, at 35.77°N, reaches 69.65°N at 145.40°W, inside the Arctic Circle: almost 30° north of both endpoints.

Distance between the true midpoint and the average of the coordinates for five routes. naive midpoint error, kmLHR-CDG (348 km)3 kmLHR-JFK (5,555 km)752 kmFRA-SIN (10,284 km)1,644 kmJFK-NRT (10,854 km)8,052 kmLAX-SYD (12,051 km)20,003 km07000 km14k km21k kmThe last two cross 180°, where averaging longitudes goes the wrong way round spatialworkflow.io
The naive midpoint's error.

3. Split at the antimeridian

When a path crosses 180°, consecutive longitudes jump from about +180 to −180, and a plotted line streaks across the whole map. Split wherever the jump exceeds 180°:

def split_path(lon, lat):
    cut = np.where(np.abs(np.diff(lon)) > 180)[0] + 1
    return [(lo, la) for lo, la in zip(np.split(lon, cut), np.split(lat, cut))]
lax, syd = AP.loc["LAX"], AP.loc["SYD"]
p = np.array(geod.npts(lax.longitude_deg, lax.latitude_deg, syd.longitude_deg, syd.latitude_deg, 300))
parts = split_path(p[:, 0], p[:, 1])
print(f"LAX-SYD: {len(p)} points in {len(parts)} pieces; longitudes {p[:, 0].min():.1f} to {p[:, 0].max():.1f}")
LAX-SYD: 300 points in 2 pieces; longitudes -179.8 to 179.9

The Los Angeles to Sydney path crosses 180°: its longitudes run from -179.8 to 179.9, and splitting at the jump gives 2 pieces that draw correctly.

A great-circle path as a GeoDataFrame

The data used on this page

Coordinates of the large airports with an IATA code in OurAirports, a public-domain database. Great-circle computations use pyproj.Geod on WGS84, whose geodesics are the ellipsoidal equivalent of great circles.

from pathlib import Path

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

Path("data").mkdir(exist_ok=True)
p = Path("data/airports.csv")
if not p.exists():
    r = requests.get("https://davidmegginson.github.io/ourairports-data/airports.csv", timeout=300)
    r.raise_for_status()
    p.write_bytes(r.content)
AP = pd.read_csv(p, keep_default_na=False, na_values=[""])
AP = AP[(AP.type == "large_airport") & AP.iata_code.notna()].set_index("iata_code")
geod = Geod(ellps="WGS84")
print(f"{len(AP):,} large airports")
1,172 large airports

The data has 1,172 large airports with an IATA code.

Example 1: a great-circle path as a GeoDataFrame

Build LineStrings for several routes, split at the antimeridian, ready to plot or write to a file:

import geopandas as gpd
from shapely.geometry import LineString, MultiLineString
routes = [("LHR", "JFK"), ("JFK", "NRT"), ("LAX", "SYD"), ("DXB", "LAX")]
geoms = []
for a, z in routes:
    A, Z = AP.loc[a], AP.loc[z]
    q = np.vstack([[A.longitude_deg, A.latitude_deg], geod.npts(A.longitude_deg, A.latitude_deg, Z.longitude_deg, Z.latitude_deg, 200), [Z.longitude_deg, Z.latitude_deg]])
    geoms.append(MultiLineString([LineString(np.c_[lo, la]) for lo, la in split_path(q[:, 0], q[:, 1])]))
G = gpd.GeoDataFrame({"route": [f"{a}-{z}" for a, z in routes]}, geometry=geoms, crs=4326)
G["km"] = [geod.geometry_length(g) / 1000 for g in G.geometry]
G["pieces"] = [len(g.geoms) for g in G.geometry]
print(G[["route", "km", "pieces"]].round(0).to_string(index=False))
  route      km  pieces
LHR-JFK  5555.0       1
JFK-NRT 10800.0       2
LAX-SYD 11991.0       2
DXB-LAX 13420.0       1

Two of the four routes cross the antimeridian and are stored as two-piece MultiLineStrings. Their lengths show a trap: measured from the split geometry, JFK to Narita is 10,800 km and Los Angeles to Sydney 11,991 km, 54 and 60 km shorter than the 10,854 and 12,051 km of step 1, because the short stretch across 180° between the two pieces is not part of either. Compute route lengths with Geod.inv on the endpoints, not from the split line.

Map of the Pacific with longitudes unwrapped to 0 to 360, showing three continuous great circles crossing the 180 degree line: Los Angeles to Sydney, New York to Tokyo over the Arctic, and San Francisco to Hong Kong. Map of the Pacific with longitudes unwrapped to 0 to 360, showing three continuous great circles crossing the 180 degree line: Los Angeles to Sydney, New York to Tokyo over the Arctic, and San Francisco to Hong Kong.
Crossing the antimeridian.

What a great circle is

Steps to draw a great-circle route correctly. drawing a great-circle route1endpointslon, lat2Geod.npts(..., 200)points along the geodesic3add the endpointsnpts excludes them4split where |Δlon| > 180or unwrap to 0-3605MultiLineStringvalid GeoJSON6length from Geod.invnot the split line: 54-60 km shortJFK-NRT and LAX-SYD cross the antimeridian spatialworkflow.io
Drawing a great-circle route.

A great circle is the intersection of the Earth's surface with a plane through its centre, and the arc of it between two points is the shortest path along the surface. On a latitude-longitude map, which stretches high latitudes east-west, that arc bows towards the nearer pole: a route between two places at 40°N can pass north of 60°N. The midpoint along the arc is therefore not the average of the coordinates, which lies on the straight line in degree space; for short routes the two nearly coincide, for intercontinental ones they can be thousands of kilometres apart.

On the ellipsoid the shortest path is a geodesic, which differs from a spherical great circle by a fraction of a percent; Geod.npts and Geod.fwd compute it exactly. The antimeridian is a property of the coordinates, not the path: the line is continuous, but its longitudes wrap from +180 to −180, so drawing software needs it split, or the longitudes unwrapped into 0–360 for a Pacific-centred map.

Antipodal points, flight paths and GeoJSON

Midpoints and great-circle paths: problems and fixes. great-circle trapsTrapInsteadaverage the coordinateshalf the geodesicstraight line on the mappoints from Geod.nptsline streaks across the mapsplit at 180°length of the split lineGeod.inv on the endpointsJFK-NRT midpoint: 69.48N over Alaska, not central Turkey spatialworkflow.io
Great-circle traps.
  • Antipodal points: for exactly opposite points every great circle is shortest; the path is undefined.
  • Flight paths: airlines fly near great circles but follow winds and airways; real tracks deviate by hundreds of kilometres.
  • Map projections: on a gnomonic projection great circles are straight lines; on Mercator rhumb lines are.
  • GeoJSON: RFC 7946 asks for lines crossing 180° to be split, as here.

FAQ

How do I find the midpoint between two coordinates in Python?

Compute the geodesic with Geod.inv, then move half the distance along the starting azimuth with Geod.fwd; do not average the latitudes and longitudes.

How do I draw a great circle route in Python?

Generate intermediate points with pyproj.Geod.npts, add the endpoints, and split the line where consecutive longitudes jump by more than 180°.

Why do flight paths curve on maps?

Because the shortest route on a sphere is a great circle, which looks curved on maps that stretch high latitudes.

How far north does a flight from New York to Tokyo go?

The great circle from JFK to Narita reaches 69.65°N over northern Alaska, almost 30° north of both airports; real flights vary with the winds.