FA-70201 / Map projection transforms / Open access
Orthographic globe view with back-side culling: view latitude unit · case 01
The globe tilt is wrong for any view not centred on the equator.
ROOT CAUSE
The view-centre latitude is used in degrees.
VERIFIED REPAIR
At the view latitude unit step restore `phi, phi0 = math.radians(lat), math.radians(lat0)`, leaving the rest of the model unchanged.
Unsuccessful approach: Negating the centre latitude tilts the globe toward the opposite pole.
Case contract
Input [lon, lat, lon0, lat0] degrees; sphere R = 6371000 viewed from infinity above (lon0, lat0). cos c = sin(p0)sin(p) + cos(p0)cos(p)cos(dl); points with cos c < 0 are on the far side and return None. Otherwise x = R cos(p) sin(dl), y = R (cos(p0) sin(p) - sin(p0) cos(p) cos(dl)), rounded to 2 decimals.
Why this case matters
Globe widgets and inset locator maps draw only the visible hemisphere; wrong culling shows features through the planet.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
lon, lat, lon0, lat0 = x
R = 6371000.0
phi, phi0 = math.radians(lat), lat0
dl = math.radians(lon - lon0)
cosc = math.sin(phi0) * math.sin(phi) + math.cos(phi0) * math.cos(phi) * math.cos(dl)
if cosc < 0:
return None
px = R * math.cos(phi) * math.sin(dl)
py = R * (math.cos(phi0) * math.sin(phi) - math.sin(phi0) * math.cos(phi) * math.cos(dl))
return [round(px, 2), round(py, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [104.501, -37.02, 140.0, 40.0], [-2953829.02, -5600456.08]), ('control #1', [-47.135, -1.795, 45.0, -60.0], [-6363453.3, -305228.13]), ('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #4', [-146.902, -55.084, -100.0, 40.0], None), ('control #6', [88.968, 40.467, 140.0, 0.0], [-3768474.08, 4134842.57]), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07])], [('control #1', [-47.135, -1.795, 45.0, -60.0], [-6363453.3, -305228.13]), ('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #5', [-138.834, -74.487, -100.0, 40.0], None), ('control #6', [88.968, 40.467, 140.0, 0.0], [-3768474.08, 4134842.57]), ('control #7', [-48.181, 83.804, -100.0, -60.0], None), ('control #8', [53.54, -27.151, 140.0, 40.0], None), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07])], [('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #8', [53.54, -27.151, 140.0, 40.0], None), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #10', [-38.255, 79.909, -100.0, 70.0], [983271.21, 1648730.24]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07]), ('control #12', [31.276, 31.413, 140.0, -60.0], None), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71])], [('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #7', [-48.181, 83.804, -100.0, -60.0], None), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07]), ('control #12', [31.276, 31.413, 140.0, -60.0], None), ('control #13', [189.902, -48.732, 140.0, 40.0], None), ('control #14', [-112.053, 44.567, 0.0, 70.0], [-4206814.33, 3130521.2]), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71]), ('control #18', [-91.691, 21.145, 0.0, 0.0], None)], [('control #4', [-146.902, -55.084, -100.0, 40.0], None), ('control #10', [-38.255, 79.909, -100.0, 70.0], [983271.21, 1648730.24]), ('control #14', [-112.053, 44.567, 0.0, 70.0], [-4206814.33, 3130521.2]), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71]), ('control #16', [-43.54, 73.198, 45.0, -35.0], None), ('control #17', [86.105, 83.034, 140.0, -60.0], None), ('control #18', [-91.691, 21.145, 0.0, 0.0], None), ('control #20', [94.122, 10.78, 45.0, 0.0], [4732133.72, 1191621.78])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| control #0 | None | [-2953829.02, -5600456.08] | Failed |
| control #1 | [-6363453.3, 262375.83] | [-6363453.3, -305228.13] | Failed |
| control #2 | None | None | Passed |
| control #3 | None | [2060140.33, -2966163.09] | Failed |
| control #4 | None | None | Passed |
| control #6 | [-3768474.08, 4134842.57] | [-3768474.08, 4134842.57] | Passed |
| control #9 | [5589676.0, -139649.64] | [5589676.0, -139649.64] | Passed |
| control #11 | [2394735.5, 5306295.07] | [2394735.5, 5306295.07] | Passed |
SHA-256 / a6addedec10b47b35bd669cfdb3face1bb825c8c518ad9c8720bfa96924cc9c4
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
lon, lat, lon0, lat0 = x
R = 6371000.0
phi, phi0 = math.radians(lat), math.radians(-lat0)
dl = math.radians(lon - lon0)
cosc = math.sin(phi0) * math.sin(phi) + math.cos(phi0) * math.cos(phi) * math.cos(dl)
if cosc < 0:
return None
px = R * math.cos(phi) * math.sin(dl)
py = R * (math.cos(phi0) * math.sin(phi) - math.sin(phi0) * math.cos(phi) * math.cos(dl))
return [round(px, 2), round(py, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [104.501, -37.02, 140.0, 40.0], [-2953829.02, -5600456.08]), ('control #1', [-47.135, -1.795, 45.0, -60.0], [-6363453.3, -305228.13]), ('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #4', [-146.902, -55.084, -100.0, 40.0], None), ('control #6', [88.968, 40.467, 140.0, 0.0], [-3768474.08, 4134842.57]), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07])], [('control #1', [-47.135, -1.795, 45.0, -60.0], [-6363453.3, -305228.13]), ('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #5', [-138.834, -74.487, -100.0, 40.0], None), ('control #6', [88.968, 40.467, 140.0, 0.0], [-3768474.08, 4134842.57]), ('control #7', [-48.181, 83.804, -100.0, -60.0], None), ('control #8', [53.54, -27.151, 140.0, 40.0], None), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07])], [('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #8', [53.54, -27.151, 140.0, 40.0], None), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #10', [-38.255, 79.909, -100.0, 70.0], [983271.21, 1648730.24]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07]), ('control #12', [31.276, 31.413, 140.0, -60.0], None), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71])], [('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #7', [-48.181, 83.804, -100.0, -60.0], None), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07]), ('control #12', [31.276, 31.413, 140.0, -60.0], None), ('control #13', [189.902, -48.732, 140.0, 40.0], None), ('control #14', [-112.053, 44.567, 0.0, 70.0], [-4206814.33, 3130521.2]), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71]), ('control #18', [-91.691, 21.145, 0.0, 0.0], None)], [('control #4', [-146.902, -55.084, -100.0, 40.0], None), ('control #10', [-38.255, 79.909, -100.0, 70.0], [983271.21, 1648730.24]), ('control #14', [-112.053, 44.567, 0.0, 70.0], [-4206814.33, 3130521.2]), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71]), ('control #16', [-43.54, 73.198, 45.0, -35.0], None), ('control #17', [86.105, 83.034, 140.0, -60.0], None), ('control #18', [-91.691, 21.145, 0.0, 0.0], None), ('control #20', [94.122, 10.78, 45.0, 0.0], [4732133.72, 1191621.78])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| control #0 | [-2953829.02, -276543.99] | [-2953829.02, -5600456.08] | Failed |
| control #1 | None | [-6363453.3, -305228.13] | Failed |
| control #2 | [-6088619.05, -1052879.59] | None | Failed |
| control #3 | [2060140.33, 4653749.76] | [2060140.33, -2966163.09] | Failed |
| control #4 | [-2662697.0, -2400419.17] | None | Failed |
| control #6 | [-3768474.08, 4134842.57] | [-3768474.08, 4134842.57] | Passed |
| control #9 | [5589676.0, -139649.64] | [5589676.0, -139649.64] | Passed |
| control #11 | [2394735.5, 5306295.07] | [2394735.5, 5306295.07] | Passed |
SHA-256 / 99631d4883d08a0ceda678ef7e059da7035c9eb1bdad8fd28c5ed92dcd6d3570
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
lon, lat, lon0, lat0 = x
R = 6371000.0
phi, phi0 = math.radians(lat), math.radians(lat0)
dl = math.radians(lon - lon0)
cosc = math.sin(phi0) * math.sin(phi) + math.cos(phi0) * math.cos(phi) * math.cos(dl)
if cosc < 0:
return None
px = R * math.cos(phi) * math.sin(dl)
py = R * (math.cos(phi0) * math.sin(phi) - math.sin(phi0) * math.cos(phi) * math.cos(dl))
return [round(px, 2), round(py, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [104.501, -37.02, 140.0, 40.0], [-2953829.02, -5600456.08]), ('control #1', [-47.135, -1.795, 45.0, -60.0], [-6363453.3, -305228.13]), ('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #4', [-146.902, -55.084, -100.0, 40.0], None), ('control #6', [88.968, 40.467, 140.0, 0.0], [-3768474.08, 4134842.57]), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07])], [('control #1', [-47.135, -1.795, 45.0, -60.0], [-6363453.3, -305228.13]), ('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #5', [-138.834, -74.487, -100.0, 40.0], None), ('control #6', [88.968, 40.467, 140.0, 0.0], [-3768474.08, 4134842.57]), ('control #7', [-48.181, 83.804, -100.0, -60.0], None), ('control #8', [53.54, -27.151, 140.0, 40.0], None), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07])], [('control #2', [-94.306, -16.588, 0.0, 70.0], None), ('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #8', [53.54, -27.151, 140.0, 40.0], None), ('control #9', [201.351, -1.256, 140.0, 0.0], [5589676.0, -139649.64]), ('control #10', [-38.255, 79.909, -100.0, 70.0], [983271.21, 1648730.24]), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07]), ('control #12', [31.276, 31.413, 140.0, -60.0], None), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71])], [('control #3', [64.166, 9.956, 45.0, 40.0], [2060140.33, -2966163.09]), ('control #7', [-48.181, 83.804, -100.0, -60.0], None), ('control #11', [-57.222, 56.396, -100.0, 0.0], [2394735.5, 5306295.07]), ('control #12', [31.276, 31.413, 140.0, -60.0], None), ('control #13', [189.902, -48.732, 140.0, 40.0], None), ('control #14', [-112.053, 44.567, 0.0, 70.0], [-4206814.33, 3130521.2]), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71]), ('control #18', [-91.691, 21.145, 0.0, 0.0], None)], [('control #4', [-146.902, -55.084, -100.0, 40.0], None), ('control #10', [-38.255, 79.909, -100.0, 70.0], [983271.21, 1648730.24]), ('control #14', [-112.053, 44.567, 0.0, 70.0], [-4206814.33, 3130521.2]), ('control #15', [191.466, 36.244, 140.0, 0.0], [4019339.93, 3766695.71]), ('control #16', [-43.54, 73.198, 45.0, -35.0], None), ('control #17', [86.105, 83.034, 140.0, -60.0], None), ('control #18', [-91.691, 21.145, 0.0, 0.0], None), ('control #20', [94.122, 10.78, 45.0, 0.0], [4732133.72, 1191621.78])]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| control #0 | [-2953829.02, -5600456.08] | [-2953829.02, -5600456.08] | Passed |
| control #1 | [-6363453.3, -305228.13] | [-6363453.3, -305228.13] | Passed |
| control #2 | None | None | Passed |
| control #3 | [2060140.33, -2966163.09] | [2060140.33, -2966163.09] | Passed |
| control #4 | None | None | Passed |
| control #6 | [-3768474.08, 4134842.57] | [-3768474.08, 4134842.57] | Passed |
| control #9 | [5589676.0, -139649.64] | [5589676.0, -139649.64] | Passed |
| control #11 | [2394735.5, 5306295.07] | [2394735.5, 5306295.07] | Passed |
SHA-256 / 621704d697dc13a8d1faecfd7fdcb8aa76cff5d15ad0236f3167e9eb84be5ff7
Verification & scope
Stipulated deterministic toy contract on a bounded input domain; results are rounded as stated and no conformance with any published standard or library is claimed. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:48:18.339522+00:00.
Case digest / 7c5de91773f36ba20d059656089f98d340bf06a7c5cda248b92100ee89b819f4