├── .gitignore ├── CMakeLists.txt ├── HOWTO-RELEASE.txt ├── LICENSE.txt ├── README.md ├── distrib-JavaScript └── 00README.md ├── dms ├── CMakeLists.txt ├── DMS.js ├── HEAD.js.in ├── README.md ├── TAIL.js ├── package.json.in ├── test │ └── dmstest.js └── types │ └── geographiclib-dms.d.ts ├── doc ├── .htaccess ├── CMakeLists.txt ├── FOOTER.html ├── GeographicLib.md.in ├── HEADER.html ├── conf.json └── tutorials │ ├── 1-geodesics.md │ ├── 2-interface.md │ ├── 3-examples.md │ └── tutorials.json ├── geodesic ├── CMakeLists.txt ├── Geodesic.js ├── GeodesicLine.js ├── HEAD.js.in ├── Math.js.in ├── PolygonArea.js ├── README.md ├── TAIL.js ├── package.json.in ├── test │ ├── geodesictest.js │ └── signtest.js └── types │ └── geographiclib-geodesic.d.ts └── samples ├── CMakeLists.txt ├── geod-calc.html ├── geod-google-instructions.html └── geod-google.html /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | BUILD* 3 | distrib-JavaScript/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.13.0) 2 | project (GeographicLib-JavaScript NONE) 3 | set (GEOD_PROJECT geographiclib-geodesic) 4 | set (DMS_PROJECT geographiclib-dms) 5 | 6 | # Version information 7 | set (PROJECT_VERSION_MAJOR 2) 8 | set (PROJECT_VERSION_MINOR 1) 9 | set (PROJECT_VERSION_PATCH 1) 10 | # Always include patch number in version 11 | set (PROJECT_VERSION 12 | "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") 13 | set (PROJECT_VERSION_SUFFIX "") 14 | set (PROJECT_FULLVERSION "${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX}") 15 | 16 | set (RELEASE_DATE "2024-08-18") 17 | 18 | # Set a default build type for single-configuration cmake generators if 19 | # no build type is set. 20 | if (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 21 | set (CMAKE_BUILD_TYPE Release) 22 | endif () 23 | 24 | set (MODDIR "lib/node_modules" 25 | CACHE STRING "Installation directory for GeographicLib-JavaScript module") 26 | option (BUILD_DOCUMENTATION "Use doxygen to create the documentation" OFF) 27 | 28 | find_program (MINIFY jsmin REQUIRED) 29 | find_program (LINT jshint) 30 | find_program (MOCHA mocha) 31 | find_program (RSYNC rsync) 32 | find_program (NPM npm) 33 | 34 | set (DESTGEOD ${PROJECT_BINARY_DIR}/node_modules/${GEOD_PROJECT}) 35 | set (PACKAGEDGEODJS ${DESTGEOD}/${GEOD_PROJECT}.js) 36 | set (PACKAGEDGEODMINJS ${DESTGEOD}/${GEOD_PROJECT}.min.js) 37 | set (PACKGEOD 38 | "${PROJECT_BINARY_DIR}/${GEOD_PROJECT}-${PROJECT_FULLVERSION}.tgz") 39 | set (DESTDMS ${PROJECT_BINARY_DIR}/node_modules/${DMS_PROJECT}) 40 | set (PACKAGEDDMSJS ${DESTDMS}/${DMS_PROJECT}.js) 41 | set (PACKAGEDDMSMINJS ${DESTDMS}/${DMS_PROJECT}.min.js) 42 | set (PACKDMS "${PROJECT_BINARY_DIR}/${DMS_PROJECT}-${PROJECT_FULLVERSION}.tgz") 43 | 44 | enable_testing () 45 | 46 | add_subdirectory (geodesic) 47 | add_subdirectory (dms) 48 | add_subdirectory (samples) 49 | 50 | if (BUILD_DOCUMENTATION) 51 | # For JavaScript documentation 52 | find_program (JSDOC jsdoc REQUIRED) 53 | add_subdirectory (doc) 54 | endif () 55 | 56 | if (LINT) 57 | add_custom_target (lint) 58 | add_dependencies (lint lintgeodesic lintdms) 59 | endif () 60 | 61 | if (NPM) 62 | add_custom_command (OUTPUT ${PACKGEOD} ${PACKDMS} 63 | COMMAND ${NPM} pack ${DESTGEOD} ${DESTDMS} 64 | DEPENDS ${PACKAGEDGEODJS} ${PACKAGEDDMSJS} 65 | ${DESTGEOD}/package.json ${DESTDMS}/package.json 66 | VERBATIM) 67 | add_custom_target (pack ALL 68 | DEPENDS ${PACKGEOD} ${PACKDMS} bundlegeod bundledms) 69 | if (RSYNC) 70 | set (USER karney) 71 | set (DATAROOT $ENV{HOME}/web/geographiclib-files/distrib-JavaScript) 72 | set (FRSDEPLOY ${USER}@frs.sourceforge.net:/home/frs/project/geographiclib) 73 | add_custom_target (stage-dist 74 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 75 | ${PACKGEOD} ${PACKDMS} ${PROJECT_SOURCE_DIR}/distrib-JavaScript/ 76 | COMMAND ${RSYNC} --delete -av --exclude '*~' 77 | ${PROJECT_SOURCE_DIR}/distrib-JavaScript/ ${DATAROOT}/) 78 | add_dependencies (stage-dist pack) 79 | add_custom_target (deploy-dist 80 | COMMAND ${RSYNC} --delete -av ${DATAROOT} ${FRSDEPLOY}/) 81 | endif () 82 | endif () 83 | 84 | if (IS_DIRECTORY ${PROJECT_SOURCE_DIR}/.git AND NOT WIN32) 85 | add_custom_target (checktrailingspace 86 | COMMAND git ls-files | xargs grep '[ \t]$$' || true 87 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 88 | COMMENT "Looking for trailing spaces") 89 | add_custom_target (checktabs 90 | COMMAND git ls-files | xargs grep '\t' || true 91 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 92 | COMMENT "Looking for tabs") 93 | add_custom_target (checkblanklines 94 | COMMAND git ls-files | 95 | while read f\; do tr 'X\\n' 'YX' < $$f | 96 | grep -E '\(^X|XXX|XX$$|[^X]$$\)' > /dev/null && echo $$f\; done || true 97 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 98 | COMMENT "Looking for extra blank lines") 99 | 100 | add_custom_target (sanitize) 101 | add_dependencies (sanitize checktrailingspace checktabs checkblanklines) 102 | endif () 103 | -------------------------------------------------------------------------------- /HOWTO-RELEASE.txt: -------------------------------------------------------------------------------- 1 | js packages needed (npm -i -g ... to install in ~/.local) 2 | jshint 3 | mocha 4 | minify 5 | jsdoc 6 | 7 | Update change log in doc/GeographicLib.md. 8 | 9 | Build with 10 | 11 | cmake -D BUILD_DOCUMENTATION=ON -D CMAKE_INSTALL_PREFIX=/tmp/js -B BUILD -S . 12 | cd BUILD 13 | make all # packages up node packages 14 | make lint 15 | make test 16 | make install 17 | 18 | This creates local modules geographiclib + geographiclib-dms in 19 | BUILD/node_modules 20 | 21 | and packages 22 | BUILD/geographiclib-${PROJECT_FULLVERSION}.tgz 23 | BUILD/geographiclib-dms-${PROJECT_FULLVERSION}.tgz 24 | 25 | installed modules in 26 | ${CMAKE_INSTALL_PREFIX}/${MODDIR} 27 | 28 | Options for make 29 | CMAKE_INSTALL_PREFIX head of install tree (default /usr/local) 30 | MODDIR where to install modules (default lib/node_modules) 31 | BUILD_DOCUMENTATION build documentation (default OFF) 32 | 33 | Other cmake targets 34 | sanitize (hygiene on source files) 35 | stage-doc, deploy-doc (move documentation to staging, sourceforge) 36 | stage-dist, deploy-dist (move distribution to staging, sourceforge) 37 | stage-scripts, deploy-scripts (move sample scripts to staging, sourceforge) 38 | 39 | Deployment to npmjs.org 40 | npm login 41 | npm publish BUILD/geographiclib-geodesic-${PROJECT_FULLVERSION}.tgz 42 | npm publish BUILD/geographiclib-dms-${PROJECT_FULLVERSION}.tgz 43 | 44 | N.B. node doesn't look in /usr/local/lib/node_modules by default, so 45 | if using globally installed package, invoke node with 46 | 47 | NODE_PATH=/usr/local/lib/node_modules node 48 | 49 | To debug path for node 50 | NODE_DEBUG=module node 51 | To set path for node 52 | 53 | To retrieve a tarball from npm use, e.g., 54 | wget `npm view geographiclib dist.tarball` 55 | 56 | Delete alpha/beta tgz files from distrib-JavaScript 57 | Reset doc link for documentation. 58 | 59 | TODO: add version number + links to files in scripts 60 | 61 | Download scripts from 62 | https://cdn.jsdelivr.com/ 63 | 64 | 70 | 71 | Get sha256 checksums with 72 | for f in node_modules/geographiclib-*/geographiclib-*.min.js; do 73 | echo $f 74 | openssl dgst -sha256 -binary $f | openssl base64 -A 75 | echo 76 | done 77 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT). 2 | 3 | Copyright (c) 2011-2022, Charles Karney 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript implementation of the some routines in GeographicLib 2 | 3 | This repository hosts two JavaScript packages 4 | [geographiclib-geodesic](https://www.npmjs.com/package/geographiclib-geodesic) 5 | and 6 | [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms). 7 | 8 | Prior to version 2.0.0, these were combined in [node package 9 | geographiclib](https://www.npmjs.com/package/geographiclib). This 10 | package will be deprecated on 2023-05-01. 11 | 12 | Licensed under the MIT/X11 License; see 13 | [LICENSE.txt](https://geographiclib.sourceforge.io/LICENSE.txt). 14 | 15 | ## Documentation 16 | 17 | Full documentation is provided at 18 | [https://geographiclib.sourceforge.io/JavaScript/doc]( 19 | https://geographiclib.sourceforge.io/JavaScript/doc/index.html). 20 | -------------------------------------------------------------------------------- /distrib-JavaScript/00README.md: -------------------------------------------------------------------------------- 1 | # JavaScript implementation of the geodesic routines in GeographicLib 2 | 3 | node packages are `.tgz` files. 4 | ```bash 5 | npm install [PACKAGE-FILE] 6 | ``` 7 | 8 | These packages are also hosted on [npmjs](https://www.npmjs.com) as 9 | [geographiclib-geodesic](https://www.npmjs.com/package/geographiclib-geodesic) 10 | and 11 | [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms). 12 | 13 | The algorithms are documented in 14 | 15 | * C. F. F. Karney, 16 | [Algorithms for geodesics](https://doi.org/10.1007/s00190-012-0578-z), 17 | J. Geodesy **87**(1), 43–55 (2013); 18 | [Addenda](https://geographiclib.sourceforge.io/geod-addenda.html). 19 | 20 | Other links: 21 | 22 | * Library documentation: https://geographiclib.sourceforge.io/JavaScript/doc 23 | * Git repository: https://github.com/geographiclib/geographiclib-js 24 | * GeographicLib: https://geographiclib.sourceforge.io 25 | * Author: Charles Karney, 26 | -------------------------------------------------------------------------------- /dms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file (package.json.in ${DESTDMS}/package.json @ONLY) 2 | configure_file (README.md ${DESTDMS} COPYONLY) 3 | configure_file (../LICENSE.txt ${DESTDMS} COPYONLY) 4 | 5 | configure_file (HEAD.js.in HEAD.js @ONLY) 6 | set (BUNDLEFILES ${CMAKE_CURRENT_BINARY_DIR}/HEAD.js) 7 | 8 | set (SOURCES DMS.js) 9 | foreach (_f ${SOURCES}) 10 | configure_file (${_f} ${DESTDMS}/src/${_f} COPYONLY) 11 | list (APPEND BUNDLEFILES ${DESTDMS}/src/${_f}) 12 | endforeach () 13 | 14 | configure_file (TAIL.js . COPYONLY) 15 | list (APPEND BUNDLEFILES ${CMAKE_CURRENT_BINARY_DIR}/TAIL.js) 16 | 17 | configure_file (test/dmstest.js ${DESTDMS}/test/dmstest.js 18 | COPYONLY) 19 | configure_file (types/${DMS_PROJECT}.d.ts ${DESTDMS}/types/${DMS_PROJECT}.d.ts 20 | COPYONLY) 21 | 22 | add_custom_command (OUTPUT ${PACKAGEDDMSJS} 23 | COMMAND ${CMAKE_COMMAND} -E cat ${BUNDLEFILES} > ${PACKAGEDDMSJS} 24 | DEPENDS ${BUNDLEFILES} 25 | COMMENT "Making JS bundle for dms") 26 | 27 | add_custom_command (OUTPUT ${PACKAGEDDMSMINJS} 28 | # unicode character in minified file needs to be replaced 29 | COMMAND ${MINIFY} ${PACKAGEDDMSJS} > ${PACKAGEDDMSMINJS} 30 | DEPENDS ${PACKAGEDDMSJS} 31 | COMMENT "Making minified JS bundle for dms") 32 | 33 | add_custom_target (bundledms ALL 34 | DEPENDS ${PACKAGEDDMSJS} ${PACKAGEDDMSMINJS}) 35 | 36 | install (DIRECTORY ${DESTDMS} 37 | DESTINATION ${MODDIR} 38 | FILES_MATCHING PATTERN "*.[jt]s" PATTERN "*.txt" PATTERN "*.json") 39 | 40 | if (MOCHA) 41 | add_test (NAME testdms 42 | COMMAND ${MOCHA} 43 | WORKING_DIRECTORY ${DESTDMS}) 44 | endif () 45 | 46 | # linting... 47 | if (LINT) 48 | add_custom_target (lintdms ${LINT} src WORKING_DIRECTORY ${DESTDMS} 49 | COMMENT "Linting dms with ${LINT}") 50 | endif () 51 | -------------------------------------------------------------------------------- /dms/DMS.js: -------------------------------------------------------------------------------- 1 | /* 2 | * DMS.js 3 | * Transcription of DMS.[ch]pp into JavaScript. 4 | * 5 | * See the documentation for the C++ class. The conversion is a literal 6 | * conversion from C++. 7 | * 8 | * Copyright (c) Charles Karney (2011-2020) and licensed 9 | * under the MIT/X11 License. For more information, see 10 | * https://geographiclib.sourceforge.io/ 11 | */ 12 | 13 | var DMS = {}; 14 | 15 | (function( 16 | /** 17 | * @exports DMS 18 | * @description Decode/Encode angles expressed as degrees, minutes, and 19 | * seconds. This module defines several constants: 20 | * - hemisphere indicator (returned by 21 | * {@link module:DMS.Decode Decode}) and a formatting 22 | * indicator (used by 23 | * {@link module:DMS.Encode Encode}) 24 | * - NONE = 0, no designator and format as plain angle; 25 | * - LATITUDE = 1, a N/S designator and format as latitude; 26 | * - LONGITUDE = 2, an E/W designator and format as longitude; 27 | * - AZIMUTH = 3, format as azimuth; 28 | * - the specification of the trailing component in 29 | * {@link module:DMS.Encode Encode} 30 | * - DEGREE = 0; 31 | * - MINUTE = 1; 32 | * - SECOND = 2. 33 | * @example 34 | * var DMS = require("geographiclib-dms"), 35 | * ang = DMS.Decode("127:54:3.123123W"); 36 | * console.log("Azimuth " + 37 | * DMS.Encode(ang.val, DMS.MINUTE, 7, ang.ind) + 38 | * " = " + ang.val.toFixed(9)); 39 | */ 40 | d) { 41 | "use strict"; 42 | 43 | var lookup, zerofill, internalDecode, numMatch, 44 | hemispheres_ = "SNWE", 45 | signs_ = "-+", 46 | digits_ = "0123456789", 47 | dmsindicators_ = "D'\":", 48 | // dmsindicatorsu_ = "\u00b0\u2032\u2033"; // Unicode variants 49 | dmsindicatorsu_ = "\u00b0'\"", // Use degree symbol 50 | // Minified js messes up degree symbol, but manually fix this 51 | // dmsindicatorsu_ = "d'\"", // Use d for degrees 52 | components_ = ["degrees", "minutes", "seconds"]; 53 | lookup = function(s, c) { 54 | return s.indexOf(c.toUpperCase()); 55 | }; 56 | zerofill = function(s, n) { 57 | return "0000".substr(0, Math.max(0, Math.min(4, n-s.length))) + s; 58 | }; 59 | d.NONE = 0; 60 | d.LATITUDE = 1; 61 | d.LONGITUDE = 2; 62 | d.AZIMUTH = 3; 63 | d.DEGREE = 0; 64 | d.MINUTE = 1; 65 | d.SECOND = 2; 66 | 67 | /** 68 | * @summary Decode a DMS string. 69 | * @param {string} dms the string. 70 | * @return {object} r where r.val is the decoded value (degrees) and r.ind 71 | * is a hemisphere designator, one of NONE, LATITUDE, LONGITUDE. 72 | * @throws an error if the string is illegal. 73 | * 74 | * @description Convert a DMS string into an angle. 75 | * Degrees, minutes, and seconds are indicated by the characters d, ' 76 | * (single quote), " (double quote), and these components may only be 77 | * given in this order. Any (but not all) components may be omitted and 78 | * other symbols (e.g., the ° symbol for degrees and the unicode prime 79 | * and double prime symbols for minutes and seconds) may be substituted; 80 | * two single quotes can be used instead of ". The last component 81 | * indicator may be omitted and is assumed to be the next smallest unit 82 | * (thus 33d10 is interpreted as 33d10'). The final component may be a 83 | * decimal fraction but the non-final components must be integers. Instead 84 | * of using d, ', and " to indicate degrees, minutes, and seconds, : 85 | * (colon) may be used to separate these components (numbers must 86 | * appear before and after each colon); thus 50d30'10.3" may be 87 | * written as 50:30:10.3, 5.5' may be written 0:5.5, and so on. The 88 | * integer parts of the minutes and seconds components must be less 89 | * than 60. A single leading sign is permitted. A hemisphere designator 90 | * (N, E, W, S) may be added to the beginning or end of the string. The 91 | * result is multiplied by the implied sign of the hemisphere designator 92 | * (negative for S and W). In addition ind is set to DMS.LATITUDE if N 93 | * or S is present, to DMS.LONGITUDE if E or W is present, and to 94 | * DMS.NONE otherwise. Leading and trailing whitespace is removed from 95 | * the string before processing. This routine throws an error on a 96 | * malformed string. No check is performed on the range of the result. 97 | * Examples of legal and illegal strings are 98 | * - LEGAL (all the entries on each line are equivalent) 99 | * - -20.51125, 20d30'40.5"S, -20°30'40.5, -20d30.675, 100 | * N-20d30'40.5", -20:30:40.5 101 | * - 4d0'9, 4d9", 4d9'', 4:0:9, 004:00:09, 4.0025, 4.0025d, 4d0.15, 102 | * 04:.15 103 | * - 4:59.99999999999999, 4:60.0, 4:59:59.9999999999999, 4:59:60.0, 5 104 | * - ILLEGAL (the exception thrown explains the problem) 105 | * - 4d5"4', 4::5, 4:5:, :4:5, 4d4.5'4", -N20.5, 1.8e2d, 4:60, 106 | * 4:59:60 107 | * 108 | * The decoding operation can also perform addition and subtraction 109 | * operations. If the string includes internal signs (i.e., not at 110 | * the beginning nor immediately after an initial hemisphere designator), 111 | * then the string is split immediately before such signs and each piece is 112 | * decoded according to the above rules and the results added; thus 113 | * S3-2.5+4.1N is parsed as the sum of S3, 114 | * -2.5, +4.1N. Any piece can include a 115 | * hemisphere designator; however, if multiple designators are given, they 116 | * must compatible; e.g., you cannot mix N and E. In addition, the 117 | * designator can appear at the beginning or end of the first piece, but 118 | * must be at the end of all subsequent pieces (a hemisphere designator is 119 | * not allowed after the initial sign). Examples of legal and illegal 120 | * combinations are 121 | * - LEGAL (these are all equivalent) 122 | * - -070:00:45, 70:01:15W+0:0.5, 70:01:15W-0:0:30W, W70:01:15+0:0:30E 123 | * - ILLEGAL (the exception thrown explains the problem) 124 | * - 70:01:15W+0:0:15N, W70:01:15+W0:0:15 125 | * 126 | * WARNING The "exponential" notation is not recognized. Thus 127 | * 7.0E1 is illegal, while 7.0E+1 is parsed as 128 | * (7.0E) + (+1), yielding the same result as 129 | * 8.0E. 130 | */ 131 | d.Decode = function(dms) { 132 | var dmsa = dms, end, 133 | // v = -0.0, so "-0" returns -0.0 134 | v = -0, i = 0, mi, pi, vals, 135 | ind1 = d.NONE, ind2, p, pa, pb; 136 | dmsa = dmsa 137 | .replace(/\u00b0/g, 'd' ) // U+00b0 degree symbol 138 | .replace(/\u00ba/g, 'd' ) // U+00ba alt symbol 139 | .replace(/\u2070/g, 'd' ) // U+2070 sup zero 140 | .replace(/\u02da/g, 'd' ) // U+02da ring above 141 | .replace(/\u2218/g, 'd' ) // U+2218 compose function 142 | .replace(/\*/g , 'd' ) // GRiD symbol for degree 143 | 144 | .replace(/`/g , 'd' ) // grave accent 145 | .replace(/\u2032/g, '\'') // U+2032 prime 146 | .replace(/\u2035/g, '\'') // U+2035 back prime 147 | .replace(/\u00b4/g, '\'') // U+00b4 acute accent 148 | .replace(/\u2018/g, '\'') // U+2018 left single quote 149 | .replace(/\u2019/g, '\'') // U+2019 right single quote 150 | .replace(/\u201b/g, '\'') // U+201b reversed-9 single quote 151 | .replace(/\u02b9/g, '\'') // U+02b9 modifier letter prime 152 | .replace(/\u02ca/g, '\'') // U+02ca modifier letter acute accent 153 | .replace(/\u02cb/g, '\'') // U+02cb modifier letter grave accent 154 | 155 | .replace(/\u2033/g, '"' ) // U+2033 double prime 156 | .replace(/\u2036/g, '"' ) // U+2036 reversed double prime 157 | .replace(/\u02dd/g, '"' ) // U+02dd double acute accent 158 | .replace(/\u201c/g, '"' ) // U+201d left double quote 159 | .replace(/\u201d/g, '"' ) // U+201d right double quote 160 | .replace(/\u201f/g, '"' ) // U+201f reversed-9 double quote 161 | .replace(/\u02ba/g, '"' ) // U+02ba modifier letter double prime 162 | 163 | .replace(/\u2795/g, '+' ) // U+2795 heavy plus 164 | .replace(/\u2064/g, '+' ) // U+2064 invisible plus 165 | 166 | .replace(/\u2010/g, '-' ) // U+2010 dash 167 | .replace(/\u2011/g, '-' ) // U+2011 non-breaking hyphen 168 | .replace(/\u2013/g, '-' ) // U+2013 en dash 169 | .replace(/\u2014/g, '-' ) // U+2014 em dash 170 | .replace(/\u2212/g, '-' ) // U+2212 minus sign 171 | .replace(/\u2796/g, '-' ) // U+2796 heavy minus 172 | 173 | .replace(/\u00a0/g, '' ) // U+00a0 non-breaking space 174 | .replace(/\u2007/g, '' ) // U+2007 figure space 175 | .replace(/\u2009/g, '' ) // U+2009 thin space 176 | .replace(/\u200a/g, '' ) // U+200a hair space 177 | .replace(/\u200b/g, '' ) // U+200b invisible space 178 | .replace(/\u202f/g, '' ) // U+202f narrow space 179 | .replace(/\u2063/g, '' ) // U+2063 invisible separator 180 | 181 | .replace(/''/g, '"' ) // '' -> " 182 | 183 | .trim(); 184 | 185 | end = dmsa.length; 186 | // p is pointer to the next piece that needs decoding 187 | for (p = 0; p < end; p = pb, ++i) { 188 | pa = p; 189 | // Skip over initial hemisphere letter (for i == 0) 190 | if (i === 0 && lookup(hemispheres_, dmsa.charAt(pa)) >= 0) 191 | ++pa; 192 | // Skip over initial sign (checking for it if i == 0) 193 | if (i > 0 || (pa < end && lookup(signs_, dmsa.charAt(pa)) >= 0)) 194 | ++pa; 195 | // Find next sign 196 | mi = dmsa.substr(pa, end - pa).indexOf('-'); 197 | pi = dmsa.substr(pa, end - pa).indexOf('+'); 198 | if (mi < 0) mi = end; else mi += pa; 199 | if (pi < 0) pi = end; else pi += pa; 200 | pb = Math.min(mi, pi); 201 | vals = internalDecode(dmsa.substr(p, pb - p)); 202 | v += vals.val; ind2 = vals.ind; 203 | if (ind1 === d.NONE) 204 | ind1 = ind2; 205 | else if (!(ind2 === d.NONE || ind1 === ind2)) 206 | throw new Error("Incompatible hemisphere specifies in " + 207 | dmsa.substr(0, pb)); 208 | } 209 | if (i === 0) 210 | throw new Error("Empty or incomplete DMS string " + dmsa); 211 | return {val: v, ind: ind1}; 212 | }; 213 | 214 | internalDecode = function(dmsa) { 215 | var vals = {}, errormsg = "", 216 | sign, beg, end, ind1, k, 217 | ipieces, fpieces, npiece, 218 | icurrent, fcurrent, ncurrent, p, 219 | pointseen, 220 | digcount, intcount, 221 | x; 222 | do { // Executed once (provides the ability to break) 223 | sign = 1; 224 | beg = 0; end = dmsa.length; 225 | ind1 = d.NONE; 226 | k = -1; 227 | if (end > beg && (k = lookup(hemispheres_, dmsa.charAt(beg))) >= 0) { 228 | ind1 = (k & 2) ? d.LONGITUDE : d.LATITUDE; 229 | sign = (k & 1) ? 1 : -1; 230 | ++beg; 231 | } 232 | if (end > beg && 233 | (k = lookup(hemispheres_, dmsa.charAt(end-1))) >= 0) { 234 | if (k >= 0) { 235 | if (ind1 !== d.NONE) { 236 | if (dmsa.charAt(beg - 1).toUpperCase() === 237 | dmsa.charAt(end - 1).toUpperCase()) 238 | errormsg = "Repeated hemisphere indicators " + 239 | dmsa.charAt(beg - 1) + " in " + 240 | dmsa.substr(beg - 1, end - beg + 1); 241 | else 242 | errormsg = "Contradictory hemisphere indicators " + 243 | dmsa.charAt(beg - 1) + " and " + dmsa.charAt(end - 1) + " in " + 244 | dmsa.substr(beg - 1, end - beg + 1); 245 | break; 246 | } 247 | ind1 = (k & 2) ? d.LONGITUDE : d.LATITUDE; 248 | sign = (k & 1) ? 1 : -1; 249 | --end; 250 | } 251 | } 252 | if (end > beg && (k = lookup(signs_, dmsa.charAt(beg))) >= 0) { 253 | if (k >= 0) { 254 | sign *= k ? 1 : -1; 255 | ++beg; 256 | } 257 | } 258 | if (end === beg) { 259 | errormsg = "Empty or incomplete DMS string " + dmsa; 260 | break; 261 | } 262 | ipieces = [0, 0, 0]; 263 | fpieces = [0, 0, 0]; 264 | npiece = 0; 265 | icurrent = 0; 266 | fcurrent = 0; 267 | ncurrent = 0; 268 | p = beg; 269 | pointseen = false; 270 | digcount = 0; 271 | intcount = 0; 272 | while (p < end) { 273 | x = dmsa.charAt(p++); 274 | if ((k = lookup(digits_, x)) >= 0) { 275 | ++ncurrent; 276 | if (digcount > 0) { 277 | ++digcount; // Count of decimal digits 278 | } else { 279 | icurrent = 10 * icurrent + k; 280 | ++intcount; 281 | } 282 | } else if (x === '.') { 283 | if (pointseen) { 284 | errormsg = "Multiple decimal points in " + 285 | dmsa.substr(beg, end - beg); 286 | break; 287 | } 288 | pointseen = true; 289 | digcount = 1; 290 | } else if ((k = lookup(dmsindicators_, x)) >= 0) { 291 | if (k >= 3) { 292 | if (p === end) { 293 | errormsg = "Illegal for colon to appear at the end of " + 294 | dmsa.substr(beg, end - beg); 295 | break; 296 | } 297 | k = npiece; 298 | } 299 | if (k === npiece - 1) { 300 | errormsg = "Repeated " + components_[k] + 301 | " component in " + dmsa.substr(beg, end - beg); 302 | break; 303 | } else if (k < npiece) { 304 | errormsg = components_[k] + " component follows " + 305 | components_[npiece - 1] + " component in " + 306 | dmsa.substr(beg, end - beg); 307 | break; 308 | } 309 | if (ncurrent === 0) { 310 | errormsg = "Missing numbers in " + components_[k] + 311 | " component of " + dmsa.substr(beg, end - beg); 312 | break; 313 | } 314 | if (digcount > 0) { 315 | fcurrent = parseFloat(dmsa.substr(p - intcount - digcount - 1, 316 | intcount + digcount)); 317 | icurrent = 0; 318 | } 319 | ipieces[k] = icurrent; 320 | fpieces[k] = icurrent + fcurrent; 321 | if (p < end) { 322 | npiece = k + 1; 323 | icurrent = fcurrent = 0; 324 | ncurrent = digcount = intcount = 0; 325 | } 326 | } else if (lookup(signs_, x) >= 0) { 327 | errormsg = "Internal sign in DMS string " + 328 | dmsa.substr(beg, end - beg); 329 | break; 330 | } else { 331 | errormsg = "Illegal character " + x + " in DMS string " + 332 | dmsa.substr(beg, end - beg); 333 | break; 334 | } 335 | } 336 | if (errormsg.length) 337 | break; 338 | if (lookup(dmsindicators_, dmsa.charAt(p - 1)) < 0) { 339 | if (npiece >= 3) { 340 | errormsg = "Extra text following seconds in DMS string " + 341 | dmsa.substr(beg, end - beg); 342 | break; 343 | } 344 | if (ncurrent === 0) { 345 | errormsg = "Missing numbers in trailing component of " + 346 | dmsa.substr(beg, end - beg); 347 | break; 348 | } 349 | if (digcount > 0) { 350 | fcurrent = parseFloat(dmsa.substr(p - intcount - digcount, 351 | intcount + digcount)); 352 | icurrent = 0; 353 | } 354 | ipieces[npiece] = icurrent; 355 | fpieces[npiece] = icurrent + fcurrent; 356 | } 357 | if (pointseen && digcount === 0) { 358 | errormsg = "Decimal point in non-terminal component of " + 359 | dmsa.substr(beg, end - beg); 360 | break; 361 | } 362 | // Note that we accept 59.999999... even though it rounds to 60. 363 | if (ipieces[1] >= 60 || fpieces[1] > 60) { 364 | errormsg = "Minutes " + fpieces[1] + " not in range [0,60)"; 365 | break; 366 | } 367 | if (ipieces[2] >= 60 || fpieces[2] > 60) { 368 | errormsg = "Seconds " + fpieces[2] + " not in range [0,60)"; 369 | break; 370 | } 371 | vals.ind = ind1; 372 | // Assume check on range of result is made by calling routine (which 373 | // might be able to offer a better diagnostic). 374 | vals.val = sign * 375 | ( fpieces[2] ? (60*(60*fpieces[0] + fpieces[1]) + fpieces[2]) / 3600 : 376 | ( fpieces[1] ? (60*fpieces[0] + fpieces[1]) / 60 : fpieces[0] ) ); 377 | return vals; 378 | } while (false); 379 | vals.val = numMatch(dmsa); 380 | if (vals.val === 0) 381 | throw new Error(errormsg); 382 | else 383 | vals.ind = d.NONE; 384 | return vals; 385 | }; 386 | 387 | numMatch = function(s) { 388 | var t, sign, p0, p1; 389 | if (s.length < 3) 390 | return 0; 391 | t = s.toUpperCase().replace(/0+$/, ""); 392 | sign = t.charAt(0) === '-' ? -1 : 1; 393 | p0 = t.charAt(0) === '-' || t.charAt(0) === '+' ? 1 : 0; 394 | p1 = t.length - 1; 395 | if (p1 + 1 < p0 + 3) 396 | return 0; 397 | // Strip off sign and trailing 0s 398 | t = t.substr(p0, p1 + 1 - p0); // Length at least 3 399 | if (t === "NAN" || t === "1.#QNAN" || t === "1.#SNAN" || t === "1.#IND" || 400 | t === "1.#R") 401 | return Number.NaN; 402 | else if (t === "INF" || t === "1.#INF" || t === "INFINITY") 403 | return sign * Number.POSITIVE_INFINITY; 404 | return 0; 405 | }; 406 | 407 | /** 408 | * @summary Decode two DMS strings interpreting them as a latitude/longitude 409 | * pair. 410 | * @param {string} stra the first string. 411 | * @param {string} strb the first string. 412 | * @param {bool} [longfirst = false] if true assume then longitude is given 413 | * first (in the absence of any hemisphere indicators). 414 | * @return {object} r where r.lat is the decoded latitude and r.lon is the 415 | * decoded longitude (both in degrees). 416 | * @throws an error if the strings are illegal. 417 | */ 418 | d.DecodeLatLon = function(stra, strb, longfirst) { 419 | var vals = {}, 420 | valsa = d.Decode(stra), 421 | valsb = d.Decode(strb), 422 | a = valsa.val, ia = valsa.ind, 423 | b = valsb.val, ib = valsb.ind, 424 | lat, lon; 425 | if (!longfirst) longfirst = false; 426 | if (ia === d.NONE && ib === d.NONE) { 427 | // Default to lat, long unless longfirst 428 | ia = longfirst ? d.LONGITUDE : d.LATITUDE; 429 | ib = longfirst ? d.LATITUDE : d.LONGITUDE; 430 | } else if (ia === d.NONE) 431 | ia = d.LATITUDE + d.LONGITUDE - ib; 432 | else if (ib === d.NONE) 433 | ib = d.LATITUDE + d.LONGITUDE - ia; 434 | if (ia === ib) 435 | throw new Error("Both " + stra + " and " + strb + " interpreted as " + 436 | (ia === d.LATITUDE ? "latitudes" : "longitudes")); 437 | lat = ia === d.LATITUDE ? a : b; 438 | lon = ia === d.LATITUDE ? b : a; 439 | if (Math.abs(lat) > 90) 440 | throw new Error("Latitude " + lat + " not in [-90,90]"); 441 | vals.lat = lat; 442 | vals.lon = lon; 443 | return vals; 444 | }; 445 | 446 | /** 447 | * @summary Decode a DMS string interpreting it as an arc length. 448 | * @param {string} angstr the string (this must not include a hemisphere 449 | * indicator). 450 | * @return {number} the arc length (degrees). 451 | * @throws an error if the string is illegal. 452 | */ 453 | d.DecodeAngle = function(angstr) { 454 | var vals = d.Decode(angstr), 455 | ang = vals.val, ind = vals.ind; 456 | if (ind !== d.NONE) 457 | throw new Error("Arc angle " + angstr + 458 | " includes a hemisphere N/E/W/S"); 459 | return ang; 460 | }; 461 | 462 | /** 463 | * @summary Decode a DMS string interpreting it as an azimuth. 464 | * @param {string} azistr the string (this may include an E/W hemisphere 465 | * indicator). 466 | * @return {number} the azimuth (degrees). 467 | * @throws an error if the string is illegal. 468 | */ 469 | d.DecodeAzimuth = function(azistr) { 470 | var vals = d.Decode(azistr), 471 | azi = vals.val, ind = vals.ind; 472 | if (ind === d.LATITUDE) 473 | throw new Error("Azimuth " + azistr + " has a latitude hemisphere N/S"); 474 | return azi; 475 | }; 476 | 477 | /** 478 | * @summary Convert angle (in degrees) into a DMS string (using °, ', 479 | * and "). 480 | * @param {number} angle input angle (degrees). 481 | * @param {number} trailing one of DEGREE, MINUTE, or SECOND to indicate 482 | * the trailing component of the string (this component is given as a 483 | * decimal number if necessary). 484 | * @param {number} prec the number of digits after the decimal point for 485 | * the trailing component. 486 | * @param {number} [ind = NONE] a formatting indicator, one of NONE, 487 | * LATITUDE, LONGITUDE, AZIMUTH. 488 | * @param {char} [dmssep = NULL] if non-null, use as the DMS separator 489 | * character. 490 | * @return {string} the resulting string formatted as follows: 491 | * * NONE, signed result no leading zeros on degrees except in the units 492 | * place, e.g., -8°03'. 493 | * * LATITUDE, trailing N or S hemisphere designator, no sign, pad 494 | * degrees to 2 digits, e.g., 08°03'S. 495 | * * LONGITUDE, trailing E or W hemisphere designator, no sign, pad 496 | * degrees to 3 digits, e.g., 008°03'W. 497 | * * AZIMUTH, convert to the range [0, 360°), no sign, pad degrees to 498 | * 3 digits, e.g., 351°57'. 499 | * 500 | * WARNING Because of implementation of JavaScript's toFixed function, 501 | * this routine rounds ties away from zero. This is different from the C++ 502 | * version of GeographicLib which implements the "round ties to even" rule. 503 | * 504 | * WARNING Angles whose magnitude is equal to or greater than 505 | * 1021 are printed as a plain number in exponential notation, 506 | * e.g., "1e21". 507 | */ 508 | d.Encode = function(angle, trailing, prec, ind, dmssep) { 509 | // Assume check on range of input angle has been made by calling 510 | // routine (which might be able to offer a better diagnostic). 511 | var scale = 1, i, sign, 512 | idegree, fdegree, degree, minute, second, s, usesep, p; 513 | if (!ind) ind = d.NONE; 514 | if (!dmssep) dmssep = '\0'; 515 | usesep = dmssep !== '\0'; 516 | if (!isFinite(angle)) 517 | return angle < 0 ? "-inf" : 518 | (angle > 0 ? "inf" : "nan"); 519 | if (Math.abs(angle) >= 1e21) 520 | // toFixed only works for numbers less that 1e21. 521 | return angle.toString().replace(/e\+/, 'e'); // remove "+" from exponent 522 | 523 | // 15 - 2 * trailing = ceiling(log10(2^53/90/60^trailing)). 524 | // This suffices to give full real precision for numbers in [-90,90] 525 | prec = Math.min(15 - 2 * trailing, prec); 526 | for (i = 0; i < trailing; ++i) 527 | scale *= 60; 528 | if (ind === d.AZIMUTH) { 529 | angle %= 360; 530 | // Only angles strictly less than 0 can become 360; since +/-180 are 531 | // folded together, we convert -0 to +0 (instead of 360). 532 | if (angle < 0) 533 | angle += 360; 534 | else 535 | angle = 0 + angle; 536 | } 537 | sign = (angle < 0 || angle === 0 && 1/angle < 0) ? -1 : 1; 538 | angle *= sign; 539 | 540 | // Break off integer part to preserve precision and avoid overflow in 541 | // manipulation of fractional part for MINUTE and SECOND 542 | idegree = trailing === d.DEGREE ? 0 : Math.floor(angle); 543 | fdegree = (angle - idegree) * scale; 544 | s = fdegree.toFixed(prec); 545 | switch (trailing) { 546 | case d.DEGREE: 547 | degree = s; 548 | break; 549 | default: // case MINUTE: case SECOND: 550 | p = s.indexOf('.'); 551 | if (p < 0) { 552 | i = parseInt(s); 553 | s = ""; 554 | } else if (p === 0) { 555 | i = 0; 556 | } else { 557 | i = parseInt(s.substr(0, p)); 558 | s = s.substr(p); 559 | } 560 | // Now i in [0,60] or [0,3600] for MINUTE/DEGREE 561 | switch (trailing) { 562 | case d.MINUTE: 563 | minute = (i % 60).toString() + s; i = Math.trunc(i / 60); 564 | degree = (i + idegree).toFixed(0); // no overflow since i in [0,1] 565 | break; 566 | default: // case SECOND: 567 | second = (i % 60).toString() + s; i = Math.trunc(i / 60); 568 | minute = (i % 60).toString() ; i = Math.trunc(i / 60); 569 | degree = (i + idegree).toFixed(0); // no overflow since i in [0,1] 570 | break; 571 | } 572 | break; 573 | } 574 | // No glue together degree+minute+second with 575 | // sign + zero-fill + delimiters + hemisphere 576 | s = ""; 577 | if (ind === d.NONE && sign < 0) 578 | s += '-'; 579 | if (prec) ++prec; // Extra width for decimal point 580 | switch (trailing) { 581 | case d.DEGREE: 582 | s += zerofill(degree, ind === d.NONE ? 0 : 1 + Math.min(ind, 2) + prec) + 583 | (usesep ? '' : dmsindicatorsu_.charAt(0)); 584 | break; 585 | case d.MINUTE: 586 | s += zerofill(degree, ind === d.NONE ? 0 : 1 + Math.min(ind, 2)) + 587 | (usesep ? dmssep : dmsindicatorsu_.charAt(0)) + 588 | zerofill(minute, 2 + prec) + 589 | (usesep ? '' : dmsindicatorsu_.charAt(1)); 590 | break; 591 | default: // case SECOND: 592 | s += zerofill(degree, ind === d.NONE ? 0 : 1 + Math.min(ind, 2)) + 593 | (usesep ? dmssep : dmsindicatorsu_.charAt(0)) + 594 | zerofill(minute, 2) + 595 | (usesep ? dmssep : dmsindicatorsu_.charAt(1)) + 596 | zerofill(second, 2 + prec) + 597 | (usesep ? '' : dmsindicatorsu_.charAt(2)); 598 | break; 599 | } 600 | if (ind !== d.NONE && ind !== d.AZIMUTH) 601 | s += hemispheres_.charAt((ind === d.LATITUDE ? 0 : 2) + 602 | (sign < 0 ? 0 : 1)); 603 | return s; 604 | }; 605 | })(DMS); 606 | -------------------------------------------------------------------------------- /dms/HEAD.js.in: -------------------------------------------------------------------------------- 1 | /* 2 | * DMS routines from GeographicLib translated to JavaScript. See 3 | * https://geographiclib.sourceforge.io/JavaScript/doc 4 | * 5 | * This file is the concatenation and compression of the JavaScript files in 6 | * src/dms in the source tree for geographiclib-js. 7 | * 8 | * Copyright (c) Charles Karney (2011-2022) and licensed 9 | * under the MIT/X11 License. For more information, see 10 | * https://geographiclib.sourceforge.io/ 11 | * 12 | * Version: @PROJECT_FULLVERSION@ 13 | * Date: @RELEASE_DATE@ 14 | */ 15 | 16 | (function(cb) { 17 | -------------------------------------------------------------------------------- /dms/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript implementation of the DMS routines in GeographicLib 2 | 3 | This package is a JavaScript implementation of the DMS (degree, 4 | minute, second) handling routines from 5 | [GeographicLib](https://geographiclib.sourceforge.io). 6 | 7 | Prior to version 2.0.0, this was a component of the [node package 8 | geographiclib](https://www.npmjs.com/package/geographiclib). As of 9 | version 2.0.0, that package was split into the packages 10 | [geographiclib-geodesic](https://www.npmjs.com/package/geographiclib-geodesic) 11 | and 12 | [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms) 13 | (this package). 14 | [geographiclib](https://www.npmjs.com/package/geographiclib) will be 15 | deprecated on 2023-05-01. 16 | 17 | Licensed under the MIT/X11 License; see 18 | [LICENSE.txt](https://geographiclib.sourceforge.io/LICENSE.txt). 19 | 20 | ## Installation 21 | 22 | ```bash 23 | $ npm install geographiclib-dms 24 | ``` 25 | 26 | ## Usage 27 | 28 | In [node](https://nodejs.org), do 29 | ```javascript 30 | var DMS = require("geographiclib-dms"); 31 | ``` 32 | 33 | ## Documentation 34 | 35 | Full documentation is provided at 36 | [https://geographiclib.sourceforge.io/JavaScript/doc]( 37 | https://geographiclib.sourceforge.io/JavaScript/doc/index.html). 38 | 39 | ## Examples 40 | 41 | ```javascript 42 | var DMS = require("geographiclib-dms"), 43 | ang = DMS.Decode("127:54:3.123123W"); 44 | console.log("Azimuth " + 45 | DMS.Encode(ang.val, DMS.MINUTE, 7, ang.ind) + 46 | " = " + ang.val.toFixed(9)); 47 | // This prints "Azimuth 127°54.0520521'W = -127.900867534" 48 | ``` 49 | 50 | ## Authors 51 | 52 | * algorithms + js code: Charles Karney (karney@alum.mit.edu) 53 | * node.js port: Yurij Mikhalevich (yurij@mikhalevi.ch) 54 | -------------------------------------------------------------------------------- /dms/TAIL.js: -------------------------------------------------------------------------------- 1 | cb(DMS); 2 | 3 | })(function(dms) { 4 | if (typeof module === 'object' && module.exports) { 5 | /******** support loading with node's require ********/ 6 | module.exports = dms; 7 | } else if (typeof define === 'function' && define.amd) { 8 | /******** support loading with AMD ********/ 9 | define('geographiclib-dms', [], function() { return dms; }); 10 | } else { 11 | /******** otherwise just pollute our global namespace ********/ 12 | window.DMS = dms; 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /dms/package.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geographiclib-dms", 3 | "version": "@PROJECT_FULLVERSION@", 4 | "description": "JavaScript implementation of DMS routines in GeographicLib", 5 | "main": "geographiclib-dms.min.js", 6 | "types": "types/geographiclib-dms.d.ts", 7 | "scripts": { 8 | "test": "mocha" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/geographiclib/geographiclib-js.git" 13 | }, 14 | "url": "https://geographiclib.sourceforge.net", 15 | "keywords": [ 16 | "DMS", 17 | "degrees-minutes-seconds" 18 | ], 19 | "devDependencies": { 20 | "mocha": "^9.1.5", 21 | "minify": "^7.0.2" 22 | }, 23 | "author": "Charles Karney ", 24 | "contributors": [ 25 | "Yurij Mikhalevich " 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/geographiclib/geographiclib-js/issues", 30 | "email": "karney@alum.mit.edu" 31 | }, 32 | "homepage": "https://github.com/geographiclib/geographiclib-js#readme", 33 | "directories": { 34 | "test": "test" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dms/test/dmstest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"), 4 | d = require("../geographiclib-dms"); 5 | 6 | describe("DMS", function() { 7 | describe("DMSTest", function () { 8 | 9 | it("check decode", function () { 10 | assert.deepEqual(d.Decode("E7:33:36"), d.Decode("-7.56W")); 11 | }); 12 | 13 | it("check encode", function () { 14 | var t; 15 | assert.strictEqual(d.Encode(-7.56, d.DEGREE, 2), "-7.56°"); 16 | assert.strictEqual(d.Encode(-7.56, d.MINUTE, 1), "-7°33.6'"); 17 | assert.strictEqual(d.Encode(-7.56, d.SECOND, 0), "-7°33'36\""); 18 | assert.strictEqual(d.Encode(-7.56, d.DEGREE, 2, d.NONE, ':'), "-7.56"); 19 | assert.strictEqual(d.Encode(-7.56, d.MINUTE, 1, d.NONE, ':'), "-7:33.6"); 20 | assert.strictEqual(d.Encode(-7.56, d.SECOND, 0, d.NONE, ':'), "-7:33:36"); 21 | 22 | assert.strictEqual(d.Encode(-7.56, d.DEGREE, 2, d.LATITUDE), 23 | "07.56°S"); 24 | assert.strictEqual(d.Encode(-7.56, d.MINUTE, 1, d.LATITUDE), 25 | "07°33.6'S"); 26 | assert.strictEqual(d.Encode(-7.56, d.SECOND, 0, d.LATITUDE), 27 | "07°33'36\"S"); 28 | assert.strictEqual(d.Encode(-7.56, d.DEGREE, 2, d.LATITUDE, ':'), 29 | "07.56S"); 30 | assert.strictEqual(d.Encode(-7.56, d.MINUTE, 1, d.LATITUDE, ':'), 31 | "07:33.6S"); 32 | assert.strictEqual(d.Encode(-7.56, d.SECOND, 0, d.LATITUDE, ':'), 33 | "07:33:36S"); 34 | // zero fill checks 35 | t = -(1 + 2/60 + 2.99/3600); 36 | assert.strictEqual(d.Encode( t,d.DEGREE,0,d.NONE ), "-1°" ); 37 | assert.strictEqual(d.Encode( t,d.DEGREE,0,d.LATITUDE ), "01°S" ); 38 | assert.strictEqual(d.Encode( t,d.DEGREE,0,d.LONGITUDE),"001°W" ); 39 | assert.strictEqual(d.Encode(-t,d.DEGREE,0,d.AZIMUTH ),"001°" ); 40 | assert.strictEqual(d.Encode( t,d.DEGREE,1,d.NONE ), "-1.0°" ); 41 | assert.strictEqual(d.Encode( t,d.DEGREE,1,d.LATITUDE ), "01.0°S" ); 42 | assert.strictEqual(d.Encode( t,d.DEGREE,1,d.LONGITUDE),"001.0°W" ); 43 | assert.strictEqual(d.Encode(-t,d.DEGREE,1,d.AZIMUTH ),"001.0°" ); 44 | assert.strictEqual(d.Encode( t,d.MINUTE,0,d.NONE ), "-1°02'" ); 45 | assert.strictEqual(d.Encode( t,d.MINUTE,0,d.LATITUDE ), "01°02'S" ); 46 | assert.strictEqual(d.Encode( t,d.MINUTE,0,d.LONGITUDE),"001°02'W" ); 47 | assert.strictEqual(d.Encode(-t,d.MINUTE,0,d.AZIMUTH ),"001°02'" ); 48 | assert.strictEqual(d.Encode( t,d.MINUTE,1,d.NONE ), "-1°02.0'" ); 49 | assert.strictEqual(d.Encode( t,d.MINUTE,1,d.LATITUDE ), "01°02.0'S" ); 50 | assert.strictEqual(d.Encode( t,d.MINUTE,1,d.LONGITUDE),"001°02.0'W" ); 51 | assert.strictEqual(d.Encode(-t,d.MINUTE,1,d.AZIMUTH ),"001°02.0'" ); 52 | assert.strictEqual(d.Encode( t,d.SECOND,0,d.NONE ), "-1°02'03\"" ); 53 | assert.strictEqual(d.Encode( t,d.SECOND,0,d.LATITUDE ), "01°02'03\"S" ); 54 | assert.strictEqual(d.Encode( t,d.SECOND,0,d.LONGITUDE),"001°02'03\"W" ); 55 | assert.strictEqual(d.Encode(-t,d.SECOND,0,d.AZIMUTH ),"001°02'03\"" ); 56 | assert.strictEqual(d.Encode( t,d.SECOND,1,d.NONE ), "-1°02'03.0\"" ); 57 | assert.strictEqual(d.Encode( t,d.SECOND,1,d.LATITUDE ), "01°02'03.0\"S"); 58 | assert.strictEqual(d.Encode( t,d.SECOND,1,d.LONGITUDE),"001°02'03.0\"W"); 59 | assert.strictEqual(d.Encode(-t,d.SECOND,1,d.AZIMUTH ),"001°02'03.0\"" ); 60 | }); 61 | 62 | it("check decode special", function () { 63 | var nan = NaN, inf = Infinity; 64 | assert.strictEqual(d.Decode(" +0 ").val, +0); 65 | assert.strictEqual(d.Decode("-0 ").val, -0); 66 | assert.strictEqual(d.Decode(" nan").val, nan); 67 | assert.strictEqual(d.Decode("+inf").val, +inf); 68 | assert.strictEqual(d.Decode(" inf").val, +inf); 69 | assert.strictEqual(d.Decode("-inf").val, -inf); 70 | assert.strictEqual(d.Decode(" +0N").val, +0); 71 | assert.strictEqual(d.Decode("-0N ").val, -0); 72 | assert.strictEqual(d.Decode("+0S ").val, -0); 73 | assert.strictEqual(d.Decode(" -0S").val, +0); 74 | }); 75 | 76 | it("check encode rounding", function () { 77 | var nan = NaN, inf = Infinity; 78 | // JavaScript rounds ties away from zero... 79 | // Round to even results given in trailing comments 80 | assert.strictEqual(d.Encode( nan , d.DEGREE, 0), "nan" ); 81 | assert.strictEqual(d.Encode(-inf , d.DEGREE, 0), "-inf" ); 82 | assert.strictEqual(d.Encode(-3.5 , d.DEGREE, 0), "-4°" ); 83 | assert.strictEqual(d.Encode(-2.5 , d.DEGREE, 0), "-3°" ); // -2 84 | assert.strictEqual(d.Encode(-1.5 , d.DEGREE, 0), "-2°" ); 85 | assert.strictEqual(d.Encode(-0.5 , d.DEGREE, 0), "-1°" ); // -0 86 | assert.strictEqual(d.Encode(-0 , d.DEGREE, 0), "-0°" ); 87 | assert.strictEqual(d.Encode(+0 , d.DEGREE, 0), "0°" ); 88 | assert.strictEqual(d.Encode(+0.5 , d.DEGREE, 0), "1°" ); // 0 89 | assert.strictEqual(d.Encode(+1.5 , d.DEGREE, 0), "2°" ); 90 | assert.strictEqual(d.Encode(+2.5 , d.DEGREE, 0), "3°" ); // 2 91 | assert.strictEqual(d.Encode(+3.5 , d.DEGREE, 0), "4°" ); 92 | assert.strictEqual(d.Encode(+inf , d.DEGREE, 0), "inf" ); 93 | assert.strictEqual(d.Encode(-1.75, d.DEGREE, 1), "-1.8°"); 94 | assert.strictEqual(d.Encode(-1.25, d.DEGREE, 1), "-1.3°"); // -1.2 95 | assert.strictEqual(d.Encode(-0.75, d.DEGREE, 1), "-0.8°"); 96 | assert.strictEqual(d.Encode(-0.25, d.DEGREE, 1), "-0.3°"); // 0.2 97 | assert.strictEqual(d.Encode(-0 , d.DEGREE, 1), "-0.0°"); 98 | assert.strictEqual(d.Encode(+0 , d.DEGREE, 1), "0.0°"); 99 | assert.strictEqual(d.Encode(+0.25, d.DEGREE, 1), "0.3°"); // 0.2 100 | assert.strictEqual(d.Encode(+0.75, d.DEGREE, 1), "0.8°"); 101 | assert.strictEqual(d.Encode(+1.25, d.DEGREE, 1), "1.3°"); // 1.2 102 | assert.strictEqual(d.Encode(+1.75, d.DEGREE, 1), "1.8°"); 103 | assert.strictEqual(d.Encode( 1e20, d.DEGREE, 0), 104 | "100000000000000000000°"); 105 | assert.strictEqual(d.Encode( 1e21, d.DEGREE, 0), "1e21"); 106 | }); 107 | 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /dms/types/geographiclib-dms.d.ts: -------------------------------------------------------------------------------- 1 | export declare const NONE: 0; 2 | export declare const LATITUDE: 1; 3 | export declare const LONGITUDE: 2; 4 | export declare const AZIMUTH: 3; 5 | 6 | export declare const DEGREE: 0; 7 | export declare const MINUTE: 1; 8 | export declare const SECOND: 2; 9 | 10 | export type DMSHemisphereIndicator = 0 | 1 | 2 | 3; 11 | export type DMSTrailingComponent = 0 | 1 | 2; 12 | 13 | export declare const Decode: (dms: string) => { 14 | val: number; 15 | ind: DMSHemisphereIndicator; 16 | }; 17 | 18 | export declare const DecodeLatLon: ( 19 | stra: string, 20 | strb: string, 21 | longfirst?: boolean // default = false 22 | ) => { 23 | lat: number; 24 | lon: number; 25 | }; 26 | 27 | export declare const DecodeAngle: (angstr: string) => number; 28 | 29 | export declare const DecodeAzimuth: (azistr: string) => number; 30 | 31 | export declare const Encode: ( 32 | angle: number, 33 | trailing: DMSTrailingComponent, 34 | prec: number, 35 | ind?: DMSHemisphereIndicator // default = NONE 36 | ) => string; 37 | -------------------------------------------------------------------------------- /doc/.htaccess: -------------------------------------------------------------------------------- 1 | Options +Indexes +FollowSymLinks 2 | IndexOptions FancyIndexing SuppressSize SuppressDescription 3 | IndexOrderDefault Descending Date 4 | IndexIgnore HEADER.html FOOTER.html 5 | ReadmeName FOOTER.html 6 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file (GeographicLib.md.in GeographicLib.md @ONLY) 2 | 3 | set (GEODSOURCES 4 | ${DESTGEOD}/src/Math.js 5 | ${DESTGEOD}/src/Geodesic.js 6 | ${DESTGEOD}/src/GeodesicLine.js 7 | ${DESTGEOD}/src/PolygonArea.js) 8 | 9 | set (DMSSOURCES 10 | ${DESTDMS}/src/DMS.js) 11 | 12 | set (DOCSOURCES GeographicLib.md 13 | tutorials/1-geodesics.md 14 | tutorials/2-interface.md 15 | tutorials/3-examples.md 16 | tutorials/tutorials.json 17 | ${GEODSOURCES} ${DMSSOURCES}) 18 | 19 | add_custom_target (doc ALL 20 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/html/index.html) 21 | add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/html/index.html 22 | DEPENDS ${DOCSOURCES} 23 | COMMAND ${JSDOC} --verbose -d html 24 | -u ${CMAKE_CURRENT_SOURCE_DIR}/tutorials 25 | -c ${CMAKE_CURRENT_SOURCE_DIR}/conf.json 26 | -R ${CMAKE_CURRENT_BINARY_DIR}/GeographicLib.md 27 | ${GEODSOURCES} ${DMSSOURCES} > jsdoc.log 28 | COMMENT "Generating JavaScript documentation tree") 29 | 30 | if (RSYNC) 31 | set (USER karney) 32 | set (DOCROOT $ENV{HOME}/web/geographiclib-web/htdocs/JavaScript) 33 | set (WEBDEPLOY ${USER},geographiclib@web.sourceforge.net:./htdocs) 34 | add_custom_target (stage-doc 35 | COMMAND ${RSYNC} --delete -av 36 | html/ ${DOCROOT}/${PROJECT_VERSION}/ 37 | COMMAND cd ${PROJECT_SOURCE_DIR}/doc && 38 | ${RSYNC} --delete -av HEADER.html FOOTER.html .htaccess ${DOCROOT}/) 39 | add_dependencies (stage-doc doc) 40 | 41 | add_custom_target (deploy-doc 42 | COMMAND ${RSYNC} --delete -av -e ssh ${DOCROOT} ${WEBDEPLOY}/) 43 | endif () 44 | -------------------------------------------------------------------------------- /doc/FOOTER.html: -------------------------------------------------------------------------------- 1 | GeographicLib home 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc/GeographicLib.md.in: -------------------------------------------------------------------------------- 1 | ## Geodesic and DMS routines from GeographicLib 2 | 3 | This documentation applies to version @PROJECT_VERSION@. 4 | 5 | The documentation for other versions is available at 6 | [`https://geographiclib.sourceforge.io/JavaScript`](..). 7 | 8 | Licensed under the MIT/X11 License; see [LICENSE.txt](../../LICENSE.txt). 9 | 10 | These packages are a JavaScript implementations of the geodesic and 11 | DMS routines from [GeographicLib](../../index.html). The two packages are 12 | 13 | * [geographiclib-geodesic](https://www.npmjs.com/package/geographiclib-geodesic) 14 | solves the direct and inverse geodesic problems for an ellipsoid of 15 | revolution. 16 | 17 | * [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms) 18 | converts angles in decimal degrees to degrees-minutes-seconds and 19 | vice versa. 20 | 21 | Prior to version 2.0.0, these were combined in a single package 22 | [geographiclib](https://www.npmjs.com/package/geographiclib). Because 23 | the geodesic and DMS components of this package were independent and 24 | because most users will primarily be interested in the geodesic 25 | calculations, it made sense to separate them into two packages; 26 | removal of the DMS routines from geographiclib-geodesic reduced it 27 | size by about 20%. The geographiclib package was be deprecated on 28 | 2023-05-01. 29 | 30 | ### Installation 31 | 32 | The libraries can be used in [node](https://nodejs.org) by first 33 | installing the packages with [npm](https://www.npmjs.com) 34 | ```bash 35 | $ npm install geographiclib-geodesic geographiclib-dms 36 | $ node 37 | > var geodesic = require("geographiclib-geodesic"); 38 | > var DMS = require("geographiclib-dms"); 39 | ``` 40 | The npm packages include test suites. Run this by, e.g., 41 | ```bash 42 | $ cd node_modules/geographiclib-geodesic 43 | $ npm test 44 | ``` 45 | 46 | Alternatively, you can use it in client-side JavaScript, by including in 47 | your HTML page 48 | ```html 49 | 53 | 57 | ``` 58 | 59 | Both of these prescriptions bring {@link geodesic} and 60 | {@link module:DMS DMS} into scope. 61 | 62 | ### Examples 63 | 64 | Now geodesic calculations can be carried out, for example, 65 | ```javascript 66 | var geod = geodesic.Geodesic.WGS84, r; 67 | 68 | // Find the distance from Wellington, NZ (41.32S, 174.81E) to 69 | // Salamanca, Spain (40.96N, 5.50W)... 70 | r = geod.Inverse(-41.32, 174.81, 40.96, -5.50); 71 | console.log("The distance is " + r.s12.toFixed(3) + " m."); 72 | // This prints "The distance is 19959679.267 m." 73 | 74 | // Find the point 20000 km SW of Perth, Australia (32.06S, 115.74E)... 75 | r = geod.Direct(-32.06, 115.74, 225, 20000e3); 76 | console.log("The position is (" + 77 | r.lat2.toFixed(8) + ", " + r.lon2.toFixed(8) + ")."); 78 | // This prints "The position is (32.11195529, -63.95925278)." 79 | ``` 80 | Two examples of this library in use are 81 | * [A geodesic calculator](../../scripts/geod-calc.html) 82 | * [Displaying geodesics on Google 83 | Maps](../../scripts/geod-google.html) 84 | 85 | ### More information 86 | * {@tutorial 1-geodesics} 87 | * {@tutorial 2-interface} 88 | * {@tutorial 3-examples} 89 | 90 | ### Other links 91 | * [GeographicLib](../../index.html). 92 | * Git repository: [https://github.com/geographiclib/geographiclib-js 93 | ](https://github.com/geographiclib/geographiclib-js) Releases are 94 | tagged in git as, e.g., v1.52, v2.0.0, etc. 95 | * Implementations in [other languages](../../doc/library.html#languages). 96 | * C. F. F. Karney, 97 | [Algorithms for geodesics](https://doi.org/10.1007/s00190-012-0578-z), 98 | J. Geodesy **87**(1), 43–55 (2013); [addenda](../../geod-addenda.html). 99 | 100 | ### Change log 101 | 102 | * Version 2.1.1 (released 2024-08-18) 103 | 104 | * Use jsmin to minize JS code (minify is broken). 105 | 106 | * Version 2.1.0 (released 2024-08-18) 107 | 108 | * Fixed a problem with the inverse geodesic calculation when points 109 | are on opposite latitude ±45°. 110 | * Fixes for new version of minify. 111 | 112 | * Version 2.0.0 (released 2022-04-29) 113 | 114 | * The `geographiclib` package is now split into two packages 115 | [geographiclib-geodesic]( 116 | https://www.npmjs.com/package/geographiclib-geodesic) and 117 | [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms) 118 | geographiclib-geodesic and geographiclib-dms. 119 | * The JavaScript package now has its own [git 120 | repository](https://github.com/geographiclib/geographiclib-js). 121 | * Fix bug where the solution of the inverse geodesic problem with 122 | φ1 = 0 and φ2 = nan was treated as 123 | equatorial. 124 | * More careful treatment of ±0° and ±180°. 125 | * These behave consistently with taking the limits 126 | * ±0 means ±ε as ε → 0+ 127 | * ±180 means ±(180 − ε) as ε 128 | → 0+ 129 | * As a consequence, azimuths of +0° and +180° are reckoned to 130 | be east-going, as far as tracking the longitude with 131 | {@link net.sf.geographiclib.GeodesicMask#LONG_UNROLL} and the area 132 | goes, while azimuths −0° and −180° are reckoned to 133 | be west-going. 134 | * When computing longitude differences, if λ2 135 | − λ1 = ±180° (mod 360°), 136 | then the sign is picked depending on the sign of the difference. 137 | * The normal range for returned longitudes and azimuths is 138 | [−180°, 180°]. 139 | * A separate test suite `signtest.js` has been added to check this 140 | treatment. 141 | * The {@link module:DMS.Encode DMS.Encode} function now rounds ties 142 | away from zero consistent the JavaScript's `toFixed` function. 143 | 144 | * Version 1.52.2 (released 2022-04-29) 145 | 146 | * No code changes. This release is merely an update to give notice 147 | that, as of version 2.x, this package is now split into two 148 | packages [geographiclib-geodesic]( 149 | https://www.npmjs.com/package/geographiclib-geodesic) and 150 | [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms). 151 | 152 | * Version 1.52 (released 2020-06-22) 153 | * Work around inaccuracy in Math.hypot (see the GeodSolve92 test). 154 | * Be more aggressive in preventing negative s12 and m12 for short lines. 155 | * Provide typescript support. 156 | 157 | * Version 1.51 (released 2020-11-22) 158 | * More symbols allowed with DMS decodings. 159 | 160 | * Version 1.50 (released 2019-09-24) 161 | * PolygonArea can now handle arbitrarily complex polygons. In the 162 | case of self-intersecting polygons the area is accumulated 163 | "algebraically", e.g., the areas of the 2 loops in a figure-8 164 | polygon will partially cancel. 165 | * Fix two bugs in the computation of areas when some vertices are specified 166 | by an added edge. 167 | * Fix bug in computing unsigned area. 168 | * When parsing DMS strings ignore various non-breaking spaces. 169 | * Fall back to system versions of hypot, cbrt, log1p, atanh if they 170 | are available. 171 | * Math.cbrt, Math.atanh, and Math.asinh preserve the sign of −0. 172 | 173 | * Version 1.49 (released 2017-10-05) 174 | * Use explicit test for nonzero real numbers. 175 | 176 | * Version 1.48 (released 2017-04-09) 177 | * Change default range for longitude and azimuth to 178 | (−180°, 180°] (instead of [−180°, 180°)). 179 | 180 | * Version 1.47 (released 2017-02-15) 181 | * Improve accuracy of area calculation (fixing a flaw introduced in 182 | version 1.46). 183 | 184 | * Version 1.46 (released 2016-02-15) 185 | * Fix bugs in PolygonArea.TestEdge (problem found by threepointone). 186 | * Add Geodesic.DirectLine, Geodesic.ArcDirectLine, 187 | Geodesic.GenDirectLine, Geodesic.InverseLine, 188 | GeodesicLine.SetDistance, GeodesicLine.SetArc, 189 | GeodesicLine.GenSetDistance, GeodesicLine.s13, GeodesicLine.a13. 190 | * More accurate inverse solution when longitude difference is close to 191 | 180°. 192 | 193 | ### Authors 194 | 195 | * algorithms + js code: Charles Karney (karney@alum.mit.edu) 196 | * node.js port: Yurij Mikhalevich (yurij@mikhalevi.ch) 197 | -------------------------------------------------------------------------------- /doc/HEADER.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of https://geographiclib.sourceforge.io/JavaScript 5 | 6 | 7 | 8 | 9 | 10 |

