diff --git a/README.md b/README.md new file mode 100644 index 0000000..9907990 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# mgrs + +Utility for converting between WGS84 lat/long and MGRS coordinates, spunoff from [proj4js](https://github.com/proj4js/proj4js) + +## Usage + +```sh +npm install mgrs +``` + +### ES6: +```js +import { LLtoMGRS, MGRStoLL, MGRStoBBoxLL } from 'mgrs'; + +const mgrs = LLtoMGRS([-77.03650573264287, 38.89773371566278]); +const [long, lat] = MGRStoLL('18SUJ2339307399'); +const boundingLatlong = MGRStoBBoxLL('18SUJ2339307399'); +``` + +### CommonJS: +```js +const mgrs = require('mgrs'); + +const mgrs = mgrs.LLtoMGRS([-77.03650573264287, 38.89773371566278]); +const [long, lat] = mgrs.MGRStoLL('18SUJ2339307399'); +const boundingLatlong = mgrs.MGRStoBBoxLL('18SUJ2339307399'); +``` + +## Contribute + +Install dependencies +```bash +npm install +``` + +Run test +```bash +npm test +``` + +Build package +```bash +npm run build +``` + +## References +- Wikipedia: https://en.wikipedia.org/wiki/Military_Grid_Reference_System +- GEOTRANS: https://earth-info.nga.mil/#geotrans + +--- + +Licensed under the MIT license except: + +Portions of this software are based on a port of components from the OpenMap +com.bbn.openmap.proj.coords Java package. An initial port was initially created +by Patrice G. Cappelaere and included in Community Mapbuilder +(http://svn.codehaus.org/mapbuilder/), which is licensed under the LGPL license +as per http://www.gnu.org/copyleft/lesser.html. OpenMap is licensed under the +[this license agreement](openmap.md) diff --git a/index.d.ts b/index.d.ts index 7ee1e10..02c2458 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,17 +1,17 @@ declare module "mgrs" { /** - * Convert lat/lon to MGRS. + * Convert lat/long to MGRS. * - * @param {[number, number]} ll Array with longitude and latitude on a + * @param {[number, number]} An array with longitude and latitude on a * WGS84 ellipsoid. * @param {number} [accuracy=5] Accuracy in digits (5 for 1 m, 4 for 10 m, 3 for * 100 m, 2 for 1 km, 1 for 10 km or 0 for 100 km). Optional, default is 5. * @return {string} the MGRS string for the given location and accuracy. */ - export function forward(ll: [number,number], accuracy?: number): string; + export function LLtoMGRS(ll: [number,number], accuracy?: number): string; /** - * Convert MGRS to lat/lon bounding box. + * Convert MGRS to lat/long bounding box. * * @param {string} mgrs MGRS string. * @return {[number,number,number,number]} An array with left (longitude), @@ -19,14 +19,14 @@ declare module "mgrs" { * (longitude) and top (latitude) values in WGS84, representing the * bounding box for the provided MGRS reference. */ - export function inverse(mgrs: string): [number,number,number,number]; + export function MGRStoBBoxLL(mgrs: string): [number,number,number,number]; /** - * Convert MGRS to lat/lon point. + * Convert MGRS to lat/long point. * * @param {string} mgrs MGRS string. * @return {[number,number]} An array with longitude and latitude values in * WGS84, representing the center point of the provided MGRS reference. */ - export function toPoint(mgrs: string): [number,number]; + export function MGRStoLL(mgrs: string): [number,number]; } diff --git a/mgrs.js b/mgrs.js index 40af854..afb660d 100644 --- a/mgrs.js +++ b/mgrs.js @@ -71,42 +71,42 @@ const UTM_ZONE_WIDTH = 6; const HALF_UTM_ZONE_WIDTH = UTM_ZONE_WIDTH / 2; /** - * Convert lat/lon to MGRS. + * Convert lat/long to MGRS. * - * @param {[number, number]} ll Array with longitude and latitude on a + * @param {[number, number]} An array with longitude and latitude on a * WGS84 ellipsoid. * @param {number} [accuracy=5] Accuracy in digits (5 for 1 m, 4 for 10 m, 3 for * 100 m, 2 for 1 km, 1 for 10 km or 0 for 100 km). Optional, default is 5. * @return {string} the MGRS string for the given location and accuracy. */ -export function forward(ll, accuracy) { +export function LLtoMGRS(ll, accuracy) { accuracy = typeof accuracy === 'number' ? accuracy : 5; // default accuracy 1m if (!Array.isArray(ll)) { - throw new TypeError('forward did not receive an array'); + throw new TypeError('Did not receive an array'); } if (typeof ll[0] === 'string' || typeof ll[1] === 'string') { - throw new TypeError('forward received an array of strings, but it only accepts an array of numbers.'); + throw new TypeError('Received an array of strings, expected an array of numbers'); } - const [ lon, lat ] = ll; - if (lon < -180 || lon > 180) { - throw new TypeError(`forward received an invalid longitude of ${lon}`); + const [ long, lat ] = ll; + if (long < -180 || long > 180) { + throw new TypeError(`Received an invalid longitude of ${long}`); } if (lat < -90 || lat > 90) { - throw new TypeError(`forward received an invalid latitude of ${lat}`); + throw new TypeError(`Received an invalid latitude of ${lat}`); } if (lat < -80 || lat > 84) { - throw new TypeError(`forward received a latitude of ${lat}, but this library does not support conversions of points in polar regions below 80°S and above 84°N`); + throw new TypeError(`Received a latitude of ${lat}. Support only conversions of points in polar regions below 80°S and above 84°N`); } - return encode(LLtoUTM({ lat, lon }), accuracy); + return encode(LLtoUTM({ lat, long }), accuracy); } /** - * Convert MGRS to lat/lon bounding box. + * Convert MGRS to lat/long bounding box. * * @param {string} mgrs MGRS string. * @return {[number,number,number,number]} An array with left (longitude), @@ -114,21 +114,21 @@ export function forward(ll, accuracy) { * (longitude) and top (latitude) values in WGS84, representing the * bounding box for the provided MGRS reference. */ -export function inverse(mgrs) { +export function MGRStoBBoxLL(mgrs) { const bbox = UTMtoLL(decode(mgrs.toUpperCase())); - if (typeof bbox.lat === 'number' && typeof bbox.lon === 'number') { - return [bbox.lon, bbox.lat, bbox.lon, bbox.lat]; + if (typeof bbox.lat === 'number' && typeof bbox.long === 'number') { + return [bbox.long, bbox.lat, bbox.long, bbox.lat]; } return [bbox.left, bbox.bottom, bbox.right, bbox.top]; } -export function toPoint(mgrs) { +export function MGRStoLL(mgrs) { if (mgrs === '') { - throw new TypeError('toPoint received a blank string'); + throw new TypeError('Received a blank string'); } const bbox = UTMtoLL(decode(mgrs.toUpperCase())); - if (typeof bbox.lat === 'number' && typeof bbox.lon === 'number') { - return [bbox.lon, bbox.lat]; + if (typeof bbox.lat === 'number' && typeof bbox.long === 'number') { + return [bbox.long, bbox.lat]; } return [(bbox.left + bbox.right) / 2, (bbox.top + bbox.bottom) / 2]; } @@ -160,7 +160,7 @@ function radToDeg(rad) { * using the WGS84 ellipsoid. * * @private - * @param {object} ll Object literal with lat and lon properties + * @param {object} ll Object literal with lat and long properties * representing the WGS84 coordinate to be converted. * @return {object} Object literal containing the UTM value with easting, * northing, zoneNumber and zoneLetter properties, and an optional @@ -168,7 +168,7 @@ function radToDeg(rad) { */ function LLtoUTM(ll) { const Lat = ll.lat; - const Long = ll.lon; + const Long = ll.long; const a = SEMI_MAJOR_AXIS; const LatRad = degToRad(Lat); const LongRad = degToRad(Long); @@ -241,7 +241,7 @@ function LLtoUTM(ll) { * and zoneLetter properties. If an optional accuracy property is * provided (in meters), a bounding box will be returned instead of * latitude and longitude. - * @return {object} An object literal containing either lat and lon values + * @return {object} An object literal containing either lat and long values * (if no accuracy was provided), or top, right, bottom and left values * for the bounding box calculated according to the provided accuracy. * Returns null if the conversion failed. @@ -291,8 +291,8 @@ function UTMtoLL(utm) { let lat = phi1Rad - (N1 * Math.tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720); lat = radToDeg(lat); - let lon = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad); - lon = LongOrigin + radToDeg(lon); + let long = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad); + long = LongOrigin + radToDeg(long); let result; if (typeof utm.accuracy === 'number') { @@ -304,15 +304,15 @@ function UTMtoLL(utm) { }); result = { top: topRight.lat, - right: topRight.lon, + right: topRight.long, bottom: lat, - left: lon + left: long }; } else { result = { lat, - lon + long }; } return result; @@ -481,7 +481,7 @@ function getLetter100kID(column, row, parm) { function decode(mgrsString) { if (mgrsString && mgrsString.length === 0) { - throw new TypeError('MGRSPoint coverting from nothing'); + throw new TypeError('Coverting from nothing'); } //remove any spaces in MGRS String @@ -497,7 +497,7 @@ function decode(mgrsString) { // get Zone number while (!(/[A-Z]/).test(testChar = mgrsString.charAt(i))) { if (i >= 2) { - throw new Error(`MGRSPoint bad conversion from: ${mgrsString}`); + throw new Error(`Bad conversion from ${mgrsString}`); } sb += testChar; i++; @@ -508,14 +508,14 @@ function decode(mgrsString) { if (i === 0 || i + 3 > length) { // A good MGRS string has to be 4-5 digits long, // ##AAA/#AAA at least. - throw new Error(`MGRSPoint bad conversion from ${mgrsString}`); + throw new Error(`Bad conversion from ${mgrsString}`); } const zoneLetter = mgrsString.charAt(i++); // Should we check the zone letter here? Why not. if (zoneLetter <= 'A' || zoneLetter === 'B' || zoneLetter === 'Y' || zoneLetter >= 'Z' || zoneLetter === 'I' || zoneLetter === 'O') { - throw new Error(`MGRSPoint zone letter ${zoneLetter} not handled: ${mgrsString}`); + throw new Error(`Zone letter "${zoneLetter}" not handled, converting from ${mgrsString}`); } hunK = mgrsString.substring(i, i += 2); @@ -537,10 +537,10 @@ function decode(mgrsString) { const remainder = length - i; if (remainder % 2 !== 0) { - throw new Error(`MGRSPoint has to have an even number + throw new Error(`Expected an even number of digits after the zone letter and two 100km letters - front half for easting meters, second half for -northing meters ${mgrsString}`); +northing meters while converting from ${mgrsString}`); } const sep = remainder / 2; @@ -625,7 +625,7 @@ function getEastingFromChar(e, set) { function getNorthingFromChar(n, set) { if (n > 'V') { - throw new TypeError(`MGRSPoint given invalid Northing ${n}`); + throw new TypeError(`Given invalid Northing ${n}`); } // rowOrigin is the letter at the origin of the set for the @@ -737,7 +737,7 @@ function getMinNorthing(zoneLetter) { return northing; } else { - throw new TypeError(`Invalid zone letter: ${zoneLetter}`); + throw new TypeError(`Invalid zone letter "${zoneLetter}"`); } } diff --git a/package.json b/package.json index b4de6ef..54f79a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mgrs", "version": "2.1.0", - "description": "Utility for converting between WGS84 lat/lng and MGRS coordinates", + "description": "Utility for converting between WGS84 lat/long and MGRS coordinates", "main": "dist/mgrs.min.js", "module": "dist/mgrs.esm.js", "types": "index.d.ts", diff --git a/readme.md b/readme.md deleted file mode 100644 index 445a87b..0000000 --- a/readme.md +++ /dev/null @@ -1,42 +0,0 @@ -mgrs -==== - -Utility for converting between WGS84 lat/lng and MGRS coordinates, spunoff from [proj4js](https://github.com/proj4js/proj4js) - -has 3 methods - -- forward, takes an array of `[lon,lat]` and optional accuracy and returns an mgrs string -- inverse, takes an mgrs string and returns a bbox. -- toPoint, takes an mgrs string, returns an array of '[lon,lat]' - -install dev dependencies with - -```bash -npm install -``` - -test with - -```bash -npm test -``` - -build with - -```bash -npm run build -``` - - -Licensed under the MIT license except: - -Portions of this software are based on a port of components from the OpenMap -com.bbn.openmap.proj.coords Java package. An initial port was initially created -by Patrice G. Cappelaere and included in Community Mapbuilder -(http://svn.codehaus.org/mapbuilder/), which is licensed under the LGPL license -as per http://www.gnu.org/copyleft/lesser.html. OpenMap is licensed under the -[this license agreement](openmap.md) - -# references: -- Wikipedia: https://en.wikipedia.org/wiki/Military_Grid_Reference_System -- GEOTRANS: https://earth-info.nga.mil/#geotrans diff --git a/test/test.js b/test/test.js index 4b824c7..866bbfb 100644 --- a/test/test.js +++ b/test/test.js @@ -4,7 +4,7 @@ const { readFileSync } = require('fs'); describe('First MGRS set', () => { const mgrsStr = '33UXP04'; - const point = mgrs.toPoint(mgrsStr); + const point = mgrs.MGRStoLL(mgrsStr); it('Longitude of point from MGRS correct.', () => { point[0].should.be.closeTo(16.41450, 0.000001); }); @@ -12,18 +12,18 @@ describe('First MGRS set', () => { point[1].should.be.closeTo(48.24949, 0.000001); }); it('MGRS reference with highest accuracy correct.', () => { - mgrs.forward(point).should.equal('33UXP0500444997'); + mgrs.LLtoMGRS(point).should.equal('33UXP0500444997'); }); it('MGRS reference with 1-digit accuracy correct.', () => { - mgrs.forward(point,1).should.equal(mgrsStr); + mgrs.LLtoMGRS(point,1).should.equal(mgrsStr); }); it('MGRS reference with 0-digit accuracy correct.', () => { - mgrs.forward(point, 0).should.equal('33UXP'); + mgrs.LLtoMGRS(point, 0).should.equal('33UXP'); }); }); describe('Second MGRS set', () => { const mgrsStr = '24XWT783908'; // near UTM zone border, so there are two ways to reference this - const point = mgrs.toPoint(mgrsStr); + const point = mgrs.MGRStoLL(mgrsStr); it('Longitude of point from MGRS correct.', () => { point[0].should.be.closeTo(-32.66433, 0.00001); }); @@ -31,16 +31,16 @@ describe('Second MGRS set', () => { point[1].should.be.closeTo(83.62778, 0.00001); }); it('MGRS reference with 3-digit accuracy correct.', () => { - mgrs.forward(point,3).should.equal('25XEN041865'); + mgrs.LLtoMGRS(point,3).should.equal('25XEN041865'); }); it('MGRS reference with 5-digit accuracy, northing all zeros', () => { - mgrs.forward([0,0],5).should.equal('31NAA6602100000'); + mgrs.LLtoMGRS([0,0],5).should.equal('31NAA6602100000'); }); it('MGRS reference with 5-digit accuracy, northing one digit', () => { - mgrs.forward([0,0.00001],5).should.equal('31NAA6602100001'); + mgrs.LLtoMGRS([0,0.00001],5).should.equal('31NAA6602100001'); }); it('MGRS reference with 0-digit accuracy correct.', () => { - mgrs.forward(point, 0).should.equal('25XEN'); + mgrs.LLtoMGRS(point, 0).should.equal('25XEN'); }); }); @@ -48,75 +48,75 @@ describe ('third mgrs set', () => { const mgrsStr = '11SPA7234911844'; const point = [-115.0820944, 36.2361322]; it('MGRS reference with highest accuracy correct.', () => { - mgrs.forward(point).should.equal(mgrsStr); + mgrs.LLtoMGRS(point).should.equal(mgrsStr); }); it('MGRS reference with 0-digit accuracy correct.', () => { - mgrs.forward(point, 0).should.equal('11SPA'); + mgrs.LLtoMGRS(point, 0).should.equal('11SPA'); }); }); describe ('data validation', () => { - describe('toPoint function', () => { - it('toPoint throws an error when a blank string is passed in', () => { + describe('MGRStoLL function', () => { + it('MGRStoLL throws an error when a blank string is passed in', () => { try { - mgrs.toPoint(''); + mgrs.MGRStoLL(''); false.should.be.true; // to make sure it errors } catch (error) { error.should.be.a('error'); - error.message.should.equal('toPoint received a blank string'); + error.message.should.equal('Received a blank string'); } }); - it('toPoint should return the same result whether or not spaces are included in the MGRS String', () => { - const [ lon1, lat1 ] = mgrs.toPoint('4QFJ 12345 67890'); - const [ lon2, lat2] = mgrs.toPoint('4QFJ1234567890'); + it('MGRStoLL should return the same result whether or not spaces are included in the MGRS String', () => { + const [ long1, lat1 ] = mgrs.MGRStoLL('4QFJ 12345 67890'); + const [ long2, lat2] = mgrs.MGRStoLL('4QFJ1234567890'); lat1.should.equal(lat2); - lon1.should.equal(lon2); + long1.should.equal(long2); }); }); - describe('forward function', () => { - it('forward throws an error when array of strings passed in', () => { + describe('LLtoMGRS function', () => { + it('LLtoMGRS throws an error when array of strings passed in', () => { try { - mgrs.forward(['40', '40']); + mgrs.LLtoMGRS(['40', '40']); false.should.be.true; // to make sure it errors } catch (error) { error.should.be.a('error'); - error.message.should.equal('forward received an array of strings, but it only accepts an array of numbers.'); + error.message.should.equal('Received an array of strings, expected an array of numbers'); } }); - it('forward throws an error when longitude is outside bounds', () => { + it('LLtoMGRS throws an error when longitude is outside bounds', () => { try { - mgrs.forward([90, 180]); + mgrs.LLtoMGRS([90, 180]); false.should.be.true; // to make sure it errors } catch (error) { error.should.be.a('error'); - error.message.should.equal('forward received an invalid latitude of 180'); + error.message.should.equal('Received an invalid latitude of 180'); } }); - it('forward throws an error when latitude is outside bounds', () => { + it('LLtoMGRS throws an error when latitude is outside bounds', () => { try { - mgrs.forward([90, 270]); + mgrs.LLtoMGRS([90, 270]); false.should.be.true; // to make sure it errors } catch (error) { error.should.be.a('error'); - error.message.should.equal('forward received an invalid latitude of 270'); + error.message.should.equal('Received an invalid latitude of 270'); } }); - it('forward throws an error when latitude is near the north pole', () => { + it('LLtoMGRS throws an error when latitude is near the north pole', () => { try { - mgrs.forward([45, 88]); + mgrs.LLtoMGRS([45, 88]); false.should.be.true; // to make sure it errors } catch (error) { error.should.be.a('error'); - error.message.should.equal('forward received a latitude of 88, but this library does not support conversions of points in polar regions below 80°S and above 84°N'); + error.message.should.equal('Received a latitude of 88. Support only conversions of points in polar regions below 80°S and above 84°N'); } }); - it('forward throws an error when latitude is near the south pole', () => { + it('LLtoMGRS throws an error when latitude is near the south pole', () => { try { - mgrs.forward([45, -88]); + mgrs.LLtoMGRS([45, -88]); false.should.be.true; // to make sure it errors } catch (error) { error.should.be.a('error'); - error.message.should.equal('forward received a latitude of -88, but this library does not support conversions of points in polar regions below 80°S and above 84°N'); + error.message.should.equal('Received a latitude of -88. Support only conversions of points in polar regions below 80°S and above 84°N'); } }); }); @@ -142,7 +142,7 @@ if (process.env.CHECK_GEOTRANS) { // mgrs library doesn't support polar regions if (['A','B','Y','Z'].includes(mgrsString[0])) return; - const [ actualLongitude, actualLatitude ] = mgrs.toPoint(mgrsString); + const [ actualLongitude, actualLatitude ] = mgrs.MGRStoLL(mgrsString); try { actualLatitude.should.be.closeTo(Number.parseFloat(expectedLatitude), 0.0000015); actualLongitude.should.be.closeTo(Number.parseFloat(expectedLongitude), 0.0000015);