FA-70121 / Map projection transforms / Open access
Spherical Lambert conformal conic forward: cone angle · case 01
Meridians fan out as if the cone were flat.
ROOT CAUSE
The polar angle omits the cone constant, using the longitude difference directly.
VERIFIED REPAIR
At the cone angle step restore `theta = n * dl`, leaving the rest of the model unchanged.
Unsuccessful approach: n is restored but the longitude difference is no longer wrapped, breaking grids centred near 180.
Case contract
Input [lon, lat, lon0, lat0, lat1, lat2] degrees, sphere R = 6371000, all latitudes strictly inside (-90, 90) and on the cone side of the equator. t(p) = tan(pi/4 + p/2). n = ln(cos p1/cos p2)/ln(t(p2)/t(p1)), or sin(p1) for a tangent cone (lat1 == lat2). F = cos(p1)*t(p1)**n/n; rho = R*F/t(phi)**n; rho0 = R*F/t(p0)**n; theta = n*dlon with dlon wrapped to [-180, 180). x = rho*sin(theta), y = rho0 - rho*cos(theta), rounded to 2 decimals.
Why this case matters
Aeronautical charts and many national grids use conformal conics; a wrong cone constant distorts whole regions.
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, lat1, lat2 = x
R = 6371000.0
p0, p1, p2, phi = [math.radians(v) for v in (lat0, lat1, lat2, lat)]
def t(p):
return math.tan(math.pi / 4 + p / 2)
if abs(lat1 - lat2) < 1e-9:
n = math.sin(p1)
else:
n = math.log(math.cos(p1) / math.cos(p2)) / math.log(t(p2) / t(p1))
Fc = math.cos(p1) * t(p1) ** n / n
rho = R * Fc / t(phi) ** n
rho0 = R * Fc / t(p0) ** n
dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
theta = dl
return [round(rho * math.sin(theta), 2), round(rho0 - rho * math.cos(theta), 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-89.295, -2.579, -96.0, 23.0, 33.0, 45.0], [944572.37, -3177093.78]), ('control #1', [43.173, 23.809, 10.0, 52.0, 35.0, 65.0], [3477264.95, -2333351.52]), ('control #2', [133.802, -22.769, 132.0, 0.0, -18.0, -36.0], [182997.23, -2612489.01]), ('control #3', [-77.146, 4.744, -60.0, -32.0, -5.0, -42.0], [-2030234.59, 3881165.57]), ('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #1', [43.173, 23.809, 10.0, 52.0, 35.0, 65.0], [3477264.95, -2333351.52]), ('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('control #5', [57.493, -17.538, 20.0, -45.0, -45.0, -45.0], [4256679.54, 2162268.41]), ('control #6', [108.376, 58.574, 100.0, 30.0, 25.0, 47.0], [519881.13, 3212621.55]), ('control #7', [-134.048, 58.473, -96.0, 23.0, 33.0, 45.0], [-2281076.94, 4475835.48]), ('regression #27', [-175.0, -30.0, 170.0, -20.0, -15.0, -45.0], [1391072.22, -1172227.35]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #2', [133.802, -22.769, 132.0, 0.0, -18.0, -36.0], [182997.23, -2612489.01]), ('control #7', [-134.048, 58.473, -96.0, 23.0, 33.0, 45.0], [-2281076.94, 4475835.48]), ('control #8', [38.766, 26.431, 10.0, 52.0, 35.0, 65.0], [2923972.77, -2242833.53]), ('control #9', [128.089, -10.995, 132.0, 0.0, -18.0, -36.0], [-438054.92, -1302331.66]), ('control #10', [-72.231, -55.506, -60.0, -32.0, -5.0, -42.0], [-871966.44, -2710910.26]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #3', [-77.146, 4.744, -60.0, -32.0, -5.0, -42.0], [-2030234.59, 3881165.57]), ('control #10', [-72.231, -55.506, -60.0, -32.0, -5.0, -42.0], [-871966.44, -2710910.26]), ('control #11', [-30.764, 26.528, 0.0, 40.0, 40.0, 40.0], [-3080105.21, -974518.51]), ('control #12', [31.835, 4.636, 20.0, -45.0, -45.0, -45.0], [1831181.26, 6076923.26]), ('control #13', [74.565, 35.028, 100.0, 30.0, 25.0, 47.0], [-2247809.44, 846928.26]), ('regression #27', [-175.0, -30.0, 170.0, -20.0, -15.0, -45.0], [1391072.22, -1172227.35]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('control #13', [74.565, 35.028, 100.0, 30.0, 25.0, 47.0], [-2247809.44, 846928.26]), ('control #14', [-102.998, 17.408, -96.0, 23.0, 33.0, 45.0], [-788758.63, -620642.12]), ('control #15', [-3.245, 2.365, 10.0, 52.0, 35.0, 65.0], [-1927899.63, -5755732.58]), ('control #16', [150.921, -43.877, 132.0, 0.0, -18.0, -36.0], [1562347.67, -5075599.0]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])]]
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 | [1496125.07, -3124313.89] | [944572.37, -3177093.78] | Failed |
| control #1 | [4384467.82, -1821212.46] | [3477264.95, -2333351.52] | Failed |
| control #2 | [-401364.18, -2617489.24] | [182997.23, -2612489.01] | Failed |
| control #3 | [4938330.16, 3260181.54] | [-2030234.59, 3881165.57] | Failed |
| control #4 | [-549170.21, 2561184.8] | [-353408.33, 2543656.82] | Failed |
| regression #26 | [-1434510.13, 1264036.62] | [-1032245.58, 1172151.39] | Failed |
| boundary #28 | [0.0, 0.0] | [0.0, 0.0] | Passed |
| boundary #29 | [0.0, 0.0] | [0.0, 0.0] | Passed |
SHA-256 / b88aebb7d33b1fc010d49573f891f14f55872a607cc0041700446a0e9ae8e8ec
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, lat1, lat2 = x
R = 6371000.0
p0, p1, p2, phi = [math.radians(v) for v in (lat0, lat1, lat2, lat)]
def t(p):
return math.tan(math.pi / 4 + p / 2)
if abs(lat1 - lat2) < 1e-9:
n = math.sin(p1)
else:
n = math.log(math.cos(p1) / math.cos(p2)) / math.log(t(p2) / t(p1))
Fc = math.cos(p1) * t(p1) ** n / n
rho = R * Fc / t(phi) ** n
rho0 = R * Fc / t(p0) ** n
dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
theta = n * math.radians(lon - lon0)
return [round(rho * math.sin(theta), 2), round(rho0 - rho * math.cos(theta), 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-89.295, -2.579, -96.0, 23.0, 33.0, 45.0], [944572.37, -3177093.78]), ('control #1', [43.173, 23.809, 10.0, 52.0, 35.0, 65.0], [3477264.95, -2333351.52]), ('control #2', [133.802, -22.769, 132.0, 0.0, -18.0, -36.0], [182997.23, -2612489.01]), ('control #3', [-77.146, 4.744, -60.0, -32.0, -5.0, -42.0], [-2030234.59, 3881165.57]), ('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #1', [43.173, 23.809, 10.0, 52.0, 35.0, 65.0], [3477264.95, -2333351.52]), ('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('control #5', [57.493, -17.538, 20.0, -45.0, -45.0, -45.0], [4256679.54, 2162268.41]), ('control #6', [108.376, 58.574, 100.0, 30.0, 25.0, 47.0], [519881.13, 3212621.55]), ('control #7', [-134.048, 58.473, -96.0, 23.0, 33.0, 45.0], [-2281076.94, 4475835.48]), ('regression #27', [-175.0, -30.0, 170.0, -20.0, -15.0, -45.0], [1391072.22, -1172227.35]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #2', [133.802, -22.769, 132.0, 0.0, -18.0, -36.0], [182997.23, -2612489.01]), ('control #7', [-134.048, 58.473, -96.0, 23.0, 33.0, 45.0], [-2281076.94, 4475835.48]), ('control #8', [38.766, 26.431, 10.0, 52.0, 35.0, 65.0], [2923972.77, -2242833.53]), ('control #9', [128.089, -10.995, 132.0, 0.0, -18.0, -36.0], [-438054.92, -1302331.66]), ('control #10', [-72.231, -55.506, -60.0, -32.0, -5.0, -42.0], [-871966.44, -2710910.26]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #3', [-77.146, 4.744, -60.0, -32.0, -5.0, -42.0], [-2030234.59, 3881165.57]), ('control #10', [-72.231, -55.506, -60.0, -32.0, -5.0, -42.0], [-871966.44, -2710910.26]), ('control #11', [-30.764, 26.528, 0.0, 40.0, 40.0, 40.0], [-3080105.21, -974518.51]), ('control #12', [31.835, 4.636, 20.0, -45.0, -45.0, -45.0], [1831181.26, 6076923.26]), ('control #13', [74.565, 35.028, 100.0, 30.0, 25.0, 47.0], [-2247809.44, 846928.26]), ('regression #27', [-175.0, -30.0, 170.0, -20.0, -15.0, -45.0], [1391072.22, -1172227.35]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('control #13', [74.565, 35.028, 100.0, 30.0, 25.0, 47.0], [-2247809.44, 846928.26]), ('control #14', [-102.998, 17.408, -96.0, 23.0, 33.0, 45.0], [-788758.63, -620642.12]), ('control #15', [-3.245, 2.365, 10.0, 52.0, 35.0, 65.0], [-1927899.63, -5755732.58]), ('control #16', [150.921, -43.877, 132.0, 0.0, -18.0, -36.0], [1562347.67, -5075599.0]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])]]
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 | [944572.37, -3177093.78] | [944572.37, -3177093.78] | Passed |
| control #1 | [3477264.95, -2333351.52] | [3477264.95, -2333351.52] | Passed |
| control #2 | [182997.23, -2612489.01] | [182997.23, -2612489.01] | Passed |
| control #3 | [-2030234.59, 3881165.57] | [-2030234.59, 3881165.57] | Passed |
| control #4 | [-353408.33, 2543656.82] | [-353408.33, 2543656.82] | Passed |
| regression #26 | [-5097012.54, 8794857.28] | [-1032245.58, 1172151.39] | Failed |
| boundary #28 | [0.0, 0.0] | [0.0, 0.0] | Passed |
| boundary #29 | [0.0, 0.0] | [0.0, 0.0] | Passed |
SHA-256 / cc06a9751ba92ff28e3065e613ff5d0fe5a94d972336616b1721d329d10d6086
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, lat1, lat2 = x
R = 6371000.0
p0, p1, p2, phi = [math.radians(v) for v in (lat0, lat1, lat2, lat)]
def t(p):
return math.tan(math.pi / 4 + p / 2)
if abs(lat1 - lat2) < 1e-9:
n = math.sin(p1)
else:
n = math.log(math.cos(p1) / math.cos(p2)) / math.log(t(p2) / t(p1))
Fc = math.cos(p1) * t(p1) ** n / n
rho = R * Fc / t(phi) ** n
rho0 = R * Fc / t(p0) ** n
dl = math.radians(((lon - lon0 + 180.0) % 360.0) - 180.0)
theta = n * dl
return [round(rho * math.sin(theta), 2), round(rho0 - rho * math.cos(theta), 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-89.295, -2.579, -96.0, 23.0, 33.0, 45.0], [944572.37, -3177093.78]), ('control #1', [43.173, 23.809, 10.0, 52.0, 35.0, 65.0], [3477264.95, -2333351.52]), ('control #2', [133.802, -22.769, 132.0, 0.0, -18.0, -36.0], [182997.23, -2612489.01]), ('control #3', [-77.146, 4.744, -60.0, -32.0, -5.0, -42.0], [-2030234.59, 3881165.57]), ('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #1', [43.173, 23.809, 10.0, 52.0, 35.0, 65.0], [3477264.95, -2333351.52]), ('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('control #5', [57.493, -17.538, 20.0, -45.0, -45.0, -45.0], [4256679.54, 2162268.41]), ('control #6', [108.376, 58.574, 100.0, 30.0, 25.0, 47.0], [519881.13, 3212621.55]), ('control #7', [-134.048, 58.473, -96.0, 23.0, 33.0, 45.0], [-2281076.94, 4475835.48]), ('regression #27', [-175.0, -30.0, 170.0, -20.0, -15.0, -45.0], [1391072.22, -1172227.35]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #2', [133.802, -22.769, 132.0, 0.0, -18.0, -36.0], [182997.23, -2612489.01]), ('control #7', [-134.048, 58.473, -96.0, 23.0, 33.0, 45.0], [-2281076.94, 4475835.48]), ('control #8', [38.766, 26.431, 10.0, 52.0, 35.0, 65.0], [2923972.77, -2242833.53]), ('control #9', [128.089, -10.995, 132.0, 0.0, -18.0, -36.0], [-438054.92, -1302331.66]), ('control #10', [-72.231, -55.506, -60.0, -32.0, -5.0, -42.0], [-871966.44, -2710910.26]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #3', [-77.146, 4.744, -60.0, -32.0, -5.0, -42.0], [-2030234.59, 3881165.57]), ('control #10', [-72.231, -55.506, -60.0, -32.0, -5.0, -42.0], [-871966.44, -2710910.26]), ('control #11', [-30.764, 26.528, 0.0, 40.0, 40.0, 40.0], [-3080105.21, -974518.51]), ('control #12', [31.835, 4.636, 20.0, -45.0, -45.0, -45.0], [1831181.26, 6076923.26]), ('control #13', [74.565, 35.028, 100.0, 30.0, 25.0, 47.0], [-2247809.44, 846928.26]), ('regression #27', [-175.0, -30.0, 170.0, -20.0, -15.0, -45.0], [1391072.22, -1172227.35]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])], [('control #4', [-6.229, 62.13, 0.0, 40.0, 40.0, 40.0], [-353408.33, 2543656.82]), ('control #13', [74.565, 35.028, 100.0, 30.0, 25.0, 47.0], [-2247809.44, 846928.26]), ('control #14', [-102.998, 17.408, -96.0, 23.0, 33.0, 45.0], [-788758.63, -620642.12]), ('control #15', [-3.245, 2.365, 10.0, 52.0, 35.0, 65.0], [-1927899.63, -5755732.58]), ('control #16', [150.921, -43.877, 132.0, 0.0, -18.0, -36.0], [1562347.67, -5075599.0]), ('regression #26', [175.0, 50.0, -170.0, 40.0, 30.0, 60.0], [-1032245.58, 1172151.39]), ('boundary #28', [-96.0, 23.0, -96.0, 23.0, 33.0, 45.0], [0.0, 0.0]), ('boundary #29', [5.0, 40.0, 5.0, 40.0, 40.0, 40.0], [0.0, 0.0])]]
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 | [944572.37, -3177093.78] | [944572.37, -3177093.78] | Passed |
| control #1 | [3477264.95, -2333351.52] | [3477264.95, -2333351.52] | Passed |
| control #2 | [182997.23, -2612489.01] | [182997.23, -2612489.01] | Passed |
| control #3 | [-2030234.59, 3881165.57] | [-2030234.59, 3881165.57] | Passed |
| control #4 | [-353408.33, 2543656.82] | [-353408.33, 2543656.82] | Passed |
| regression #26 | [-1032245.58, 1172151.39] | [-1032245.58, 1172151.39] | Passed |
| boundary #28 | [0.0, 0.0] | [0.0, 0.0] | Passed |
| boundary #29 | [0.0, 0.0] | [0.0, 0.0] | Passed |
SHA-256 / 3bf183b5f3d5ed94f774fed1ce6ec9cae2622236161c0964ace0bc11de0d98e8
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:17.704867+00:00.
Case digest / a70899524becd3cfcc3d150316964e8872bcceeaf22004570bee86df78a3d011