Flat Earth To LLA — Robotics/Axes Transformations
Robotics/Axes_Transformations/Flat_Earth_To_LLA · 2 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.
Flat Earth To LLA
Robotics / Axes Transformations
Converts offsets in a local flat-Earth frame back into a geodetic position. With e² = f(2−f) and the reference latitude φ₀:
- Rn = R / √(1 − e²·sin²φ₀) and Rm = Rn(1 − e²) / (1 − e²·sin²φ₀), both evaluated at the reference
- dN = cosψ·x − sinψ·y and dE = sinψ·x + cosψ·y
- φ = dN / (Rm·π/180) + φ₀ and λ = dE / (Rn·cosφ₀·π/180) + λ₀, then folded back into range
- h = −z − href
The frame is North-East-Down: z grows downward, which is why both heights leave it negated.
Ports
- p_flat – the offset as one [3,1], [x; y; z] in R's length unit, North-East-Down. The shape is fixed: it is how the Simulink block is drawn, and one point crosses at a time.
- href – the reference height, a scalar in the same unit. It enters only the altitude, and only as an offset.
- 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 Rn and Rm are both R. 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 offsets are read in and the altitude is written in.
- Reference Location – the geodetic [latitude longitude] the flat frame is pinned at, in DEGREES, given as a two-element vector either way up. Defaults to [0 10]. It is folded once, when the configuration is loaded. A reference latitude of exactly ±90 is refused: cosφ₀ is then zero and the longitude would divide by it.
- Reference Heading – the angle ψ from north to the flat frame's x axis, in DEGREES, positive clockwise. Defaults to 0, at which x is north and y is east.
- 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.
Everything that depends on the ellipsoid or the reference – Rm·π/180, Rn·cosφ₀·π/180, sinψ, cosψ and the folded reference pair – is folded into four literals at export time, so no target evaluates a trigonometric function of a constant. What is left per sample is two multiply-adds, two divisions and one fold.
The three hardware targets are simulation-only. The fold needs a
floor, which all three have, but VHDL carries the running
latitude and longitude in Q16.16 because a VHDL process's scratch storage is
fixed-point only; that costs about 1.5×10−5 degrees on
each. Verilog and SystemVerilog keep theirs in floating point. A fixed-point
port also cannot carry an Earth-sized offset – 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
FLOOR in IEC 61131-3, so it is rebuilt from TRUNC,
which rounds toward zero and therefore differs on every negative
non-integer.
Simulink bridge
Import and export, mapped to Aerospace Blockset's
aerolibtransform2/Flat Earth to LLA. Flattening →
F, Equatorial Radius → R, Reference
Location → LL0 and Reference Heading →
psi, values passing straight through.
Three Simulink parameters are always implied and carry no configuration here:
ptype is always Custom, units always
Metric (MKS), and refPosPort always off. Custom is
not cosmetic – measured in R2026a, a block left on its Earth
(WGS84) setting accepts a written F or R and discards it.
refPosPort would move the reference location and heading onto
two more input ports, taking the Simulink block from two inputs to four;
this block reads them from its configuration, so it is pinned off. The Simulink
block defines no SampleTime, so the rate stays on the ICore
side.
Notes
- Algebraic and stateless: the output depends only on this sample's inputs.
- Not linear, so the block carries no state space and model reduction correctly reports it as unmergeable.
- It is not an exact inverse of LLA To Flat Earth, and cannot be. The forward block folds its input pair before differencing, so a latitude past a pole and its reflection land on the same offsets; nothing here can recover which was meant. A position that needed no folding round-trips exactly.
- The output pair is folded, and that is not cosmetic: an offset large enough walks over a pole. Measured against R2026a at x = 8×106 and at y = −3×107, the Simulink block returns a folded pair that the raw arithmetic does not produce; both reproduce here to 4.3×10−14 degrees.
- The latitude fold is branchless: 90 − |w − 180| with w = mod(φ + 90, 360) is the whole two-arm rule, and the two arms agree at the pole where they meet.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Axes_Transformations/Flat_Earth_To_LLA |
| family | Robotics/Axes_Transformations |
| solver environment class | ICoreBlock_0_Robotics_1_Axes_Transformations_2_Flat_Earth_To_LLA |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Axes_Transformations/Flat_Earth_To_LLA/ICoreBlock_0_Robotics_1_Axes_Transformations_2_Flat_Earth_To_LLA.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Axes_Transformations/Flat_Earth_To_LLA/ICoreBlock_0_Robotics_1_Axes_Transformations_2_Flat_Earth_To_LLA.h |
| default size on canvas | 160 × 90 px |
| ports at insert | 2 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_flat |
| 2 | in | ICoreDouble | href |
| 3 | out | ICoreDouble | mu_iota |
| 4 | 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) | — |
Reference Location | [0 10] | — |
Reference Heading | 0 | — |
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/Flat Earth 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), refPosPort = off |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
CONFIG_F.c_str() | F | passes through |
CONFIG_R.c_str() | R | passes through |
CONFIG_LL0.c_str() | LL0 | passes through |
CONFIG_PSI.c_str() | psi | passes through |
Caveat (shown to the user): the offset crosses as one [3,1] in North-East-DOWN 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. 'refPosPort' is always off: switching it on moves the reference location and heading onto TWO MORE INPUT PORTS, taking the Simulink block from two inputs to four, and this block reads them from its configuration. The Simulink block has no SampleTime, so the rate stays on the ICore side. ⚠ The OUTPUT pair is folded on both sides -- an offset large enough walks over a pole -- which was measured at x = 8e6 and at y = -3e7 rather than assumed
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:
B04 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).
Flat Earth To LLA -- local flat-Earth offsets back to geodetic coordinates Rn = R / sqrt(1 - e2 * sin(lat0)^2) prime vertical radius AT THE REFERENCE Rm = Rn * (1 - e2) / (1 - e2 * sin(lat0)^2) meridional radius AT THE REFERENCE dN = cos(psi)*px - sin(psi)*py dE = sin(psi)*px + cos(psi)*py lat = dN / (Rm * pi/180) + lat0 lon = dE / (Rn * cos(lat0) * pi/180) + lon0 h = -pz - href then fold (lat, lon) back into range
The inverse of LLA To Flat Earth, and the cheaper half of the pair: only its OUTPUT needs folding, where the forward direction folds twice.
⚠ IT IS NOT AN EXACT INVERSE, AND CANNOT BE MADE ONE. The forward block folds its input pair before differencing, so a latitude past a pole and its reflection land on the same offsets; no arithmetic here can recover which of the two was meant. A position that needed no folding round-trips exactly, and that is the whole of what the pair promises.
⚠ THE FOLD ON THE WAY OUT IS NOT COSMETIC. An offset large enough walks over a pole, and MEASURED against R2026a at px = 8e6 and at py = -3e7 the Simulink block returns a folded pair -- 70.98 degrees latitude in the first case, and a longitude of -80.58 in the second, neither of which the raw arithmetic produces. Both reproduce here to 4.3e-14 degrees.
The latitude fold is written as lat = 90 - |w - 180|, w = mod(lat + 90, 360), which is the two-arm rule with no branch and no boundary case -- the sibling's banner has the reasoning in full. Only the longitude's half-turn still needs a test.
ALGEBRAIC and STATELESS.
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at Flat Earth To LLA block: ICore Blocks/Home/Flat Earth 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__Flat_Earth_To_LLA.json