FA-69996 / Map projection transforms / Open access
UTM zone and latitude band lookup: norway exception west edge · case 01
Points on the 3 degree meridian in southern Norway stay in zone 31.
ROOT CAUSE
The Norway widening of zone 32 excludes its western edge lon=3.
VERIFIED REPAIR
At the norway exception west edge step restore `3.0 <= lon < 12.0`, leaving the rest of the model unchanged.
Unsuccessful approach: Starting the exception at 2 degrees pulls genuine zone 31 points into zone 32.
Case contract
Input [lon, lat]. zone = floor((lon+180)/6)+1, with lon=180 mapped to zone 60. Exceptions: lat in [56, 64) and lon in [3, 12) is zone 32; lat in [72, 84] uses zone 31 for lon in [0, 9), 33 for [9, 21), 35 for [21, 33) and 37 for [33, 42). Band letters "CDEFGHJKLMNPQRSTUVWX" (no I or O) cover 8 degrees each from -80; band X also covers up to 84 inclusive. Outside [-80, 84] the band is None. Return [zone, band].
Why this case matters
Grid references, MGRS labels and CRS selection rely on the zone and band, including the Norway and Svalbard exceptions.
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 = x
zone = int(math.floor((lon + 180.0) / 6.0)) + 1
if zone > 60:
zone = 60
if 56.0 <= lat < 64.0 and 3.0 < lon < 12.0:
zone = 32
if 72.0 <= lat <= 84.0 and 0.0 <= lon < 42.0:
if lon < 9.0:
zone = 31
elif lon < 21.0:
zone = 33
elif lon < 33.0:
zone = 35
else:
zone = 37
letters = 'CDEFGHJKLMNPQRSTUVWX'
if lat < -80.0 or lat > 84.0:
band = None
else:
band = letters[min(int((lat + 80.0) // 8), 19)]
return [zone, band]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-63.1, 21.44], [20, 'Q']), ('control #1', [-175.96, 80.22], [1, 'X']), ('control #2', [115.33, 82.84], [50, 'X']), ('control #3', [-84.72, -46.68], [16, 'G']), ('control #4', [-159.4, -47.6], [4, 'G']), ('control #5', [-170.34, 33.06], [2, 'S']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V'])], [('control #3', [-84.72, -46.68], [16, 'G']), ('control #4', [-159.4, -47.6], [4, 'G']), ('control #5', [-170.34, 33.06], [2, 'S']), ('control #6', [-48.09, 53.12], [22, 'U']), ('control #7', [93.32, -22.41], [46, 'K']), ('control #8', [-119.29, -22.68], [11, 'K']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #39', [3.0, 60.5], [32, 'V'])], [('control #6', [-48.09, 53.12], [22, 'U']), ('control #7', [93.32, -22.41], [46, 'K']), ('control #8', [-119.29, -22.68], [11, 'K']), ('control #9', [-168.15, 26.03], [2, 'R']), ('control #10', [-111.05, -54.91], [12, 'F']), ('control #11', [139.05, -8.2], [54, 'L']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V'])], [('control #9', [-168.15, 26.03], [2, 'R']), ('control #10', [-111.05, -54.91], [12, 'F']), ('control #11', [139.05, -8.2], [54, 'L']), ('boundary #12', [180.0, 10.0], [60, 'P']), ('boundary #13', [-180.0, -10.0], [1, 'L']), ('regression #14', [5.0, 60.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #39', [3.0, 60.5], [32, 'V'])], [('boundary #12', [180.0, 10.0], [60, 'P']), ('boundary #13', [-180.0, -10.0], [1, 'L']), ('regression #14', [5.0, 60.0], [32, 'V']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #17', [11.99, 63.99], [32, 'V']), ('boundary #18', [8.0, 64.0], [32, 'W']), ('regression #19', [8.0, 78.0], [31, 'X'])]]
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 | [20, 'Q'] | [20, 'Q'] | Passed |
| control #1 | [1, 'X'] | [1, 'X'] | Passed |
| control #2 | [50, 'X'] | [50, 'X'] | Passed |
| control #3 | [16, 'G'] | [16, 'G'] | Passed |
| control #4 | [4, 'G'] | [4, 'G'] | Passed |
| control #5 | [2, 'S'] | [2, 'S'] | Passed |
| boundary #15 | [31, 'V'] | [32, 'V'] | Failed |
| boundary #16 | [31, 'V'] | [31, 'V'] | Passed |
SHA-256 / e5e58a6d3ed8d2fe000bbae7143b22f0185e9b04caa306bdd416c31abe18cb3d
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 = x
zone = int(math.floor((lon + 180.0) / 6.0)) + 1
if zone > 60:
zone = 60
if 56.0 <= lat < 64.0 and 2.0 <= lon < 12.0:
zone = 32
if 72.0 <= lat <= 84.0 and 0.0 <= lon < 42.0:
if lon < 9.0:
zone = 31
elif lon < 21.0:
zone = 33
elif lon < 33.0:
zone = 35
else:
zone = 37
letters = 'CDEFGHJKLMNPQRSTUVWX'
if lat < -80.0 or lat > 84.0:
band = None
else:
band = letters[min(int((lat + 80.0) // 8), 19)]
return [zone, band]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-63.1, 21.44], [20, 'Q']), ('control #1', [-175.96, 80.22], [1, 'X']), ('control #2', [115.33, 82.84], [50, 'X']), ('control #3', [-84.72, -46.68], [16, 'G']), ('control #4', [-159.4, -47.6], [4, 'G']), ('control #5', [-170.34, 33.06], [2, 'S']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V'])], [('control #3', [-84.72, -46.68], [16, 'G']), ('control #4', [-159.4, -47.6], [4, 'G']), ('control #5', [-170.34, 33.06], [2, 'S']), ('control #6', [-48.09, 53.12], [22, 'U']), ('control #7', [93.32, -22.41], [46, 'K']), ('control #8', [-119.29, -22.68], [11, 'K']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #39', [3.0, 60.5], [32, 'V'])], [('control #6', [-48.09, 53.12], [22, 'U']), ('control #7', [93.32, -22.41], [46, 'K']), ('control #8', [-119.29, -22.68], [11, 'K']), ('control #9', [-168.15, 26.03], [2, 'R']), ('control #10', [-111.05, -54.91], [12, 'F']), ('control #11', [139.05, -8.2], [54, 'L']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V'])], [('control #9', [-168.15, 26.03], [2, 'R']), ('control #10', [-111.05, -54.91], [12, 'F']), ('control #11', [139.05, -8.2], [54, 'L']), ('boundary #12', [180.0, 10.0], [60, 'P']), ('boundary #13', [-180.0, -10.0], [1, 'L']), ('regression #14', [5.0, 60.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #39', [3.0, 60.5], [32, 'V'])], [('boundary #12', [180.0, 10.0], [60, 'P']), ('boundary #13', [-180.0, -10.0], [1, 'L']), ('regression #14', [5.0, 60.0], [32, 'V']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #17', [11.99, 63.99], [32, 'V']), ('boundary #18', [8.0, 64.0], [32, 'W']), ('regression #19', [8.0, 78.0], [31, 'X'])]]
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 | [20, 'Q'] | [20, 'Q'] | Passed |
| control #1 | [1, 'X'] | [1, 'X'] | Passed |
| control #2 | [50, 'X'] | [50, 'X'] | Passed |
| control #3 | [16, 'G'] | [16, 'G'] | Passed |
| control #4 | [4, 'G'] | [4, 'G'] | Passed |
| control #5 | [2, 'S'] | [2, 'S'] | Passed |
| boundary #15 | [32, 'V'] | [32, 'V'] | Passed |
| boundary #16 | [32, 'V'] | [31, 'V'] | Failed |
SHA-256 / 70b44c164def6b9cb5a111f9b3af6188d9638e97c39fa6d54c18b5dd031d6891
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 = x
zone = int(math.floor((lon + 180.0) / 6.0)) + 1
if zone > 60:
zone = 60
if 56.0 <= lat < 64.0 and 3.0 <= lon < 12.0:
zone = 32
if 72.0 <= lat <= 84.0 and 0.0 <= lon < 42.0:
if lon < 9.0:
zone = 31
elif lon < 21.0:
zone = 33
elif lon < 33.0:
zone = 35
else:
zone = 37
letters = 'CDEFGHJKLMNPQRSTUVWX'
if lat < -80.0 or lat > 84.0:
band = None
else:
band = letters[min(int((lat + 80.0) // 8), 19)]
return [zone, band]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [-63.1, 21.44], [20, 'Q']), ('control #1', [-175.96, 80.22], [1, 'X']), ('control #2', [115.33, 82.84], [50, 'X']), ('control #3', [-84.72, -46.68], [16, 'G']), ('control #4', [-159.4, -47.6], [4, 'G']), ('control #5', [-170.34, 33.06], [2, 'S']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V'])], [('control #3', [-84.72, -46.68], [16, 'G']), ('control #4', [-159.4, -47.6], [4, 'G']), ('control #5', [-170.34, 33.06], [2, 'S']), ('control #6', [-48.09, 53.12], [22, 'U']), ('control #7', [93.32, -22.41], [46, 'K']), ('control #8', [-119.29, -22.68], [11, 'K']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #39', [3.0, 60.5], [32, 'V'])], [('control #6', [-48.09, 53.12], [22, 'U']), ('control #7', [93.32, -22.41], [46, 'K']), ('control #8', [-119.29, -22.68], [11, 'K']), ('control #9', [-168.15, 26.03], [2, 'R']), ('control #10', [-111.05, -54.91], [12, 'F']), ('control #11', [139.05, -8.2], [54, 'L']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V'])], [('control #9', [-168.15, 26.03], [2, 'R']), ('control #10', [-111.05, -54.91], [12, 'F']), ('control #11', [139.05, -8.2], [54, 'L']), ('boundary #12', [180.0, 10.0], [60, 'P']), ('boundary #13', [-180.0, -10.0], [1, 'L']), ('regression #14', [5.0, 60.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #39', [3.0, 60.5], [32, 'V'])], [('boundary #12', [180.0, 10.0], [60, 'P']), ('boundary #13', [-180.0, -10.0], [1, 'L']), ('regression #14', [5.0, 60.0], [32, 'V']), ('boundary #15', [3.0, 56.0], [32, 'V']), ('boundary #16', [2.99, 60.0], [31, 'V']), ('boundary #17', [11.99, 63.99], [32, 'V']), ('boundary #18', [8.0, 64.0], [32, 'W']), ('regression #19', [8.0, 78.0], [31, 'X'])]]
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 | [20, 'Q'] | [20, 'Q'] | Passed |
| control #1 | [1, 'X'] | [1, 'X'] | Passed |
| control #2 | [50, 'X'] | [50, 'X'] | Passed |
| control #3 | [16, 'G'] | [16, 'G'] | Passed |
| control #4 | [4, 'G'] | [4, 'G'] | Passed |
| control #5 | [2, 'S'] | [2, 'S'] | Passed |
| boundary #15 | [32, 'V'] | [32, 'V'] | Passed |
| boundary #16 | [31, 'V'] | [31, 'V'] | Passed |
SHA-256 / 111f07db2949a7aa18e7135561a1a1544568a50f294c48fffb0f9920f2bc191b
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:16.486150+00:00.
Case digest / efc546bfeb2a05c95c0654978f2cf0e49259ba490edf706e10a73723f0261cc6