typed op• Data kinds: points → points
• Call: fullseye.apply(img, "tb_dem_ecef_to_geodetic", a=0.5, b=0.5) (the 2-D model is one image plus two scalar knobs a,b∈[0,1])
*No figure: the type is reachable, but the generic synthetic input is outside this op's domain — ECEF は地球表面座標(中心から ~6.4M m)を要るが、画像由来の合成点は原点付近で楕円体の evolute 内に落ち必ず拒否される. See the runnable examples below for usage.*
ECEF → geodetic coordinates. Returns (..., 3) as (latitude[deg], longitude[deg], height[m]).
A method close to the closed form of Bowring (1976). The round-trip error (geodetic → ECEF → geodetic) moves by more than 1 order of magnitude depending on where you sample, so it is stated with ranges (re-measured 2026-09-08. Earlier this read "latitude/longitude below 1e-12 deg, height below 1e-7 m", but ★on 4000 points at latitude ±85 deg and height -500 to 9000 m this was already exceeded at max 6.4e-12 deg / 8.5e-07 m —— it had been confused with the median 2.6e-13 deg / 3.3e-08 m):
• latitude ±85 deg, height -500 to 9000 m … latitude median 2.6e-13 / max 6.4e-12 deg, height median 3.3e-08 / max 8.5e-07 m
• latitude ±89.9 deg, height -11 km to 40 km … latitude median 2.9e-12 / max 1.3e-10 deg, height median 3.5e-07 / max 1.7e-05 m (20 times worse when pushed towards the poles)
Longitude is at most 2.8e-14 deg in both cases. All of these are below a µm on the ground, and in practice they can be treated as "the error floor", but do not quote the median as the maximum.
If all you want is 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 deg, about 21 km in distance). What this op returns is the geodetic latitude, the same as maps and GPS.
★Near the centre of the Earth 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 does not settle on 1 line, and geodetic latitude is not unique in the first place. Until then it silently returned lat = 180° 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 e²a = 42697.7 m from the axis in the equatorial plane and (a²-b²)/b = 42841.3 m on the polar axis (measured: 180 deg at 42600 m, 0 deg at 42700 m —— exactly matching the closed-form boundary).
The dem op dem_ecef_to_geodetic, bridged into the 2-D evolution registry. The implementation is the same; only the calling convention is adapted to op(v, a, b). This op has no tuning knobs; neither a nor b is used.
• 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 examples below call the underlying ledger op dem_ecef_to_geodetic. This bridge op is the same implementation adapted to the fn(v, a, b) convention, so the behaviour carries over unchanged (only the call form differs).
• 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)identity · tb_points_to_voxel · tb_estimate_point_normals · tb_iss_keypoints · tb_project_points · tb_render_point_depth · tb_statistical_outlier_removal · tb_radius_outlier_removal
typed)tb_points_to_voxel · tb_estimate_point_normals · tb_iss_keypoints · tb_project_points · tb_render_point_depth · tb_statistical_outlier_removal · tb_radius_outlier_removal · tb_voxel_grid_downsample
*Provenance: ops.py — 2D 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.