UNPKG

@iota/area-codes

Version:
889 lines (873 loc) 41.8 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.IotaAreaCodes = {})); }(this, function (exports) { 'use strict'; /** * Code precision to use for locations. */ var CodePrecision = /** @class */ (function () { function CodePrecision() { } /** * Provides a normal precision code, approximately 14x14 meters. */ CodePrecision.NORMAL = 10; /** * Provides an extra precision code, approximately 2x3 meters. */ CodePrecision.EXTRA = 11; return CodePrecision; }()); function unwrapExports (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var openLocationCode = createCommonjsModule(function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); const LATITUDE_MAX = 90; const LONGITUDE_MAX = 180; /** * Coordinates of a decoded Open Location Code. * * The coordinates include the latitude and longitude of the lower left and * upper right corners and the center of the bounding box for the area the * code represents. * * @constructor */ class CodeArea { constructor(latitudeLo, longitudeLo, latitudeHi, longitudeHi, codeLength) { this.latitudeLo = latitudeLo; this.longitudeLo = longitudeLo; this.latitudeHi = latitudeHi; this.longitudeHi = longitudeHi; this.codeLength = codeLength; this.latitudeCenter = Math.min(latitudeLo + (latitudeHi - latitudeLo) / 2, LATITUDE_MAX); this.longitudeCenter = Math.min(longitudeLo + (longitudeHi - longitudeLo) / 2, LONGITUDE_MAX); } getLatitudeHeight() { return this.latitudeHi - this.latitudeLo; } getLongitudeWidth() { return this.longitudeHi - this.longitudeLo; } } exports.CodeArea = CodeArea; /** * Open Location Code implementation for TypeScript */ class OpenLocationCode { constructor(code) { this.code = code; } getCode() { return this.code; } /** * Returns whether this {@link OpenLocationCode} is a padded Open Location Code, meaning that it * contains less than 8 valid digits. */ isPadded() { return this.code.indexOf(OpenLocationCode.PADDING_CHARACTER_) >= 0; } /** * Determines if a code is valid. * * To be valid, all characters must be from the Open Location Code character * set with at most one separator. The separator can be in any even-numbered * position up to the eighth digit. * * @param {string} code The string to check. * @return {boolean} True if the string is a valid code. */ static isValid(code) { if (!code) { return false; } // The separator is required. if (code.indexOf(OpenLocationCode.SEPARATOR_) === -1) { return false; } if (code.indexOf(OpenLocationCode.SEPARATOR_) !== code.lastIndexOf(OpenLocationCode.SEPARATOR_)) { return false; } // Is it the only character? if (code.length === 1) { return false; } // Is it in an illegal position? if (code.indexOf(OpenLocationCode.SEPARATOR_) > OpenLocationCode.SEPARATOR_POSITION_ || code.indexOf(OpenLocationCode.SEPARATOR_) % 2 === 1) { return false; } // We can have an even number of padding characters before the separator, // but then it must be the final character. if (code.indexOf(OpenLocationCode.PADDING_CHARACTER_) > -1) { // Not allowed to start with them! if (code.indexOf(OpenLocationCode.PADDING_CHARACTER_) === 0) { return false; } // There can only be one group and it must have even length. const padMatch = code.match(new RegExp("(" + OpenLocationCode.PADDING_CHARACTER_ + "+)", "g")); if (padMatch.length > 1 || padMatch[0].length % 2 === 1 || padMatch[0].length > OpenLocationCode.SEPARATOR_POSITION_ - 2) { return false; } // If the code is long enough to end with a separator, make sure it does. if (code.charAt(code.length - 1) !== OpenLocationCode.SEPARATOR_) { return false; } } // If there are characters after the separator, make sure there isn't just // one of them (not legal). if (code.length - code.indexOf(OpenLocationCode.SEPARATOR_) - 1 === 1) { return false; } // Strip the separator and any padding characters. const strippedCode = code.replace(new RegExp("\\" + OpenLocationCode.SEPARATOR_ + "+"), "") .replace(new RegExp(OpenLocationCode.PADDING_CHARACTER_ + "+"), ""); // Check the code contains only valid characters. for (let i = 0, len = strippedCode.length; i < len; i++) { const character = strippedCode.charAt(i).toUpperCase(); if (character !== OpenLocationCode.SEPARATOR_ && OpenLocationCode.CODE_ALPHABET_.indexOf(character) === -1) { return false; } } return true; } ; /** * Returns whether the provided Open Location Code is a padded Open Location Code, meaning that it * contains less than 8 valid digits. */ static isPadded(code) { return new OpenLocationCode(code).isPadded(); } /** * Determines if a code is a valid short code. * * @param {string} code The string to check. * @return {boolean} True if the string can be produced by removing four or * more characters from the start of a valid code. */ static isShort(code) { if (!OpenLocationCode.isValid(code)) { return false; } // If there are less characters than expected before the SEPARATOR. return code.indexOf(OpenLocationCode.SEPARATOR_) >= 0 && code.indexOf(OpenLocationCode.SEPARATOR_) < OpenLocationCode.SEPARATOR_POSITION_; } ; /** * Determines if a code is a valid full Open Location Code. * * @param {string} code The string to check. * @return {boolean} True if the code represents a valid latitude and longitude combination. */ static isFull(code) { if (!OpenLocationCode.isValid(code)) { return false; } // If it's short, it's not full. if (OpenLocationCode.isShort(code)) { return false; } // Work out what the first latitude character indicates for latitude. const firstLatValue = OpenLocationCode.CODE_ALPHABET_.indexOf(code.charAt(0).toUpperCase()) * OpenLocationCode.ENCODING_BASE_; if (firstLatValue >= OpenLocationCode.LATITUDE_MAX_ * 2) { return false; // The code would decode to a latitude of >= 90 degrees. } if (code.length > 1) { // Work out what the first longitude character indicates for longitude. const firstLngValue = OpenLocationCode.CODE_ALPHABET_.indexOf(code.charAt(1).toUpperCase()) * OpenLocationCode.ENCODING_BASE_; if (firstLngValue >= OpenLocationCode.LONGITUDE_MAX_ * 2) { return false; // The code would decode to a longitude of >= 180 degrees. } } return true; } ; contains(latitude, longitude) { const codeArea = OpenLocationCode.decode(this.getCode()); return codeArea.latitudeLo <= latitude && latitude < codeArea.latitudeHi && codeArea.longitudeLo <= longitude && longitude < codeArea.longitudeHi; } /** * Encode a location into an Open Location Code. * * @param {number} latitude The latitude in signed decimal degrees. It will * be clipped to the range -90 to 90. * @param {number} longitude The longitude in signed decimal degrees. Will be * normalised to the range -180 to 180. * @param {?number} codeLength The length of the code to generate. If * omitted, the value OpenLocationCode.CODE_PRECISION_NORMAL will be used. * For a more precise result, OpenLocationCode.CODE_PRECISION_EXTRA is * recommended. * @return {string} The code. * @throws {Exception} if any of the input values are not numbers. */ static encode(latitude, longitude, codeLength = OpenLocationCode.CODE_PRECISION_NORMAL) { if (codeLength < 2 || (codeLength < OpenLocationCode.PAIR_CODE_LENGTH_ && codeLength % 2 === 1)) { throw new Error("IllegalArgumentException: Invalid Open Location Code length"); } codeLength = Math.min(OpenLocationCode.MAX_DIGIT_COUNT, codeLength); // Ensure that latitude and longitude are valid. let clippedLatitude = OpenLocationCode.clipLatitude(latitude); const clippedLongitude = OpenLocationCode.normalizeLongitude(longitude); // Latitude 90 needs to be adjusted to be just less, so the returned code // can also be decoded. if (clippedLatitude === 90) { clippedLatitude = clippedLatitude - OpenLocationCode.computeLatitudePrecision(codeLength); } let code = OpenLocationCode.encodePairs(clippedLatitude, clippedLongitude, Math.min(codeLength, OpenLocationCode.PAIR_CODE_LENGTH_)); // If the requested length indicates we want grid refined codes. if (codeLength > OpenLocationCode.PAIR_CODE_LENGTH_) { code += OpenLocationCode.encodeGrid(clippedLatitude, clippedLongitude, codeLength - OpenLocationCode.PAIR_CODE_LENGTH_); } return code; } ; /** * Decodes an Open Location Code into its location coordinates. * * Returns a CodeArea object that includes the coordinates of the bounding * box - the lower left, center and upper right. * * @param {string} code The code to decode. * @return {CodeArea} An object with the coordinates of the * area of the code. * @throws {Exception} If the code is not valid. */ static decode(code) { if (!OpenLocationCode.isFull(code)) { throw new Error("IllegalArgumentException: Passed Open Location Code is not a valid full code: " + code); } // Strip out separator character (we've already established the code is // valid so the maximum is one), padding characters and convert to upper // case. let editedCode = code.replace(OpenLocationCode.SEPARATOR_, ""); editedCode = editedCode.replace(new RegExp(OpenLocationCode.PADDING_CHARACTER_ + "+"), ""); editedCode = editedCode.toUpperCase(); const codeArea = OpenLocationCode.decodePairs(editedCode.substring(0, OpenLocationCode.PAIR_CODE_LENGTH_)); // Decode the lat/lng pair component. if (editedCode.length <= OpenLocationCode.PAIR_CODE_LENGTH_) { return codeArea; // If there is a grid refinement component, decode that. } const gridArea = OpenLocationCode.decodeGrid(editedCode.substring(OpenLocationCode.PAIR_CODE_LENGTH_, OpenLocationCode.MAX_DIGIT_COUNT)); return new CodeArea(codeArea.latitudeLo + gridArea.latitudeLo, codeArea.longitudeLo + gridArea.longitudeLo, codeArea.latitudeLo + gridArea.latitudeHi, codeArea.longitudeLo + gridArea.longitudeHi, codeArea.codeLength + gridArea.codeLength); } ; /** * Recover the nearest matching code to a specified location. * * Given a valid short Open Location Code this recovers the nearest matching * full code to the specified location. * * @param {string} shortCode A valid short code. * @param {number} latitude The latitude to use for the reference * location. * @param {number} longitude The longitude to use for the reference * location. * @return {string} The nearest matching full code to the reference location. * @throws {Exception} if the short code is not valid, or the reference * position values are not numbers. */ static recoverNearest(shortCode, latitude, longitude) { if (!OpenLocationCode.isShort(shortCode)) { if (OpenLocationCode.isFull(shortCode)) { return shortCode; } else { throw new Error("ValueError: Passed short code is not valid: " + shortCode); } } const referenceLatitude = OpenLocationCode.clipLatitude(latitude); const referenceLongitude = OpenLocationCode.normalizeLongitude(longitude); const shortCodeUpper = shortCode.toUpperCase(); // Clean up the passed code. // Compute the number of digits we need to recover. const paddingLength = OpenLocationCode.SEPARATOR_POSITION_ - shortCodeUpper.indexOf(OpenLocationCode.SEPARATOR_); const resolution = Math.pow(20, 2 - (paddingLength / 2)); // The resolution (height and width) of the padded area in degrees. const halfResolution = resolution / 2.0; // Distance from the center to an edge (in degrees). // Use the reference location to pad the supplied short code and decode it. const codeArea = OpenLocationCode.decode(OpenLocationCode.encode(referenceLatitude, referenceLongitude).substr(0, paddingLength) + shortCodeUpper); // How many degrees latitude is the code from the reference? If it is more // than half the resolution, we need to move it north or south but keep it // within -90 to 90 degrees. if (referenceLatitude + halfResolution < codeArea.latitudeCenter && codeArea.latitudeCenter - resolution >= -OpenLocationCode.LATITUDE_MAX_) { // If the proposed code is more than half a cell north of the reference location, // it's too far, and the best match will be one cell south. codeArea.latitudeCenter -= resolution; } else if (referenceLatitude - halfResolution > codeArea.latitudeCenter && codeArea.latitudeCenter + resolution <= OpenLocationCode.LATITUDE_MAX_) { // If the proposed code is more than half a cell south of the reference location, // it's too far, and the best match will be one cell north. codeArea.latitudeCenter += resolution; } // How many degrees longitude is the code from the reference? if (referenceLongitude + halfResolution < codeArea.longitudeCenter) { codeArea.longitudeCenter -= resolution; } else if (referenceLongitude - halfResolution > codeArea.longitudeCenter) { codeArea.longitudeCenter += resolution; } return OpenLocationCode.encode(codeArea.latitudeCenter, codeArea.longitudeCenter, codeArea.codeLength); } ; /** * Remove characters from the start of an OLC code. * * This uses a reference location to determine how many initial characters * can be removed from the OLC code. The number of characters that can be * removed depends on the distance between the code center and the reference * location. * * @param {string} code The full code to shorten. * @param {number} latitude The latitude to use for the reference location. * @param {number} longitude The longitude to use for the reference location. * @return {string} The code, shortened as much as possible that it is still * the closest matching code to the reference location. * @throws {Exception} if the passed code is not a valid full code or the * reference location values are not numbers. */ static shorten(code, latitude, longitude) { if (!OpenLocationCode.isFull(code)) { throw new Error("ValueError: Passed code is not valid and full: " + code); } if (code.indexOf(OpenLocationCode.PADDING_CHARACTER_) !== -1) { throw new Error("ValueError: Cannot shorten padded codes: " + code); } const codeUpper = code.toUpperCase(); const codeArea = OpenLocationCode.decode(codeUpper); if (codeArea.codeLength < OpenLocationCode.MIN_TRIMMABLE_CODE_LEN_) { throw new Error("ValueError: Code length must be at least " + OpenLocationCode.MIN_TRIMMABLE_CODE_LEN_); } const latitudeClipped = OpenLocationCode.clipLatitude(latitude); const longitudeClipped = OpenLocationCode.normalizeLongitude(longitude); // How close are the latitude and longitude to the code center. const range = Math.max(Math.abs(codeArea.latitudeCenter - latitudeClipped), Math.abs(codeArea.longitudeCenter - longitudeClipped)); for (let i = OpenLocationCode.PAIR_RESOLUTIONS_.length - 2; i >= 1; i--) { // Check if we're close enough to shorten. The range must be less than 1/2 // the resolution to shorten at all, and we want to allow some safety, so // use 0.3 instead of 0.5 as a multiplier. if (range < (OpenLocationCode.PAIR_RESOLUTIONS_[i] * 0.3)) { // Trim it. return codeUpper.substring((i + 1) * 2); } } return codeUpper; } ; /** * Clip a latitude into the range -90 to 90. * * @param {number} latitude * @return {number} The latitude value clipped to be in the range. */ static clipLatitude(latitude) { return Math.min(90, Math.max(-90, latitude)); } ; /** * Compute the latitude precision value for a given code length. * Lengths <= 10 have the same precision for latitude and longitude, but * lengths > 10 have different precisions due to the grid method having * fewer columns than rows. * @param {number} codeLength * @return {number} The latitude precision in degrees. */ static computeLatitudePrecision(codeLength) { if (codeLength <= 10) { return Math.pow(20, Math.floor(codeLength / -2 + 2)); } return Math.pow(20, -3) / Math.pow(OpenLocationCode.GRID_ROWS_, codeLength - 10); } ; /** * Normalize a longitude into the range -180 to 180, not including 180. * * @param {number} longitude * @return {number} Normalized into the range -180 to 180. */ static normalizeLongitude(longitude) { let longitudeOutput = longitude; while (longitudeOutput < -180) { longitudeOutput = longitudeOutput + 360; } while (longitudeOutput >= 180) { longitudeOutput = longitudeOutput - 360; } return longitudeOutput; } ; /** * Encode a location into a sequence of OLC lat/lng pairs. * * This uses pairs of characters (longitude and latitude in that order) to * represent each step in a 20x20 grid. Each code, therefore, has 1/400th * the area of the previous code. * * This algorithm is used up to 10 digits. * * @param {number} latitude The location to encode. * @param {number} longitude The location to encode. * @param {number} codeLength Requested code length. * @return {string} The up to 10-digit OLC code for the location. */ static encodePairs(latitude, longitude, codeLength) { let code = ""; // Adjust latitude and longitude so they fall into positive ranges. let adjustedLatitude = latitude + OpenLocationCode.LATITUDE_MAX_; let adjustedLongitude = longitude + OpenLocationCode.LONGITUDE_MAX_; // Count digits - can't use string length because it may include a separator // character. let digitCount = 0; while (digitCount < codeLength) { // Provides the value of digits in this place in decimal degrees. const placeValue = OpenLocationCode.PAIR_RESOLUTIONS_[Math.floor(digitCount / 2)]; // Do the latitude - gets the digit for this place and subtracts that for // the next digit. let digitValue = Math.floor(adjustedLatitude / placeValue); adjustedLatitude -= digitValue * placeValue; code += OpenLocationCode.CODE_ALPHABET_.charAt(digitValue); digitCount += 1; // And do the longitude - gets the digit for this place and subtracts that // for the next digit. digitValue = Math.floor(adjustedLongitude / placeValue); adjustedLongitude -= digitValue * placeValue; code += OpenLocationCode.CODE_ALPHABET_.charAt(digitValue); digitCount += 1; // Should we add a separator here? if (digitCount === OpenLocationCode.SEPARATOR_POSITION_ && digitCount < codeLength) { code += OpenLocationCode.SEPARATOR_; } } if (code.length < OpenLocationCode.SEPARATOR_POSITION_) { code = code + Array(OpenLocationCode.SEPARATOR_POSITION_ - code.length + 1).join(OpenLocationCode.PADDING_CHARACTER_); } if (code.length === OpenLocationCode.SEPARATOR_POSITION_) { code = code + OpenLocationCode.SEPARATOR_; } return code; } ; /** * Encode a location using the grid refinement method into an OLC string. * * The grid refinement method divides the area into a grid of 4x5, and uses a * single character to refine the area. This allows default accuracy OLC codes * to be refined with just a single character. * * This algorithm is used for codes longer than 10 digits. * * @param {number} latitude The location to encode. * @param {number} longitude The location to encode. * @param {number} codeLength Requested code length. * @return {string} The OLC code digits from the 11th digit on. */ static encodeGrid(latitude, longitude, codeLength) { let code = ""; let latPlaceValue = OpenLocationCode.GRID_SIZE_DEGREES_; let lngPlaceValue = OpenLocationCode.GRID_SIZE_DEGREES_; // Adjust latitude and longitude so they fall into positive ranges and // get the offset for the required places. let lat = latitude + OpenLocationCode.LATITUDE_MAX_; let lon = longitude + OpenLocationCode.LONGITUDE_MAX_; // To avoid problems with floating point, get rid of the degrees. lat = lat % 1.0; lon = lon % 1.0; let adjustedLatitude = lat % latPlaceValue; let adjustedLongitude = lon % lngPlaceValue; for (let i = 0; i < codeLength; i++) { // Work out the row and column. const row = Math.floor(adjustedLatitude / (latPlaceValue / OpenLocationCode.GRID_ROWS_)); const col = Math.floor(adjustedLongitude / (lngPlaceValue / OpenLocationCode.GRID_COLUMNS_)); latPlaceValue /= OpenLocationCode.GRID_ROWS_; lngPlaceValue /= OpenLocationCode.GRID_COLUMNS_; adjustedLatitude -= row * latPlaceValue; adjustedLongitude -= col * lngPlaceValue; code += OpenLocationCode.CODE_ALPHABET_.charAt(row * OpenLocationCode.GRID_COLUMNS_ + col); } return code; } ; /** * Decode an OLC code made up of lat/lng pairs. * * This decodes an OLC code made up of alternating latitude and longitude * characters, encoded using base 20. * * @param {string} code The code to decode, assumed to be a valid full code, * but with the separator removed. * @return {CodeArea} The code area object. */ static decodePairs(code) { // Get the latitude and longitude values. These will need correcting from // positive ranges. const latitude = OpenLocationCode.decodePairsSequence(code, 0); const longitude = OpenLocationCode.decodePairsSequence(code, 1); // Correct the values and set them into the CodeArea object. return new CodeArea(latitude[0] - OpenLocationCode.LATITUDE_MAX_, longitude[0] - OpenLocationCode.LONGITUDE_MAX_, latitude[1] - OpenLocationCode.LATITUDE_MAX_, longitude[1] - OpenLocationCode.LONGITUDE_MAX_, code.length); } ; /** * Decode either a latitude or longitude sequence. * * This decodes the latitude or longitude sequence of a lat/lng pair encoding. * Starting at the character at position offset, every second character is * decoded and the value returned. * * @param {string} code A valid full OLC code, with the separator removed. * @param {number} offset The character to start from. * @return {[number]} An array of two numbers, representing the lower and * upper range in decimal degrees. These are in positive ranges and will * need to be corrected appropriately. */ static decodePairsSequence(code, offset) { let i = 0; let value = 0; while (i * 2 + offset < code.length) { value += OpenLocationCode.CODE_ALPHABET_.indexOf(code.charAt(i * 2 + offset)) * OpenLocationCode.PAIR_RESOLUTIONS_[i]; i += 1; } return [value, value + OpenLocationCode.PAIR_RESOLUTIONS_[i - 1]]; } ; /** * Decode the grid refinement portion of an OLC code. * * @param {string} code The grid refinement section of a code. * @return {CodeArea} The area of the code. */ static decodeGrid(code) { let latitudeLo = 0.0; let longitudeLo = 0.0; let latPlaceValue = OpenLocationCode.GRID_SIZE_DEGREES_; let lngPlaceValue = OpenLocationCode.GRID_SIZE_DEGREES_; let i = 0; while (i < code.length) { const codeIndex = OpenLocationCode.CODE_ALPHABET_.indexOf(code.charAt(i)); const row = Math.floor(codeIndex / OpenLocationCode.GRID_COLUMNS_); const col = codeIndex % OpenLocationCode.GRID_COLUMNS_; latPlaceValue /= OpenLocationCode.GRID_ROWS_; lngPlaceValue /= OpenLocationCode.GRID_COLUMNS_; latitudeLo += row * latPlaceValue; longitudeLo += col * lngPlaceValue; i += 1; } return new CodeArea(latitudeLo, longitudeLo, latitudeLo + latPlaceValue, longitudeLo + lngPlaceValue, code.length); } ; } OpenLocationCode.CODE_PRECISION_NORMAL = 10; OpenLocationCode.CODE_PRECISION_EXTRA = 11; OpenLocationCode.MAX_DIGIT_COUNT = 15; // A separator used to break the code into two parts to aid memorability. OpenLocationCode.SEPARATOR_ = "+"; // The number of characters to place before the separator. OpenLocationCode.SEPARATOR_POSITION_ = 8; // The character used to pad codes. OpenLocationCode.PADDING_CHARACTER_ = "0"; // The character set used to encode the values. OpenLocationCode.CODE_ALPHABET_ = "23456789CFGHJMPQRVWX"; // The base to use to convert numbers to/from. OpenLocationCode.ENCODING_BASE_ = OpenLocationCode.CODE_ALPHABET_.length; // The maximum value for latitude in degrees. OpenLocationCode.LATITUDE_MAX_ = LATITUDE_MAX; // The maximum value for longitude in degrees. OpenLocationCode.LONGITUDE_MAX_ = LONGITUDE_MAX; // Maximum code length using lat/lng pair encoding. The area of such a // code is approximately 13x13 meters (at the equator), and should be suitable // for identifying buildings. This excludes prefix and separator characters. OpenLocationCode.PAIR_CODE_LENGTH_ = 10; // The resolution values in degrees for each position in the lat/lng pair // encoding. These give the place value of each position, and therefore the // dimensions of the resulting area. OpenLocationCode.PAIR_RESOLUTIONS_ = [20.0, 1.0, .05, .0025, .000125]; // Number of columns in the grid refinement method. OpenLocationCode.GRID_COLUMNS_ = 4; // Number of rows in the grid refinement method. OpenLocationCode.GRID_ROWS_ = 5; // Size of the initial grid in degrees. OpenLocationCode.GRID_SIZE_DEGREES_ = 0.000125; // Minimum length of a code that can be shortened. OpenLocationCode.MIN_TRIMMABLE_CODE_LEN_ = 6; exports.default = OpenLocationCode; }); var OpenLocationCode = unwrapExports(openLocationCode); var openLocationCode_1 = openLocationCode.CodeArea; /** * The character set used in Open Location Code encoding. * @private */ var OLC_APHABET = "23456789CFGHJMPQRVWX0+"; /** * The character set used in IOTA Area Code encoding. * @private */ var IAC_APHABET = "FGHJKLMNOPQRSTUVXWYZA9"; /** * Convert the IOTA Area Code to Open Location Code with no validation. * @private * @param iotaAreaCode The IOTA Area Code to convert. * @returns The Open Location Code. */ function iacToOlcInternal(iotaAreaCode) { var olc = ""; for (var i = 0; i < iotaAreaCode.length; i++) { var idx = IAC_APHABET.indexOf(iotaAreaCode[i]); olc += OLC_APHABET[idx]; } return olc; } /** * Is the IOTA Area Code valid. * @param iotaAreaCode The IOTA Area Code to validate. * @returns True if the code is valid. */ function isValid(iotaAreaCode) { // Check if all the characters fall within our alphabet var re = new RegExp("^[" + IAC_APHABET + "]*$"); var codeIsValid = re.test(iotaAreaCode); if (codeIsValid) { // Now validate using OLC validation codeIsValid = OpenLocationCode.isFull(iacToOlcInternal(iotaAreaCode)); } return codeIsValid; } /** * Is the IOTA Area Code a valid partial code. * @param iotaAreaCode The IOTA Area Code to validate. * @returns True if the code is a partial. */ function isValidPartial(iotaAreaCode) { if (iotaAreaCode === undefined || iotaAreaCode === null || typeof iotaAreaCode !== "string" || iotaAreaCode.length > 9) { return false; } else { if (!iotaAreaCode.endsWith("AA9")) { return false; } else { var remaining = iotaAreaCode.replace(/A*9$/, ""); if (remaining.length < 2 || remaining.length % 2 === 1) { return false; } else { // Check if all the remaining characters before the AA*9 are within our alphabet var re = new RegExp("^[" + IAC_APHABET.substr(0, 20) + "]*$"); return re.test(remaining); } } } } /** * Encode a location into an IOTA Area Code. * @param latitude The latitude in signed decimal degrees. Values less than -90 will be clipped to -90, values over 90 will be clipped to 90. * @param longitude The longitude in signed decimal degrees. This will be normalised to the range -180 to 180. * @param precision The desired code length. If omitted, CodePrecision.NORMAL will be used. For precision CodePrecision.EXTRA is recommended. * @returns The IOTA Area Code for the location. */ function encode(latitude, longitude, precision) { if (precision === void 0) { precision = CodePrecision.NORMAL; } if (precision < 2) { throw new Error("The precision is not valid"); } return fromOpenLocationCode(OpenLocationCode.encode(latitude, longitude, precision)); } /** * Decode an IOTA Area Code into a location. * @param iotaAreaCode The IOTA Area Code to convert. * @returns The location object. */ function decode(iotaAreaCode) { var olc = OpenLocationCode.decode(toOpenLocationCode(iotaAreaCode)); return { latitude: olc.latitudeCenter, longitude: olc.longitudeCenter, codePrecision: olc.codeLength, latitudeLow: olc.latitudeLo, latitudeHigh: olc.latitudeHi, longitudeLow: olc.longitudeLo, longitudeHigh: olc.longitudeHi }; } /** * Convert the Open Location Code to IOTA Area Code. * @param openLocationCode The Open Location Code to convert. * @returns The IOTA Area Code. */ function fromOpenLocationCode(openLocationCode) { if (!OpenLocationCode.isValid(openLocationCode)) { throw new Error("The openLocationCode is not valid"); } var iac = ""; for (var i = 0; i < openLocationCode.length; i++) { var idx = OLC_APHABET.indexOf(openLocationCode[i]); iac += IAC_APHABET[idx]; } return iac; } /** * Convert the IOTA Area Code to Open Location Code. * @param iotaAreaCode The IOTA Area Code to convert. * @returns The Open Location Code. */ function toOpenLocationCode(iotaAreaCode) { if (!isValid(iotaAreaCode)) { throw new Error("The iotaAreaCode is not valid"); } return iacToOlcInternal(iotaAreaCode); } /** * All the available precisions. */ var PRECISIONS = [2, 4, 6, 8, 10, 11]; /** * Decrease the precision of an area code. * @param iotaAreaCode The IOTA Area Code to decrease the precision. * @returns The decreased precision area code. */ function decreasePrecision(iotaAreaCode) { var decoded = decode(iotaAreaCode); if (decoded.codePrecision <= PRECISIONS[0]) { throw new Error("The precision can not be decreased any more"); } return internalSetPrecision(iotaAreaCode, PRECISIONS[PRECISIONS.indexOf(decoded.codePrecision) - 1], decoded); } /** * Increase the precision of an area code. * @param iotaAreaCode The IOTA Area Code to increase the precision. * @returns The increased precision area code. */ function increasePrecision(iotaAreaCode) { var decoded = decode(iotaAreaCode); if (decoded.codePrecision >= PRECISIONS[PRECISIONS.length - 1]) { throw new Error("The precision can not be increased any more"); } return internalSetPrecision(iotaAreaCode, PRECISIONS[PRECISIONS.indexOf(decoded.codePrecision) + 1], decoded); } /** * Set the precision of an area code. * @param iotaAreaCode The IOTA Area Code to set the precision. * @param codePrecision The new precision to set. * @returns The updated precision area code. */ function setPrecision(iotaAreaCode, codePrecision) { var decoded = decode(iotaAreaCode); return internalSetPrecision(iotaAreaCode, codePrecision, decoded); } /** * Set the precision of an area code. * @private * @param iotaAreaCode The IOTA Area Code to set the precision. * @param codePrecision The new precision to set. * @param decoded The decoded area code. * @returns The updated precision area code. */ function internalSetPrecision(iotaAreaCode, codePrecision, decoded) { if (PRECISIONS.indexOf(codePrecision) < 0) { throw new Error("codePrecision must be one of " + PRECISIONS.join(", ")); } if (codePrecision === decoded.codePrecision) { return iotaAreaCode; } else { // The new code precision is less than the current one // so just strip back the characters and pad if (codePrecision < decoded.codePrecision) { var reduced = iotaAreaCode.replace("9", "").substr(0, codePrecision); if (codePrecision <= 8) { return "" + reduced + "A".repeat(8 - codePrecision) + "9"; } else { return reduced.substr(0, 8) + "9" + reduced.substr(8); } } else { // New precision is higher so we need to do some maths // so just recalculate the location code based // on the current center. return encode(decoded.latitude, decoded.longitude, codePrecision); } } } /** * Get the display dimensions for a area code precision. * @param codePrecision The precision of an area code. * @returns The display dimensions for the code precision. */ function getPrecisionDimensions(codePrecision) { var dimensions = { 2: { blocksSizeDegrees: 20, blocksSizeDegreesFormatted: "20°", sizeMetres: 2200000, sizeMetresFormatted: "2200km" }, 4: { blocksSizeDegrees: 1, blocksSizeDegreesFormatted: "1°", sizeMetres: 110000, sizeMetresFormatted: "110km" }, 6: { blocksSizeDegrees: 0.05, blocksSizeDegreesFormatted: "0.05°", sizeMetres: 5500, sizeMetresFormatted: "110km" }, 8: { blocksSizeDegrees: 0.0025, blocksSizeDegreesFormatted: "0.0025°", sizeMetres: 275, sizeMetresFormatted: "275m" }, 10: { blocksSizeDegrees: 0.000125, blocksSizeDegreesFormatted: "0.000125°", sizeMetres: 14, sizeMetresFormatted: "14m" }, 11: { blocksSizeDegrees: undefined, blocksSizeDegreesFormatted: undefined, sizeMetres: 3.5, sizeMetresFormatted: "3.5m" } }; if (!dimensions[codePrecision]) { throw new Error("codePrecision must be 2, 4, 6, 8, 10 or 11"); } return dimensions[codePrecision]; } /** * Extract an IOTA Area Code from trytes. * @param trytes The trytes from which to try and extract the IOTA Area Code. * @returns The IOTA Area Code if one could be extracted, or undefined. */ function extract(trytes) { var reString = "([" + IAC_APHABET.substr(0, 9) + "][" + IAC_APHABET.substr(0, 18) + "][" + IAC_APHABET.substr(0, 21) + "]{6}9(?:[" + IAC_APHABET.substr(0, 20) + "]{2,3})?)"; var result = new RegExp(reString).exec(trytes); return result ? result[1] : undefined; } exports.CodePrecision = CodePrecision; exports.PRECISIONS = PRECISIONS; exports.decode = decode; exports.decreasePrecision = decreasePrecision; exports.encode = encode; exports.extract = extract; exports.fromOpenLocationCode = fromOpenLocationCode; exports.getPrecisionDimensions = getPrecisionDimensions; exports.increasePrecision = increasePrecision; exports.internalSetPrecision = internalSetPrecision; exports.isValid = isValid; exports.isValidPartial = isValidPartial; exports.setPrecision = setPrecision; exports.toOpenLocationCode = toOpenLocationCode; Object.defineProperty(exports, '__esModule', { value: true }); }));