diff --git a/README.md b/README.md index 4d154b1..fcb421a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index ba98a42..f04a8ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qth-locator", - "version": "2.0.0", + "version": "2.1.0", "description": "Maidenhead locator calculator", "repository": { "type": "git", diff --git a/src/index.js b/src/index.js index 088bd43..6e69e24 100644 --- a/src/index.js +++ b/src/index.js @@ -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. @@ -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 }; \ No newline at end of file diff --git a/test/index.spec.js b/test/index.spec.js index 5335003..7390a3d 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -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); @@ -55,7 +48,7 @@ describe('QTH locator', () => { }); it('Detect invalid grid', () => { - expectInvalifGridErr(qthLocator.locatorToLatLng, 'RZ73'); + expectInvalidGridErr(() => qthLocator.locatorToLatLng('RZ73')); }); it('Locate debatable grid - It is in spec!', () => { @@ -63,10 +56,25 @@ describe('QTH locator', () => { }); 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)); }); });