Datum Shifts and Why Coordinates Move by Metres

A datum fixes where the coordinate origin sits relative to the Earth and what shape the Earth is assumed to have. Two datums make different choices, so…

A datum fixes where the coordinate origin sits relative to the Earth and what shape the Earth is assumed to have. Two datums make different choices, so the same physical point has two different coordinate pairs — commonly tens to hundreds of metres apart, and the difference is not a constant offset you can subtract. That non-constancy is the whole reason transformations exist, and why a three-parameter shift is an approximation rather than an answer.

This page belongs to the Datum Transformations & Projection Accuracy guide.

Why This Matters

Datum errors are the quietest class of spatial defect. A projection mistake moves data to the wrong ocean and is noticed in a minute. A datum mistake moves it a few metres — inside the error people attribute to GPS, invisible at any sensible map scale, and consequential exactly where it matters: a boundary, a setback, a utility clearance.

The confusion is understandable, because both a datum and a projection are described by the same object in most software. EPSG:4326 and EPSG:4258 both look like “lat/long, WGS 84-ish” and differ by a datum whose coordinates diverge by centimetres today and by a growing amount over the years. EPSG:4277 and EPSG:4326 differ by well over a hundred metres in places.

Prerequisites

  • pyproj 3.4+ and a working proj.db
  • Knowing which datum your source data is actually on — not just which projection
  • A projected reference system for measuring the shift in metres
  • The parent guide’s distinction between datum, projection and transformation

Primary Method

python
# Measure the datum shift across an extent, in metres
from pyproj import Transformer, Geod


def shift_field(source_crs: str, target_crs: str, samples: list[tuple[float, float]]):
    """
    For each (lon, lat) sample, report how far the point moves when converted
    between two geographic datums. Distances are geodesic, so they are metres.
    """
    t = Transformer.from_crs(source_crs, target_crs, always_xy=True)
    geod = Geod(ellps="WGS84")

    out = []
    for lon, lat in samples:
        lon2, lat2 = t.transform(lon, lat)
        _, _, metres = geod.inv(lon, lat, lon2, lat2)
        out.append((lon, lat, metres))
    return out


for lon, lat, metres in shift_field(
    "EPSG:4277", "EPSG:4326",           # OSGB36 -> WGS 84
    [(-5.0, 50.0), (-2.0, 52.0), (0.0, 54.0), (-4.0, 57.0)],
):
    print(f"({lon:6.2f}, {lat:5.2f})  shift {metres:7.2f} m")

Run that over a handful of points spread across the extent and the key property is visible immediately: the shift is different at every one. It is a field, not an offset.

A datum shift is a field, not a constantFour sample points across one national extent, each showing a different shift magnitude when converted between two datums — around 90 metres in the south-west, 106 in the midlands, 118 in the east and 131 in the north. Because the magnitude and the direction both vary with position, subtracting a single average offset leaves residual error of tens of metres at the extremes.Shift between two datums, sampled across one extentsouth-west≈ 90 mbearing 048°midlands≈ 106 mbearing 055°east≈ 118 mbearing 061°north≈ 131 mbearing 067°subtracting an average leaves tens of metres at the extremesboth magnitude and bearing vary with positionwhich is exactly what a grid-based transformation models
Figures are illustrative of the pattern rather than authoritative values; the point is that no single number is correct everywhere.

Step-by-Step Walkthrough

1. Separate the datum from the projection

Ask pyproj what the datum is, rather than inferring it from the projection’s name.

python
from pyproj import CRS

for code in (4326, 4258, 4277, 27700, 3857):
    crs = CRS.from_epsg(code)
    print(f"EPSG:{code:<6} {crs.name}")
    print(f"    datum:      {crs.datum.name}")
    print(f"    projected:  {crs.is_projected}")

Two projected systems can share a datum — a UTM zone and a national grid over the same country often do not — and two geographic systems can differ only by datum, which is the case that catches people.

2. Understand what makes the shift vary

A datum is realised by a network of physical control points, and those points were surveyed with the instruments of their era. The resulting reference frame is not a perfect rigid body relative to a modern satellite-based one: it stretches and rotates slightly, differently in different regions, because the original survey accumulated error across the country.

That is why the shift is a field. A three-parameter transformation models it as pure translation; a seven-parameter one adds rotation and scale; a grid-based one stores the residual at every node and interpolates. Each step models more of the real deformation.

How much of the real deformation each transformation modelsA three-parameter transformation models translation only and leaves rotation, scale and local distortion unmodelled, giving a few metres of residual error. A seven-parameter transformation adds rotation and scale and leaves only local distortion, giving about a metre. A grid-based transformation stores the residual at every node and interpolates between them, giving a few centimetres, at the cost of a data file that must be installed.3 parameters — translation onlyrotation, scale and local distortion all unmodelled · residual of a few metres7 parameters — plus rotation and scalelocal distortion still unmodelled · residual of about a metregrid — the residual stored at every nodeinterpolated between nodes · residual of a few centimetres · needs a data file
Each row models more of the original survey's accumulated distortion, which is what the residual error actually consists of.

3. Find out which model your environment will use

The route PROJ picks depends on what data it can find, which means the accuracy you get is a property of the machine rather than of your code.

python
from pyproj.transformer import TransformerGroup

tg = TransformerGroup("EPSG:27700", "EPSG:4326")
print("will use:", tg.transformers[0].description)
print("accuracy:", tg.transformers[0].accuracy, "m")

