diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb254a..1da8c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Oblique Mercator: compute the natural-origin (uc) offset from the central-line azimuth (`+alpha`) instead of the rectified bearing (`+gamma`), so the projection centre maps to the false easting/northing when `+gamma` differs from `+alpha` (e.g. an explicit `+gamma=0` with a non-zero azimuth) + ## [1.4.3] - 2026-06-02 ### Added diff --git a/core/src/main/java/org/locationtech/proj4j/proj/ObliqueMercatorProjection.java b/core/src/main/java/org/locationtech/proj4j/proj/ObliqueMercatorProjection.java index 28e97c7..46f7319 100644 --- a/core/src/main/java/org/locationtech/proj4j/proj/ObliqueMercatorProjection.java +++ b/core/src/main/java/org/locationtech/proj4j/proj/ObliqueMercatorProjection.java @@ -158,8 +158,12 @@ else if (con > Math.PI) sinrot = Math.sin(Gamma); cosrot = Math.cos(Gamma); + // The natural-origin offset uses the azimuth of the central line (alpha), following Snyder's + // Hotine Oblique Mercator: u_c = (A / B) * atan(sqrt(D^2 - 1) / cos(alpha_c)). Using the + // rectified bearing (Gamma) here misplaces the projection centre whenever Gamma != alpha, + // e.g. for definitions with an explicit "+gamma=0" and a non-zero azimuth. u_0 = no_uoff ? 0. : - Math.abs(al * Math.atan(Math.sqrt(d * d - 1.) / cosrot) / bl); + Math.abs(al * Math.atan(Math.sqrt(d * d - 1.) / Math.cos(alpha)) / bl); if (projectionLatitude < 0.) u_0 = - u_0; } diff --git a/core/src/test/java/org/locationtech/proj4j/Proj4VariousTest.java b/core/src/test/java/org/locationtech/proj4j/Proj4VariousTest.java index fd87456..a8eb135 100755 --- a/core/src/test/java/org/locationtech/proj4j/Proj4VariousTest.java +++ b/core/src/test/java/org/locationtech/proj4j/Proj4VariousTest.java @@ -91,6 +91,19 @@ public void testSTS() { 1e-5); } + @Test + public void testObliqueMercatorExplicitGamma() { + // Hotine Oblique Mercator with an explicit "+gamma=0" and a non-zero azimuth. The natural + // origin offset must place the projection centre (lonc, lat_0) exactly at the false + // easting/northing; a regression here shifted the centre by ~2.5 km. Reference coordinates + // computed with PROJ. + String omerc = + "+proj=omerc +lat_0=-20 +lonc=140 +alpha=30 +gamma=0 +k=1 +x_0=200000 +y_0=300000 +ellps=GRS80 +units=m"; + checkTransform("+proj=latlong +ellps=GRS80", p("140 -20"), omerc, p("200000.00 300000.00"), 1e-2); + checkTransform("+proj=latlong +ellps=GRS80", p("140.5 -20.2"), omerc, p("256366.4343 306886.2489"), 1e-2); + checkTransform("+proj=latlong +ellps=GRS80", p("139.7 -19.8"), omerc, p("161721.1705 303433.2577"), 1e-2); + } + // disabled - gamma param not implemented // @Test public void XXX_testRSOBorneo() {