geodesy op• Data kinds: points → points
• Call: import fullseye as fs; fs.ledger.dem_ecef_to_geodetic(xyz) (to call the implementation directly, import demops; demops.dem_ecef_to_geodetic(xyz); from the registry, opsdem.get("dem_ecef_to_geodetic"))
ECEF → geodetic coordinates. Returns (..., 3) holding (latitude[deg], longitude[deg], height[m]).
A method close to the closed form of Bowring (1976). The round-trip (geodetic→ECEF→geodetic) error shifts by more than an order of magnitude depending on where it is sampled, so it is stated with ranges (re-measured 2026-09-08. Previously this read "latitude/longitude under 1e-12 degrees, height under 1e-7 m", but ★with 4000 points at latitude ±85 degrees and height -500 to 9000 m that was already exceeded at max 6.4e-12 degrees / 8.5e-07 m —— it had been confused with the median 2.6e-13 degrees / 3.3e-08 m):
• latitude ±85 degrees, height -500 to 9000 m ... latitude median 2.6e-13 / max 6.4e-12 degrees, height median 3.3e-08 / max 8.5e-07 m
• latitude ±89.9 degrees, height -11 km to 40 km ... latitude median 2.9e-12 / max 1.3e-10 degrees, height median 3.5e-07 / max 1.7e-05 m (pushing toward the poles makes it 20x worse)
Longitude is at most 2.8e-14 degrees in both. All are below a µm on the ground, and in practice can be treated as the "error floor", but do not quote the median as the maximum.
If you only want geocentric spherical coordinates, r = |xyz| and geocentric_lat = asin(z/r) suffice —— but that is not geodetic latitude (the two differ by up to 0.19 degrees, about 21 km in distance). What this op returns is the geodetic latitude, the same as maps and GPS.
★Near the Earth's centre it is rejected fail-closed (2026-09-08, hit by poc_geodetic_height_frames). Inside the evolute of the ellipsoid (a·p)^(2/3) + (b·|z|)^(2/3) < (a²-b²)^(2/3), the normal raised from the ellipsoid surface is not unique, and geodetic latitude is not unique in the first place. Until then it silently returned lat = 180 degrees here —— a value that does not exist as a latitude, and moreover one that its own inverse dem_geodetic_to_ecef rejects with "lat_deg must be within [-90, 90]". The region extends from the axis to e²a = 42697.7 m in the equatorial plane and to (a²-b²)/b = 42841.3 m on the polar axis (measured: 180 degrees at 42600 m, 0 degrees at 42700 m —— exactly matching the closed-form boundary).
• dem_terrain_analysis family guide
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• The canonical algorithm (author, year) and its uses are named in the family usage guide above.
• dem_geodesy_tour — py -3.11 examples/dem_geodesy_tour.py
• poc_geodetic_benchmarks_real — py -3.11 examples/poc_geodetic_benchmarks_real.py
• poc_geodetic_height_frames — py -3.11 examples/poc_geodetic_height_frames.py
points as input)geodesy)dem_geodetic_to_ecef · dem_geocentric_grid · dem_earth_curvature_drop · dem_cell_size_webmercator · dem_geodetic_slope · dem_geoid_height · dem_height_frame_convert · dem_height_frame_residual
*Provenance: demops.py — DEM operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.