/** * dms module * @module dms */ // Just return a value to define the module export. // This example returns an object, but the module // can return a function as the exported value. // Matches DMS DmsCoordinates // http://regexpal.com/?flags=gim®ex=^%28-%3F\d%2B%28%3F%3A\.\d%2B%29%3F%29[%C2%B0%3Ad]%3F\s%3F%28%3F%3A%28\d%2B%28%3F%3A\.\d%2B%29%3F%29[%27%E2%80%B2%3A]%3F\s%3F%28%3F%3A%28\d%2B%28%3F%3A\.\d%2B%29%3F%29[%22%E2%80%B3]%3F%29%3F%29%3F\s%3F%28[NSEW]%29%3F&input=40%3A26%3A46N%2C79%3A56%3A55W%0A40%3A26%3A46.302N%2079%3A56%3A55.903W%0A40%C2%B026%E2%80%B247%E2%80%B3N%2079%C2%B058%E2%80%B236%E2%80%B3W%0A40d%2026%E2%80%B2%2047%E2%80%B3%20N%2079d%2058%E2%80%B2%2036%E2%80%B3%20W%0A40.446195N%2079.948862W%0A40.446195%2C%20-79.948862%0A40%C2%B0%2026.7717%2C%20-79%C2%B0%2056.93172%0A const dmsRe = /^(-?\d+(?:\.\d+)?)[°:d]?\s?(?:(\d+(?:\.\d+)?)['′ʹ:]?\s?(?:(\d+(?:\.\d+)?)["″ʺ]?)?)?\s?([NSEW])?/i; /** * Removes the decimal part of a number without rounding up. * @param {number} n * @returns {number} * @private */ function truncate(n) { return n > 0 ? Math.floor(n) : Math.ceil(n); } export class Dms { _dd; _hemisphere; /** * Value in decimal degrees * @member {number} * @readonly */ get dd() { return this._dd; } /** * Hemisphere * @member {string} * @readonly */ get hemisphere() { return this._hemisphere; } /** * @constructor module:dms.Dms * @param {number} dd * @param {string} longOrLat */ constructor(dd, longOrLat) { this._dd = dd; this._hemisphere = /^[WE]|(?:lon)/i.test(longOrLat) ? dd < 0 ? "W" : "E" : dd < 0 ? "S" : "N"; } /** * Returns the DMS parts as an array. * The first three elements of the returned array are numbers: * degrees, minutes, and seconds respectively. The fourth * element is a string indicating the hemisphere: "N", "S", "E", or "W". * @returns {Array.<(number|string)>} * @deprecated */ getDmsArray() { return this.dmsArray; } /** * Returns the DMS parts as an array. * The first three elements of the returned array are numbers: * degrees, minutes, and seconds respectively. The fourth * element is a string indicating the hemisphere: "N", "S", "E", or "W". * @returns {Array.<(number|string)>} */ get dmsArray() { const absDD = Math.abs(this._dd); const degrees = truncate(absDD); const minutes = truncate((absDD - degrees) * 60); const seconds = (absDD - degrees - minutes / 60) * Math.pow(60, 2); return [degrees, minutes, seconds, this._hemisphere]; } /** * Returns the DMS value as a string. * @param {number} [precision] - number of digits after the decimal point in seconds * @returns {string} */ toString(precision) { const dmsArray = this.getDmsArray(); const second = isNaN(Number(precision)) ? dmsArray[2] : dmsArray[2].toFixed(precision); return `${dmsArray[0]}°${dmsArray[1]}′${second}″ ${dmsArray[3]}`; } } /** * @typedef {Object} DmsArrays * @property {Array.<(number|string)>} longitude * @property {Array.<(number|string)>} latitude */ export default class DmsCoordinates { lat; lon; // Results of match will be [full coords string, Degrees, minutes (if any), seconds (if any), hemisphere (if any)] // E.g., ["40:26:46.302N", "40", "26", "46.302", "N"] // E.g., ["40.446195N", "40.446195", undefined, undefined, "N"] /** * A regular expression matching DMS coordinate. * Example matches: * E.g., ["40:26:46.302N", "40", "26", "46.302", "N"] * E.g., ["40.446195N", "40.446195", undefined, undefined, "N"] * @type {RegExp} * @static */ static dmsRe = dmsRe; _longitude; _latitude; /** * Longitude * @type {module:dms.Dms} longitude - Longitude (X coordinate); */ get longitude() { return this._longitude; } /** * Latitude * @type {module:dms.Dms} longitude - Latitude (y coordinate); */ get latitude() { return this._latitude; } /** * Represents a location on the earth in WGS 1984 coordinates. * @constructor module:dms.DmsCoordinates * @param {number} latitude - WGS 84 Y coordinates * @param {number} longitude - WGS 84 X coordinates * @throws {TypeError} - latitude and longitude must be numbers. * @throws {RangeError} - latitude must be between -180 and 180, and longitude between -90 and 90. Neither can be NaN. */ constructor(lat, lon) { this.lat = lat; this.lon = lon; if (typeof lat !== "number" || typeof lon !== "number") { throw TypeError("The longitude and latitude parameters must be numbers."); } if (isNaN(lon) || lon < -180 || lon > 180) { throw RangeError("longitude must be between -180 and 180"); } if (isNaN(lat) || lat < -90 || lat > 90) { throw RangeError("latitude must be between -90 and 90"); } this._longitude = new Dms(lon, "long"); this._latitude = new Dms(lat, "lat"); } /** * Returns an object containing arrays containing degree / minute / second components. * @returns {DmsArrays} * @deprecated */ getDmsArrays() { return this.dmsArrays; } /** * Returns an object containing arrays containing degree / minute / second components. * @type {DmsArrays} */ get dmsArrays() { return { longitude: this.longitude.dmsArray, latitude: this.latitude.dmsArray, }; } /** * Returns the coordinates to a comma-separated string. * @returns {string} */ toString() { return [this.latitude, this.longitude].join(", "); } } /** * Parses a Degrees Minutes Seconds string into a Decimal Degrees number. * @param {string} dmsStr A string containing a coordinate in either DMS or DD format. * @return {Number} If dmsStr is a valid coordinate string, the value in decimal degrees will be returned. Otherwise NaN will be returned. */ export function parseDms(dmsStr) { let output = NaN; const dmsMatch = dmsRe.exec(dmsStr); if (dmsMatch) { const degrees = Number(dmsMatch[1]); const minutes = typeof (dmsMatch[2]) !== "undefined" ? Number(dmsMatch[2]) / 60 : 0; const seconds = typeof (dmsMatch[3]) !== "undefined" ? Number(dmsMatch[3]) / 3600 : 0; const hemisphere = dmsMatch[4] || null; if (hemisphere !== null && /[SW]/i.test(hemisphere)) { output = -Math.abs(degrees) - minutes - seconds; } else { output = degrees + minutes + seconds; } } return output; }