FA-69886 / Map projection transforms / Open access
Spherical web mercator inverse projection: latitude radian conversion · case 01
Latitudes are returned in radians.
ROOT CAUSE
The inverse Gudermannian result is never converted from radians to degrees.
VERIFIED REPAIR
At the latitude radian conversion step restore `lat = math.degrees(2 * math.atan(math.exp(py / R)) - math.pi / 2)`, leaving the rest of the model unchanged.
Unsuccessful approach: The degree conversion is applied before subtracting pi/2, mixing units in the offset.
Case contract
Input [x, y] metres (sphere radius 6378137). Eastings outside the world extent E = 20037508.342789244 are wrapped into [-E, E) first. Return [lon, lat] in degrees rounded to 7 decimals.
Why this case matters
Converting clicked map positions or tile corners back to geographic coordinates uses this inverse; faults misplace every reverse-geocoded point.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
px, py = x
R = 6378137.0
E = 20037508.342789244
px = ((px + E) % (2 * E)) - E
lon = math.degrees(px / R)
lat = 2 * math.atan(math.exp(py / R)) - math.pi / 2
return [round(lon, 7), round(lat, 7)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression #0', [25000000.0, 1000000.0], [-135.421179, 8.9465739]), ('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #3', [0.0, 0.0], [0.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('boundary #7', [0.0, 20037508.342789244], [0.0, 85.0511288])], [('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('boundary #7', [0.0, 20037508.342789244], [0.0, 85.0511288]), ('boundary #8', [0.0, -20037508.342789244], [0.0, -85.0511288]), ('control #9', [500000.0, -500000.0], [4.4915764, -4.486983]), ('control #10', [-19000000.0, 12000000.0], [-170.679904, 72.6726763])], [('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('boundary #8', [0.0, -20037508.342789244], [0.0, -85.0511288]), ('control #9', [500000.0, -500000.0], [4.4915764, -4.486983]), ('control #10', [-19000000.0, 12000000.0], [-170.679904, 72.6726763]), ('control #11', [3000000.0, 9000000.0], [26.9494585, 62.5882773]), ('regression #12', [45000000.0, 3000000.0], [44.2418779, 26.0074202]), ('control #13', [-1234567.0, 7654321.0], [-11.0903041, 56.4787668])], [('control #3', [0.0, 0.0], [0.0, 0.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #11', [3000000.0, 9000000.0], [26.9494585, 62.5882773]), ('regression #12', [45000000.0, 3000000.0], [44.2418779, 26.0074202]), ('control #13', [-1234567.0, 7654321.0], [-11.0903041, 56.4787668]), ('regression #14', [-21000000.0, 500000.0], [171.3537903, 4.486983]), ('regression #15', [-25000000.0, -4000000.0], [135.421179, -33.7852301]), ('regression #16', [-40075016.0, 0.0], [6.2e-06, 0.0])], [('regression #0', [25000000.0, 1000000.0], [-135.421179, 8.9465739]), ('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('regression #14', [-21000000.0, 500000.0], [171.3537903, 4.486983]), ('regression #15', [-25000000.0, -4000000.0], [135.421179, -33.7852301]), ('regression #16', [-40075016.0, 0.0], [6.2e-06, 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 |
|---|---|---|---|
| regression #0 | [-135.421179, 0.1561472] | [-135.421179, 8.9465739] | Failed |
| regression #1 | [90.5054148, -0.3085553] | [90.5054148, -17.6789142] | Failed |
| boundary #2 | [-180.0, 0.0] | [-180.0, 0.0] | Passed |
| control #3 | [0.0, 0.0] | [0.0, 0.0] | Passed |
| control #4 | [9.9999999, 0.1745329] | [9.9999999, 10.0] | Failed |
| control #5 | [-73.9999998, 0.7105926] | [-73.9999998, 40.7139556] | Failed |
| control #6 | [134.7472926, -0.8264781] | [134.7472926, -47.3537047] | Failed |
| boundary #7 | [0.0, 1.4844222] | [0.0, 85.0511288] | Failed |
SHA-256 / 36db59cf449ad17b80b004580255bc96057109f42ded3120be456dd9283ba314
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
px, py = x
R = 6378137.0
E = 20037508.342789244
px = ((px + E) % (2 * E)) - E
lon = math.degrees(px / R)
lat = math.degrees(2 * math.atan(math.exp(py / R))) - math.pi / 2
return [round(lon, 7), round(lat, 7)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression #0', [25000000.0, 1000000.0], [-135.421179, 8.9465739]), ('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #3', [0.0, 0.0], [0.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('boundary #7', [0.0, 20037508.342789244], [0.0, 85.0511288])], [('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('boundary #7', [0.0, 20037508.342789244], [0.0, 85.0511288]), ('boundary #8', [0.0, -20037508.342789244], [0.0, -85.0511288]), ('control #9', [500000.0, -500000.0], [4.4915764, -4.486983]), ('control #10', [-19000000.0, 12000000.0], [-170.679904, 72.6726763])], [('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('boundary #8', [0.0, -20037508.342789244], [0.0, -85.0511288]), ('control #9', [500000.0, -500000.0], [4.4915764, -4.486983]), ('control #10', [-19000000.0, 12000000.0], [-170.679904, 72.6726763]), ('control #11', [3000000.0, 9000000.0], [26.9494585, 62.5882773]), ('regression #12', [45000000.0, 3000000.0], [44.2418779, 26.0074202]), ('control #13', [-1234567.0, 7654321.0], [-11.0903041, 56.4787668])], [('control #3', [0.0, 0.0], [0.0, 0.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #11', [3000000.0, 9000000.0], [26.9494585, 62.5882773]), ('regression #12', [45000000.0, 3000000.0], [44.2418779, 26.0074202]), ('control #13', [-1234567.0, 7654321.0], [-11.0903041, 56.4787668]), ('regression #14', [-21000000.0, 500000.0], [171.3537903, 4.486983]), ('regression #15', [-25000000.0, -4000000.0], [135.421179, -33.7852301]), ('regression #16', [-40075016.0, 0.0], [6.2e-06, 0.0])], [('regression #0', [25000000.0, 1000000.0], [-135.421179, 8.9465739]), ('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('regression #14', [-21000000.0, 500000.0], [171.3537903, 4.486983]), ('regression #15', [-25000000.0, -4000000.0], [135.421179, -33.7852301]), ('regression #16', [-40075016.0, 0.0], [6.2e-06, 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 |
|---|---|---|---|
| regression #0 | [-135.421179, 97.3757775] | [-135.421179, 8.9465739] | Failed |
| regression #1 | [90.5054148, 70.7502894] | [90.5054148, -17.6789142] | Failed |
| boundary #2 | [-180.0, 88.4292037] | [-180.0, 0.0] | Failed |
| control #3 | [0.0, 88.4292037] | [0.0, 0.0] | Failed |
| control #4 | [9.9999999, 98.4292036] | [9.9999999, 10.0] | Failed |
| control #5 | [-73.9999998, 129.1431593] | [-73.9999998, 40.7139556] | Failed |
| control #6 | [134.7472926, 41.075499] | [134.7472926, -47.3537047] | Failed |
| boundary #7 | [0.0, 173.4803325] | [0.0, 85.0511288] | Failed |
SHA-256 / 766924d6ae12b4029bcf2d531f3f8a076c2e13ff86102344aefa790e6f02e8f2
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
px, py = x
R = 6378137.0
E = 20037508.342789244
px = ((px + E) % (2 * E)) - E
lon = math.degrees(px / R)
lat = math.degrees(2 * math.atan(math.exp(py / R)) - math.pi / 2)
return [round(lon, 7), round(lat, 7)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression #0', [25000000.0, 1000000.0], [-135.421179, 8.9465739]), ('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #3', [0.0, 0.0], [0.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('boundary #7', [0.0, 20037508.342789244], [0.0, 85.0511288])], [('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('boundary #7', [0.0, 20037508.342789244], [0.0, 85.0511288]), ('boundary #8', [0.0, -20037508.342789244], [0.0, -85.0511288]), ('control #9', [500000.0, -500000.0], [4.4915764, -4.486983]), ('control #10', [-19000000.0, 12000000.0], [-170.679904, 72.6726763])], [('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('boundary #8', [0.0, -20037508.342789244], [0.0, -85.0511288]), ('control #9', [500000.0, -500000.0], [4.4915764, -4.486983]), ('control #10', [-19000000.0, 12000000.0], [-170.679904, 72.6726763]), ('control #11', [3000000.0, 9000000.0], [26.9494585, 62.5882773]), ('regression #12', [45000000.0, 3000000.0], [44.2418779, 26.0074202]), ('control #13', [-1234567.0, 7654321.0], [-11.0903041, 56.4787668])], [('control #3', [0.0, 0.0], [0.0, 0.0]), ('control #5', [-8237642.3, 4970241.3], [-73.9999998, 40.7139556]), ('control #11', [3000000.0, 9000000.0], [26.9494585, 62.5882773]), ('regression #12', [45000000.0, 3000000.0], [44.2418779, 26.0074202]), ('control #13', [-1234567.0, 7654321.0], [-11.0903041, 56.4787668]), ('regression #14', [-21000000.0, 500000.0], [171.3537903, 4.486983]), ('regression #15', [-25000000.0, -4000000.0], [135.421179, -33.7852301]), ('regression #16', [-40075016.0, 0.0], [6.2e-06, 0.0])], [('regression #0', [25000000.0, 1000000.0], [-135.421179, 8.9465739]), ('regression #1', [-30000000.0, -2000000.0], [90.5054148, -17.6789142]), ('boundary #2', [20037508.342789244, 0.0], [-180.0, 0.0]), ('control #4', [1113194.9, 1118889.97], [9.9999999, 10.0]), ('control #6', [15000000.0, -6000000.0], [134.7472926, -47.3537047]), ('regression #14', [-21000000.0, 500000.0], [171.3537903, 4.486983]), ('regression #15', [-25000000.0, -4000000.0], [135.421179, -33.7852301]), ('regression #16', [-40075016.0, 0.0], [6.2e-06, 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 |
|---|---|---|---|
| regression #0 | [-135.421179, 8.9465739] | [-135.421179, 8.9465739] | Passed |
| regression #1 | [90.5054148, -17.6789142] | [90.5054148, -17.6789142] | Passed |
| boundary #2 | [-180.0, 0.0] | [-180.0, 0.0] | Passed |
| control #3 | [0.0, 0.0] | [0.0, 0.0] | Passed |
| control #4 | [9.9999999, 10.0] | [9.9999999, 10.0] | Passed |
| control #5 | [-73.9999998, 40.7139556] | [-73.9999998, 40.7139556] | Passed |
| control #6 | [134.7472926, -47.3537047] | [134.7472926, -47.3537047] | Passed |
| boundary #7 | [0.0, 85.0511288] | [0.0, 85.0511288] | Passed |
SHA-256 / d0b617e3650aada5398d846d37a5b750d23ff047f2a5d8eb36ca8974c9021eec
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:15.572353+00:00.
Case digest / b806f7e8e26b8ce900bd91207ca911d259667503b962c835c74292d80b90d6a5