Last active
April 11, 2024 07:37
-
-
Save avanavana/76ff5851a359100dde276b364d31c561 to your computer and use it in GitHub Desktop.
plusCodeUtils – Module exposing encode and decode methods for Open Location/Plus Codes (from/to map coordinates)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file Open Location/Plus Code Encoding/Decoding Utilities | |
* @author Avana Vana <dear.avana@gmail.com> | |
* @module plusCodeUtils | |
* @version 2.6.0 | |
*/ | |
/** | |
* @constant {string} _numerals - string listing official base-20 open location/plus code numerals | |
* @readonly | |
*/ | |
const _numerals = '23456789CFGHJMPQRVWX'; | |
/** | |
* WGS-84 geodetic map coordinates, in the form of the array, [ latitude, longitude ], where both latitude and longitude are either numbers or strings | |
* @typedef {(number[]|string[])} Coordinates | |
* @property {(number|string)} latitude - WGS-84 geodetic latitude from 90 to -90, as either a number or a string | |
* @property {(number|string)} longitude - WGS-84 geodetic longitude from 180 to -180, as either a number or a string | |
*/ | |
module.exports = { | |
/** | |
* Encodes an open location/plus code, given map coordinates | |
* | |
* @method encodePlusCode | |
* @requires _numerals | |
* @param {Coordinates} coords - WGS-84 geodetic latitude and longitude as an array of either strings or numbers | |
* @param {number} [resolution=5] - number of pairs of digits in the return code, greater is more specific | |
* @returns {string} an open location/plus code of the specified {@link resolution} | |
* | |
* @example | |
* // encodePlusCode([ 37.944027, -93.635504 ]); | |
* // returns '8698W9V7+JQ' | |
* | |
* @example <caption>Takes either an array of numbers or an array of strings as an argument, so you can do:</caption> | |
* // encodePlusCode('37.944027, -93.635504'.split(', ')); | |
* // returns '8698W9V7+JQ' | |
* | |
* @example <caption>encodePlusCode() is the inverse of decodePlusCode(). A small amount of uncertainty is part of the spec.</caption> | |
* // encodePlusCode(decodePlusCode('8698W9V7+JQ')); | |
* // returns '8698W9V7+JQ' | |
* | |
* @todo validation... | |
*/ | |
encodePlusCode: (coords, resolution = 5) => { | |
const [ latitude, longitude ] = [ Math.floor((+coords[0] + 90) * 8000), Math.floor((+coords[1] + 180) * 8000) ] | |
.map((meridian) => [ ...Array(resolution) ] | |
.map((_, i) => Math.floor((meridian / Math.pow(20, i)) % 20).toString()) | |
.reverse()); | |
return latitude | |
.flatMap((_, digit) => [ latitude, longitude ] | |
.map((meridian) => meridian[digit])) | |
.map((digit, i) => (i === 8 ? '+' : '') + _numerals.charAt(digit)) | |
.join(''); | |
// Alternative to above line for earlier versions of Javascript without Array.flatMap(): | |
// | |
// return latitude | |
// .map((_, digit) => [ latitude, longitude ] | |
// .map((meridian) => meridian[digit])) | |
// .reduce((code, pair) => code.concat(pair)) | |
// .map((digit, i) => (i === 8 ? '+' : '') + _numerals.charAt(digit)) | |
// .join(''); | |
}, | |
/** | |
* Decodes an open location/plus code, given a valid code | |
* | |
* @method decodePlusCode | |
* @requires _numerals | |
* @param {string} plusCode - a valid open location/plus code | |
* @param {number} [places=6] - the number of decimal places for all return values | |
* @returns {Coordinates} WGS-84 geodetic latitude and longitude, as an array of numbers, each with the number of specified decimal {@link places} | |
* | |
* @example | |
* // decodePlusCode('8698W9V7+JQ'); | |
* // returns [ 37.944063, -93.635563 ] | |
* | |
* @example <caption>decodePlusCode() is the inverse of encodePlusCode(). A small amount of uncertainty is part of the spec.</caption> | |
* // decodePlusCode(encodePlusCode([ 37.944027, -93.635504 ])); | |
* // returns [ 37.944063, -93.635563 ] | |
* | |
* @todo validation... | |
* @todo handle odd-length codes with length > 10 | |
* @todo handle spacer chars | |
*/ | |
decodePlusCode: (plusCode, places = 6) => { | |
const [ latitude, longitude ] = plusCode | |
.replace('+','') | |
.split('') | |
.map((digit) => _numerals.indexOf(digit)) | |
.reduce(([ lat, lon ], digit, i) => i % 2 === 0 ? [ [ ...lat, digit ], lon ] : [ lat, [ ...lon, digit ] ], [ [], [] ]) | |
.map((meridian) => meridian.reduce((total, digit, i) => total + digit * Math.pow(20, 1 - i), 0)) | |
.map((meridian) => meridian + Math.pow(20, 2 - plusCode.replace('+', '').length / 2) / 2); | |
return [ +(latitude - 90).toFixed(places), +(longitude - 180).toFixed(places) ]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment