Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/utils/geo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 48 additions & 17 deletions src/utils/geo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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 at equator just west of antimeridian
{
grid: 'RJ90XA90',
actualLatLon: { lat: 0, lon: 179.999 },
},

// 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
{
grid: 'AJ00AA00',
actualLatLon: { lat: 0, lon: 180.0 },
},

// location at equator on antimeridian
{
grid: 'AJ00AA00',
actualLatLon: { lat: 0, lon: -180.0 },
},
];

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
},
);
}
}
});
Loading