JavaScript implementation of geodesic routines in GeographicLib

11 |

12 | Each numbered directory gives the documentation for that specific 13 | version. doc points to the latest stable 14 | version. 15 |

16 | -------------------------------------------------------------------------------- /doc/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "includePattern": ".+\\.((js(doc)?)|(\\.d\\.ts))$", 7 | "excludePattern": "(^|\\/|\\\\)_" 8 | }, 9 | "plugins": [ "plugins/markdown" ], 10 | "templates": { 11 | "cleverLinks": false, 12 | "monospaceLinks": false, 13 | "default": { 14 | "outputSourceFiles": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /doc/tutorials/1-geodesics.md: -------------------------------------------------------------------------------- 1 | Jump to 2 | * [Introduction](#intro) 3 | * [Additional properties](#additional) 4 | * [Multiple shortest geodesics](#multiple) 5 | * [Background](#background) 6 | * [References](#references) 7 | 8 | ### Introduction 9 | 10 | Consider an ellipsoid of revolution with equatorial radius *a*, polar 11 | semi-axis *b*, and flattening *f* = (*a* − *b*)/*a* . Points on 12 | the surface of the ellipsoid are characterized by their latitude φ 13 | and longitude λ. (Note that latitude here means the 14 | *geographical latitude*, the angle between the normal to the ellipsoid 15 | and the equatorial plane). 16 | 17 | The shortest path between two points on the ellipsoid at 18 | (φ1, λ1) and (φ2, 19 | λ2) is called the geodesic. Its length is 20 | *s*12 and the geodesic from point 1 to point 2 has forward 21 | azimuths α1 and α2 at the two end 22 | points. In this figure, we have λ12 = 23 | λ2 − λ1. 24 |
25 | 26 |
27 | A geodesic can be extended indefinitely by requiring that any 28 | sufficiently small segment is a shortest path; geodesics are also the 29 | straightest curves on the surface. 30 | 31 | Traditionally two geodesic problems are considered: 32 | * the direct problem — given φ1, 33 | λ1, α1, *s*12, 34 | determine φ2, λ2, and 35 | α2; this is solved by 36 | {@link module:geodesic/Geodesic.Geodesic#Direct Geodesic.Direct}. 37 | 38 | * the inverse problem — given φ1, 39 | λ1, φ2, λ2, 40 | determine *s*12, α1, and 41 | α2; this is solved by 42 | {@link module:geodesic/Geodesic.Geodesic#Inverse Geodesic.Inverse}. 43 | 44 | ### Additional properties 45 | 46 | The routines also calculate several other quantities of interest 47 | * *S*12 is the area between the geodesic from point 1 to 48 | point 2 and the equator; i.e., it is the area, measured 49 | counter-clockwise, of the quadrilateral with corners 50 | (φ11), (0,λ1), 51 | (0,λ2), and 52 | (φ22). It is given in 53 | meters2. 54 | * *m*12, the reduced length of the geodesic is defined such 55 | that if the initial azimuth is perturbed by *d*α1 56 | (radians) then the second point is displaced by *m*12 57 | *d*α1 in the direction perpendicular to the 58 | geodesic. *m*12 is given in meters. On a curved surface 59 | the reduced length obeys a symmetry relation, *m*12 + 60 | *m*21 = 0. On a flat surface, we have *m*12 = 61 | *s*12. 62 | * *M*12 and *M*21 are geodesic scales. If two 63 | geodesics are parallel at point 1 and separated by a small distance 64 | *dt*, then they are separated by a distance *M*12 *dt* at 65 | point 2. *M*21 is defined similarly (with the geodesics 66 | being parallel to one another at point 2). *M*12 and 67 | *M*21 are dimensionless quantities. On a flat surface, 68 | we have *M*12 = *M*21 = 1. 69 | * σ12 is the arc length on the auxiliary sphere. 70 | This is a construct for converting the problem to one in spherical 71 | trigonometry. The spherical arc length from one equator crossing to 72 | the next is always 180°. 73 | 74 | If points 1, 2, and 3 lie on a single geodesic, then the following 75 | addition rules hold: 76 | * *s*13 = *s*12 + *s*23 77 | * σ13 = σ12 + σ23 78 | * *S*13 = *S*12 + *S*23 79 | * *m*13 = *m*12*M*23 + 80 | *m*23*M*21 81 | * *M*13 = *M*12*M*23 − 82 | (1 − *M*12*M*21) 83 | *m*23/*m*12 84 | * *M*31 = *M*32*M*21 − 85 | (1 − *M*23*M*32) 86 | *m*12/*m*23 87 | 88 | ### Multiple shortest geodesics 89 | 90 | The shortest distance found by solving the inverse problem is 91 | (obviously) uniquely defined. However, in a few special cases there are 92 | multiple azimuths which yield the same shortest distance. Here is a 93 | catalog of those cases: 94 | * φ1 = −φ2 (with neither point at 95 | a pole). If α1 = α2, the geodesic 96 | is unique. Otherwise there are two geodesics and the second one is 97 | obtained by setting [α12] ← 98 | [α21], 99 | [*M*12,*M*21] ← 100 | [*M*21,*M*12], *S*12 ← 101 | −*S*12. (This occurs when the longitude difference 102 | is near ±180° for oblate ellipsoids.) 103 | * λ2 = λ1 ± 180° (with 104 | neither point at a pole). If α1 = 0° or 105 | ±180°, the geodesic is unique. Otherwise there are two 106 | geodesics and the second one is obtained by setting 107 | [α12] ← 108 | [−α1,−α2], 109 | *S*12 ← −*S*12. (This occurs when 110 | φ2 is near −φ1 for prolate 111 | ellipsoids.) 112 | * Points 1 and 2 at opposite poles. There are infinitely many 113 | geodesics which can be generated by setting 114 | [α12] ← 115 | [α12] + 116 | [δ,−δ], for arbitrary δ. (For spheres, this 117 | prescription applies when points 1 and 2 are antipodal.) 118 | * *s*12 = 0 (coincident points). There are infinitely many 119 | geodesics which can be generated by setting 120 | [α12] ← 121 | [α12] + [δ,δ], for 122 | arbitrary δ. 123 | 124 | ### Area of a polygon 125 | 126 | The area of a geodesic polygon can be determined by summing −*S12* 127 | for successive edges of the polygon (*S12* is negated so that 128 | clockwise traversal of a polygon gives a positive area). However, if 129 | the polygon encircles a pole, the sum must be adjusted by 130 | ±*A*/2, where *A* is the area of the full ellipsoid, with 131 | the sign chosen to place the result in (−*A*/2, *A*/2]. 132 | 133 | ### Background 134 | 135 | The algorithms implemented by this package are given in Karney (2013) 136 | and are based on Bessel (1825) and Helmert (1880); the algorithm for 137 | areas is based on Danielsen (1989). These improve on the work of 138 | Vincenty (1975) in the following respects: 139 | * The results are accurate to round-off for terrestrial ellipsoids (the 140 | error in the distance is less than 15 nanometers, compared to 0.1 mm 141 | for Vincenty). 142 | * The solution of the inverse problem is always found. (Vincenty's 143 | method fails to converge for nearly antipodal points.) 144 | * The routines calculate differential and integral properties of a 145 | geodesic. This allows, for example, the area of a geodesic polygon to 146 | be computed. 147 | 148 | ### References 149 | 150 | * F. W. Bessel, 151 | {@link https://arxiv.org/abs/0908.1824 The calculation of longitude and 152 | latitude from geodesic measurements (1825)}, 153 | Astron. Nachr. **331**(8), 852–861 (2010), 154 | translated by C. F. F. Karney and R. E. Deakin. 155 | * F. R. Helmert, 156 | {@link https://doi.org/10.5281/zenodo.32050 157 | Mathematical and Physical Theories of Higher Geodesy, Vol 1}, 158 | (Teubner, Leipzig, 1880), Chaps. 5–7. 159 | * T. Vincenty, 160 | {@link http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf 161 | Direct and inverse solutions of geodesics on the ellipsoid with 162 | application of nested equations}, 163 | Survey Review **23**(176), 88–93 (1975). 164 | * J. Danielsen, 165 | {@link https://doi.org/10.1179/003962689791474267 The area under 166 | the geodesic}, Survey Review **30**(232), 61–66 (1989). 167 | * C. F. F. Karney, 168 | {@link https://doi.org/10.1007/s00190-012-0578-z 169 | Algorithms for geodesics}, J. Geodesy **87**(1) 43–55 (2013); 170 | {@link https://geographiclib.sourceforge.io/geod-addenda.html addenda}. 171 | * C. F. F. Karney, 172 | {@link https://arxiv.org/abs/1102.1215v1 173 | Geodesics on an ellipsoid of revolution}, 174 | Feb. 2011; 175 | {@link https://geographiclib.sourceforge.io/geod-addenda.html#geod-errata 176 | errata}. 177 | * {@link https://geographiclib.sourceforge.io/geodesic-papers/biblio.html 178 | A geodesic bibliography}. 179 | * The wikipedia page, 180 | {@link https://en.wikipedia.org/wiki/Geodesics_on_an_ellipsoid 181 | Geodesics on an ellipsoid}. 182 | -------------------------------------------------------------------------------- /doc/tutorials/2-interface.md: -------------------------------------------------------------------------------- 1 | Jump to 2 | * [The units](#units) 3 | * [The results](#results) 4 | * [The *outmask* and *caps* parameters](#outmask) 5 | * [Restrictions on the parameters](#restrict) 6 | 7 | ### The units 8 | 9 | All angles (latitude, longitude, azimuth, arc length) are measured in 10 | degrees with latitudes increasing northwards, longitudes increasing 11 | eastwards, and azimuths measured clockwise from north. For a point at a 12 | pole, the azimuth is defined by keeping the longitude fixed, writing 13 | φ = ±(90° − ε), and taking the limit 14 | ε → 0+. 15 | 16 | ### The results 17 | 18 | The results returned by 19 | {@link module:geodesic/Geodesic.Geodesic#Inverse Geodesic.Direct}, 20 | {@link module:geodesic/Geodesic.Geodesic#Inverse Geodesic.Inverse}, 21 | {@link module:geodesic/GeodesicLine.GeodesicLine#Position 22 | GeodesicLine.Position}, etc., return an object with 23 | (some) of the following 12 fields set: 24 | * *lat1* = φ1, latitude of point 1 (degrees) 25 | * *lon1* = λ1, longitude of point 1 (degrees) 26 | * *azi1* = α1, azimuth of line at point 1 (degrees) 27 | * *lat2* = φ2, latitude of point 2 (degrees) 28 | * *lon2* = λ2, longitude of point 2 (degrees) 29 | * *azi2* = α2, (forward) azimuth of line at point 2 (degrees) 30 | * *s12* = *s*12, distance from 1 to 2 (meters) 31 | * *a12* = σ12, arc length on auxiliary sphere from 1 to 2 32 | (degrees) 33 | * *m12* = *m*12, reduced length of geodesic (meters) 34 | * *M12* = *M*12, geodesic scale at 2 relative to 1 (dimensionless) 35 | * *M21* = *M*21, geodesic scale at 1 relative to 2 (dimensionless) 36 | * *S12* = *S*12, area between geodesic and equator 37 | (meters2) 38 | 39 | The input parameters together with *a12* are always included in the 40 | object. Azimuths are reduced to the range [−180°, 180°]. 41 | See {@tutorial 1-geodesics} for the definitions of these quantities. 42 | 43 | ### The *outmask* and *caps* parameters 44 | 45 | By default, the geodesic routines return the 7 basic quantities: *lat1*, 46 | *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, together with the arc 47 | length *a12*. The optional output mask parameter, *outmask*, can be 48 | used to tailor which quantities to calculate. In addition, when a 49 | {@link module:geodesic/GeodesicLine.GeodesicLine GeodesicLine} is 50 | constructed it can be provided with the optional capabilities parameter, 51 | *caps*, which specifies what quantities can be returned from the 52 | resulting object. 53 | 54 | Both *outmask* and *caps* are obtained by or'ing together the following 55 | values 56 | * Geodesic.NONE, no capabilities, no output; 57 | * Geodesic.ARC, compute arc length, *a12*; this is always implicitly set; 58 | * Geodesic.LATITUDE, compute latitude, *lat2*; 59 | * Geodesic.LONGITUDE, compute longitude, *lon2*; 60 | * Geodesic.AZIMUTH, compute azimuths, *azi1* and *azi2*; 61 | * Geodesic.DISTANCE, compute distance, *s12*; 62 | * Geodesic.STANDARD, all of the above; 63 | * Geodesic.DISTANCE_IN, allow *s12* to be used as input in the direct problem; 64 | * Geodesic.REDUCEDLENGTH, compute reduced length, *m12*; 65 | * Geodesic.GEODESICSCALE, compute geodesic scales, *M12* and *M21*; 66 | * Geodesic.AREA, compute area, *S12*; 67 | * Geodesic.ALL, all of the above; 68 | * Geodesic.LONG_UNROLL, unroll longitudes. 69 | 70 | Geodesic.DISTANCE_IN is a capability provided to the 71 | {@link module:geodesic/GeodesicLine.GeodesicLine GeodesicLine} 72 | constructor. It allows the position on the line to specified in terms 73 | of distance. (Without this, the position can only be specified in terms 74 | of the arc length.) This only makes sense in the *caps* parameter. 75 | 76 | Geodesic.LONG_UNROLL controls the treatment of longitude. If it is not 77 | set then the *lon1* and *lon2* fields are both reduced to the range 78 | [−180°, 180°]. If it is set, then *lon1* is as given in 79 | the function call and (*lon2* − *lon1*) determines how many times 80 | and in what sense the geodesic has encircled the ellipsoid. This only 81 | makes sense in the *outmask* parameter. 82 | 83 | Note that *a12* is always included in the result. 84 | 85 | ### Restrictions on the parameters 86 | 87 | * Latitudes must lie in [−90°, 90°]. Latitudes outside 88 | this range are replaced by NaNs. 89 | * The distance *s12* is unrestricted. This allows geodesics to wrap 90 | around the ellipsoid. Such geodesics are no longer shortest paths. 91 | However they retain the property that they are the straightest curves 92 | on the surface. 93 | * Similarly, the spherical arc length *a12* is unrestricted. 94 | * Longitudes and azimuths are unrestricted; internally these are exactly 95 | reduced to the range [−180°, 180°]; but see also the 96 | LONG_UNROLL bit. 97 | * The equatorial radius *a* and the polar semi-axis *b* must both be 98 | positive and finite (this implies that −∞ < *f* < 1). 99 | * The flattening *f* should satisfy *f* ∈ [−1/50,1/50] in 100 | order to retain full accuracy. This condition holds for most 101 | applications in geodesy. 102 | 103 | Reasonably accurate results can be obtained for −0.2 ≤ *f* ≤ 104 | 0.2. Here is a table of the approximate maximum error (expressed as a 105 | distance) for an ellipsoid with the same equatorial radius as the WGS84 106 | ellipsoid and different values of the flattening. 107 | 108 | | abs(f) | error 109 | |:-------|------: 110 | | 0.003 | 15 nm 111 | | 0.01 | 25 nm 112 | | 0.02 | 30 nm 113 | | 0.05 | 10 μm 114 | | 0.1 | 1.5 mm 115 | | 0.2 | 300 mm 116 | 117 | Here 1 nm = 1 nanometer = 10−9 m (*not* 1 nautical mile!) 118 | -------------------------------------------------------------------------------- /doc/tutorials/3-examples.md: -------------------------------------------------------------------------------- 1 | Jump to 2 | * [Using packages online](#online) 3 | * [geodesic namespace](#namespace) 4 | * [Specifying the ellipsoid](#ellipsoid) 5 | * [Basic geodesic calculations](#basic) 6 | * [Computing waypoints](#waypoints) 7 | * [Measuring areas](#area) 8 | * [Degrees, minutes, seconds conversion](#dms) 9 | 10 | ### Online examples 11 | 12 | JavaScript is most useful for deploying applications that run in the 13 | browser. Two such examples that illustrate the use of these 14 | JavaScript packages are: 15 | 16 | * [geod-calc](../../scripts/geod-calc.html): an online 17 | geodesic calculator. 18 | 19 | * [geod-google](../../scripts/geod-google.html): a tool for viewing 20 | geodesic on Google Map; here are the [instructions for using this 21 | tool](../../scripts/geod-google-instructions.html) 22 | 23 | These are available in the [samples 24 | ](https://github.com/geographiclib/geographiclib-js/tree/main/samples) 25 | directory of the git repository. 26 | 27 | ### geodesic namespace 28 | 29 | This capabilities of these package are all exposed through the {@link 30 | geodesic} namespace and {@link module:DMS DMS} module. These can 31 | brought into scope in various ways. 32 | 33 | #### Using node after installing the package with npm 34 | 35 | If [npm](https://www.npmjs.com) has been used to install 36 | geographiclib-geodesic and geographiclib-dms via 37 | ```bash 38 | $ npm install geographiclib-geodesic geographiclib-dms 39 | ``` 40 | then in [node](https://nodejs.org), you can do 41 | ```javascript 42 | var geodesic = require("geographiclib-geodesic"); 43 | var DMS = require("geographiclib-dms"); 44 | ``` 45 | 46 | The following descriptions mostly focus in the 47 | `geographiclib-geodesic` package. Make the obvious substitutions are 48 | to load `geographiclib-dms`. 49 | 50 | #### Using node with a free-standing geographiclib-geodesic.js 51 | 52 | If you have `geographiclib-geodesic.js` in your current 53 | directory, then [node](https://nodejs.org) can access it with 54 | ```javascript 55 | var geodesic = require("./geographiclib-geodesic"); 56 | ``` 57 | A similar prescription works if `geographiclib-geodesic.js` is 58 | installed elsewhere in your filesystem, replacing "./" above with the 59 | correct directory. Note that the directory must begin with "./", 60 | "../", or "/". 61 | 62 | #### HTML with your own version of geographiclib-geodesic.min.js 63 | 64 | Load geographiclib-geodesic.min.js with 65 | ```html 66 | 68 | ``` 69 | This ".min.js" version has been "minified" by removing comments and 70 | redundant white space; this is appropriate for web applications. 71 | 72 | #### HTML downloading geographiclib-geodesic.min.js from SourceForge 73 | 74 | Load `geographiclib-geodesic.min.js` with 75 | ```html 76 | 80 | ``` 81 | 82 | #### Loading scripts with AMD 83 | 84 | This uses [require.js](http://requirejs.org/) (which you can download 85 | [here](http://requirejs.org/docs/download.html)) to load geographiclib 86 | asynchronously. Your web page includes 87 | ```html 88 | 89 | ``` 90 | where `main.js` contains, for example, 91 | ```javascript 92 | requirejs(["geographiclib-geodesic", "geographiclib-dms"], 93 | function(geodesic, DMS) { 94 | // do something with geodesic and DMS here. 95 | }); 96 | ``` 97 | 98 | ### Specifying the ellipsoid 99 | 100 | Once {@link geodesic} has been brought into scope, the ellipsoid is 101 | defined via the {@link module:geodesic/Geodesic.Geodesic Geodesic} 102 | constructor using the equatorial radius *a* in meters and the flattening 103 | *f*, for example 104 | ```javascipt 105 | var geod = new geodesic.Geodesic.Geodesic(6378137, 1/298.257223563); 106 | ``` 107 | These are the parameters for the WGS84 ellipsoid and this comes predefined 108 | by the package as 109 | ```javascipt 110 | var geod = geodesic.Geodesic.WGS84; 111 | ``` 112 | Note that you can set *f* = 0 to give a sphere (on which geodesics are 113 | great circles) and *f* < 0 to give a prolate ellipsoid. 114 | 115 | The rest of the examples on this page assume the following assignments 116 | ```javascript 117 | var geodesic = require("geographiclib-geodesic"), 118 | DMS = require("geographiclib-dms"); 119 | geod = geodesic.Geodesic.WGS84; 120 | ``` 121 | with the understanding that the first two lines should be replaced 122 | with the appropriate construction needed to bring the {@link geodesic} 123 | and {@link module:DMS DMS} into scope. Of course, if you aren't doing 124 | DMS conversions, there's no need to define `DMS`. 125 | 126 | ### Basic geodesic calculations 127 | 128 | The distance from Wellington, NZ (41.32S, 174.81E) to Salamanca, Spain 129 | (40.96N, 5.50W) using 130 | {@link module:geodesic/Geodesic.Geodesic#Inverse 131 | Geodesic.Inverse}: 132 | ```javascript 133 | var r = geod.Inverse(-41.32, 174.81, 40.96, -5.50); 134 | console.log("The distance is " + r.s12.toFixed(3) + " m."); 135 | ``` 136 | →`The distance is 19959679.267 m.` 137 | 138 | The point the point 20000 km SW of Perth, Australia (32.06S, 115.74E) using 139 | {@link module:geodesic/Geodesic.Geodesic#Direct 140 | Geodesic.Direct}: 141 | ```javascript 142 | var r = geod.Direct(-32.06, 115.74, 225, 20000e3); 143 | console.log("The position is (" + 144 | r.lat2.toFixed(8) + ", " + r.lon2.toFixed(8) + ")."); 145 | ``` 146 | →`The position is (32.11195529, -63.95925278).` 147 | 148 | The area between the geodesic from JFK Airport (40.6N, 73.8W) to LHR 149 | Airport (51.6N, 0.5W) and the equator. This is an example of setting 150 | the *outmask* parameter, see {@tutorial 2-interface}, "The *outmask* and 151 | *caps* parameters". 152 | ```javascript 153 | var r = geod.Inverse(40.6, -73.8, 51.6, -0.5, Geodesic.AREA); 154 | console.log("The area is " + r.S12.toFixed(1) + " m^2"); 155 | ``` 156 | →`The area is 40041368848742.5 m^2` 157 | 158 | ### Computing waypoints 159 | 160 | Consider the geodesic between Beijing Airport (40.1N, 116.6E) and San 161 | Fransisco Airport (37.6N, 122.4W). Compute waypoints and azimuths at 162 | intervals of 1000 km using 163 | {@link module:geodesic/Geodesic.Geodesic#InverseLine 164 | Geodesic.InverseLine} and 165 | {@link module:geodesic/GeodesicLine.GeodesicLine#Position 166 | GeodesicLine.Position}: 167 | ```javascript 168 | var l = geod.InverseLine(40.1, 116.6, 37.6, -122.4), 169 | n = Math.ceil(l.s13 / ds), 170 | i, s; 171 | console.log("distance latitude longitude azimuth"); 172 | for (i = 0; i <= n; ++i) { 173 | s = Math.min(ds * i, l.s13); 174 | r = l.Position(s, Geodesic.STANDARD | Geodesic.LONG_UNROLL); 175 | console.log(r.s12.toFixed(0) + " " + r.lat2.toFixed(5) + " " + 176 | r.lon2.toFixed(5) + " " + r.azi2.toFixed(5)); 177 | } 178 | ``` 179 | gives 180 | ```text 181 | distance latitude longitude azimuth 182 | 0 40.10000 116.60000 42.91642 183 | 1000000 46.37321 125.44903 48.99365 184 | 2000000 51.78786 136.40751 57.29433 185 | 3000000 55.92437 149.93825 68.24573 186 | 4000000 58.27452 165.90776 81.68242 187 | 5000000 58.43499 183.03167 96.29014 188 | 6000000 56.37430 199.26948 109.99924 189 | 7000000 52.45769 213.17327 121.33210 190 | 8000000 47.19436 224.47209 129.98619 191 | 9000000 41.02145 233.58294 136.34359 192 | 9513998 37.60000 237.60000 138.89027 193 | ``` 194 | The inclusion of Geodesic.LONG_UNROLL in the call to 195 | {@link module:geodesic/GeodesicLine.GeodesicLine#Position 196 | GeodesicLine.Position} ensures that the longitude does not jump on 197 | crossing the international dateline. 198 | 199 | If the purpose of computing the waypoints is to plot a smooth geodesic, 200 | then it's not important that they be exactly equally spaced. In this 201 | case, it's faster to parameterize the line in terms of the spherical arc 202 | length with 203 | {@link module:geodesic/GeodesicLine.GeodesicLine#ArcPosition 204 | GeodesicLine.ArcPosition} instead of the distance. Here the spacing is 205 | about 1° of arc which means that the distance between the waypoints 206 | will be about 60 NM. 207 | ```javascript 208 | var l = geod.InverseLine(40.1, 116.6, 37.6, -122.4, 209 | Geodesic.LATITUDE | Geodesic.LONGITUDE), 210 | da = 1, n = Math.ceil(l.a13 / da), 211 | i, a; 212 | da = l.a13 / n; 213 | console.log("latitude longitude"); 214 | for (i = 0; i <= n; ++i) { 215 | a = da * i; 216 | r = l.ArcPosition(a, Geodesic.LATITUDE | 217 | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL); 218 | console.log(r.lat2.toFixed(5) + " " + r.lon2.toFixed(5)); 219 | } 220 | ``` 221 | gives 222 | ```text 223 | latitude longitude 224 | 40.10000 116.60000 225 | 40.82573 117.49243 226 | 41.54435 118.40447 227 | 42.25551 119.33686 228 | 42.95886 120.29036 229 | 43.65403 121.26575 230 | 44.34062 122.26380 231 | ... 232 | 39.82385 235.05331 233 | 39.08884 235.91990 234 | 38.34746 236.76857 235 | 37.60000 237.60000 236 | ``` 237 | The variation in the distance between these waypoints is on the order of 238 | 1/*f*. 239 | 240 | ### Measuring areas 241 | 242 | Measure the area of Antarctica using 243 | {@link module:geodesic/Geodesic.Geodesic#Polygon 244 | Geodesic.Polygon} and the 245 | {@link module:geodesic/PolygonArea.PolygonArea 246 | PolygonArea} class: 247 | ```javascript 248 | var p = geod.Polygon(false), i, 249 | antarctica = [ 250 | [-63.1, -58], [-72.9, -74], [-71.9,-102], [-74.9,-102], [-74.3,-131], 251 | [-77.5,-163], [-77.4, 163], [-71.7, 172], [-65.9, 140], [-65.7, 113], 252 | [-66.6, 88], [-66.9, 59], [-69.8, 25], [-70.0, -4], [-71.0, -14], 253 | [-77.3, -33], [-77.9, -46], [-74.7, -61] 254 | ]; 255 | for (i = 0; i < antarctica.length; ++i) 256 | p.AddPoint(antarctica[i][0], antarctica[i][1]); 257 | p = p.Compute(false, true); 258 | console.log("Perimeter/area of Antarctica are " + 259 | p.perimeter.toFixed(3) + " m / " + 260 | p.area.toFixed(1) + " m^2."); 261 | ``` 262 | →`Perimeter/area of Antarctica are 16831067.893 m / 13662703680020.1 m^2.` 263 | 264 | If the points of the polygon are being selected interactively, then 265 | {@link module:geodesic/PolygonArea.PolygonArea#TestPoint 266 | PolygonArea.TestPoint} can be used to report the area and perimeter for 267 | a polygon with a tentative final vertex which tracks the mouse pointer. 268 | 269 | ### Degrees, minutes, seconds conversion 270 | 271 | Compute the azimuth for geodesic from JFK (73.8W, 40.6N) to Paris CDG 272 | (49°01'N, 2°33'E) using the {@link module:DMS DMS} module: 273 | ```javascript 274 | var c = "73.8W 40.6N 49°01'N 2°33'E".split(" "), 275 | p1 = DMS.DecodeLatLon(c[0], c[1]), 276 | p2 = DMS.DecodeLatLon(c[2], c[3]), 277 | r = geod.Inverse(p1.lat, p1.lon, p2.lat, p2.lon); 278 | console.log("Start = (" + 279 | DMS.Encode(r.lat1, DMS.MINUTE, 0, DMS.LATITUDE) + ", " + 280 | DMS.Encode(r.lon1, DMS.MINUTE, 0, DMS.LONGITUDE) + 281 | "), azimuth = " + 282 | DMS.Encode(r.azi1, DMS.MINUTE, 1, DMS.AZIMUTH)); 283 | ``` 284 | →`Start = (40°36'N, 073°48'W), azimuth = 053°28.2'` 285 | -------------------------------------------------------------------------------- /doc/tutorials/tutorials.json: -------------------------------------------------------------------------------- 1 | { 2 | "1-geodesics": { 3 | "title": "General information on geodesics" 4 | }, 5 | "2-interface": { 6 | "title": "The library interface" 7 | }, 8 | "3-examples": { 9 | "title": "Examples of use" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /geodesic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file (package.json.in ${DESTGEOD}/package.json @ONLY) 2 | configure_file (README.md ${DESTGEOD} COPYONLY) 3 | configure_file (../LICENSE.txt ${DESTGEOD} COPYONLY) 4 | 5 | configure_file (HEAD.js.in HEAD.js @ONLY) 6 | set (BUNDLEFILES ${CMAKE_CURRENT_BINARY_DIR}/HEAD.js) 7 | 8 | set (CONFIGSOURCES Math.js) 9 | foreach (_f ${CONFIGSOURCES}) 10 | configure_file (${_f}.in ${DESTGEOD}/src/${_f} @ONLY) 11 | list (APPEND BUNDLEFILES ${DESTGEOD}/src/${_f}) 12 | endforeach () 13 | 14 | set (SOURCES Geodesic.js GeodesicLine.js PolygonArea.js) 15 | foreach (_f ${SOURCES}) 16 | configure_file (${_f} ${DESTGEOD}/src/${_f} COPYONLY) 17 | list (APPEND BUNDLEFILES ${DESTGEOD}/src/${_f}) 18 | endforeach () 19 | 20 | configure_file (TAIL.js . COPYONLY) 21 | list (APPEND BUNDLEFILES ${CMAKE_CURRENT_BINARY_DIR}/TAIL.js) 22 | 23 | set (TESTS geodesictest.js signtest.js) 24 | foreach (_f ${TESTS}) 25 | configure_file (test/${_f} ${DESTGEOD}/test/${_f} COPYONLY) 26 | endforeach () 27 | 28 | configure_file (types/${GEOD_PROJECT}.d.ts 29 | ${DESTGEOD}/types/${GEOD_PROJECT}.d.ts COPYONLY) 30 | 31 | add_custom_command (OUTPUT ${PACKAGEDGEODJS} 32 | COMMAND ${CMAKE_COMMAND} -E cat ${BUNDLEFILES} > ${PACKAGEDGEODJS} 33 | DEPENDS ${BUNDLEFILES} 34 | COMMENT "Making JS bundle for geodesic" 35 | VERBATIM) 36 | 37 | add_custom_command (OUTPUT ${PACKAGEDGEODMINJS} 38 | COMMAND ${MINIFY} ${PACKAGEDGEODJS} > ${PACKAGEDGEODMINJS} 39 | DEPENDS ${PACKAGEDGEODJS} 40 | COMMENT "Making minified JS bundle for geodesic" 41 | VERBATIM) 42 | 43 | add_custom_target (bundlegeod ALL 44 | DEPENDS ${PACKAGEDGEODJS} ${PACKAGEDGEODMINJS}) 45 | 46 | install (DIRECTORY ${DESTGEOD} 47 | DESTINATION ${MODDIR} 48 | FILES_MATCHING PATTERN "*.[jt]s" PATTERN "*.txt" PATTERN "*.json") 49 | 50 | if (MOCHA) 51 | add_test (NAME testsuite 52 | COMMAND ${MOCHA} 53 | WORKING_DIRECTORY ${DESTGEOD}) 54 | endif () 55 | 56 | # linting... 57 | if (LINT) 58 | add_custom_target (lintgeodesic ${LINT} src WORKING_DIRECTORY ${DESTGEOD} 59 | COMMENT "Linting geodesic with ${LINT}") 60 | endif () 61 | -------------------------------------------------------------------------------- /geodesic/GeodesicLine.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GeodesicLine.js 3 | * Transcription of GeodesicLine.[ch]pp into JavaScript. 4 | * 5 | * See the documentation for the C++ class. The conversion is a literal 6 | * conversion from C++. 7 | * 8 | * The algorithms are derived in 9 | * 10 | * Charles F. F. Karney, 11 | * Algorithms for geodesics, J. Geodesy 87, 43-55 (2013); 12 | * https://doi.org/10.1007/s00190-012-0578-z 13 | * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html 14 | * 15 | * Copyright (c) Charles Karney (2011-2022) and licensed 16 | * under the MIT/X11 License. For more information, see 17 | * https://geographiclib.sourceforge.io/ 18 | */ 19 | 20 | // Load AFTER geodesic/Math.js, geodesic/Geodesic.js 21 | 22 | (function( 23 | g, 24 | /** 25 | * @exports geodesic/GeodesicLine 26 | * @description Solve geodesic problems on a single geodesic line via the 27 | * {@link module:geodesic/GeodesicLine.GeodesicLine GeodesicLine} 28 | * class. 29 | */ 30 | l, m) { 31 | "use strict"; 32 | 33 | /** 34 | * @class 35 | * @property {number} a the equatorial radius (meters). 36 | * @property {number} f the flattening. 37 | * @property {number} lat1 the initial latitude (degrees). 38 | * @property {number} lon1 the initial longitude (degrees). 39 | * @property {number} azi1 the initial azimuth (degrees). 40 | * @property {number} salp1 the sine of the azimuth at the first point. 41 | * @property {number} calp1 the cosine the azimuth at the first point. 42 | * @property {number} s13 the distance to point 3 (meters). 43 | * @property {number} a13 the arc length to point 3 (degrees). 44 | * @property {bitmask} caps the capabilities of the object. 45 | * @summary Initialize a GeodesicLine object. For details on the caps 46 | * parameter, see {@tutorial 2-interface}, "The outmask and caps 47 | * parameters". 48 | * @classdesc Performs geodesic calculations along a given geodesic line. 49 | * This object is usually instantiated by 50 | * {@link module:geodesic/Geodesic.Geodesic#Line Geodesic.Line}. 51 | * The methods 52 | * {@link module:geodesic/Geodesic.Geodesic#DirectLine 53 | * Geodesic.DirectLine} and 54 | * {@link module:geodesic/Geodesic.Geodesic#InverseLine 55 | * Geodesic.InverseLine} set in addition the position of a reference point 56 | * 3. 57 | * @param {object} geod a {@link module:geodesic/Geodesic.Geodesic 58 | * Geodesic} object. 59 | * @param {number} lat1 the latitude of the first point in degrees. 60 | * @param {number} lon1 the longitude of the first point in degrees. 61 | * @param {number} azi1 the azimuth at the first point in degrees. 62 | * @param {bitmask} [caps = STANDARD | DISTANCE_IN] which capabilities to 63 | * include; LATITUDE | AZIMUTH are always included. 64 | */ 65 | l.GeodesicLine = function(geod, lat1, lon1, azi1, caps, salp1, calp1) { 66 | var t, cbet1, sbet1, eps, s, c; 67 | if (!caps) caps = g.STANDARD | g.DISTANCE_IN; 68 | 69 | this.a = geod.a; 70 | this.f = geod.f; 71 | this._b = geod._b; 72 | this._c2 = geod._c2; 73 | this._f1 = geod._f1; 74 | this.caps = caps | g.LATITUDE | g.AZIMUTH | g.LONG_UNROLL; 75 | 76 | this.lat1 = m.LatFix(lat1); 77 | this.lon1 = lon1; 78 | if (typeof salp1 === 'undefined' || typeof calp1 === 'undefined') { 79 | this.azi1 = m.AngNormalize(azi1); 80 | t = m.sincosd(m.AngRound(this.azi1)); this.salp1 = t.s; this.calp1 = t.c; 81 | } else { 82 | this.azi1 = azi1; this.salp1 = salp1; this.calp1 = calp1; 83 | } 84 | t = m.sincosd(m.AngRound(this.lat1)); sbet1 = this._f1 * t.s; cbet1 = t.c; 85 | // norm(sbet1, cbet1); 86 | t = m.hypot(sbet1, cbet1); sbet1 /= t; cbet1 /= t; 87 | // Ensure cbet1 = +epsilon at poles 88 | cbet1 = Math.max(g.tiny_, cbet1); 89 | this._dn1 = Math.sqrt(1 + geod._ep2 * m.sq(sbet1)); 90 | 91 | // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), 92 | this._salp0 = this.salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|] 93 | // Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following 94 | // is slightly better (consider the case salp1 = 0). 95 | this._calp0 = m.hypot(this.calp1, this.salp1 * sbet1); 96 | // Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). 97 | // sig = 0 is nearest northward crossing of equator. 98 | // With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). 99 | // With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 100 | // With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 101 | // Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). 102 | // With alp0 in (0, pi/2], quadrants for sig and omg coincide. 103 | // No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. 104 | // With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. 105 | this._ssig1 = sbet1; this._somg1 = this._salp0 * sbet1; 106 | this._csig1 = this._comg1 = 107 | sbet1 !== 0 || this.calp1 !== 0 ? cbet1 * this.calp1 : 1; 108 | // norm(this._ssig1, this._csig1); // sig1 in (-pi, pi] 109 | t = m.hypot(this._ssig1, this._csig1); 110 | this._ssig1 /= t; this._csig1 /= t; 111 | // norm(this._somg1, this._comg1); -- don't need to normalize! 112 | 113 | this._k2 = m.sq(this._calp0) * geod._ep2; 114 | eps = this._k2 / (2 * (1 + Math.sqrt(1 + this._k2)) + this._k2); 115 | 116 | if (this.caps & g.CAP_C1) { 117 | this._A1m1 = g.A1m1f(eps); 118 | this._C1a = new Array(g.nC1_ + 1); 119 | g.C1f(eps, this._C1a); 120 | this._B11 = g.SinCosSeries(true, this._ssig1, this._csig1, this._C1a); 121 | s = Math.sin(this._B11); c = Math.cos(this._B11); 122 | // tau1 = sig1 + B11 123 | this._stau1 = this._ssig1 * c + this._csig1 * s; 124 | this._ctau1 = this._csig1 * c - this._ssig1 * s; 125 | // Not necessary because C1pa reverts C1a 126 | // _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa); 127 | } 128 | 129 | if (this.caps & g.CAP_C1p) { 130 | this._C1pa = new Array(g.nC1p_ + 1); 131 | g.C1pf(eps, this._C1pa); 132 | } 133 | 134 | if (this.caps & g.CAP_C2) { 135 | this._A2m1 = g.A2m1f(eps); 136 | this._C2a = new Array(g.nC2_ + 1); 137 | g.C2f(eps, this._C2a); 138 | this._B21 = g.SinCosSeries(true, this._ssig1, this._csig1, this._C2a); 139 | } 140 | 141 | if (this.caps & g.CAP_C3) { 142 | this._C3a = new Array(g.nC3_); 143 | geod.C3f(eps, this._C3a); 144 | this._A3c = -this.f * this._salp0 * geod.A3f(eps); 145 | this._B31 = g.SinCosSeries(true, this._ssig1, this._csig1, this._C3a); 146 | } 147 | 148 | if (this.caps & g.CAP_C4) { 149 | this._C4a = new Array(g.nC4_); // all the elements of _C4a are used 150 | geod.C4f(eps, this._C4a); 151 | // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) 152 | this._A4 = m.sq(this.a) * this._calp0 * this._salp0 * geod._e2; 153 | this._B41 = g.SinCosSeries(false, this._ssig1, this._csig1, this._C4a); 154 | } 155 | 156 | this.a13 = this.s13 = NaN; 157 | }; 158 | 159 | /** 160 | * @summary Find the position on the line (general case). 161 | * @param {bool} arcmode is the next parameter an arc length? 162 | * @param {number} s12_a12 the (arcmode ? arc length : distance) from the 163 | * first point to the second in (arcmode ? degrees : meters). 164 | * @param {bitmask} [outmask = STANDARD] which results to include; this is 165 | * subject to the capabilities of the object. 166 | * @return {object} the requested results. 167 | * @description The lat1, lon1, azi1, and a12 fields of the result are 168 | * always set; s12 is included if arcmode is false. For details on the 169 | * outmask parameter, see {@tutorial 2-interface}, "The outmask and caps 170 | * parameters". 171 | */ 172 | l.GeodesicLine.prototype.GenPosition = function(arcmode, s12_a12, 173 | outmask) { 174 | var vals = {}, 175 | sig12, ssig12, csig12, B12, AB1, ssig2, csig2, tau12, s, c, serr, 176 | omg12, lam12, lon12, E, sbet2, cbet2, somg2, comg2, salp2, calp2, dn2, 177 | B22, AB2, J12, t, B42, salp12, calp12; 178 | if (!outmask) outmask = g.STANDARD; 179 | else if (outmask === g.LONG_UNROLL) outmask |= g.STANDARD; 180 | outmask &= this.caps & g.OUT_MASK; 181 | vals.lat1 = this.lat1; vals.azi1 = this.azi1; 182 | vals.lon1 = outmask & g.LONG_UNROLL ? 183 | this.lon1 : m.AngNormalize(this.lon1); 184 | if (arcmode) 185 | vals.a12 = s12_a12; 186 | else 187 | vals.s12 = s12_a12; 188 | if (!( arcmode || (this.caps & g.DISTANCE_IN & g.OUT_MASK) )) { 189 | // Uninitialized or impossible distance calculation requested 190 | vals.a12 = NaN; 191 | return vals; 192 | } 193 | 194 | // Avoid warning about uninitialized B12. 195 | B12 = 0; AB1 = 0; 196 | if (arcmode) { 197 | // Interpret s12_a12 as spherical arc length 198 | sig12 = s12_a12 * m.degree; 199 | t = m.sincosd(s12_a12); ssig12 = t.s; csig12 = t.c; 200 | } else { 201 | // Interpret s12_a12 as distance 202 | tau12 = s12_a12 / (this._b * (1 + this._A1m1)); 203 | s = Math.sin(tau12); 204 | c = Math.cos(tau12); 205 | // tau2 = tau1 + tau12 206 | B12 = -g.SinCosSeries(true, 207 | this._stau1 * c + this._ctau1 * s, 208 | this._ctau1 * c - this._stau1 * s, 209 | this._C1pa); 210 | sig12 = tau12 - (B12 - this._B11); 211 | ssig12 = Math.sin(sig12); csig12 = Math.cos(sig12); 212 | if (Math.abs(this.f) > 0.01) { 213 | // Reverted distance series is inaccurate for |f| > 1/100, so correct 214 | // sig12 with 1 Newton iteration. The following table shows the 215 | // approximate maximum error for a = WGS_a() and various f relative to 216 | // GeodesicExact. 217 | // erri = the error in the inverse solution (nm) 218 | // errd = the error in the direct solution (series only) (nm) 219 | // errda = the error in the direct solution 220 | // (series + 1 Newton) (nm) 221 | // 222 | // f erri errd errda 223 | // -1/5 12e6 1.2e9 69e6 224 | // -1/10 123e3 12e6 765e3 225 | // -1/20 1110 108e3 7155 226 | // -1/50 18.63 200.9 27.12 227 | // -1/100 18.63 23.78 23.37 228 | // -1/150 18.63 21.05 20.26 229 | // 1/150 22.35 24.73 25.83 230 | // 1/100 22.35 25.03 25.31 231 | // 1/50 29.80 231.9 30.44 232 | // 1/20 5376 146e3 10e3 233 | // 1/10 829e3 22e6 1.5e6 234 | // 1/5 157e6 3.8e9 280e6 235 | ssig2 = this._ssig1 * csig12 + this._csig1 * ssig12; 236 | csig2 = this._csig1 * csig12 - this._ssig1 * ssig12; 237 | B12 = g.SinCosSeries(true, ssig2, csig2, this._C1a); 238 | serr = (1 + this._A1m1) * (sig12 + (B12 - this._B11)) - 239 | s12_a12 / this._b; 240 | sig12 = sig12 - serr / Math.sqrt(1 + this._k2 * m.sq(ssig2)); 241 | ssig12 = Math.sin(sig12); csig12 = Math.cos(sig12); 242 | // Update B12 below 243 | } 244 | } 245 | 246 | // sig2 = sig1 + sig12 247 | ssig2 = this._ssig1 * csig12 + this._csig1 * ssig12; 248 | csig2 = this._csig1 * csig12 - this._ssig1 * ssig12; 249 | dn2 = Math.sqrt(1 + this._k2 * m.sq(ssig2)); 250 | if (outmask & (g.DISTANCE | g.REDUCEDLENGTH | g.GEODESICSCALE)) { 251 | if (arcmode || Math.abs(this.f) > 0.01) 252 | B12 = g.SinCosSeries(true, ssig2, csig2, this._C1a); 253 | AB1 = (1 + this._A1m1) * (B12 - this._B11); 254 | } 255 | // sin(bet2) = cos(alp0) * sin(sig2) 256 | sbet2 = this._calp0 * ssig2; 257 | // Alt: cbet2 = hypot(csig2, salp0 * ssig2); 258 | cbet2 = m.hypot(this._salp0, this._calp0 * csig2); 259 | if (cbet2 === 0) 260 | // I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case 261 | cbet2 = csig2 = g.tiny_; 262 | // tan(alp0) = cos(sig2)*tan(alp2) 263 | salp2 = this._salp0; calp2 = this._calp0 * csig2; // No need to normalize 264 | 265 | if (arcmode && (outmask & g.DISTANCE)) 266 | vals.s12 = this._b * ((1 + this._A1m1) * sig12 + AB1); 267 | 268 | if (outmask & g.LONGITUDE) { 269 | // tan(omg2) = sin(alp0) * tan(sig2) 270 | somg2 = this._salp0 * ssig2; comg2 = csig2; // No need to normalize 271 | E = m.copysign(1, this._salp0); 272 | // omg12 = omg2 - omg1 273 | omg12 = outmask & g.LONG_UNROLL ? 274 | E * (sig12 - 275 | (Math.atan2(ssig2, csig2) - 276 | Math.atan2(this._ssig1, this._csig1)) + 277 | (Math.atan2(E * somg2, comg2) - 278 | Math.atan2(E * this._somg1, this._comg1))) : 279 | Math.atan2(somg2 * this._comg1 - comg2 * this._somg1, 280 | comg2 * this._comg1 + somg2 * this._somg1); 281 | lam12 = omg12 + this._A3c * 282 | ( sig12 + (g.SinCosSeries(true, ssig2, csig2, this._C3a) - 283 | this._B31)); 284 | lon12 = lam12 / m.degree; 285 | vals.lon2 = outmask & g.LONG_UNROLL ? this.lon1 + lon12 : 286 | m.AngNormalize(m.AngNormalize(this.lon1) + m.AngNormalize(lon12)); 287 | } 288 | 289 | if (outmask & g.LATITUDE) 290 | vals.lat2 = m.atan2d(sbet2, this._f1 * cbet2); 291 | 292 | if (outmask & g.AZIMUTH) 293 | vals.azi2 = m.atan2d(salp2, calp2); 294 | 295 | if (outmask & (g.REDUCEDLENGTH | g.GEODESICSCALE)) { 296 | B22 = g.SinCosSeries(true, ssig2, csig2, this._C2a); 297 | AB2 = (1 + this._A2m1) * (B22 - this._B21); 298 | J12 = (this._A1m1 - this._A2m1) * sig12 + (AB1 - AB2); 299 | if (outmask & g.REDUCEDLENGTH) 300 | // Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure 301 | // accurate cancellation in the case of coincident points. 302 | vals.m12 = this._b * (( dn2 * (this._csig1 * ssig2) - 303 | this._dn1 * (this._ssig1 * csig2)) - 304 | this._csig1 * csig2 * J12); 305 | if (outmask & g.GEODESICSCALE) { 306 | t = this._k2 * (ssig2 - this._ssig1) * (ssig2 + this._ssig1) / 307 | (this._dn1 + dn2); 308 | vals.M12 = csig12 + 309 | (t * ssig2 - csig2 * J12) * this._ssig1 / this._dn1; 310 | vals.M21 = csig12 - 311 | (t * this._ssig1 - this._csig1 * J12) * ssig2 / dn2; 312 | } 313 | } 314 | 315 | if (outmask & g.AREA) { 316 | B42 = g.SinCosSeries(false, ssig2, csig2, this._C4a); 317 | if (this._calp0 === 0 || this._salp0 === 0) { 318 | // alp12 = alp2 - alp1, used in atan2 so no need to normalize 319 | salp12 = salp2 * this.calp1 - calp2 * this.salp1; 320 | calp12 = calp2 * this.calp1 + salp2 * this.salp1; 321 | } else { 322 | // tan(alp) = tan(alp0) * sec(sig) 323 | // tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) 324 | // = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) 325 | // If csig12 > 0, write 326 | // csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) 327 | // else 328 | // csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 329 | // No need to normalize 330 | salp12 = this._calp0 * this._salp0 * 331 | (csig12 <= 0 ? this._csig1 * (1 - csig12) + ssig12 * this._ssig1 : 332 | ssig12 * (this._csig1 * ssig12 / (1 + csig12) + this._ssig1)); 333 | calp12 = m.sq(this._salp0) + m.sq(this._calp0) * this._csig1 * csig2; 334 | } 335 | vals.S12 = this._c2 * Math.atan2(salp12, calp12) + 336 | this._A4 * (B42 - this._B41); 337 | } 338 | 339 | if (!arcmode) 340 | vals.a12 = sig12 / m.degree; 341 | return vals; 342 | }; 343 | 344 | /** 345 | * @summary Find the position on the line given s12. 346 | * @param {number} s12 the distance from the first point to the second in 347 | * meters. 348 | * @param {bitmask} [outmask = STANDARD] which results to include; this is 349 | * subject to the capabilities of the object. 350 | * @return {object} the requested results. 351 | * @description The lat1, lon1, azi1, s12, and a12 fields of the result are 352 | * always set; s12 is included if arcmode is false. For details on the 353 | * outmask parameter, see {@tutorial 2-interface}, "The outmask and caps 354 | * parameters". 355 | */ 356 | l.GeodesicLine.prototype.Position = function(s12, outmask) { 357 | return this.GenPosition(false, s12, outmask); 358 | }; 359 | 360 | /** 361 | * @summary Find the position on the line given a12. 362 | * @param {number} a12 the arc length from the first point to the second in 363 | * degrees. 364 | * @param {bitmask} [outmask = STANDARD] which results to include; this is 365 | * subject to the capabilities of the object. 366 | * @return {object} the requested results. 367 | * @description The lat1, lon1, azi1, and a12 fields of the result are 368 | * always set. For details on the outmask parameter, see {@tutorial 369 | * 2-interface}, "The outmask and caps parameters". 370 | */ 371 | l.GeodesicLine.prototype.ArcPosition = function(a12, outmask) { 372 | return this.GenPosition(true, a12, outmask); 373 | }; 374 | 375 | /** 376 | * @summary Specify position of point 3 in terms of either distance or arc 377 | * length. 378 | * @param {bool} arcmode boolean flag determining the meaning of the second 379 | * parameter; if arcmode is false, then the GeodesicLine object must have 380 | * been constructed with caps |= DISTANCE_IN. 381 | * @param {number} s13_a13 if arcmode is false, this is the distance from 382 | * point 1 to point 3 (meters); otherwise it is the arc length from 383 | * point 1 to point 3 (degrees); it can be negative. 384 | */ 385 | l.GeodesicLine.prototype.GenSetDistance = function(arcmode, s13_a13) { 386 | if (arcmode) 387 | this.SetArc(s13_a13); 388 | else 389 | this.SetDistance(s13_a13); 390 | }; 391 | 392 | /** 393 | * @summary Specify position of point 3 in terms distance. 394 | * @param {number} s13 the distance from point 1 to point 3 (meters); it 395 | * can be negative. 396 | */ 397 | l.GeodesicLine.prototype.SetDistance = function(s13) { 398 | var r; 399 | this.s13 = s13; 400 | r = this.GenPosition(false, this.s13, g.ARC); 401 | this.a13 = 0 + r.a12; // the 0+ converts undefined into NaN 402 | }; 403 | 404 | /** 405 | * @summary Specify position of point 3 in terms of arc length. 406 | * @param {number} a13 the arc length from point 1 to point 3 (degrees); 407 | * it can be negative. 408 | */ 409 | l.GeodesicLine.prototype.SetArc = function(a13) { 410 | var r; 411 | this.a13 = a13; 412 | r = this.GenPosition(true, this.a13, g.DISTANCE); 413 | this.s13 = 0 + r.s12; // the 0+ converts undefined into NaN 414 | }; 415 | 416 | })(geodesic.Geodesic, geodesic.GeodesicLine, geodesic.Math); 417 | -------------------------------------------------------------------------------- /geodesic/HEAD.js.in: -------------------------------------------------------------------------------- 1 | /* 2 | * Geodesic routines from GeographicLib translated to JavaScript. See 3 | * https://geographiclib.sourceforge.io/JavaScript/doc 4 | * 5 | * The algorithms are derived in 6 | * 7 | * Charles F. F. Karney, 8 | * Algorithms for geodesics, J. Geodesy 87, 43-55 (2013), 9 | * https://doi.org/10.1007/s00190-012-0578-z 10 | * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html 11 | * 12 | * This file is the concatenation and compression of the JavaScript files in 13 | * src/geodesic in the source tree for geographiclib-js. 14 | * 15 | * Copyright (c) Charles Karney (2011-2022) and licensed 16 | * under the MIT/X11 License. For more information, see 17 | * https://geographiclib.sourceforge.io/ 18 | * 19 | * Version: @PROJECT_FULLVERSION@ 20 | * Date: @RELEASE_DATE@ 21 | */ 22 | 23 | (function(cb) { 24 | -------------------------------------------------------------------------------- /geodesic/Math.js.in: -------------------------------------------------------------------------------- 1 | /* 2 | * Math.js 3 | * Transcription of Math.hpp, Constants.hpp, and Accumulator.hpp into 4 | * JavaScript. 5 | * 6 | * Copyright (c) Charles Karney (2011-2021) and licensed 7 | * under the MIT/X11 License. For more information, see 8 | * https://geographiclib.sourceforge.io/ 9 | */ 10 | 11 | /** 12 | * @namespace geodesic 13 | * @description The parent namespace for the following modules: 14 | * - {@link module:geodesic/Geodesic geodesic/Geodesic} The main 15 | * engine for solving geodesic problems via the 16 | * {@link module:geodesic/Geodesic.Geodesic Geodesic} class. 17 | * - {@link module:geodesic/GeodesicLine geodesic/GeodesicLine} 18 | * computes points along a single geodesic line via the 19 | * {@link module:geodesic/GeodesicLine.GeodesicLine GeodesicLine} 20 | * class. 21 | * - {@link module:geodesic/PolygonArea geodesic/PolygonArea} 22 | * computes the area of a geodesic polygon via the 23 | * {@link module:geodesic/PolygonArea.PolygonArea PolygonArea} 24 | * class. 25 | * - {@link module:geodesic/Constants geodesic/Constants} defines 26 | * constants specifying the version numbers and the parameters for the WGS84 27 | * ellipsoid. 28 | * 29 | * The following modules are used internally by the package: 30 | * - {@link module:geodesic/Math geodesic/Math} defines various 31 | * mathematical functions. 32 | * - {@link module:geodesic/Accumulator geodesic/Accumulator} 33 | * interally used by 34 | * {@link module:geodesic/PolygonArea.PolygonArea PolygonArea} (via the 35 | * {@link module:geodesic/Accumulator.Accumulator Accumulator} class) 36 | * for summing the contributions to the area of a polygon. 37 | */ 38 | 39 | // To allow swap via [y, x] = [x, y] 40 | /* jshint esversion: 6 */ 41 | 42 | var geodesic = {}; 43 | geodesic.Constants = {}; 44 | geodesic.Math = {}; 45 | geodesic.Accumulator = {}; 46 | 47 | (function( 48 | /** 49 | * @exports geodesic/Constants 50 | * @description Define constants defining the version and WGS84 parameters. 51 | */ 52 | c) { 53 | "use strict"; 54 | 55 | /** 56 | * @constant 57 | * @summary WGS84 parameters. 58 | * @property {number} a the equatorial radius (meters). 59 | * @property {number} f the flattening. 60 | */ 61 | c.WGS84 = { a: 6378137, f: 1/298.257223563 }; 62 | /** 63 | * @constant 64 | * @summary an array of version numbers. 65 | * @property {number} major the major version number. 66 | * @property {number} minor the minor version number. 67 | * @property {number} patch the patch number. 68 | */ 69 | c.version = { major: @PROJECT_VERSION_MAJOR@, 70 | minor: @PROJECT_VERSION_MINOR@, 71 | patch: @PROJECT_VERSION_PATCH@ }; 72 | /** 73 | * @constant 74 | * @summary version string 75 | */ 76 | c.version_string = "@PROJECT_FULLVERSION@"; 77 | })(geodesic.Constants); 78 | 79 | (function( 80 | /** 81 | * @exports geodesic/Math 82 | * @description Some useful mathematical constants and functions (mainly for 83 | * internal use). 84 | */ 85 | m) { 86 | "use strict"; 87 | 88 | /** 89 | * @summary The number of digits of precision in floating-point numbers. 90 | * @constant {number} 91 | */ 92 | m.digits = 53; 93 | /** 94 | * @summary The machine epsilon. 95 | * @constant {number} 96 | */ 97 | m.epsilon = Math.pow(0.5, m.digits - 1); 98 | /** 99 | * @summary The factor to convert degrees to radians. 100 | * @constant {number} 101 | */ 102 | m.degree = Math.PI/180; 103 | 104 | /** 105 | * @summary Square a number. 106 | * @param {number} x the number. 107 | * @return {number} the square. 108 | */ 109 | m.sq = function(x) { return x * x; }; 110 | 111 | /** 112 | * @summary The hypotenuse function. 113 | * @param {number} x the first side. 114 | * @param {number} y the second side. 115 | * @return {number} the hypotenuse. 116 | */ 117 | m.hypot = function(x, y) { 118 | // Built in Math.hypot give incorrect results from GeodSolve92. 119 | return Math.sqrt(x*x + y*y); 120 | }; 121 | 122 | /** 123 | * @summary Cube root function. 124 | * @param {number} x the argument. 125 | * @return {number} the real cube root. 126 | */ 127 | m.cbrt = Math.cbrt || function(x) { 128 | var y = Math.pow(Math.abs(x), 1/3); 129 | return x > 0 ? y : (x < 0 ? -y : x); 130 | }; 131 | 132 | /** 133 | * @summary The log1p function. 134 | * @param {number} x the argument. 135 | * @return {number} log(1 + x). 136 | */ 137 | m.log1p = Math.log1p || function(x) { 138 | var y = 1 + x, 139 | z = y - 1; 140 | // Here's the explanation for this magic: y = 1 + z, exactly, and z 141 | // approx x, thus log(y)/z (which is nearly constant near z = 0) returns 142 | // a good approximation to the true log(1 + x)/x. The multiplication x * 143 | // (log(y)/z) introduces little additional error. 144 | return z === 0 ? x : x * Math.log(y) / z; 145 | }; 146 | 147 | /** 148 | * @summary Inverse hyperbolic tangent. 149 | * @param {number} x the argument. 150 | * @return {number} tanh−1 x. 151 | */ 152 | m.atanh = Math.atanh || function(x) { 153 | var y = Math.abs(x); // Enforce odd parity 154 | y = m.log1p(2 * y/(1 - y))/2; 155 | return x > 0 ? y : (x < 0 ? -y : x); 156 | }; 157 | 158 | /** 159 | * @summary Copy the sign. 160 | * @param {number} x gives the magitude of the result. 161 | * @param {number} y gives the sign of the result. 162 | * @return {number} value with the magnitude of x and with the sign of y. 163 | */ 164 | m.copysign = function(x, y) { 165 | return Math.abs(x) * (y < 0 || (y === 0 && 1/y < 0) ? -1 : 1); 166 | }; 167 | 168 | /** 169 | * @summary An error-free sum. 170 | * @param {number} u 171 | * @param {number} v 172 | * @return {object} sum with sum.s = round(u + v) and sum.t is u + v − 173 | * round(u + v) 174 | */ 175 | m.sum = function(u, v) { 176 | var s = u + v, 177 | up = s - v, 178 | vpp = s - up, 179 | t; 180 | up -= u; 181 | vpp -= v; 182 | // if s = 0, then t = 0 and give t the same sign as s 183 | t = s ? 0 - (up + vpp) : s; 184 | // u + v = s + t 185 | // = round(u + v) + t 186 | return {s: s, t: t}; 187 | }; 188 | 189 | /** 190 | * @summary Evaluate a polynomial. 191 | * @param {integer} N the order of the polynomial. 192 | * @param {array} p the coefficient array (of size N + 1) (leading 193 | * order coefficient first) 194 | * @param {number} x the variable. 195 | * @return {number} the value of the polynomial. 196 | */ 197 | m.polyval = function(N, p, s, x) { 198 | var y = N < 0 ? 0 : p[s++]; 199 | while (--N >= 0) y = y * x + p[s++]; 200 | return y; 201 | }; 202 | 203 | /** 204 | * @summary Coarsen a value close to zero. 205 | * @param {number} x 206 | * @return {number} the coarsened value. 207 | */ 208 | m.AngRound = function(x) { 209 | // The makes the smallest gap in x = 1/16 - nextafter(1/16, 0) = 1/2^57 for 210 | // reals = 0.7 pm on the earth if x is an angle in degrees. (This is about 211 | // 1000 times more resolution than we get with angles around 90 degrees.) 212 | // We use this to avoid having to deal with near singular cases when x is 213 | // non-zero but tiny (e.g., 1.0e-200). This converts -0 to +0; however 214 | // tiny negative numbers get converted to -0. 215 | var z = 1/16, 216 | y = Math.abs(x); 217 | // The compiler mustn't "simplify" z - (z - y) to y 218 | y = y < z ? z - (z - y) : y; 219 | return m.copysign(y, x); 220 | }; 221 | 222 | /** 223 | * @summary The remainder function. 224 | * @param {number} x the numerator of the division 225 | * @param {number} y the denominator of the division 226 | * @return {number} the remainder in the range [−y/2, y/2]. 227 | *

228 | * The range of x is unrestricted; y must be positive. 229 | */ 230 | m.remainder = function(x, y) { 231 | x %= y; 232 | return x < -y/2 ? x + y : (x < y/2 ? x : x - y); 233 | }; 234 | 235 | /** 236 | * @summary Normalize an angle. 237 | * @param {number} x the angle in degrees. 238 | * @return {number} the angle reduced to the range [−180°, 239 | * 180°]. 240 | * 241 | * The range of x is unrestricted. If the result is ±0° or 242 | * ±180° then the sign is the sign of \e x. 243 | */ 244 | m.AngNormalize = function(x) { 245 | // Place angle in [-180, 180]. 246 | var y = m.remainder(x, 360); 247 | return Math.abs(y) === 180 ? m.copysign(180, x) : y; 248 | }; 249 | 250 | /** 251 | * @summary Normalize a latitude. 252 | * @param {number} x the angle in degrees. 253 | * @return {number} x if it is in the range [−90°, 90°], 254 | * otherwise return NaN. 255 | */ 256 | m.LatFix = function(x) { 257 | // Replace angle with NaN if outside [-90, 90]. 258 | return Math.abs(x) > 90 ? NaN : x; 259 | }; 260 | 261 | /** 262 | * @summary The exact difference of two angles reduced to [−180°, 263 | * 180°] 264 | * @param {number} x the first angle in degrees. 265 | * @param {number} y the second angle in degrees. 266 | * @return {object} diff the exact difference, diff.d + diff.e = y − x 267 | * mod 360°. 268 | * 269 | * This computes z = y − x exactly, reduced to 270 | * [−180°, 180°]; and then sets z = d + e where d 271 | * is the nearest representable number to z and e is the truncation 272 | * error. If z = ±0° or ±180°, then the sign of 273 | * d is given by the sign of y − x. The maximum absolute 274 | * value of e is 2−26 (for doubles). 275 | */ 276 | m.AngDiff = function(x, y) { 277 | // Compute y - x and reduce to [-180,180] accurately. 278 | var r = m.sum(m.remainder(-x, 360), m.remainder(y, 360)), d, e; 279 | r = m.sum(m.remainder(r.s, 360), r.t); 280 | d = r.s; 281 | e = r.t; 282 | // Fix the sign if d = -180, 0, 180. 283 | if (d === 0 || Math.abs(d) === 180) 284 | // If e == 0, take sign from y - x 285 | // else (e != 0, implies d = +/-180), d and e must have opposite signs 286 | d = m.copysign(d, e === 0 ? y - x : -e); 287 | return {d: d, e: e}; 288 | }; 289 | 290 | /** 291 | * @summary Evaluate the sine and cosine function with the argument in 292 | * degrees 293 | * @param {number} x in degrees. 294 | * @return {object} r with r.s = sin(x) and r.c = cos(x). 295 | */ 296 | m.sincosd = function(x) { 297 | // In order to minimize round-off errors, this function exactly reduces 298 | // the argument to the range [-45, 45] before converting it to radians. 299 | var d, r, q, s, c, sinx, cosx; 300 | d = x % 360; 301 | q = Math.round(d / 90); // If d is NaN this returns NaN 302 | d -= 90 * q; 303 | // now abs(d) <= 45 304 | r = d * this.degree; 305 | // Possibly could call the gnu extension sincos 306 | s = Math.sin(r); c = Math.cos(r); 307 | if (Math.abs(d) === 45) { 308 | c = Math.sqrt(0.5); 309 | s = m.copysign(c, r); 310 | } else if (Math.abs(d) === 30) { 311 | c = Math.sqrt(0.75); 312 | s = m.copysign(0.5, r); 313 | } 314 | switch (q & 3) { 315 | case 0: sinx = s; cosx = c; break; 316 | case 1: sinx = c; cosx = -s; break; 317 | case 2: sinx = -s; cosx = -c; break; 318 | default: sinx = -c; cosx = s; break; // case 3 319 | } 320 | cosx += 0; 321 | if (sinx === 0) sinx = m.copysign(sinx, x); 322 | return {s: sinx, c: cosx}; 323 | }; 324 | 325 | /** 326 | * @summary Evaluate the sine and cosine with reduced argument plus correction 327 | * 328 | * @param {number} x reduced angle in degrees. 329 | * @param {number} t correction in degrees. 330 | * @return {object} r with r.s = sin(x + t) and r.c = cos(x + t). 331 | */ 332 | m.sincosde = function(x, t) { 333 | // In order to minimize round-off errors, this function exactly reduces 334 | // the argument to the range [-45, 45] before converting it to radians. 335 | var d, r, q, s, c, sinx, cosx; 336 | d = x % 360; 337 | q = Math.round(d / 90); // If d is NaN this returns NaN 338 | d = m.AngRound((d - 90 * q) + t); 339 | // now abs(d) <= 45 340 | r = d * this.degree; 341 | // Possibly could call the gnu extension sincos 342 | s = Math.sin(r); c = Math.cos(r); 343 | if (Math.abs(d) === 45) { 344 | c = Math.sqrt(0.5); 345 | s = m.copysign(c, r); 346 | } else if (Math.abs(d) === 30) { 347 | c = Math.sqrt(0.75); 348 | s = m.copysign(0.5, r); 349 | } 350 | switch (q & 3) { 351 | case 0: sinx = s; cosx = c; break; 352 | case 1: sinx = c; cosx = -s; break; 353 | case 2: sinx = -s; cosx = -c; break; 354 | default: sinx = -c; cosx = s; break; // case 3 355 | } 356 | cosx += 0; 357 | if (sinx === 0) sinx = m.copysign(sinx, x+t); 358 | return {s: sinx, c: cosx}; 359 | }; 360 | 361 | /** 362 | * @summary Evaluate the atan2 function with the result in degrees 363 | * @param {number} y 364 | * @param {number} x 365 | * @return atan2(y, x) in degrees, in the range [−180° 366 | * 180°]. 367 | */ 368 | m.atan2d = function(y, x) { 369 | // In order to minimize round-off errors, this function rearranges the 370 | // arguments so that result of atan2 is in the range [-pi/4, pi/4] before 371 | // converting it to degrees and mapping the result to the correct 372 | // quadrant. 373 | var q = 0, ang; 374 | if (Math.abs(y) > Math.abs(x)) { [y, x] = [x, y]; q = 2; } // swap(x, y) 375 | if (m.copysign(1, x) < 0) { x = -x; ++q; } 376 | // here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] 377 | ang = Math.atan2(y, x) / this.degree; 378 | switch (q) { 379 | // Note that atan2d(-0.0, 1.0) will return -0. However, we expect that 380 | // atan2d will not be called with y = -0. If need be, include 381 | // 382 | // case 0: ang = 0 + ang; break; 383 | // 384 | // and handle mpfr as in AngRound. 385 | case 1: ang = m.copysign(180, y) - ang; break; 386 | case 2: ang = 90 - ang; break; 387 | case 3: ang = -90 + ang; break; 388 | default: break; 389 | } 390 | return ang; 391 | }; 392 | })(geodesic.Math); 393 | 394 | (function( 395 | /** 396 | * @exports geodesic/Accumulator 397 | * @description Accurate summation via the 398 | * {@link module:geodesic/Accumulator.Accumulator Accumulator} class 399 | * (mainly for internal use). 400 | */ 401 | a, m) { 402 | "use strict"; 403 | 404 | /** 405 | * @class 406 | * @summary Accurate summation of many numbers. 407 | * @classdesc This allows many numbers to be added together with twice the 408 | * normal precision. In the documentation of the member functions, sum 409 | * stands for the value currently held in the accumulator. 410 | * @param {number | Accumulator} [y = 0] set sum = y. 411 | */ 412 | a.Accumulator = function(y) { 413 | this.Set(y); 414 | }; 415 | 416 | /** 417 | * @summary Set the accumulator to a number. 418 | * @param {number | Accumulator} [y = 0] set sum = y. 419 | */ 420 | a.Accumulator.prototype.Set = function(y) { 421 | if (!y) y = 0; 422 | if (y.constructor === a.Accumulator) { 423 | this._s = y._s; 424 | this._t = y._t; 425 | } else { 426 | this._s = y; 427 | this._t = 0; 428 | } 429 | }; 430 | 431 | /** 432 | * @summary Add a number to the accumulator. 433 | * @param {number} [y = 0] set sum += y. 434 | */ 435 | a.Accumulator.prototype.Add = function(y) { 436 | // Here's Shewchuk's solution... 437 | // Accumulate starting at least significant end 438 | var u = m.sum(y, this._t), 439 | v = m.sum(u.s, this._s); 440 | u = u.t; 441 | this._s = v.s; 442 | this._t = v.t; 443 | // Start is _s, _t decreasing and non-adjacent. Sum is now (s + t + u) 444 | // exactly with s, t, u non-adjacent and in decreasing order (except 445 | // for possible zeros). The following code tries to normalize the 446 | // result. Ideally, we want _s = round(s+t+u) and _u = round(s+t+u - 447 | // _s). The follow does an approximate job (and maintains the 448 | // decreasing non-adjacent property). Here are two "failures" using 449 | // 3-bit floats: 450 | // 451 | // Case 1: _s is not equal to round(s+t+u) -- off by 1 ulp 452 | // [12, -1] - 8 -> [4, 0, -1] -> [4, -1] = 3 should be [3, 0] = 3 453 | // 454 | // Case 2: _s+_t is not as close to s+t+u as it shold be 455 | // [64, 5] + 4 -> [64, 8, 1] -> [64, 8] = 72 (off by 1) 456 | // should be [80, -7] = 73 (exact) 457 | // 458 | // "Fixing" these problems is probably not worth the expense. The 459 | // representation inevitably leads to small errors in the accumulated 460 | // values. The additional errors illustrated here amount to 1 ulp of 461 | // the less significant word during each addition to the Accumulator 462 | // and an additional possible error of 1 ulp in the reported sum. 463 | // 464 | // Incidentally, the "ideal" representation described above is not 465 | // canonical, because _s = round(_s + _t) may not be true. For 466 | // example, with 3-bit floats: 467 | // 468 | // [128, 16] + 1 -> [160, -16] -- 160 = round(145). 469 | // But [160, 0] - 16 -> [128, 16] -- 128 = round(144). 470 | // 471 | if (this._s === 0) // This implies t == 0, 472 | this._s = u; // so result is u 473 | else 474 | this._t += u; // otherwise just accumulate u to t. 475 | }; 476 | 477 | /** 478 | * @summary Return the result of adding a number to sum (but 479 | * don't change sum). 480 | * @param {number} [y = 0] the number to be added to the sum. 481 | * @return sum + y. 482 | */ 483 | a.Accumulator.prototype.Sum = function(y) { 484 | var b; 485 | if (!y) 486 | return this._s; 487 | else { 488 | b = new a.Accumulator(this); 489 | b.Add(y); 490 | return b._s; 491 | } 492 | }; 493 | 494 | /** 495 | * @summary Set sum = −sum. 496 | */ 497 | a.Accumulator.prototype.Negate = function() { 498 | this._s *= -1; 499 | this._t *= -1; 500 | }; 501 | 502 | /** 503 | * @summary Take the remainder 504 | * @param {number} y the divisor of the remainder operation. 505 | * @return sum in range [−y/2, y/2]. 506 | */ 507 | a.Accumulator.prototype.Remainder = function(y) { 508 | this._s = m.remainder(this._s, y); 509 | this.Add(0); 510 | }; 511 | })(geodesic.Accumulator, geodesic.Math); 512 | -------------------------------------------------------------------------------- /geodesic/PolygonArea.js: -------------------------------------------------------------------------------- 1 | /* 2 | * PolygonArea.js 3 | * Transcription of PolygonArea.[ch]pp into JavaScript. 4 | * 5 | * See the documentation for the C++ class. The conversion is a literal 6 | * conversion from C++. 7 | * 8 | * The algorithms are derived in 9 | * 10 | * Charles F. F. Karney, 11 | * Algorithms for geodesics, J. Geodesy 87, 43-55 (2013); 12 | * https://doi.org/10.1007/s00190-012-0578-z 13 | * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html 14 | * 15 | * Copyright (c) Charles Karney (2011-2022) and licensed 16 | * under the MIT/X11 License. For more information, see 17 | * https://geographiclib.sourceforge.io/ 18 | */ 19 | 20 | // Load AFTER geodesic/Math.js and geodesic/Geodesic.js 21 | 22 | (function( 23 | /** 24 | * @exports geodesic/PolygonArea 25 | * @description Compute the area of geodesic polygons via the 26 | * {@link module:geodesic/PolygonArea.PolygonArea PolygonArea} 27 | * class. 28 | */ 29 | p, g, m, a) { 30 | "use strict"; 31 | 32 | var transit, transitdirect, AreaReduceA, AreaReduceB; 33 | transit = function(lon1, lon2) { 34 | // Return 1 or -1 if crossing prime meridian in east or west direction. 35 | // Otherwise return zero. longitude = +/-0 considered to be positive. 36 | // This is (should be?) compatible with transitdirect which computes 37 | // exactly the parity of 38 | // int(floor((lon1 + lon12) / 360)) - int(floor(lon1 / 360))) 39 | var lon12 = m.AngDiff(lon1, lon2).d; 40 | lon1 = m.AngNormalize(lon1); 41 | lon2 = m.AngNormalize(lon2); 42 | // edge case lon1 = 180, lon2 = 360->0, lon12 = 180 to give 1 43 | return lon12 > 0 && 44 | // lon12 > 0 && lon1 > 0 && lon2 == 0 implies lon1 == 180 45 | ((lon1 < 0 && lon2 >= 0) || (lon1 > 0 && lon2 === 0)) ? 1 : 46 | // non edge case lon1 = -180, lon2 = -360->-0, lon12 = -180 47 | (lon12 < 0 && lon1 >= 0 && lon2 < 0 ? -1 : 0); 48 | }; 49 | 50 | // an alternate version of transit to deal with longitudes in the direct 51 | // problem. 52 | transitdirect = function(lon1, lon2) { 53 | // We want to compute exactly 54 | // int(floor(lon2 / 360)) - int(floor(lon1 / 360)) 55 | lon1 = lon1 % 720; lon2 = lon2 % 720; 56 | return ((0 <= lon2 && lon2 < 360) || lon2 < -360 ? 0 : 1) - 57 | ((0 <= lon1 && lon1 < 360) || lon1 < -360 ? 0 : 1 ); 58 | }; 59 | 60 | // Reduce Accumulator area 61 | AreaReduceA = function(area, area0, crossings, reverse, sign) { 62 | area.Remainder(area0); 63 | if (crossings & 1) 64 | area.Add( (area.Sum() < 0 ? 1 : -1) * area0/2 ); 65 | // area is with the clockwise sense. If !reverse convert to 66 | // counter-clockwise convention. 67 | if (!reverse) 68 | area.Negate(); 69 | // If sign put area in (-area0/2, area0/2], else put area in [0, area0) 70 | if (sign) { 71 | if (area.Sum() > area0/2) 72 | area.Add( -area0 ); 73 | else if (area.Sum() <= -area0/2) 74 | area.Add( +area0 ); 75 | } else { 76 | if (area.Sum() >= area0) 77 | area.Add( -area0 ); 78 | else if (area.Sum() < 0) 79 | area.Add( +area0 ); 80 | } 81 | return 0 + area.Sum(); 82 | }; 83 | 84 | // Reduce double area 85 | AreaReduceB = function(area, area0, crossings, reverse, sign) { 86 | area = m.remainder(area, area0); 87 | if (crossings & 1) 88 | area += (area < 0 ? 1 : -1) * area0/2; 89 | // area is with the clockwise sense. If !reverse convert to 90 | // counter-clockwise convention. 91 | if (!reverse) 92 | area *= -1; 93 | // If sign put area in (-area0/2, area0/2], else put area in [0, area0) 94 | if (sign) { 95 | if (area > area0/2) 96 | area -= area0; 97 | else if (area <= -area0/2) 98 | area += area0; 99 | } else { 100 | if (area >= area0) 101 | area -= area0; 102 | else if (area < 0) 103 | area += area0; 104 | } 105 | return 0 + area; 106 | }; 107 | 108 | /** 109 | * @class 110 | * @property {number} a the equatorial radius (meters). 111 | * @property {number} f the flattening. 112 | * @property {bool} polyline whether the PolygonArea object describes a 113 | * polyline or a polygon. 114 | * @property {number} num the number of vertices so far. 115 | * @property {number} lat the current latitude (degrees). 116 | * @property {number} lon the current longitude (degrees). 117 | * @summary Initialize a PolygonArea object. 118 | * @classdesc Computes the area and perimeter of a geodesic polygon. 119 | * This object is usually instantiated by 120 | * {@link module:geodesic/Geodesic.Geodesic#Polygon Geodesic.Polygon}. 121 | * @param {object} geod a {@link module:geodesic/Geodesic.Geodesic 122 | * Geodesic} object. 123 | * @param {bool} [polyline = false] if true the new PolygonArea object 124 | * describes a polyline instead of a polygon. 125 | */ 126 | p.PolygonArea = function(geod, polyline) { 127 | this._geod = geod; 128 | this.a = this._geod.a; 129 | this.f = this._geod.f; 130 | this._area0 = 4 * Math.PI * geod._c2; 131 | this.polyline = !polyline ? false : polyline; 132 | this._mask = g.LATITUDE | g.LONGITUDE | g.DISTANCE | 133 | (this.polyline ? g.NONE : g.AREA | g.LONG_UNROLL); 134 | if (!this.polyline) 135 | this._areasum = new a.Accumulator(0); 136 | this._perimetersum = new a.Accumulator(0); 137 | this.Clear(); 138 | }; 139 | 140 | /** 141 | * @summary Clear the PolygonArea object, setting the number of vertices to 142 | * 0. 143 | */ 144 | p.PolygonArea.prototype.Clear = function() { 145 | this.num = 0; 146 | this._crossings = 0; 147 | if (!this.polyline) 148 | this._areasum.Set(0); 149 | this._perimetersum.Set(0); 150 | this._lat0 = this._lon0 = this.lat = this.lon = NaN; 151 | }; 152 | 153 | /** 154 | * @summary Add the next vertex to the polygon. 155 | * @param {number} lat the latitude of the point (degrees). 156 | * @param {number} lon the longitude of the point (degrees). 157 | * @description This adds an edge from the current vertex to the new vertex. 158 | */ 159 | p.PolygonArea.prototype.AddPoint = function(lat, lon) { 160 | var t; 161 | if (this.num === 0) { 162 | this._lat0 = this.lat = lat; 163 | this._lon0 = this.lon = lon; 164 | } else { 165 | t = this._geod.Inverse(this.lat, this.lon, lat, lon, this._mask); 166 | this._perimetersum.Add(t.s12); 167 | if (!this.polyline) { 168 | this._areasum.Add(t.S12); 169 | this._crossings += transit(this.lon, lon); 170 | } 171 | this.lat = lat; 172 | this.lon = lon; 173 | } 174 | ++this.num; 175 | }; 176 | 177 | /** 178 | * @summary Add the next edge to the polygon. 179 | * @param {number} azi the azimuth at the current the point (degrees). 180 | * @param {number} s the length of the edge (meters). 181 | * @description This specifies the new vertex in terms of the edge from the 182 | * current vertex. 183 | */ 184 | p.PolygonArea.prototype.AddEdge = function(azi, s) { 185 | var t; 186 | if (this.num) { 187 | t = this._geod.Direct(this.lat, this.lon, azi, s, this._mask); 188 | this._perimetersum.Add(s); 189 | if (!this.polyline) { 190 | this._areasum.Add(t.S12); 191 | this._crossings += transitdirect(this.lon, t.lon2); 192 | } 193 | this.lat = t.lat2; 194 | this.lon = t.lon2; 195 | } 196 | ++this.num; 197 | }; 198 | 199 | /** 200 | * @summary Compute the perimeter and area of the polygon. 201 | * @param {bool} reverse if true then clockwise (instead of 202 | * counter-clockwise) traversal counts as a positive area. 203 | * @param {bool} sign if true then return a signed result for the area if the 204 | * polygon is traversed in the "wrong" direction instead of returning the 205 | * area for the rest of the earth. 206 | * @return {object} r where r.number is the number of vertices, r.perimeter 207 | * is the perimeter (meters), and r.area (only returned if polyline is 208 | * false) is the area (meters2). 209 | * @description Arbitrarily complex polygons are allowed. In the case of 210 | * self-intersecting polygons the area is accumulated "algebraically", 211 | * e.g., the areas of the 2 loops in a figure-8 polygon will partially 212 | * cancel. If the object is a polygon (and not a polyline), the perimeter 213 | * includes the length of a final edge connecting the current point to the 214 | * initial point. If the object is a polyline, then area is nan. More 215 | * points can be added to the polygon after this call. 216 | */ 217 | p.PolygonArea.prototype.Compute = function(reverse, sign) { 218 | var vals = {number: this.num}, t, tempsum; 219 | if (this.num < 2) { 220 | vals.perimeter = 0; 221 | if (!this.polyline) 222 | vals.area = 0; 223 | return vals; 224 | } 225 | if (this.polyline) { 226 | vals.perimeter = this._perimetersum.Sum(); 227 | return vals; 228 | } 229 | t = this._geod.Inverse(this.lat, this.lon, this._lat0, this._lon0, 230 | this._mask); 231 | vals.perimeter = this._perimetersum.Sum(t.s12); 232 | tempsum = new a.Accumulator(this._areasum); 233 | tempsum.Add(t.S12); 234 | vals.area = AreaReduceA(tempsum, this._area0, 235 | this._crossings + transit(this.lon, this._lon0), 236 | reverse, sign); 237 | return vals; 238 | }; 239 | 240 | /** 241 | * @summary Compute the perimeter and area of the polygon with a tentative 242 | * new vertex. 243 | * @param {number} lat the latitude of the point (degrees). 244 | * @param {number} lon the longitude of the point (degrees). 245 | * @param {bool} reverse if true then clockwise (instead of 246 | * counter-clockwise) traversal counts as a positive area. 247 | * @param {bool} sign if true then return a signed result for the area if the 248 | * polygon is traversed in the "wrong" direction instead of returning the 249 | * area for the rest of the earth. 250 | * @return {object} r where r.number is the number of vertices, r.perimeter 251 | * is the perimeter (meters), and r.area (only returned if polyline is 252 | * false) is the area (meters2). 253 | * @description A new vertex is *not* added to the polygon. 254 | */ 255 | p.PolygonArea.prototype.TestPoint = function(lat, lon, reverse, sign) { 256 | var vals = {number: this.num + 1}, t, tempsum, crossings, i; 257 | if (this.num === 0) { 258 | vals.perimeter = 0; 259 | if (!this.polyline) 260 | vals.area = 0; 261 | return vals; 262 | } 263 | vals.perimeter = this._perimetersum.Sum(); 264 | tempsum = this.polyline ? 0 : this._areasum.Sum(); 265 | crossings = this._crossings; 266 | for (i = 0; i < (this.polyline ? 1 : 2); ++i) { 267 | t = this._geod.Inverse( 268 | i === 0 ? this.lat : lat, i === 0 ? this.lon : lon, 269 | i !== 0 ? this._lat0 : lat, i !== 0 ? this._lon0 : lon, 270 | this._mask); 271 | vals.perimeter += t.s12; 272 | if (!this.polyline) { 273 | tempsum += t.S12; 274 | crossings += transit(i === 0 ? this.lon : lon, 275 | i !== 0 ? this._lon0 : lon); 276 | } 277 | } 278 | 279 | if (this.polyline) 280 | return vals; 281 | 282 | vals.area = AreaReduceB(tempsum, this._area0, crossings, reverse, sign); 283 | return vals; 284 | }; 285 | 286 | /** 287 | * @summary Compute the perimeter and area of the polygon with a tentative 288 | * new edge. 289 | * @param {number} azi the azimuth of the edge (degrees). 290 | * @param {number} s the length of the edge (meters). 291 | * @param {bool} reverse if true then clockwise (instead of 292 | * counter-clockwise) traversal counts as a positive area. 293 | * @param {bool} sign if true then return a signed result for the area if the 294 | * polygon is traversed in the "wrong" direction instead of returning the 295 | * area for the rest of the earth. 296 | * @return {object} r where r.number is the number of vertices, r.perimeter 297 | * is the perimeter (meters), and r.area (only returned if polyline is 298 | * false) is the area (meters2). 299 | * @description A new vertex is *not* added to the polygon. 300 | */ 301 | p.PolygonArea.prototype.TestEdge = function(azi, s, reverse, sign) { 302 | var vals = {number: this.num ? this.num + 1 : 0}, t, tempsum, crossings; 303 | if (this.num === 0) 304 | return vals; 305 | vals.perimeter = this._perimetersum.Sum() + s; 306 | if (this.polyline) 307 | return vals; 308 | 309 | tempsum = this._areasum.Sum(); 310 | crossings = this._crossings; 311 | t = this._geod.Direct(this.lat, this.lon, azi, s, this._mask); 312 | tempsum += t.S12; 313 | crossings += transitdirect(this.lon, t.lon2); 314 | crossings += transit(t.lon2, this._lon0); 315 | t = this._geod.Inverse(t.lat2, t.lon2, this._lat0, this._lon0, this._mask); 316 | vals.perimeter += t.s12; 317 | tempsum += t.S12; 318 | 319 | vals.area = AreaReduceB(tempsum, this._area0, crossings, reverse, sign); 320 | return vals; 321 | }; 322 | 323 | })(geodesic.PolygonArea, geodesic.Geodesic, 324 | geodesic.Math, geodesic.Accumulator); 325 | -------------------------------------------------------------------------------- /geodesic/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript implementation of the geodesic routines in GeographicLib 2 | 3 | This package is a JavaScript implementation of the geodesic routines 4 | from [GeographicLib](https://geographiclib.sourceforge.io). This solves the 5 | direct and inverse geodesic problems for an ellipsoid of revolution. 6 | 7 | Prior to version 2.0.0, this was a component of the [node package 8 | geographiclib](https://www.npmjs.com/package/geographiclib). As of 9 | version 2.0.0, that package was split into the packages 10 | [geographiclib-geodesic](https://www.npmjs.com/package/geographiclib-geodesic) 11 | (this package) and 12 | [geographiclib-dms](https://www.npmjs.com/package/geographiclib-dms). 13 | [geographiclib](https://www.npmjs.com/package/geographiclib) will be 14 | deprecated on 2023-05-01. 15 | 16 | Licensed under the MIT/X11 License; see 17 | [LICENSE.txt](https://geographiclib.sourceforge.io/LICENSE.txt). 18 | 19 | ## Installation 20 | 21 | ```bash 22 | $ npm install geographiclib-geodesic 23 | ``` 24 | 25 | ## Usage 26 | 27 | In [node](https://nodejs.org), do 28 | ```javascript 29 | var geodesic = require("geographiclib-geodesic"); 30 | ``` 31 | 32 | ## Documentation 33 | 34 | Full documentation is provided at 35 | [https://geographiclib.sourceforge.io/JavaScript/doc]( 36 | https://geographiclib.sourceforge.io/JavaScript/doc/index.html). 37 | 38 | ## Examples 39 | 40 | ```javascript 41 | var geodesic = require("geographiclib-geodesic"), 42 | geod = geodesic.Geodesic.WGS84, r; 43 | 44 | // Find the distance from Wellington, NZ (41.32S, 174.81E) to 45 | // Salamanca, Spain (40.96N, 5.50W)... 46 | r = geod.Inverse(-41.32, 174.81, 40.96, -5.50); 47 | console.log("The distance is " + r.s12.toFixed(3) + " m."); 48 | // This prints "The distance is 19959679.267 m." 49 | 50 | // Find the point 20000 km SW of Perth, Australia (32.06S, 115.74E)... 51 | r = geod.Direct(-32.06, 115.74, 225, 20000e3); 52 | console.log("The position is (" + 53 | r.lat2.toFixed(8) + ", " + r.lon2.toFixed(8) + ")."); 54 | // This prints "The position is (32.11195529, -63.95925278)." 55 | ``` 56 | 57 | ## Authors 58 | 59 | * algorithms + js code: Charles Karney (karney@alum.mit.edu) 60 | * node.js port: Yurij Mikhalevich (yurij@mikhalevi.ch) 61 | -------------------------------------------------------------------------------- /geodesic/TAIL.js: -------------------------------------------------------------------------------- 1 | cb(geodesic); 2 | 3 | })(function(geo) { 4 | if (typeof module === 'object' && module.exports) { 5 | /******** support loading with node's require ********/ 6 | module.exports = geo; 7 | } else if (typeof define === 'function' && define.amd) { 8 | /******** support loading with AMD ********/ 9 | define('geographiclib-geodesic', [], function() { return geo; }); 10 | } else { 11 | /******** otherwise just pollute our global namespace ********/ 12 | window.geodesic = geo; 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /geodesic/package.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geographiclib-geodesic", 3 | "version": "@PROJECT_FULLVERSION@", 4 | "description": "JavaScript implementation of geodesic routines in GeographicLib", 5 | "main": "geographiclib-geodesic.min.js", 6 | "types": "types/geographiclib-geodesic.d.ts", 7 | "scripts": { 8 | "test": "mocha" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/geographiclib/geographiclib-js.git" 13 | }, 14 | "url": "https://geographiclib.sourceforge.net", 15 | "keywords": [ 16 | "geodesics", 17 | "geometry", 18 | "geographiclib", 19 | "geography", 20 | "geodesic problem", 21 | "direct geodesic problem", 22 | "inverse geodesic problem", 23 | "geodesic calculator" 24 | ], 25 | "devDependencies": { 26 | "mocha": "^9.1.5", 27 | "minify": "^7.0.2" 28 | }, 29 | "author": "Charles Karney ", 30 | "contributors": [ 31 | "Yurij Mikhalevich " 32 | ], 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/geographiclib/geographiclib-js/issues", 36 | "email": "karney@alum.mit.edu" 37 | }, 38 | "homepage": "https://github.com/geographiclib/geographiclib-js#readme", 39 | "directories": { 40 | "test": "test" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /geodesic/test/signtest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"), 4 | G = require("../geographiclib-geodesic"), 5 | g = G.Geodesic, 6 | m = G.Math; 7 | 8 | assert.approx = function(x, y, d) { 9 | assert(Math.abs(x-y) <= d, x + " = " + y + " +/- " + d); 10 | }; 11 | 12 | assert.sincosCheck = function(v, s, c) { 13 | assert.strictEqual(v.s, s); 14 | assert.strictEqual(v.c, c); 15 | }; 16 | 17 | describe("geodesic", function() { 18 | describe("SignTest", function () { 19 | 20 | it("check AngRound", function () { 21 | var eps = Number.EPSILON; 22 | assert.strictEqual(m.AngRound(-eps/32), -eps/32); 23 | assert.strictEqual(m.AngRound(-eps/64), -0 ); 24 | assert.strictEqual(m.AngRound(- 0 ), -0 ); 25 | assert.strictEqual(m.AngRound( 0 ), +0 ); 26 | assert.strictEqual(m.AngRound( eps/64), +0 ); 27 | assert.strictEqual(m.AngRound( eps/32), +eps/32); 28 | assert.strictEqual(m.AngRound((1-2*eps)/64), (1-2*eps)/64); 29 | assert.strictEqual(m.AngRound((1-eps )/64), 1 /64); 30 | assert.strictEqual(m.AngRound((1-eps/2)/64), 1 /64); 31 | assert.strictEqual(m.AngRound((1-eps/4)/64), 1 /64); 32 | assert.strictEqual(m.AngRound( 1 /64), 1 /64); 33 | assert.strictEqual(m.AngRound((1+eps/2)/64), 1 /64); 34 | assert.strictEqual(m.AngRound((1+eps )/64), 1 /64); 35 | assert.strictEqual(m.AngRound((1+2*eps)/64), (1+2*eps)/64); 36 | assert.strictEqual(m.AngRound((1-eps )/32), (1-eps )/32); 37 | assert.strictEqual(m.AngRound((1-eps/2)/32), 1 /32); 38 | assert.strictEqual(m.AngRound((1-eps/4)/32), 1 /32); 39 | assert.strictEqual(m.AngRound( 1 /32), 1 /32); 40 | assert.strictEqual(m.AngRound((1+eps/2)/32), 1 /32); 41 | assert.strictEqual(m.AngRound((1+eps )/32), (1+eps )/32); 42 | assert.strictEqual(m.AngRound((1-eps )/16), (1-eps )/16); 43 | assert.strictEqual(m.AngRound((1-eps/2)/16), (1-eps/2)/16); 44 | assert.strictEqual(m.AngRound((1-eps/4)/16), 1 /16); 45 | assert.strictEqual(m.AngRound( 1 /16), 1 /16); 46 | assert.strictEqual(m.AngRound((1+eps/4)/16), 1 /16); 47 | assert.strictEqual(m.AngRound((1+eps/2)/16), 1 /16); 48 | assert.strictEqual(m.AngRound((1+eps )/16), (1+eps )/16); 49 | assert.strictEqual(m.AngRound((1-eps )/ 8), (1-eps )/ 8); 50 | assert.strictEqual(m.AngRound((1-eps/2)/ 8), (1-eps/2)/ 8); 51 | assert.strictEqual(m.AngRound((1-eps/4)/ 8), 1 / 8); 52 | assert.strictEqual(m.AngRound((1+eps/2)/ 8), 1 / 8); 53 | assert.strictEqual(m.AngRound((1+eps )/ 8), (1+eps )/ 8); 54 | assert.strictEqual(m.AngRound( 1-eps ), 1-eps ); 55 | assert.strictEqual(m.AngRound( 1-eps/2 ), 1-eps/2 ); 56 | assert.strictEqual(m.AngRound( 1-eps/4 ), 1 ); 57 | assert.strictEqual(m.AngRound( 1 ), 1 ); 58 | assert.strictEqual(m.AngRound( 1+eps/4 ), 1 ); 59 | assert.strictEqual(m.AngRound( 1+eps/2 ), 1 ); 60 | assert.strictEqual(m.AngRound( 1+eps ), 1+ eps ); 61 | assert.strictEqual(m.AngRound( 90 -64*eps), 90-64*eps ); 62 | assert.strictEqual(m.AngRound( 90 -32*eps), 90 ); 63 | assert.strictEqual(m.AngRound( 90 ), 90 ); 64 | }); 65 | 66 | it("check sincosd", function () { 67 | var nan = NaN, inf = Infinity, v1, v2, v3; 68 | assert.sincosCheck(m.sincosd(-inf),nan,nan); 69 | assert.sincosCheck(m.sincosd(-810), -1, +0); 70 | assert.sincosCheck(m.sincosd(-720), -0, +1); 71 | assert.sincosCheck(m.sincosd(-630), +1, +0); 72 | assert.sincosCheck(m.sincosd(-540), -0, -1); 73 | assert.sincosCheck(m.sincosd(-450), -1, +0); 74 | assert.sincosCheck(m.sincosd(-360), -0, +1); 75 | assert.sincosCheck(m.sincosd(-270), +1, +0); 76 | assert.sincosCheck(m.sincosd(-180), -0, -1); 77 | assert.sincosCheck(m.sincosd(- 90), -1, +0); 78 | assert.sincosCheck(m.sincosd(- 0), -0, +1); 79 | assert.sincosCheck(m.sincosd(+ 0), +0, +1); 80 | assert.sincosCheck(m.sincosd(+ 90), +1, +0); 81 | assert.sincosCheck(m.sincosd(+180), +0, -1); 82 | assert.sincosCheck(m.sincosd(+270), -1, +0); 83 | assert.sincosCheck(m.sincosd(+360), +0, +1); 84 | assert.sincosCheck(m.sincosd(+450), +1, +0); 85 | assert.sincosCheck(m.sincosd(+540), +0, -1); 86 | assert.sincosCheck(m.sincosd(+630), -1, +0); 87 | assert.sincosCheck(m.sincosd(+720), +0, +1); 88 | assert.sincosCheck(m.sincosd(+810), +1, +0); 89 | assert.sincosCheck(m.sincosd(+inf),nan,nan); 90 | assert.sincosCheck(m.sincosd( nan),nan,nan); 91 | v1 = m.sincosd( 9); 92 | v2 = m.sincosd( 81); 93 | v3 = m.sincosd(-123456789); 94 | assert.strictEqual(v1.s, v2.c); 95 | assert.strictEqual(v1.s, v3.s); 96 | assert.strictEqual(v1.c, v2.s); 97 | assert.strictEqual(v1.c, -v3.c); 98 | }); 99 | 100 | it("check atan2d", function () { 101 | var nan = NaN, inf = Infinity, s; 102 | assert.strictEqual( m.atan2d(+ 0 , - 0 ), +180 ); 103 | assert.strictEqual( m.atan2d(- 0 , - 0 ), -180 ); 104 | assert.strictEqual( m.atan2d(+ 0 , + 0 ), +0 ); 105 | assert.strictEqual( m.atan2d(- 0 , + 0 ), -0 ); 106 | assert.strictEqual( m.atan2d(+ 0 , - 1 ), +180 ); 107 | assert.strictEqual( m.atan2d(- 0 , - 1 ), -180 ); 108 | assert.strictEqual( m.atan2d(+ 0 , + 1 ), +0 ); 109 | assert.strictEqual( m.atan2d(- 0 , + 1 ), -0 ); 110 | assert.strictEqual( m.atan2d(- 1 , + 0 ), -90 ); 111 | assert.strictEqual( m.atan2d(- 1 , - 0 ), -90 ); 112 | assert.strictEqual( m.atan2d(+ 1 , + 0 ), +90 ); 113 | assert.strictEqual( m.atan2d(+ 1 , - 0 ), +90 ); 114 | assert.strictEqual( m.atan2d(+ 1 , -inf), +180 ); 115 | assert.strictEqual( m.atan2d(- 1 , -inf), -180 ); 116 | assert.strictEqual( m.atan2d(+ 1 , +inf), +0 ); 117 | assert.strictEqual( m.atan2d(- 1 , +inf), -0 ); 118 | assert.strictEqual( m.atan2d( +inf, + 1 ), +90 ); 119 | assert.strictEqual( m.atan2d( +inf, - 1 ), +90 ); 120 | assert.strictEqual( m.atan2d( -inf, + 1 ), -90 ); 121 | assert.strictEqual( m.atan2d( -inf, - 1 ), -90 ); 122 | assert.strictEqual( m.atan2d( +inf, -inf), +135 ); 123 | assert.strictEqual( m.atan2d( -inf, -inf), -135 ); 124 | assert.strictEqual( m.atan2d( +inf, +inf), +45 ); 125 | assert.strictEqual( m.atan2d( -inf, +inf), -45 ); 126 | assert.strictEqual( m.atan2d( nan, + 1 ), nan ); 127 | assert.strictEqual( m.atan2d(+ 1 , nan), nan ); 128 | assert.strictEqual( m.atan2d(s, -1), 180 - m.atan2d(s, 1) ); 129 | }); 130 | 131 | it("check sum", function () { 132 | assert.strictEqual( m.sum(+9, -9).s, +0 ); 133 | assert.strictEqual( m.sum(-9, +9).s, +0 ); 134 | assert.strictEqual( m.sum(-0, +0).s, +0 ); 135 | assert.strictEqual( m.sum(+0, -0).s, +0 ); 136 | assert.strictEqual( m.sum(-0, -0).s, -0 ); 137 | assert.strictEqual( m.sum(+0, +0).s, +0 ); 138 | }); 139 | 140 | it("check AngNormalize", function () { 141 | assert.strictEqual( m.AngNormalize(-900), -180 ); 142 | assert.strictEqual( m.AngNormalize(-720), -0 ); 143 | assert.strictEqual( m.AngNormalize(-540), -180 ); 144 | assert.strictEqual( m.AngNormalize(-360), -0 ); 145 | assert.strictEqual( m.AngNormalize(-180), -180 ); 146 | assert.strictEqual( m.AngNormalize( -0), -0 ); 147 | assert.strictEqual( m.AngNormalize( +0), +0 ); 148 | assert.strictEqual( m.AngNormalize( 180), +180 ); 149 | assert.strictEqual( m.AngNormalize( 360), +0 ); 150 | assert.strictEqual( m.AngNormalize( 540), +180 ); 151 | assert.strictEqual( m.AngNormalize( 720), +0 ); 152 | assert.strictEqual( m.AngNormalize( 900), +180 ); 153 | }); 154 | 155 | it("check AngDiff", function () { 156 | var eps = Number.EPSILON, x, y; 157 | assert.strictEqual( m.AngDiff(+ 0, + 0).d, +0 ); 158 | assert.strictEqual( m.AngDiff(+ 0, - 0).d, -0 ); 159 | assert.strictEqual( m.AngDiff(- 0, + 0).d, +0 ); 160 | assert.strictEqual( m.AngDiff(- 0, - 0).d, +0 ); 161 | assert.strictEqual( m.AngDiff(+ 5, +365).d, +0 ); 162 | assert.strictEqual( m.AngDiff(+365, + 5).d, -0 ); 163 | assert.strictEqual( m.AngDiff(+ 5, +185).d, +180 ); 164 | assert.strictEqual( m.AngDiff(+185, + 5).d, -180 ); 165 | assert.strictEqual( m.AngDiff(+eps, +180).d, +180 ); 166 | assert.strictEqual( m.AngDiff(-eps, +180).d, -180 ); 167 | assert.strictEqual( m.AngDiff(+eps, -180).d, +180 ); 168 | assert.strictEqual( m.AngDiff(-eps, -180).d, -180 ); 169 | x = 138 + 128 * eps; y = -164; 170 | assert.strictEqual( m.AngDiff(x, y).d, 58 - 128 * eps ); 171 | }); 172 | 173 | it("azimuth with coincident point on equator", function () { 174 | var geod = g.WGS84, i, inv, 175 | // lat1 lat2 azi1/2 176 | C = [ 177 | [ +0, -0, 180 ], 178 | [ -0, +0, 0 ] 179 | ]; 180 | for (i = 0; i < C.length; ++i) { 181 | inv = geod.Inverse(C[i][0], 0, C[i][1], 0); 182 | assert.strictEqual(inv.azi1, C[i][2]); 183 | assert.strictEqual(inv.azi2, C[i][2]); 184 | } 185 | }); 186 | 187 | it("Direction of nearly antipodal equatorial solution", function () { 188 | var geod = g.WGS84, i, inv, 189 | // lat1 lat2 azi1 azi2 190 | C = [ 191 | [ +0, +0, 56, 124], 192 | [ -0, -0, 124, 56] 193 | ]; 194 | for (i = 0; i < C.length; ++i) { 195 | inv = geod.Inverse(C[i][0], 0, C[i][1], 179.5); 196 | assert.approx(inv.azi1, C[i][2], 0.5); 197 | assert.approx(inv.azi2, C[i][3], 0.5); 198 | } 199 | }); 200 | 201 | it("Direction of the exact antipodal equatorial path", function () { 202 | var geod = g.WGS84, i, inv, 203 | // lat1 lat2 lon2 azi1 azi2 204 | C = [ 205 | [ +0, +0, +180, +0, +180], 206 | [ -0, -0, +180, +180, +0], 207 | [ +0, +0, -180, -0, -180], 208 | [ -0, -0, -180, -180, -0] 209 | ]; 210 | for (i = 0; i < C.length; ++i) { 211 | inv = geod.Inverse(C[i][0], 0, C[i][1], C[i][2]); 212 | assert.strictEqual(inv.azi1, C[i][3]); 213 | assert.strictEqual(inv.azi2, C[i][4]); 214 | } 215 | }); 216 | 217 | it("Antipodal points on the equator with prolate ellipsoid", function () { 218 | var geod = new g.Geodesic(6.4e6, -1/300), i, inv, 219 | // lon2 azi1/2 220 | C = [ 221 | [ +180, +90 ], 222 | [ -180, -90 ] 223 | ]; 224 | for (i = 0; i < C.length; ++i) { 225 | inv = geod.Inverse(0, 0, 0, C[i][0]); 226 | assert.strictEqual(inv.azi1, C[i][1]); 227 | assert.strictEqual(inv.azi2, C[i][1]); 228 | } 229 | }); 230 | 231 | it("Meridional azimuths for the direct problem", function () { 232 | var geod = g.WGS84, i, dir, 233 | // azi1, lon2, azi2 234 | C = [ 235 | [ +0, +180, +180 ], 236 | [ -0, -180, -180 ], 237 | [ +180, +180, +0 ], 238 | [ -180, -180, -0 ] 239 | ]; 240 | for (i = 0; i < C.length; ++i) { 241 | dir = geod.Direct(0, 0, C[i][0], 15e6, g.LONG_UNROLL); 242 | assert.strictEqual(dir.lon2, C[i][1]); 243 | assert.strictEqual(dir.azi2, C[i][2]); 244 | } 245 | }); 246 | 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /geodesic/types/geographiclib-geodesic.d.ts: -------------------------------------------------------------------------------- 1 | declare class GeodesicClass { 2 | constructor(a: number, f: number); 3 | 4 | ArcDirect( 5 | lat1: number, 6 | lon1: number, 7 | azi1: number, 8 | a12: number, 9 | outmask?: number 10 | ): { 11 | lat1: number; 12 | lon1: number; 13 | azi1: number; 14 | a12: number; 15 | }; 16 | 17 | ArcDirectLine( 18 | lat1: number, 19 | lon1: number, 20 | azi1: number, 21 | a12: number, 22 | caps?: number 23 | ): any; // TODO: define GeodesicLine object 24 | 25 | Direct( 26 | lat1: number, 27 | lon1: number, 28 | azi1: number, 29 | s12: number, 30 | outmask?: number 31 | ): { 32 | lat1: number; 33 | lon1: number; 34 | azi1: number; 35 | s12: number; 36 | a12: number; 37 | lat2?: number 38 | lon2?: number 39 | azi2?: number 40 | m12?: number 41 | M12?: number 42 | M21?: number 43 | S12?: number 44 | }; 45 | 46 | DirectLine( 47 | lat1: number, 48 | lon1: number, 49 | azi1: number, 50 | s12: number, 51 | caps?: number 52 | ): any; // TODO: define GeodesicLine object 53 | 54 | GenDirect( 55 | lat1: number, 56 | lon1: number, 57 | azi1: number, 58 | arcmode: boolean, 59 | s12_a12: number, 60 | outmask?: number 61 | ): { 62 | lat1: number; 63 | lon1: number; 64 | azi1: number; 65 | s12?: number; 66 | a12: number; 67 | }; 68 | 69 | GenDirectLine( 70 | lat1: number, 71 | lon1: number, 72 | azi1: number, 73 | arcmode: boolean, 74 | s12_a12: number, 75 | caps?: number 76 | ): any; // TODO: define GeodesicLine object 77 | 78 | Inverse( 79 | lat1: number, 80 | lon1: number, 81 | lat2: number, 82 | lon2: number, 83 | outmask?: number 84 | ): { 85 | lat1: number; 86 | lon1: number; 87 | lat2: number; 88 | lon2: number; 89 | a12: number; 90 | s12?: number; 91 | azi1?: number 92 | azi2?: number 93 | m12?: number 94 | M12?: number 95 | M21?: number 96 | S12?: number 97 | } 98 | 99 | InverseLine( 100 | lat1: number, 101 | lon1: number, 102 | lat2: number, 103 | lon2: number, 104 | caps?: number 105 | ): any; // TODO: define GeodesicLine object 106 | 107 | Polygon(polyline?: boolean): any // TODO: define PolygonArea object 108 | } 109 | 110 | export declare const Geodesic: { 111 | NONE: number 112 | ARC: number 113 | LATITUDE: number 114 | LONGITUDE: number 115 | AZIMUTH: number 116 | DISTANCE: number 117 | STANDARD: number 118 | DISTANCE_IN: number 119 | REDUCEDLENGTH: number 120 | GEODESICSCALE: number 121 | AREA: number 122 | ALL: number 123 | LONG_UNROLL: number 124 | Geodesic: typeof GeodesicClass, 125 | WGS84: GeodesicClass 126 | }; 127 | 128 | // TODO: Type 129 | export declare const GeodesicLine: any; 130 | export declare const Math: any; 131 | export declare const PolygonArea: any; 132 | -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (SAMPLES geod-calc.html geod-google.html geod-google-instructions.html) 2 | 3 | if (RSYNC) 4 | set (USER karney) 5 | set (DOCROOT $ENV{HOME}/web/geographiclib-web/htdocs) 6 | set (WEBDEPLOY ${USER},geographiclib@web.sourceforge.net:./htdocs) 7 | 8 | add_custom_target (stage-scripts 9 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 10 | ${SAMPLES} ${DOCROOT}/scripts 11 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 12 | # Remove this dependency so checksums can be updated without 13 | # triggering an accidental rebuilt of the JS packages. 14 | # add_dependencies (stage-scripts bundlegeod bundledms) 15 | add_custom_target (deploy-scripts 16 | COMMAND ${RSYNC} --delete -av -e ssh 17 | ${DOCROOT}/scripts/ ${WEBDEPLOY}/scripts/) 18 | endif () 19 | -------------------------------------------------------------------------------- /samples/geod-calc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Geodesic calculations for an ellipsoid done right 5 | 7 | 8 | 25 | 26 | 32 | 39 | 46 | 236 | 237 | 238 |

239 |

Geodesic calculations for an ellipsoid done right

240 |
241 |

242 | This page illustrates the geodesic routines available in 243 | JavaScript package 244 | 245 | geographiclib-geodesic 246 | (documentation). 247 | The algorithms are considerably more accurate than Vincenty's 248 | method, and offer more functionality (an inverse method which 249 | never fails to converge, differential properties of the geodesic, 250 | and the area under a geodesic). The algorithms are derived in 251 |

252 | Charles F. F. Karney,
253 | 254 | Algorithms for geodesics,
255 | J. Geodesy 87(1), 43–55 (Jan. 2013);
256 | DOI: 257 | 258 | 10.1007/s00190-012-0578-z 259 | (pdf); 260 | addenda: 261 | geod-addenda.html. 262 |
263 | This page just provides a basic interface. Enter latitudes, 264 | longitudes, and azimuths as degrees and distances as meters using 265 | spaces or commas as separators. (Angles may be entered as decimal 266 | degrees or as degrees, minutes, and seconds, e.g. -20.51125, 267 | 20°30′40.5″S, S20d30'40.5", or 268 | -20:30:40.5.) The results are accurate to about 269 | 15 nanometers (or 0.1 m2 per vertex for 270 | areas). A slicker page where the geodesics are incorporated into 271 | Google Maps is given here. Basic 272 | online tools which provide similar capabilities are 273 | 274 | GeodSolve 275 | and 276 | 277 | Planimeter; 278 | these call a C++ backend. This page uses version 279 | 282 | of the geodesic code. 283 |

284 | Jump to: 285 |

293 |

294 |
295 |
296 |

Inverse problem

297 |

298 | Find the shortest path between two points on the earth. The 299 | path is characterized by its length s12 and its azimuth 300 | at the two ends azi1 and azi2. See 301 | 302 | this tutorial 303 | for the definition of the 304 | quantities a12, m12, M12, M21, 305 | and S12. The sample calculation finds the shortest path 306 | between Wellington, New Zealand, and Salamanca, Spain. To 307 | perform the calculation, press the “COMPUTE” button. 308 |

309 |

Enter “lat1 lon1 lat2 lon2”:

310 |

input: 311 | 312 |

313 |

314 | Output format:    318 |
322 | Output precision:   334 |

335 |

336 | 348 |

349 |

350 | status: 351 | 352 |

353 |

354 | lat1 lon1 azi1: 355 | 356 |

357 |

358 | lat2 lon2 azi2: 359 | 360 |

361 |

362 | s12: 363 | 364 |

365 |

366 | a12: 367 | 368 |

369 |

370 | m12: 371 | 372 |

373 |

374 | M12 M21: 375 | 376 |

377 |

378 | S12: 379 | 380 |

381 |
382 |
383 |
384 |

Direct problem

385 |

386 | Find the destination traveling a given distance along a geodesic 387 | with a given azimuth at the starting point. The destination is 388 | characterized by its position lat2, lon2 and its azimuth 389 | at the destination azi2. See 390 | 391 | this tutorial 392 | for the definition of the 393 | quantities a12, m12, M12, M21, 394 | and S12. The sample calculation shows the result of 395 | travelling 10000 km NE from JFK airport. To perform the 396 | calculation, press the “COMPUTE” button. 397 |

398 |

Enter “lat1 lon1 azi1 s12”:

399 |

input: 400 | 401 |

402 |

403 | Output format:    407 |
411 | Output precision:   423 |

424 |

425 | 437 |

438 |

439 | status: 440 | 441 |

442 |

443 | lat1 lon1 azi1: 444 | 445 |

446 |

447 | lat2 lon2 azi2: 448 | 449 |

450 |

451 | s12: 452 | 453 |

454 |

455 | a12: 456 | 457 |

458 |

459 | m12: 460 | 461 |

462 |

463 | M12 M21: 464 | 465 |

466 |

467 | S12: 468 | 469 |

470 |
471 |
472 |
473 |

Geodesic path

474 |

475 | Find intermediate points along a geodesic. In addition to 476 | specifying the endpoints, give ds12, the maximum distance 477 | between the intermediate points and maxk, the maximum 478 | number of intervals the geodesic is broken into. The output 479 | gives a sequence of positions lat, lon together with the 480 | corresponding azimuths azi. The sample shows the path 481 | from JFK to Singapore's Changi Airport at about 1000 km 482 | intervals. (In this example, the path taken by Google Earth 483 | deviates from the shortest path by about 2.9 km.) To perform 484 | the calculation, press the “COMPUTE” button. 485 |

486 |

Enter “lat1 lon1 lat2 lon2 ds12 maxk”:

487 |

input: 488 | 489 |

490 |

491 | Output format:    495 |
499 | Output precision:   511 |

512 |

513 | 519 |

520 |

521 | status: 522 | 523 |

524 |

525 | points (lat lon azi):
526 | 527 |

528 | 529 |
530 |
531 |

Polygon area

532 |

533 | Find the perimeter and area of a polygon whose sides are 534 | geodesics. The polygon must be simple (i.e., must not intersect 535 | itself). (There's no need to ensure that the polygon is 536 | closed.) Counter-clockwise traversal of the polygon results in 537 | a positive area. The polygon can encircle one or both poles. 538 | The sample gives the approximate perimeter (in m) and area (in 539 | m2) of Antarctica. (For this example, Google Earth 540 | Pro returns an area which is 30 times too large! However this 541 | is a little unfair, since Google Earth has no concept of 542 | polygons which encircle a pole.) If the polyline option 543 | is selected then just the length of the line joining the points 544 | is returned. To perform the calculation, press the 545 | “COMPUTE” button. 546 |

547 |

Enter points, one per line, as “lat lon”:

548 |

points (lat lon):
549 | 568 |

569 |

570 | Treat points as:    574 | 578 |

579 |

580 | 585 |

586 |

587 | status: 588 | 589 |

590 |

591 | number perimeter area: 592 | 593 |

594 | 595 |
596 |
Charles Karney 597 | <karney@alum.mit.edu> 598 | (2022-05-02)
599 |
600 | Geographiclib 601 | 602 | 603 | -------------------------------------------------------------------------------- /samples/geod-google-instructions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Geodesic lines, circles, envelopes in Google Maps (instructions) 6 | 7 | 10 | 11 | 28 | 29 | 30 | 31 |

32 | Geodesic lines, circles, envelopes in Google Maps (instructions) 33 |

34 |

35 | The page allows you to draw 36 | accurate ellipsoidal geodesics on Google Maps. You can specify the 37 | geodesic in one of two forms: 38 |

    39 |
  • 40 | The direct problem: specify a starting point, an 41 | azimuth and a distance as lat1 lon1 azi1 s12 as degrees 42 | and meters. 43 |
  • 44 | The inverse problem: specify the two end points 45 | as lat1 lon1 lat2 lon2 as degrees; this finds the 46 | shortest path between the two points. 47 |
48 | (Angles may be entered as decimal degrees or as degrees, minutes, 49 | and seconds, e.g. -20.51125, 20°30′40.5″S, 50 | S20d30'40.5", or -20:30:40.5.) Click on the 51 | corresponding "compute" button. The display then shows 52 |
    53 |
  • The requested geodesic as a blue 54 | line; the WGS84 ellipsoid model is used. 55 |
  • The geodesic circle as a green 56 | curve; this shows the locus of points a 57 | distance s12 from lat1, lon1. 58 |
  • The geodesic envelopes as red 59 | curves; all the geodesics emanating from lat1, 60 | lon1 are tangent to the envelopes (providing they are 61 | extended far enough). The number of solutions to the inverse 62 | problem changes depending on whether lat2, lon2 lies 63 | inside the envelopes. For example, there are four (resp. two) 64 | approximately hemispheroidal geodesics if this point lies 65 | inside (resp. outside) the inner envelope (only one of which 66 | is a shortest path). 67 |
68 |

69 |

70 | The sample data has lat1, lon1 in Wellington, New 71 | Zealand, lat2, lon2 in Salamanca, Spain, and s12 72 | about 1.5 times the earth's circumference. Try clicking on the 73 | "compute" button next to the "Direct:" input box when the page 74 | first loads. You can navigate around the map using the normal 75 | Google Map controls. 76 |

77 |

78 | The precision of output for the geodesic is 0.1" or 1 m. 79 | A text-only geodesic calculator based 80 | on the same JavaScript library is also available; this calculator 81 | solves the inverse and direct geodesic problems, computes 82 | intermediate points on a geodesic, and finds the area of a 83 | geodesic polygon; it allows you to specify the precision of the 84 | output and choose between decimal degrees and degrees, minutes, 85 | and seconds. Basic online tools which provide similar capabilities 86 | are 87 | GeodSolve 88 | and 89 | Planimeter; 90 | these call a C++ backend. 91 |

92 |

93 | The JavaScript code for computing the geodesic lines, circles, and 94 | envelopes available in the JavaScript package 95 | 96 | geographiclib-geodesic 97 | (documentation). The 98 | algorithms are derived in 99 |

100 | Charles F. F. Karney,
101 | 102 | Algorithms for geodesics,
103 | J. Geodesy 87(1), 43–55 (Jan. 2013);
104 | DOI: 105 | 106 | 10.1007/s00190-012-0578-z 107 | (pdf);
108 | addenda: 109 | geod-addenda.html. 110 |
111 | In putting together this Google Maps demonstration, I started with 112 | the sample code 113 | 114 | geometry-headings. 115 |

116 |
117 |
Charles Karney 118 | <karney@alum.mit.edu> 119 | (2022-04-28)
120 |
121 | Geographiclib 122 | 123 | 124 | -------------------------------------------------------------------------------- /samples/geod-google.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Geodesic lines, circles, envelopes in Google Maps 6 | 8 | 9 | 26 | 27 | 34 | 40 | 47 | 54 | 61 | 64 | 304 | 305 | 306 |
309 |
310 |
311 |

312 |  Direct:   313 | 314 | 321 |

322 |

323 |  Inverse: 324 | 325 | 332 |

333 |

334 |  lat1 lon1 azi1: 335 |

336 |

337 |  lat2 lon2 azi2: 338 |

339 |

340 |  s12: 341 |    status: 342 |    343 | INSTRUCTIONS 344 | (v) 347 |

348 |
349 | 350 | 351 | --------------------------------------------------------------------------------