Bursa-Wolf vs Molodensky-Badekas Transformations
Both the Bursa-Wolf and Molodensky-Badekas models carry the same seven quantities — three translations, three rotations, one scale — that drive Helmert 7-parameter transformations in Python, the parent topic under Core Transformation Fundamentals & Standards; the difference is where the rotations act. Bursa-Wolf rotates the position vector about the geocentre, while Molodensky-Badekas rotates it about a local centroid, and because the two publish different translation numbers for the very same physical transformation, using one set through the other’s formula is a silent, systematic error.
Figure — Bursa-Wolf applies the rotation about the distant geocentre; Molodensky-Badekas applies it about the local control centroid.
Why the Two Models Are Not Interchangeable
Bursa-Wolf writes the transformation as a scaled rotation of the raw geocentric vector plus a translation:
Molodensky-Badekas first subtracts the centroid
The rotation matrix
The correction term
Comparison Table
| Property | Bursa-Wolf | Molodensky-Badekas |
|---|---|---|
| Rotation origin | geocentre |
control centroid |
| Extra published quantity | none | centroid coordinates |
| Translation meaning | offset at the geocentre | offset at the centroid |
| Rotation |
shared, identical | shared, identical |
| Parameter correlation | high when survey far from |
low (decorrelated by centring) |
| Typical use | global/continental sets, EPSG 1033/1032 | regional/national fits, EPSG 1034/9636 |
| Interchangeable numbers? | no — translations differ by tens of metres | no — requires the conversion above |
Complete Runnable Implementation
The module applies both forms and converts between their translations. Feeding a Bursa-Wolf and its equivalent Molodensky-Badekas set through their respective functions must transform any point identically.
from __future__ import annotations
from dataclasses import dataclass
import math
import numpy as np
ARCSEC_TO_RAD: float = math.pi / (180.0 * 3600.0)
@dataclass(frozen=True)
class SevenParameters:
"""Shared rotation/scale, plus a translation whose meaning depends on model."""
tx: float # metres
ty: float # metres
tz: float # metres
rx: float # arc-seconds
ry: float # arc-seconds
rz: float # arc-seconds
ds: float # ppm
def _rot_and_scale(p: SevenParameters) -> tuple[np.ndarray, float]:
rx, ry, rz = (p.rx * ARCSEC_TO_RAD, p.ry * ARCSEC_TO_RAD, p.rz * ARCSEC_TO_RAD)
rot = np.array([[1.0, -rz, ry],
[rz, 1.0, -rx],
[-ry, rx, 1.0]], dtype=np.float64) # position-vector sense
return rot, 1.0 + p.ds * 1.0e-6
def bursa_wolf(xyz: np.ndarray, p: SevenParameters) -> np.ndarray:
"""Rotate/scale about the geocentre, then translate (EPSG method 1033)."""
pts = np.atleast_2d(np.asarray(xyz, dtype=np.float64))
rot, scale = _rot_and_scale(p)
out = scale * (pts @ rot.T) + np.array([p.tx, p.ty, p.tz])
return out[0] if np.ndim(xyz) == 1 else out
def molodensky_badekas(xyz: np.ndarray, p: SevenParameters,
centroid: np.ndarray) -> np.ndarray:
"""Rotate/scale about the control centroid, then translate (EPSG method 1034)."""
pts = np.atleast_2d(np.asarray(xyz, dtype=np.float64))
xm = np.asarray(centroid, dtype=np.float64)
rot, scale = _rot_and_scale(p)
out = xm + np.array([p.tx, p.ty, p.tz]) + scale * ((pts - xm) @ rot.T)
return out[0] if np.ndim(xyz) == 1 else out
def mb_translation_to_bw(p: SevenParameters, centroid: np.ndarray) -> np.ndarray:
"""Convert a Molodensky-Badekas translation to the Bursa-Wolf translation:
T_BW = T_MB + (I - (1+ds)R) Xm."""
rot, scale = _rot_and_scale(p)
xm = np.asarray(centroid, dtype=np.float64)
return np.array([p.tx, p.ty, p.tz]) + (np.eye(3) - scale * rot) @ xm
Parameter Reference
| Name | Type | Units | Valid range | Notes |
|---|---|---|---|---|
tx, ty, tz |
float |
metres | published | translation; meaning differs by model |
rx, ry, rz |
float |
arc-seconds | typ. ±5″ | shared rotations, position-vector convention |
ds |
float |
ppm | typ. ±50 ppm | shared scale |
centroid |
np.ndarray |
metres | (3,) ECEF |
control centroid; mandatory for Molodensky-Badekas |
xyz |
np.ndarray |
metres | (3,) or (n,3) |
geocentric ECEF to transform |
| return | np.ndarray |
metres | finite | target-datum ECEF |
Worked Example
Define a Bursa-Wolf set, choose a centroid, derive the matching Molodensky-Badekas translation, and confirm both transform a point to the same place.
bw = SevenParameters(tx=-446.448, ty=125.157, tz=-542.060,
rx=-0.1502, ry=-0.2470, rz=-0.8421, ds=20.4894)
xm = np.array([3874938.0, -114020.0, 5047165.0]) # a British control centroid
rot, scale = _rot_and_scale(bw)
t_mb = np.array([bw.tx, bw.ty, bw.tz]) - (np.eye(3) - scale * rot) @ xm
mb = SevenParameters(*t_mb, bw.rx, bw.ry, bw.rz, bw.ds)
point = np.array([3888891.410, -129007.815, 5036943.920])
print(np.round(bursa_wolf(point, bw), 4))
print(np.round(molodensky_badekas(point, mb, xm), 4))
# both: [ 3888518.0845 -128897.5105 5036509.8149 ]
Both models land the point on the identical target coordinate, yet the Molodensky-Badekas translation t_mb differs from the Bursa-Wolf [-446.448, 125.157, -542.060] by tens of metres — the visible proof that the two translation vectors are not interchangeable.
Validation Check
out_bw = bursa_wolf(point, bw)
out_mb = molodensky_badekas(point, mb, xm)
assert np.allclose(out_bw, out_mb, atol=1e-3), "models disagree — check centroid"
recovered = mb_translation_to_bw(mb, xm)
assert np.allclose(recovered, [bw.tx, bw.ty, bw.tz], atol=1e-6), "round-trip failed"
Common Mistakes
Applying Bursa-Wolf translations through the Molodensky-Badekas formula
(I - (1+ds)R)·Xm. Convert first with the explicit formula.Using the wrong centroid, or omitting it entirely
Flipping the sign of the rotations during conversion
Related
- Helmert 7-parameter transformations in Python — the shared rotation, scale, and convention machinery both models use.
- Deriving 7-parameter Helmert from control points — why centring the control leads naturally to the Molodensky-Badekas form.
- Geodetic conversion math: ellipsoid to Cartesian — producing the ECEF coordinates and centroid these models operate on.
- Validating datum alignment with control points — confirming either model against independent monuments.
- Core Transformation Fundamentals & Standards — the parent reference on datum shifts and compliance.