...
Code Block |
---|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* Geodesy representation conversion functions (c) Chris Veness 2002-2019 */ /* MIT Licence */ /* www.movable-type.co.uk/scripts/latlong.html */ /* www.movable-type.co.uk/scripts/js/geodesy/geodesy-library.html#dms */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* eslint no-irregular-whitespace: [2, { skipComments: true }] */ /** * Latitude/longitude points may be represented as decimal degrees, or subdivided into sexagesimal * minutes and seconds. This module provides methods for parsing and representing degrees / minutes * / seconds. * * @module dms */ /* Degree-minutes-seconds (& cardinal directions) separator character */ let dmsSeparator = '\u202f'; // U+202F = 'narrow no-break space' /** * Functions for parsing and representing degrees / minutes / seconds. */ class Dms { // note Unicode Degree = U+00B0. Prime = U+2032, Double prime = U+2033 /** * Separator character to be used to separate degrees, minutes, seconds, and cardinal directions. * * Default separator is U+202F ‘narrow no-break space’. * * To change this (e.g. to empty string or full space), set Dms.separator prior to invoking * formatting. * * @example * import LatLon, { Dms } from '/js/geodesy/latlon-spherical.js'; * const p = new LatLon(51.2, 0.33).toString('dms'); // 51° 12′ 00″ N, 000° 19′ 48″ E * Dms.separator = ''; // no separator * const pʹ = new LatLon(51.2, 0.33).toString('dms'); // 51°12′00″N, 000°19′48″E */ static get separator() { return dmsSeparator; } static set separator(char) { dmsSeparator = char; } /** * Parses string representing degrees/minutes/seconds into numeric degrees. * * This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally * suffixed by compass direction (NSEW); a variety of separators are accepted. Examples -3.62, * '3 37 12W', '3°37′12″W'. * * Thousands/decimal separators must be comma/dot; use Dms.fromLocale to convert locale-specific * thousands/decimal separators. * * @param {string|number} dms - Degrees or deg/min/sec in variety of formats. * @returns {number} Degrees as decimal number. * * @example * const lat = Dms.parse('51° 28′ 40.37″ N'); * const lon = Dms.parse('000° 00′ 05.29″ W'); * const p1 = new LatLon(lat, lon); // 51.4779°N, 000.0015°W */ static parse(dms) { // check for signed decimal degrees without NSEW, if so return it directly if (!isNaN(parseFloat(dms)) && isFinite(dms)) return Number(dms); // strip off any sign or compass dir'n & split out separate d/m/s const dmsParts = String(dms).trim().replace(/^-/, '').replace(/[NSEW]$/i, '').split(/[^0-9.,]+/); if (dmsParts[dmsParts.length-1]=='') dmsParts.splice(dmsParts.length-1); // from trailing symbol if (dmsParts == '') return NaN; // and convert to decimal degrees... let deg = null; switch (dmsParts.length) { case 3: // interpret 3-part result as d/m/s deg = dmsParts[0]/1 + dmsParts[1]/60 + dmsParts[2]/3600; break; case 2: // interpret 2-part result as d/m deg = dmsParts[0]/1 + dmsParts[1]/60; break; case 1: // just d (possibly decimal) or non-separated dddmmss deg = dmsParts[0]; // check for fixed-width unseparated format eg 0033709W //if (/[NS]/i.test(dmsParts)) deg = '0' + deg; // - normalise N/S to 3-digit degrees //if (/[0-9]{7}/.test(deg)) deg = deg.slice(0,3)/1 + deg.slice(3,5)/60 + deg.slice(5)/3600; break; default: return NaN; } if (/^-|[WS]$/i.test(dms.trim())) deg = -deg; // take '-', west and south as -ve return Number(deg); } /** * Converts decimal degrees to deg/min/sec format * - degree, prime, double-prime symbols are added, but sign is discarded, though no compass * direction is added. * - degrees are zero-padded to 3 digits; for degrees latitude, use .slice(1) to remove leading * zero. * * @private * @param {number} deg - Degrees to be formatted as specified. * @param {string} [format=d] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec. * @param {number} [dp=4|2|0] - Number of decimal places to use – default 4 for d, 2 for dm, 0 for dms. * @returns {string} Degrees formatted as deg/min/secs according to specified format. */ static toDms(deg, format='d', dp=undefined) { if (isNaN(deg)) return null; // give up here if we can't make a number from deg if (typeof deg == 'string' && deg.trim() == '') return null; if (typeof deg == 'boolean') return null; if (deg == Infinity) return null; if (deg == null) return null; // default values if (dp === undefined) { switch (format) { case 'd': case 'deg': dp = 4; break; case 'dm': case 'deg+min': dp = 2; break; case 'dms': case 'deg+min+sec': dp = 0; break; default: format = 'd'; dp = 4; break; // be forgiving on invalid format } } deg = Math.abs(deg); // (unsigned result ready for appending compass dir'n) let dms = null, d = null, m = null, s = null; switch (format) { default: // invalid format spec! case 'd': case 'deg': d = deg.toFixed(dp); // round/right-pad degrees if (d<100) d = '0' + d; // left-pad with leading zeros (note may include decimals) if (d<10) d = '0' + d; dms = d + '°'; break; case 'dm': case 'deg+min': d = Math.floor(deg); // get component deg m = ((deg*60) % 60).toFixed(dp); // get component min & round/right-pad if (m == 60) { m = (0).toFixed(dp); d++; } // check for rounding up d = ('000'+d).slice(-3); // left-pad with leading zeros if (m<10) m = '0' + m; // left-pad with leading zeros (note may include decimals) dms = d + '°'+Dms.separator + m + '′'; break; case 'dms': case 'deg+min+sec': d = Math.floor(deg); // get component deg m = Math.floor((deg*3600)/60) % 60; // get component min s = (deg*3600 % 60).toFixed(dp); // get component sec & round/right-pad if (s == 60) { s = (0).toFixed(dp); m++; } // check for rounding up if (m == 60) { m = 0; d++; } // check for rounding up d = ('000'+d).slice(-3); // left-pad with leading zeros m = ('00'+m).slice(-2); // left-pad with leading zeros if (s<10) s = '0' + s; // left-pad with leading zeros (note may include decimals) dms = d + '°'+Dms.separator + m + '′'+Dms.separator + s + '″'; break; } return dms; } /** * Converts numeric degrees to deg/min/sec latitude (2-digit degrees, suffixed with N/S). * * @param {number} deg - Degrees to be formatted as specified. * @param {string} [format=d] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec. * @param {number} [dp=4|2|0] - Number of decimal places to use – default 4 for d, 2 for dm, 0 for dms. * @returns {string} Degrees formatted as deg/min/secs according to specified format. * * @example * const lat = Dms.toLat(-3.62, 'dms'); // 3°37′12″S */ static toLat(deg, format, dp) { const lat = Dms.toDms(Dms.wrap90(deg), format, dp); return lat===null ? '–' : lat.slice(1) + Dms.separator + (deg<0 ? 'S' : 'N'); // knock off initial '0' for lat! } /** * Convert numeric degrees to deg/min/sec longitude (3-digit degrees, suffixed with E/W). * * @param {number} deg - Degrees to be formatted as specified. * @param {string} [format=d] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec. * @param {number} [dp=4|2|0] - Number of decimal places to use – default 4 for d, 2 for dm, 0 for dms. * @returns {string} Degrees formatted as deg/min/secs according to specified format. * * @example * const lon = Dms.toLon(-3.62, 'dms'); // 3°37′12″W */ static toLon(deg, format, dp) { const lon = Dms.toDms(Dms.wrap180(deg), format, dp); return lon===null ? '–' : lon + Dms.separator + (deg<0 ? 'W' : 'E'); } /** * Converts numeric degrees to deg/min/sec as a bearing (0°..360°). * * @param {number} deg - Degrees to be formatted as specified. * @param {string} [format=d] - Return value as 'd', 'dm', 'dms' for deg, deg+min, deg+min+sec. * @param {number} [dp=4|2|0] - Number of decimal places to use – default 4 for d, 2 for dm, 0 for dms. * @returns {string} Degrees formatted as deg/min/secs according to specified format. * * @example * const lon = Dms.toBrng(-3.62, 'dms'); // 356°22′48″ */ static toBrng(deg, format, dp) { const brng = Dms.toDms(Dms.wrap360(deg), format, dp); return brng===null ? '–' : brng.replace('360', '0'); // just in case rounding took us up to 360°! } /** * Converts DMS string from locale thousands/decimal separators to JavaScript comma/dot separators * for subsequent parsing. * * Both thousands and decimal separators must be followed by a numeric character, to facilitate * parsing of single lat/long string (in which whitespace must be left after the comma separator). * * @param {string} str - Degrees/minutes/seconds formatted with locale separators. * @returns {string} Degrees/minutes/seconds formatted with standard Javascript separators. * * @example * const lat = Dms.fromLocale('51°28′40,12″N'); // '51°28′40.12″N' in France * const p = new LatLon(Dms.fromLocale('51°28′40,37″N, 000°00′05,29″W'); // '51.4779°N, 000.0015°W' in France */ static fromLocale(str) { const locale = (123456.789).toLocaleString(); const separator = { thousands: locale.slice(3, 4), decimal: locale.slice(7, 8) }; return str.replace(separator.thousands, '⁜').replace(separator.decimal, '.').replace('⁜', ','); } /** * Converts DMS string from JavaScript comma/dot thousands/decimal separators to locale separators. * * Can also be used to format standard numbers such as distances. * * @param {string} str - Degrees/minutes/seconds formatted with standard Javascript separators. * @returns {string} Degrees/minutes/seconds formatted with locale separators. * * @example * const Dms.toLocale('123,456.789'); // '123.456,789' in France * const Dms.toLocale('51°28′40.12″N, 000°00′05.31″W'); // '51°28′40,12″N, 000°00′05,31″W' in France */ static toLocale(str) { const locale = (123456.789).toLocaleString(); const separator = { thousands: locale.slice(3, 4), decimal: locale.slice(7, 8) }; return str.replace(/,([0-9])/, '⁜$1').replace('.', separator.decimal).replace('⁜', separator.thousands); } /** * Returns compass point (to given precision) for supplied bearing. * * @param {number} bearing - Bearing in degrees from north. * @param {number} [precision=3] - Precision (1:cardinal / 2:intercardinal / 3:secondary-intercardinal). * @returns {string} Compass point for supplied bearing. * * @example * const point = Dms.compassPoint(24); // point = 'NNE' * const point = Dms.compassPoint(24, 1); // point = 'N' */ static compassPoint(bearing, precision=3) { if (![ 1, 2, 3 ].includes(Number(precision))) throw new RangeError(`invalid precision ‘${precision}’`); // note precision could be extended to 4 for quarter-winds (eg NbNW), but I think they are little used bearing = Dms.wrap360(bearing); // normalise to range 0..360° const cardinals = [ 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW' ]; const n = 4 * 2**(precision-1); // no of compass points at req’d precision (1=>4, 2=>8, 3=>16) const cardinal = cardinals[Math.round(bearing*n/360)%n * 16/n]; return cardinal; } /** * Constrain degrees to range 0..360 (e.g. for bearings); -1 => 359, 361 => 1. * * @private * @param {number} degrees * @returns degrees within range 0..360. */ static wrap360(degrees) { if (0<=degrees && degrees<360) return degrees; // avoid rounding due to arithmetic ops if within range return (degrees%360+360) % 360; // sawtooth wave p:360, a:360 } /** * Constrain degrees to range -180..+180 (e.g. for longitude); -181 => 179, 181 => -179. * * @private * @param {number} degrees * @returns degrees within range -180..+180. */ static wrap180(degrees) { if (-180<degrees && degrees<=180) return degrees; // avoid rounding due to arithmetic ops if within range return (degrees+540)%360-180; // sawtooth wave p:180, a:±180 } /** * Constrain degrees to range -90..+90 (e.g. for latitude); -91 => -89, 91 => 89. * * @private * @param {number} degrees * @returns degrees within range -90..+90. */ static wrap90(degrees) { if (-90<=degrees && degrees<=90) return degrees; // avoid rounding due to arithmetic ops if within range return Math.abs((degrees%360 + 270)%360 - 180) - 90; // triangle wave p:360 a:±90 TODO: fix e.g. -315° } } // Extend Number object with methods to convert between degrees & radians Number.prototype.toRadians = function() { return this * Math.PI / 180; }; Number.prototype.toDegrees = function() { return this * 180 / Math.PI; }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ export default Dms; |
how to get polygons for a circle from lat/long