FA-70141 / Map projection transforms / Open access
Spherical Albers equal-area conic forward: origin radius latitude · case 01
False northings are measured from a standard parallel instead of the grid origin.
ROOT CAUSE
The origin radius rho0 is evaluated at lat1 instead of lat0.
VERIFIED REPAIR
At the origin radius latitude step restore `math.sqrt(C - 2 * n * math.sin(p0))`, leaving the rest of the model unchanged.
Unsuccessful approach: Switching to lat2 is just as wrong unless the origin lies on that parallel.
Case contract
Input [lon, lat, lon0, lat0, lat1, lat2] degrees, sphere R = 6371000, lat1 != -lat2. n = (sin p1 + sin p2)/2; C = cos(p1)**2 + 2*n*sin(p1); rho = R*sqrt(C - 2*n*sin(phi))/n; rho0 likewise at lat0; 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
Continental statistics maps use Albers because it preserves area; a wrong constant silently biases area totals.
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)]
n = (math.sin(p1) + math.sin(p2)) / 2
C = math.cos(p1) ** 2 + 2 * n * math.sin(p1)
rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n
rho0 = R * math.sqrt(C - 2 * n * math.sin(p1)) / 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', [-74.411, 16.812, -96.0, 23.0, 33.0, 45.0], [2405679.11, -375515.8]), ('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16])], [('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #9', [128.053, -60.735, 132.0, 0.0, -18.0, -36.0], [-277286.95, -6470938.42]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91]), ('control #19', [-7.283, -57.541, 20.0, -45.0, -45.0, -45.0], [-1648585.12, -1661270.39]), ('control #25', [5.399, 28.616, 0.0, 40.0, 40.0, 40.0], [535782.96, -1242187.2])]]
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 | [2405679.11, -1474011.34] | [2405679.11, -375515.8] | Failed |
| control #1 | [-2368752.85, -3527724.08] | [-2368752.85, -5460256.95] | Failed |
| control #2 | [2661851.12, -2988451.25] | [2661851.12, -4920407.16] | Failed |
| control #3 | [601606.86, -4652930.26] | [601606.86, -1530950.07] | Failed |
| control #4 | [-3459655.33, -3813601.11] | [-3459655.33, -3813601.11] | Passed |
| control #5 | [499045.1, 3152788.69] | [499045.1, 3152788.69] | Passed |
| control #6 | [-331403.06, 2950506.66] | [-331403.06, 2390895.63] | Failed |
| control #11 | [1977746.83, 796959.72] | [1977746.83, 796959.72] | Passed |
SHA-256 / 62e3635e311a2c53e60e7889256773a63fda9ba6a57dbb282246403cd0fe51a9
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)]
n = (math.sin(p1) + math.sin(p2)) / 2
C = math.cos(p1) ** 2 + 2 * n * math.sin(p1)
rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n
rho0 = R * math.sqrt(C - 2 * n * math.sin(p2)) / 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', [-74.411, 16.812, -96.0, 23.0, 33.0, 45.0], [2405679.11, -375515.8]), ('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16])], [('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #9', [128.053, -60.735, 132.0, 0.0, -18.0, -36.0], [-277286.95, -6470938.42]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91]), ('control #19', [-7.283, -57.541, 20.0, -45.0, -45.0, -45.0], [-1648585.12, -1661270.39]), ('control #25', [5.399, 28.616, 0.0, 40.0, 40.0, 40.0], [535782.96, -1242187.2])]]
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 | [2405679.11, -2813249.5] | [2405679.11, -375515.8] | Failed |
| control #1 | [-2368752.85, -6941932.69] | [-2368752.85, -5460256.95] | Failed |
| control #2 | [2661851.12, -970316.72] | [2661851.12, -4920407.16] | Failed |
| control #3 | [601606.86, -389516.7] | [601606.86, -1530950.07] | Failed |
| control #4 | [-3459655.33, -3813601.11] | [-3459655.33, -3813601.11] | Passed |
| control #5 | [499045.1, 3152788.69] | [499045.1, 3152788.69] | Passed |
| control #6 | [-331403.06, 473712.76] | [-331403.06, 2390895.63] | Failed |
| control #11 | [1977746.83, 796959.72] | [1977746.83, 796959.72] | Passed |
SHA-256 / 343e5da0141de70f361ca311bc1eefbd55369afaf606dfd3ec15aebf798cb425
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)]
n = (math.sin(p1) + math.sin(p2)) / 2
C = math.cos(p1) ** 2 + 2 * n * math.sin(p1)
rho = R * math.sqrt(C - 2 * n * math.sin(phi)) / n
rho0 = R * math.sqrt(C - 2 * n * math.sin(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', [-74.411, 16.812, -96.0, 23.0, 33.0, 45.0], [2405679.11, -375515.8]), ('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #4', [-26.25, -1.472, 0.0, 40.0, 40.0, 40.0], [-3459655.33, -3813601.11]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72])], [('control #1', [-7.054, -2.555, 10.0, 52.0, 35.0, 65.0], [-2368752.85, -5460256.95]), ('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #5', [24.242, -15.571, 20.0, -45.0, -45.0, -45.0], [499045.1, 3152788.69]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16])], [('control #2', [163.654, -41.81, 132.0, 0.0, -18.0, -36.0], [2661851.12, -4920407.16]), ('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #8', [-25.931, 8.989, 10.0, 52.0, 35.0, 65.0], [-4374379.83, -3619965.56]), ('control #9', [128.053, -60.735, 132.0, 0.0, -18.0, -36.0], [-277286.95, -6470938.42]), ('control #10', [-40.992, -24.699, -60.0, -32.0, -5.0, -42.0], [1816236.48, 739740.04]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #3', [-52.492, -45.412, -60.0, -32.0, -5.0, -42.0], [601606.86, -1530950.07]), ('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #11', [25.244, 44.637, 0.0, 40.0, 40.0, 40.0], [1977746.83, 796959.72]), ('control #12', [-19.298, -12.334, 20.0, -45.0, -45.0, -45.0], [-4597338.48, 2353100.16]), ('control #13', [99.747, 65.471, 100.0, 30.0, 25.0, 47.0], [-14324.17, 3825527.6]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91])], [('control #6', [95.34, 51.232, 100.0, 30.0, 25.0, 47.0], [-331403.06, 2390895.63]), ('control #7', [-119.961, 31.565, -96.0, 23.0, 33.0, 45.0], [-2250314.51, 1235327.67]), ('control #14', [-128.991, 27.029, -96.0, 23.0, 33.0, 45.0], [-3243071.92, 1029568.66]), ('control #15', [-24.721, 3.031, 10.0, 52.0, 35.0, 65.0], [-4481724.64, -4195384.79]), ('control #16', [154.974, -67.172, 132.0, 0.0, -18.0, -36.0], [1514049.99, -7114831.09]), ('control #18', [9.83, 73.683, 0.0, 40.0, 40.0, 40.0], [462004.5, 3420297.91]), ('control #19', [-7.283, -57.541, 20.0, -45.0, -45.0, -45.0], [-1648585.12, -1661270.39]), ('control #25', [5.399, 28.616, 0.0, 40.0, 40.0, 40.0], [535782.96, -1242187.2])]]
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 | [2405679.11, -375515.8] | [2405679.11, -375515.8] | Passed |
| control #1 | [-2368752.85, -5460256.95] | [-2368752.85, -5460256.95] | Passed |
| control #2 | [2661851.12, -4920407.16] | [2661851.12, -4920407.16] | Passed |
| control #3 | [601606.86, -1530950.07] | [601606.86, -1530950.07] | Passed |
| control #4 | [-3459655.33, -3813601.11] | [-3459655.33, -3813601.11] | Passed |
| control #5 | [499045.1, 3152788.69] | [499045.1, 3152788.69] | Passed |
| control #6 | [-331403.06, 2390895.63] | [-331403.06, 2390895.63] | Passed |
| control #11 | [1977746.83, 796959.72] | [1977746.83, 796959.72] | Passed |
SHA-256 / e1263e8da38727005b86e92efdde16005a76ca52247c1fee53fda4766df1cfaf
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.828580+00:00.
Case digest / 25217cb081cd2d91e261c55bf51bbd41aa80760d08a74277b088f6268391fd0d