From aa7ee3084df5b53ed6bc70909e7529118ec96235 Mon Sep 17 00:00:00 2001 From: Michael R Wheeley Date: Tue, 12 May 2026 10:12:55 -0700 Subject: [PATCH 1/3] normalize longitude to [-180, 180), there is a special case where the user might expect longitude +180E (which strictly should be -180W) to work. --- src/utils/geo.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/geo.js b/src/utils/geo.js index da21f55a..efbaa64c 100644 --- a/src/utils/geo.js +++ b/src/utils/geo.js @@ -104,7 +104,10 @@ export const latLonToMaidenhead = ({ lat, lon }, precision = 6) => { if (lon < -180 || lon > 180) throw new Error('invalid longitude, it should be between -180 and 180'); const latNorm = lat + 90; - const lonNorm = lon + 180; + + // Handle case where longitude is given as +180, which should be treated as -180, + // by normalizing to 0-360 range first then applying modulus again to get back to -180..180 range. + const lonNorm = (((lon + 180) % 360) + 360) % 360; // Field (2 chars): 20° lon x 10° lat const field1 = String.fromCharCode(65 + Math.floor(lonNorm / 20)); // A-R From 654cb9e97b20b1a5c19b4eb78f257b95924b7020 Mon Sep 17 00:00:00 2001 From: Michael R Wheeley Date: Tue, 12 May 2026 10:13:56 -0700 Subject: [PATCH 2/3] add unit test cases on the meridian and antimeridian --- src/utils/geo.test.js | 65 ++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/src/utils/geo.test.js b/src/utils/geo.test.js index 7edbe29d..daca3c73 100644 --- a/src/utils/geo.test.js +++ b/src/utils/geo.test.js @@ -3,6 +3,9 @@ import { validateGridLocator, latLonToMaidenhead, maidenheadToLatLon, maidenhead describe('Maidenhead Grid tests', () => { const gridCases = [ + // note that 'grid' fields entered in the test cases are all 8 characters long, + // as tests will also validate the first 2, 4, 6 and 8 characters. + // location in San Diego, CA, USA { grid: 'DM12kv99', @@ -15,8 +18,32 @@ describe('Maidenhead Grid tests', () => { { grid: 'QF56od55', actualLatLon: { lat: -33.8519, lon: 151.210886 }, - latLonSWCornerGrid6: [-33.875, 151.1667], - latLonNECornerGrid6: [-33.8333, 151.25], + }, + + // location at equator / prime meridian + { + grid: 'JJ00aa00', + actualLatLon: { lat: 0, lon: 0 }, + }, + + // location just west of antimeridian + { + grid: 'RJ90XA90', + actualLatLon: { lat: 0, lon: 179.999 }, + }, + + // location on antimeridian + // note that this is not strictly valid as longitude should be given as -180 rather than +180, + // however some sources may expected +180 to be functional so it should be tested + { + grid: 'AJ00AA00', + actualLatLon: { lat: 0, lon: 180.0 }, + }, + + // location on antimeridian + { + grid: 'AJ00AA00', + actualLatLon: { lat: 0, lon: -180.0 }, }, ]; @@ -64,7 +91,9 @@ describe('Maidenhead Grid tests', () => { for (const size of sizes) { it("should convert Maidenhead Grid '" + grid.substring(0, size) + "' to Lat/Lon", () => { const { lat, lon } = maidenheadToLatLon(grid.substring(0, size)); - const { lat: expectedLat, lon: expectedLon } = actualLatLon; + // handle case where longitude is given as +180 or -180, although +180 is strictly invalid it can sometimes be used so should be tested + const { lat: expectedLat, lon: rawLon } = actualLatLon, + expectedLon = ((rawLon + 180) % 360) - 180; let latBucketSize, lonBucketSize, latBucketStart, latBucketEnd, lonBucketStart, lonBucketEnd; switch (size) { @@ -119,19 +148,21 @@ describe('Maidenhead Grid tests', () => { }); } - it( - "should convert Maidenhead Grid '" + grid.substring(0, defaultSize) + "' to Lat/Lon bounding box coordinates", - () => { - const result = maidenheadToBoundingBox(grid.substring(0, defaultSize)); - expect(result).toHaveLength(2); - expect(result[0]).toHaveLength(2); - expect(result[1]).toHaveLength(2); - - expect(result[0][0]).toBeCloseTo(latLonSWCornerGrid6[0], 3); - expect(result[0][1]).toBeCloseTo(latLonSWCornerGrid6[1], 3); - expect(result[1][0]).toBeCloseTo(latLonNECornerGrid6[0], 3); - expect(result[1][1]).toBeCloseTo(latLonNECornerGrid6[1], 3); - }, - ); + if (latLonSWCornerGrid6 && latLonNECornerGrid6) { + it( + "should convert Maidenhead Grid '" + grid.substring(0, defaultSize) + "' to Lat/Lon bounding box coordinates", + () => { + const result = maidenheadToBoundingBox(grid.substring(0, defaultSize)); + expect(result).toHaveLength(2); + expect(result[0]).toHaveLength(2); + expect(result[1]).toHaveLength(2); + + expect(result[0][0]).toBeCloseTo(latLonSWCornerGrid6[0], 3); + expect(result[0][1]).toBeCloseTo(latLonSWCornerGrid6[1], 3); + expect(result[1][0]).toBeCloseTo(latLonNECornerGrid6[0], 3); + expect(result[1][1]).toBeCloseTo(latLonNECornerGrid6[1], 3); + }, + ); + } } }); From 41249e3d60ae5a42b1cfc551001dcf7db8f02c64 Mon Sep 17 00:00:00 2001 From: Michael R Wheeley Date: Tue, 12 May 2026 10:23:17 -0700 Subject: [PATCH 3/3] comment update --- src/utils/geo.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/geo.test.js b/src/utils/geo.test.js index daca3c73..566acac7 100644 --- a/src/utils/geo.test.js +++ b/src/utils/geo.test.js @@ -26,13 +26,13 @@ describe('Maidenhead Grid tests', () => { actualLatLon: { lat: 0, lon: 0 }, }, - // location just west of antimeridian + // location at equator just west of antimeridian { grid: 'RJ90XA90', actualLatLon: { lat: 0, lon: 179.999 }, }, - // location on antimeridian + // location at equator on antimeridian // note that this is not strictly valid as longitude should be given as -180 rather than +180, // however some sources may expected +180 to be functional so it should be tested { @@ -40,7 +40,7 @@ describe('Maidenhead Grid tests', () => { actualLatLon: { lat: 0, lon: 180.0 }, }, - // location on antimeridian + // location at equator on antimeridian { grid: 'AJ00AA00', actualLatLon: { lat: 0, lon: -180.0 },