Skip to content

Commit

Permalink
Merge pull request #10 from jleh/latlng-to-locator
Browse files Browse the repository at this point in the history
Convert coordinates to locator string.
  • Loading branch information
jleh authored Jul 18, 2020
2 parents 57a1961 + 2db2581 commit 34ed3b5
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 20 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ Operations with [Maidenhead locator system](https://en.wikipedia.org/wiki/Maiden
## Usage

```javascript
const { locatorToLatLng, distance } = require('qth-locator');
const { locatorToLatLng, distance, bearingDistance, latLngToLocator } = require('qth-locator');

locatorToLatLng('IO91wm'); // [51.521, -0.125]
distance('IO91wm', 'KP20le'); // 1821.5 km
bearingDistance('FN20qr', 'KP21ol') // 6586.72 km, 49.16 degrees

latLngToLocator(60.179, 24.945) // KP21le
```

## License
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "qth-locator",
"version": "2.0.0",
"version": "2.1.0",
"description": "Maidenhead locator calculator",
"repository": {
"type": "git",
Expand Down
34 changes: 30 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const CHAR_CODE_OFFSET = 65;
const isValidLocatorString = locatorString => locatorString.match(/^[A-Ra-r][A-Ra-r]\d\d[A-Xa-x][A-Xa-x]/) !== null;

const charToNumber = char => char.toUpperCase().charCodeAt(0) - CHAR_CODE_OFFSET;
const numberToChar = number => String.fromCharCode(number + CHAR_CODE_OFFSET);

const locatorToLatLng = (locatorString) => {
locatorString += 'll'; // append subsquare in case is 4 chars long... If not, is ignored.
Expand Down Expand Up @@ -56,9 +57,34 @@ const bearingDistance = (from, to) => {

const distance = (from, to) => bearingDistance(from, to).km;

const isValidPoint = (lat, lng) => (lat >= -90 && lat <= 90) && (lng >= -180 && lng <= 180);

const latLngToLocator = (lat, lng) => {
if (!isValidPoint(lat, lng)) {
throw new Error('Input is not a valid coordinate');
}

const longitude = lng + 180;
const latitude = lat + 90;

const squareLng = numberToChar(Math.floor(longitude / 20));
const squareLat = numberToChar(Math.floor(latitude / 10));

const fieldLng = Math.floor(longitude % 20 / 2);
const fieldLat = Math.floor(latitude % 10);

const subsquareLng = numberToChar(Math.floor((longitude % 20 % 2) * 12)).toLowerCase();
const subsquareLat = numberToChar((latitude % 10 - fieldLat) * 24).toLowerCase();

return squareLng + squareLat
+ fieldLng + fieldLat
+ subsquareLng + subsquareLat;
};

module.exports = {
isValidLocatorString: isValidLocatorString,
locatorToLatLng: locatorToLatLng,
distance: distance,
bearingDistance: bearingDistance
isValidLocatorString,
locatorToLatLng,
distance,
bearingDistance,
latLngToLocator
};
34 changes: 21 additions & 13 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,9 @@ describe('QTH locator', () => {
expect(BDPair.deg).toBeCloseTo(deg);
};

const expectInvalifGridErr = (fn, a, b) => {
expect.assertions(2);

try {
fn(a, b);
} catch (error) {
expect(error).toHaveProperty('message', 'Input is not valid locator string');
expect(error).toBeInstanceOf(Error);
}
};
const expectInvalidGridErr = fn => expect(fn).toThrow('Input is not valid locator string');

const expectInvalidLatLngErr = fn => expect(fn).toThrow('Input is not a valid coordinate');

it('Converts locator string to coordinates', () => {
expectCoordinates(qthLocator.locatorToLatLng('KP20le'), 60.188, 24.958);
Expand All @@ -55,18 +48,33 @@ describe('QTH locator', () => {
});

it('Detect invalid grid', () => {
expectInvalifGridErr(qthLocator.locatorToLatLng, 'RZ73');
expectInvalidGridErr(() => qthLocator.locatorToLatLng('RZ73'));
});

it('Locate debatable grid - It is in spec!', () => {
expectCoordinates(qthLocator.locatorToLatLng('RR73'), 83.479, 174.96);
});

it('Detect short grid', () => {
expectInvalifGridErr(qthLocator.locatorToLatLng, 'R73');
expectInvalidGridErr(() => qthLocator.locatorToLatLng('R73'));
});

it('detect invalid grid in bearingDistance 1', () => {
expectInvalifGridErr(qthLocator.bearingDistance, 'FN20qr', 'F030ll');
expectInvalidGridErr(() => qthLocator.bearingDistance('FN20qr', 'F030ll'));
});

it('Converts latLng to grid', () => {
expect(qthLocator.latLngToLocator(14.3125, -32.125)).toBe('HK34wh');
expect(qthLocator.latLngToLocator(60.179, 24.945)).toBe('KP20le');
expect(qthLocator.latLngToLocator(-33.886048, 151.193546)).toBe('QF56oc');
expect(qthLocator.latLngToLocator(-22.904788, -43.184915)).toBe('GG87jc');
});

it('Throws error for invalid coordinates', () => {
expectInvalidLatLngErr(() => qthLocator.latLngToLocator(91, 120));
expectInvalidLatLngErr(() => qthLocator.latLngToLocator(-91, 120));
expectInvalidLatLngErr(() => qthLocator.latLngToLocator(55, 181));
expectInvalidLatLngErr(() => qthLocator.latLngToLocator(55, -181));
expectInvalidLatLngErr(() => qthLocator.latLngToLocator(-91, -181));
});
});

0 comments on commit 34ed3b5

Please sign in to comment.