ECEF Position To LLA — Robotics/Axes Transformations
Robotics/Axes_Transformations/ECEF_Position_To_LLA · 1 input / 2 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
ECEF Position To LLA
Robotics / Axes Transformations
Converts a Cartesian position in the planet-fixed ECEF frame back into geodetic latitude, longitude and altitude above the ellipsoid. With e² = f(2−f):
- λ = atan2(y, x), the longitude, which is closed form
- ρ = √(x² + y²), and φ = atan2(z, ρ) to start, which is the answer for a sphere
- then, 32 times: N = R / √(1 − e²·sin²φ) and φ = atan2(z + e²·N·sinφ, ρ)
- h = ρ·cosφ + (z + e²·N·sinφ)·sinφ − N
It is the exact inverse of LLA To ECEF Position, and much the more expensive half: that direction is four lines of algebra, this one is a root of a quartic.
Ports
- p_ECEF – the position as one [3,1], [x; y; z] in R's length unit. The shape is fixed: it is how the Simulink block is drawn, and one point crosses at a time.
- mu_iota – the geodetic latitude φ and longitude λ stacked as one [2,1], both in DEGREES. The latitude lands in [−90, 90] and the longitude in (−180, 180].
- h – the altitude above the ellipsoid, a scalar in R's length unit.
Parameters
- Flattening – the ellipsoid's flattening f, a dimensionless scalar. Defaults to 0.0033528106647474805, WGS84's 1/298.257223563. Zero gives a sphere, on which the iteration converges in one pass. A value of exactly 1 is refused: it is a degenerate ellipsoid with no polar extent.
- Equatorial Radius – the ellipsoid's equatorial radius R, a scalar. Defaults to WGS84's 6378137 metres, and its unit is the unit the input position is read in and the altitude is written in.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text.
Every target runs the same fixed 32 passes, and that is a deliberate choice rather than an oversight: a loop that stopped on a convergence test would stop at a different pass in different languages, and the export would then disagree with the simulation for reasons having nothing to do with the arithmetic. Thirty-two is margin – measured against R2026a, eight passes already reach 7×10−15 degrees and stay there at Earth's flattening.
The three hardware targets are simulation-only. Beyond the
sine, the square root and the arctangent, VHDL carries the running latitude
in Q16.16, because a VHDL process's scratch storage is fixed-point only;
Verilog and SystemVerilog keep theirs in floating point, where they are allowed
to declare one. A fixed-point port cannot carry an Earth-sized position at
all – Q16.16 saturates past about 32767 – so a hardware export
of this block is for a small custom body or for lengths in a larger unit.
PLC Structured Text has no ATAN2 in IEC 61131-3, so it is
rebuilt from ATAN with the quadrant tests written out, inside the
loop.
Simulink bridge
Import and export, mapped to Aerospace Blockset's
aerolibtransform2/ECEF Position to LLA. Flattening →
F and Equatorial Radius → R, values
passing straight through.
Two Simulink parameters are always implied and carry no configuration here:
ptype is always Custom and units always
Metric (MKS). Custom is not cosmetic – measured in R2026a, a block
left on its Earth (WGS84) setting accepts a written F or R and
discards it. The Simulink block defines no SampleTime,
so the rate stays on the ICore side.
Notes
- Algebraic and stateless: the 32 passes all happen inside one sample and nothing survives it.
- Not linear, so the block carries no state space and model reduction correctly reports it as unmergeable.
- The two sides do not run the same algorithm. MATLAB's reference iterates Bowring's recursion until a pair of auxiliary sines stops moving, capped at five passes; this block runs a fixed thirty-two. They agree to about 7×10−15 degrees rather than bit for bit, which is why the parity band is 10−12.
- The altitude is first-order insensitive to a latitude error, which is what makes it safe to evaluate from the iterate: at the fixed point the derivatives of its two leading terms cancel exactly, leaving O(e²²).
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Axes_Transformations/ECEF_Position_To_LLA |
| family | Robotics/Axes_Transformations |
| solver environment class | ICoreBlock_0_Robotics_1_Axes_Transformations_2_ECEF_Position_To_LLA |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Axes_Transformations/ECEF_Position_To_LLA/ICoreBlock_0_Robotics_1_Axes_Transformations_2_ECEF_Position_To_LLA.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Axes_Transformations/ECEF_Position_To_LLA/ICoreBlock_0_Robotics_1_Axes_Transformations_2_ECEF_Position_To_LLA.h |
| default size on canvas | 150 × 78 px |
| ports at insert | 1 in, 2 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | p_ECEF |
| 2 | out | ICoreDouble | mu_iota |
| 3 | out | ICoreDouble | h |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Flattening | cFmt(WGS84_F) | — |
Equatorial Radius | cFmt(WGS84_R) | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::Both |
| Simulink path | aerolibtransform2/ECEF Position to LLA |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| always set | ptype = Custom, units = Metric (MKS) |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
CONFIG_F.c_str() | F | passes through |
CONFIG_R.c_str() | R | passes through |
Caveat (shown to the user): the position crosses as one [3,1] and the latitude and longitude come back TOGETHER on one [2,1] in DEGREES, with the altitude on a port of its own -- exactly as the Simulink block is drawn. 'ptype' is always written as Custom, because a block left on Earth (WGS84) accepts a written F or R and discards it -- measured in R2026a. The Simulink block has no SampleTime, so the rate stays on the ICore side. ⚠ The two sides do not run the same algorithm -- MATLAB iterates Bowring's recursion to a convergence test capped at five passes, this block runs a fixed 32 -- so they agree to about 7e-15 degrees rather than bit for bit, which is the reason the parity band is 1e-12 and not 0
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B02 Simulink params rule(s) this tool cannot resolveB0every stimulus in the sample errored — cross-checks skipped
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
ECEF Position To LLA -- a planet-fixed Cartesian position back to geodetic coordinates lon = atan2(y, x) closed form, and the easy one rho = sqrt(x^2 + y^2) lat = atan2(z, rho) the spherical answer, as a start repeat 32: N = R / sqrt(1 - e2 * sin(lat)^2) lat = atan2(z + e2 * N * sin(lat), rho) h = rho*cos(lat) + (z + e2*N*sin(lat))*sin(lat) - N
The exact inverse of LLA To ECEF Position, and much the more expensive half: that direction is four lines of algebra, this one is a root of a quartic.
⚠ THE PASS COUNT IS A CONSTANT, NOT A CONVERGENCE TEST, for the reason its sibling Geocentric To Geodetic Latitude states in full: a loop that stops when it likes cannot be written identically in ten languages -- the stopping sample would differ by a unit in the last place between backends and the export would disagree with the simulation for reasons having nothing to do with the arithmetic.
⚠ MATLAB'S OWN REFERENCE DOES STOP ON A TEST, and that is worth knowing rather than copying.
map.geodesy.internal.cylindrical2geodeticruns Bowring's recursion on (cos beta, sin beta) until the pair moves less than eps(pi/2), capped at five passes. MEASURED against R2026a's block over twelve random points spanning the whole ellipsoid, worst case:1 fixed pass lat 8.3e-04 h 6.7e-04 2 passes lat 4.0e-06 h 1.3e-08 3 passes lat 1.9e-08 h 1.9e-09 4 passes lat 1.2e-10 h 1.9e-09 8 passes lat 7.1e-15 h 9.3e-10 32 passes lat 7.1e-15 h 9.3e-10
Eight passes are already at the floor and thirty-two is margin for a flattening far larger than Earth's, exactly as the sibling measured. The residual 9.3e-10 on the altitude is not the iteration at all -- MATLAB's own ecef2lla shows the same figure -- it is the cancellation in the h line at a radius of 6.4e6.
⚠ AND THE ALTITUDE IS FIRST-ORDER INSENSITIVE TO A LATITUDE ERROR, which is what makes it safe to evaluate from the iterate rather than from a separately converged angle. At the fixed point z + e2*N*sin(lat) = rho*tan(lat), so d/dlat of the first two terms is -rho*sin(lat) + rho*tan(lat)*cos(lat) = 0, and what is left is O(e2^2).
ALGEBRAIC and STATELESS: the 32 passes happen inside one sample, and nothing survives it.
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at ECEF Position To LLA block: ICore Blocks/Home/ECEF Position To LLA. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.
Category unsampled · sample time 0.1 · 60 steps · commit d6e8ad5247e8aa12ff4e80d2fcde8399b98c5f00 · produced by docsSample --out <folder> --blocks LLA_To_ECEF_Position ECEF_Position_To_LLA LLA_To_Flat_Earth Flat_Earth_To_LLA --steps 60
Sample data: docs/generated/samples/Robotics__Axes_Transformations__ECEF_Position_To_LLA.json