for missing in tg.unavailable_operations:
    print("UNAVAILABLE (grid not installed):", missing.name)

4. Check whether the shift matters for your data

The honest test is to compare the shift against the accuracy of the measurements. A handheld receiver giving three metres paired with a transformation accurate to three metres roughly doubles the error budget — noticeable but often acceptable. The same transformation paired with centimetre-grade survey data throws away everything the equipment provided.

python
CAPTURE_ACCURACY_M = 0.02          # survey grade
route_accuracy = tg.transformers[0].accuracy or 999

if route_accuracy > CAPTURE_ACCURACY_M * 5:
    raise RuntimeError(
        f"transformation accurate to {route_accuracy} m, but capture is "
        f"{CAPTURE_ACCURACY_M} m — install the grid or record the degradation"
    )

5. Record which datum the data is on

Most of the damage from datum shifts comes from data whose datum was never recorded and had to be guessed. A container that states its reference system precisely — including the datum, which the WKT definition carries — can always be transformed correctly later. One that says “lat/long” cannot.

Verification

python
# Round-tripping through another datum must return to the start
t_out = Transformer.from_crs("EPSG:4277", "EPSG:4326", always_xy=True)
t_back = Transformer.from_crs("EPSG:4326", "EPSG:4277", always_xy=True)

lon, lat = -2.0, 52.0
lon2, lat2 = t_out.transform(lon, lat)
lon3, lat3 = t_back.transform(lon2, lat2)

geod = Geod(ellps="WGS84")
_, _, residual = geod.inv(lon, lat, lon3, lat3)
assert residual < 0.01, f"round trip lost {residual:.4f} m"
print(f"round trip closes to {residual * 1000:.2f} mm")

A round trip that does not close means the two directions took different routes — usually because a grid is available in one direction and not the other, which is a real and confusing configuration. Testing it costs nothing and catches an environment problem that would otherwise show up as a slow drift across repeated processing.

How the shift compares with the errors around itA comparison of magnitudes. A datum shift between a mid-century national datum and WGS 84 is typically one to two hundred metres. A three-parameter approximation of that shift leaves a few metres of residual. A handheld receiver contributes a few metres of its own. Survey-grade capture contributes centimetres. The approximation is therefore invisible next to handheld capture and dominant next to survey-grade capture.Magnitudes, for choosing what to worry aboutdatum shift, uncorrected100–200 m3-parameter residuala few metreshandheld receivera few metres — comparablesurvey-grade capturecentimetres — the approximation now dominatesthe same transformation is defensible for one row and wasteful of the equipment for another
Whether an approximate route is acceptable is a question about the measurements it will be combined with, not about the transformation alone.

Alternative Approaches or Edge Cases

Datums that move over time. Plate-fixed datums such as ETRS89 are tied to a continent and drift relative to global ITRF-based frames by a couple of centimetres a year. For survey-grade work over a decade that is not negligible, and the correct handling involves an epoch as well as a datum — which most file formats, including GeoPackage, have no field for.

Data with no recorded datum. Where the datum is unknown, the only sound approach is to determine it empirically from control points whose coordinates you have in a known system. Guessing WGS 84 because the numbers look like degrees is how a systematic hundred-metre error enters a dataset and stays.

Small extents. Over a site of a few hundred metres, the shift is effectively constant, and a single offset really does capture it. That is why local engineering grids work, and why the “just subtract the difference” instinct is right at site scale and wrong at national scale.

Troubleshooting

Everything is offset by a similar amount in a similar direction

Cause: A datum mismatch — the data is on one datum and being interpreted as another. Fix: Identify the source datum, then transform rather than translate. A consistent offset is the fingerprint; the residual variation across the extent is what tells you it is a datum and not a simple coordinate error.

The offset changed after a library upgrade

Cause: A newer PROJ preferred a different route, or the published transformation was revised. Fix: Compare the two routes with projinfo, decide which one your published data should use, and pin it explicitly — see the parent guide for the pinning pattern.

Two datasets of the same area do not line up, and both claim EPSG:4326

Cause: One of them is not really on WGS 84 — most often it was converted with a three-parameter shift while the other used a grid, so both are labelled 4326 and sit a metre or two apart. Fix: This is unresolvable from the files alone; it needs the provenance of each. It is the strongest argument for recording the transformation operation alongside the container.

Frequently Asked Questions

Is WGS 84 a datum or a coordinate system?

Both names get used for both things, which is much of the confusion. WGS 84 is a datum; EPSG:4326 is a geographic coordinate reference system that uses it. Adding to the difficulty, the WGS 84 datum has itself been realised several times, and the realisations differ by centimetres — so “WGS 84” is precise enough for metre-level work and ambiguous below that.

How big can a datum shift get?

Hundreds of metres. Datums defined before satellite geodesy were fitted to a region rather than the globe, so their origin can sit a long way from the Earth’s centre of mass. Shifts of one to two hundred metres between a mid-twentieth-century national datum and WGS 84 are ordinary, and some older datums differ by considerably more.

Does a datum shift affect area and distance?

Only very slightly. A shift moves every point in a small area by nearly the same amount, so the relative geometry — and therefore area, distance and shape — is almost unchanged. What changes is absolute position, which is what matters for anything that must line up with the ground or with another dataset. This is why an area report can be right while every boundary in it is in the wrong place.