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:
Figure — same seven numbers, different pivot: the rotation origin is the whole difference.
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"
Figure — parameter correlation with distance from the network centroid (illustrative regional fit).
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
Frequently Asked Questions
Can I convert a Bursa-Wolf parameter set into a Molodensky-Badekas one?
Yes, exactly, provided you know the rotation origin the Molodensky-Badekas set uses. The rotation and scale terms are identical between the two forms; only the translations differ, and they differ by the amount the rotation and scale move the chosen origin. Convert by applying the rotation and scale to the origin point and adjusting the translations accordingly. What you cannot do is take the seven numbers from one form, feed them to the other, and expect anything sensible — the translations will be wrong by the size of the origin offset, which for a geocentric pivot is thousands of kilometres scaled by the rotation, typically several metres on the ground.
Which form should I publish for a regional transformation?
Molodensky-Badekas, with the pivot stated explicitly. Over a regional network the Bursa-Wolf translations and rotations are so strongly correlated that the individual parameter values are close to meaningless, and any uncertainty quoted on them is misleading even though the transformation as a whole is fine. The Molodensky-Badekas form decorrelates them, which makes the parameter uncertainties readable and makes it obvious to a reviewer that the set applies to a specific region.
Do the two forms give different coordinates?
No. Applied correctly with their own parameter sets, both produce the same transformed coordinate to within floating-point noise. The difference is entirely in the conditioning of the estimation and the interpretability of the parameters, not in the result. If your two implementations disagree on the output, the cause is a parameter set used in the wrong form or a sign convention mismatch in the rotations, not the choice of model.
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.