From 306e362098b084581392bf1bc52bb492eefd0e6d Mon Sep 17 00:00:00 2001 From: Filip Johansson Date: Mon, 21 Sep 2026 16:51:13 +0200 Subject: [PATCH] fix: Norway kmm2 files that already store projected coordinates are not converted again Co-Authored-By: Claude Fable 5.1 --- kmm/positions/read_kmm2.py | 38 +++++++++++++++++++++++++++++++++++-- tests/norway_projected.kmm2 | 5 +++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/norway_projected.kmm2 diff --git a/kmm/positions/read_kmm2.py b/kmm/positions/read_kmm2.py index 9bcd98f..7c91bbc 100644 --- a/kmm/positions/read_kmm2.py +++ b/kmm/positions/read_kmm2.py @@ -11,8 +11,9 @@ pattern2 = re.compile(r"CMAST") # Norway ("Banenor") kmm2 files are identified by this token in the VER header -# line. They store WGS84 latitude/longitude in the coordinate columns instead of -# SWEREF99 TM northing/easting. +# line. Older ones store WGS84 latitude/longitude in the coordinate columns +# instead of SWEREF99 TM northing/easting; newer ones, under the same header, +# store projected northing/easting already. norway_header_token = "NorKmmToKmm" tm = projections.make_transverse_mercator("SWEREF_99_TM") @@ -53,6 +54,20 @@ ) +def is_degrees(dataframe): + """ + The same converter version (``NorKmmToKmm2_1.03``) writes either WGS84 + degrees or projected metres into the coordinate columns, so the header + cannot tell them apart; the values can. A latitude is at most 90, while a + northing anywhere in Scandinavia is in the millions. The projected files + use zone 33 ETRS89 coordinates, which SWEREF99 TM shares its projection + with, so they pass through unchanged. + """ + northing = dataframe["northing"].abs() + easting = dataframe["easting"].abs() + return bool(northing.median() <= 90 and easting.median() <= 180) + + def latlon_to_sweref(dataframe): """ Norway kmm2 files store WGS84 latitude/longitude in the coordinate columns @@ -62,6 +77,8 @@ def latlon_to_sweref(dataframe): """ if len(dataframe) == 0 or not {"northing", "easting"}.issubset(dataframe.columns): return dataframe + if not is_degrees(dataframe): + return dataframe latitude = dataframe["northing"].to_numpy() longitude = dataframe["easting"].to_numpy() @@ -166,3 +183,20 @@ def test_norway_latlon_to_sweref(): ) assert abs(latitude - 59.9100025) < 1e-4 assert abs(longitude - 10.7545870) < 1e-4 + + +def test_norway_projected_coordinates_pass_through(): + # Newer Norway files carry the same VER header but already store projected + # northing/easting. Converting those as degrees produced garbage; they must + # land on the same place as the lat/lon fixture (same four positions). + projected = read_kmm2(Path("tests/norway_projected.kmm2")) + degrees = read_kmm2(Path("tests/norway.kmm2")) + assert len(projected) == 4 + assert np.allclose(projected["northing"], degrees["northing"], atol=1.0) + assert np.allclose(projected["easting"], degrees["easting"], atol=1.0) + + latitude, longitude = tm.grid_to_geodetic( + projected["northing"].iloc[0], projected["easting"].iloc[0] + ) + assert abs(latitude - 59.9100025) < 1e-4 + assert abs(longitude - 10.7545870) < 1e-4 diff --git a/tests/norway_projected.kmm2 b/tests/norway_projected.kmm2 new file mode 100644 index 0000000..ffb37ea --- /dev/null +++ b/tests/norway_projected.kmm2 @@ -0,0 +1,5 @@ +VER NorKmmToKmm2_1.03 SplSetup NO_NORM +POS 23060200 10 0.0 200.0 ? 3.0 0.0 0.0 0.0 6649001.564084955 262665.94670750335 0.0 5.0 0.0 2026-05-19T02:59:50.0000000+02:00 +POS 23060300 10 0.0 201.0 ? 3.0 0.0 0.0 0.0 6649001.118494233 262666.7812106609 0.0 5.0 0.0 2026-05-19T02:59:51.0000000+02:00 +POS 23060400 10 0.0 202.0 ? 3.0 0.0 0.0 0.0 6649000.615133018 262667.6301772379 0.0 5.0 0.0 2026-05-19T02:59:52.0000000+02:00 +POS 23060500 10 0.0 203.0 ? 3.0 0.0 0.0 0.0 6649000.143033189 262668.4762151675 0.0 5.0 0.0 2026-05-19T02:59:53.0000000+02:00