├── .gitignore ├── CMakeLists.txt ├── HOWTO-RELEASE.txt ├── LICENSE.txt ├── README.md ├── distrib-C └── 00README.md ├── doc ├── .htaccess ├── CMakeLists.txt ├── FOOTER.html ├── HEADER.html ├── doxyfile.in └── geodesic-c.dox.in ├── proj-example ├── CMakeLists.txt └── proj-example.c ├── src ├── CMakeLists.txt ├── geodesic.c └── geodesic.h ├── tests ├── CMakeLists.txt ├── geodsigntest.c └── geodtest.c └── tools ├── CMakeLists.txt ├── direct.c ├── inverse.c └── planimeter.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | BUILD*/ 3 | distrib-C/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.13.0) 2 | project (GeographicLib-C C) 3 | 4 | # Version information 5 | set (PROJECT_VERSION_MAJOR 2) 6 | set (PROJECT_VERSION_MINOR 1) 7 | set (PROJECT_VERSION_PATCH 0) 8 | set (PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") 9 | if (PROJECT_VERSION_PATCH GREATER 0) 10 | set (PROJECT_VERSION "${PROJECT_VERSION}.${PROJECT_VERSION_PATCH}") 11 | endif () 12 | set (PROJECT_VERSION_SUFFIX "") 13 | set (PROJECT_FULLVERSION ${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX}) 14 | 15 | set (PACKAGE_DIR "${PROJECT_NAME}-${PROJECT_VERSION}") 16 | set (PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_FULLVERSION}") 17 | 18 | option (CONVERT_WARNINGS_TO_ERRORS "Convert warnings into errors?" ON) 19 | option (BUILD_DOCUMENTATION "Use doxygen to create the documentation" ON) 20 | option (BUILD_SHARED_LIBS "Build as a shared library" ON) 21 | 22 | # Set a default build type for single-configuration cmake generators if 23 | # no build type is set. 24 | if (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 25 | set (CMAKE_BUILD_TYPE Release) 26 | endif () 27 | 28 | # We require C99 29 | set (CMAKE_C_STANDARD 99) 30 | set (CMAKE_C_STANDARD_REQUIRED ON) 31 | set (CMAKE_C_EXTENSIONS OFF) 32 | 33 | # Make the compiler more picky. 34 | if (MSVC) 35 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") 36 | else () 37 | set (CMAKE_C_FLAGS 38 | "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-array-bounds -pedantic") 39 | if (NOT CMAKE_C_COMPILER_ID STREQUAL "Intel") 40 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wfloat-conversion") 41 | endif () 42 | endif () 43 | 44 | # Tell Intel compiler to do arithmetic accurately. This is needed to 45 | # stop the compiler from ignoring parentheses in expressions like 46 | # (a + b) + c and from simplifying 0.0 + x to x (which is wrong if 47 | # x = -0.0). 48 | if (CMAKE_C_COMPILER_ID STREQUAL "Intel") 49 | if (MSVC) 50 | set (CMAKE_C_FLAGS 51 | "${CMAKE_C_FLAGS} /fp:precise /Qdiag-disable:11074,11076") 52 | else () 53 | set (CMAKE_C_FLAGS 54 | "${CMAKE_C_FLAGS} -fp-model precise -diag-disable=11074,11076") 55 | endif () 56 | endif () 57 | 58 | if (CONVERT_WARNINGS_TO_ERRORS) 59 | if (MSVC) 60 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /WX") 61 | else () 62 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") 63 | endif () 64 | endif () 65 | 66 | set (LIBNAME geodc) 67 | if (MSVC OR CMAKE_CONFIGURATION_TYPES) 68 | # For multi-config systems and for Visual Studio, the debug version of 69 | # the library is called Geographic_d. 70 | set (CMAKE_DEBUG_POSTFIX _d) 71 | endif () 72 | set (TOOLS direct inverse planimeter) 73 | 74 | add_subdirectory (src) 75 | add_subdirectory (tools) 76 | add_subdirectory (tests) 77 | 78 | enable_testing () 79 | # Run the test suite 80 | add_test (NAME geodtest COMMAND geodtest) 81 | add_test (NAME signtest COMMAND geodsigntest) 82 | 83 | add_custom_target (proj-example 84 | COMMAND 85 | ${CMAKE_COMMAND} -B proj-example-build -S ${PROJECT_SOURCE_DIR}/proj-example 86 | COMMAND cd proj-example-build && make 87 | COMMENT "Compile sample code linking against PROJ") 88 | 89 | # The documentation depends on doxygen. 90 | if (BUILD_DOCUMENTATION) 91 | set (DOXYGEN_SKIP_DOT ON) 92 | # Version 1.8.7 or later needed for … 93 | find_package (Doxygen 1.8.7) 94 | if (DOXYGEN_FOUND) 95 | add_subdirectory (doc) 96 | else () 97 | message (WARNING "Doxygen not found, not building the documentation") 98 | set (BUILD_DOCUMENTATION OFF) 99 | endif () 100 | endif () 101 | 102 | if (IS_DIRECTORY ${PROJECT_SOURCE_DIR}/.git AND NOT WIN32) 103 | add_custom_target (checktrailingspace 104 | COMMAND git ls-files | xargs grep ' $$' || true 105 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 106 | COMMENT "Looking for trailing spaces") 107 | add_custom_target (checktabs 108 | COMMAND git ls-files | xargs grep '\t' || true 109 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 110 | COMMENT "Looking for tabs") 111 | add_custom_target (checkblanklines 112 | COMMAND git ls-files | 113 | while read f\; do tr 'X\\n' 'YX' < $$f | 114 | egrep '\(^X|XXX|XX$$|[^X]$$\)' > /dev/null && echo $$f\; done || true 115 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 116 | COMMENT "Looking for extra blank lines") 117 | 118 | add_custom_target (sanitize) 119 | add_dependencies (sanitize checktrailingspace checktabs checkblanklines) 120 | endif () 121 | 122 | # The configuration of CPack is via variables that need to be set before 123 | # the include (CPack). 124 | set (CPACK_PACKAGE_CONTACT charles@karney.com) 125 | set (CPACK_PACKAGE_VENDOR "GeographicLib-C") 126 | set (CPACK_PACKAGE_DESCRIPTION_SUMMARY 127 | "GeographicLib C library and documentation") 128 | # The list of files to be excluded from the source distribution. 129 | set (CPACK_SOURCE_IGNORE_FILES 130 | "#" 131 | "~\$" 132 | "/\\\\.git" 133 | "/\\\\.htaccess" 134 | "${PROJECT_SOURCE_DIR}/doc/.*html" 135 | "${PROJECT_SOURCE_DIR}/BUILD" 136 | "${PROJECT_SOURCE_DIR}/distrib-C") 137 | set (CPACK_SOURCE_GENERATOR TGZ) 138 | 139 | set (CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 140 | set (CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 141 | set (CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 142 | 143 | set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PACKAGE_DIR}") 144 | set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PACKAGE_DIR}") 145 | 146 | include (CPack) 147 | 148 | if (IS_DIRECTORY ${PROJECT_SOURCE_DIR}/.git AND NOT WIN32) 149 | 150 | set (DISTRIB_DIR "${CMAKE_BINARY_DIR}/distrib") 151 | add_custom_target (dist 152 | COMMAND ${CMAKE_MAKE_PROGRAM} package_source 153 | COMMAND ${CMAKE_COMMAND} -E 154 | copy ${CPACK_SOURCE_PACKAGE_FILE_NAME}.tar.gz 155 | ${DISTRIB_DIR}/${PACKAGE_NAME}.tar.gz 156 | COMMAND 157 | cd _CPack_Packages/Linux-Source/TGZ/${CPACK_PACKAGE_INSTALL_DIRECTORY} && 158 | find -type f | cut -f2- -d/ | sort > ${DISTRIB_DIR}/package.list && 159 | cd ${PROJECT_SOURCE_DIR} && 160 | git ls-files | sort > ${DISTRIB_DIR}/git.list && 161 | comm -23 ${DISTRIB_DIR}/package.list ${DISTRIB_DIR}/git.list 162 | > ${DISTRIB_DIR}/unwanted.list && 163 | if test -s ${DISTRIB_DIR}/unwanted.list\; then 164 | echo "WARNING: Unwanted files in source package" && 165 | cat ${DISTRIB_DIR}/unwanted.list\; fi && 166 | echo "created distrib/${PACKAGE_NAME}.tar.gz") 167 | 168 | find_program (RSYNC rsync) 169 | if (IS_DIRECTORY ${PROJECT_SOURCE_DIR}/distrib-C AND RSYNC) 170 | set (USER karney) 171 | set (DATAROOT $ENV{HOME}/web/geographiclib-files/distrib-C) 172 | set (DOCROOT $ENV{HOME}/web/geographiclib-web/htdocs/C) 173 | set (FRSDEPLOY ${USER}@frs.sourceforge.net:/home/frs/project/geographiclib) 174 | set (WEBDEPLOY ${USER},geographiclib@web.sourceforge.net:./htdocs) 175 | set (PROJDIR $ENV{HOME}/git/PROJ/src) 176 | 177 | add_custom_target (stage-dist 178 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 179 | ${DISTRIB_DIR}/${PACKAGE_NAME}.tar.gz ${PROJECT_SOURCE_DIR}/distrib-C/ 180 | COMMAND ${RSYNC} --delete -av --exclude '*~' 181 | ${PROJECT_SOURCE_DIR}/distrib-C/ ${DATAROOT}/) 182 | add_dependencies (stage-dist dist) 183 | 184 | if (BUILD_DOCUMENTATION) 185 | add_custom_target (stage-doc 186 | COMMAND ${RSYNC} --delete -a doc/html/ ${DOCROOT}/${PROJECT_VERSION}/ 187 | COMMAND cd ${PROJECT_SOURCE_DIR}/doc && 188 | ${RSYNC} --delete -av HEADER.html FOOTER.html .htaccess ${DOCROOT}/) 189 | add_dependencies (stage-doc doc) 190 | endif () 191 | 192 | add_custom_target (deploy-dist 193 | COMMAND ${RSYNC} --delete -av ${DATAROOT} ${FRSDEPLOY}/) 194 | add_custom_target (deploy-doc 195 | COMMAND ${RSYNC} --delete -av -e ssh ${DOCROOT} ${WEBDEPLOY}/) 196 | endif () 197 | 198 | if (IS_DIRECTORY ${PROJDIR}/tests) 199 | add_custom_target (stage-proj 200 | COMMAND 201 | ${CMAKE_COMMAND} -E copy_if_different src/geodesic.h ${PROJDIR}/ && 202 | ${CMAKE_COMMAND} -E copy_if_different src/geodesic.c ${PROJDIR}/ && 203 | ${CMAKE_COMMAND} -E copy_if_different 204 | tests/geodtest.c ${PROJDIR}/tests/ && 205 | ${CMAKE_COMMAND} -E copy_if_different 206 | tests/geodsigntest.c ${PROJDIR}/tests/ 207 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 208 | COMMENT "Exporting source to ${PROJDIR}") 209 | endif () 210 | endif () 211 | -------------------------------------------------------------------------------- /HOWTO-RELEASE.txt: -------------------------------------------------------------------------------- 1 | CMake options 2 | CONVERT_WARNINGS_TO_ERRORS = ON 3 | BUILD_DOCUMENTATION = ON 4 | BUILD_SHARED_LIBS = ON 5 | 6 | Targets 7 | all test 8 | doc (if BUILD_DOCUMENTATION and doxygen found) 9 | dist (makes package_source and checks for unwanted files) 10 | sanitize 11 | stage-{doc,dist} 12 | deploy-{doc,dist} 13 | proj-example (uses version of library included in PROJ) 14 | stage-proj (copies source to PROJ) 15 | 16 | not supported: 17 | install (distribution is via PROJ) 18 | package 19 | 20 | Version update checks 21 | 22 | Add PROJECT_VERSION_SUFFIX = "-alpha" to 23 | tar package of source 24 | 25 | Do not add PROJECT_VERSION_SUFFIX to 26 | unpack directory for tar package 27 | documentation 28 | 29 | Update version in 30 | src/geodesic.h GEODESIC_VERSION_* 31 | doc/geodesic-c.dox.in (date + update change log) 32 | remove alpha versions in distrib-C 33 | 34 | PROJ integration... 35 | 36 | make stage-proj does 37 | PROJDIR=$HOME/git/PROJ 38 | cp -p src/geodesic.? $PROJDIR/src 39 | cp -p tests/geod{,sign}test.c $PROJDIR/src/tests/ 40 | 41 | also changes needed in 42 | docs/source/apps/geod.rst 43 | docs/source/geodesic.rst 44 | src/apps/geod_interface.cpp 45 | src/apps/bin_geod.cmake 46 | src/init.cpp 47 | src/tests/CMakeLists.txt 48 | 49 | After release, issue a PR for PROJ 50 | 51 | R package geosphere: 52 | Robert J. Hijmans 53 | 54 | Notify @xylar about need for updated patch for 55 | https://github.com/conda-forge/proj.4-feedstock 56 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT). 2 | 3 | Copyright (c) 2012-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 | # C implementation of the geodesic routines in GeographicLib 2 | 3 | This is a library to solve geodesic problems on an ellipsoid model of 4 | the earth. 5 | 6 | Note that this library is incorporated into 7 | [PROJ](https://proj.org/geodesic.html), and for many people using the 8 | geodesic routines via PROJ may be more convenient. 9 | 10 | Licensed under the MIT/X11 License; see 11 | [LICENSE.txt](https://geographiclib.sourceforge.io/LICENSE.txt). 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/C/doc 23 | * Change log: https://geographiclib.sourceforge.io/C/doc/changes.html 24 | * Git repository: https://github.com/geographiclib/geographiclib-c 25 | Releases are tagged in git as, e.g., [`v1.52`](../../tree/v1.52), 26 | [`v2.0`](../../tree/v2.0), etc. 27 | * Source distribution: 28 | https://sourceforge.net/projects/geographiclib/files/distrib-C 29 | * GeographicLib: https://geographiclib.sourceforge.io 30 | * Author: Charles Karney, 31 | -------------------------------------------------------------------------------- /distrib-C/00README.md: -------------------------------------------------------------------------------- 1 | # C implementation of the geodesic routines in GeographicLib 2 | 3 | This directory provide source packages for the library. Programmers 4 | should download the latest `.tar.gz` file. However, this library is 5 | incorporated into [PROJ](https://proj.org/geodesic.html), and for many 6 | people using the geodesic routines via PROJ may be more convenient. 7 | 8 | The algorithms are documented in 9 | 10 | * C. F. F. Karney, 11 | [Algorithms for geodesics](https://doi.org/10.1007/s00190-012-0578-z), 12 | J. Geodesy **87**(1), 43–55 (2013); 13 | [Addenda](https://geographiclib.sourceforge.io/geod-addenda.html). 14 | 15 | Other links: 16 | 17 | * Library documentation: https://geographiclib.sourceforge.io/C/doc 18 | * Git repository: https://github.com/geographiclib/geographiclib-c 19 | * Source distribution: 20 | https://sourceforge.net/projects/geographiclib/files/distrib-C 21 | * GeographicLib: https://geographiclib.sourceforge.io 22 | * Author: Charles Karney, 23 | -------------------------------------------------------------------------------- /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 (doxyfile.in doxyfile @ONLY) 2 | configure_file (geodesic-c.dox.in geodesic-c.dox @ONLY) 3 | set (CSOURCES 4 | ../src/geodesic.h 5 | ../src/geodesic.c 6 | ../tools/direct.c 7 | ../tools/inverse.c 8 | ../tools/planimeter.c 9 | ) 10 | 11 | add_custom_target (doc ALL 12 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/html/index.html) 13 | 14 | add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/html/index.html 15 | DEPENDS ${CSOURCES} 16 | ${CMAKE_CURRENT_BINARY_DIR}/doxyfile 17 | ${CMAKE_CURRENT_BINARY_DIR}/geodesic-c.dox 18 | COMMAND ${DOXYGEN_EXECUTABLE} doxyfile > doxygen.log 19 | COMMENT "Generating C documentation tree") 20 | -------------------------------------------------------------------------------- /doc/FOOTER.html: -------------------------------------------------------------------------------- 1 | GeographicLib home 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc/HEADER.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of https://geographiclib.sourceforge.io/C 5 | 6 | 7 | 8 | 9 | 10 |

C 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/geodesic-c.dox.in: -------------------------------------------------------------------------------- 1 | // -*- text -*- 2 | /** 3 | * \file geodesic-c.dox 4 | * \brief Documentation for geodesic routines implemented in C 5 | * 6 | * Written by Charles Karney and licensed under the 7 | * MIT/X11 License. For more information, see 8 | * https://geographiclib.sourceforge.io/ 9 | **********************************************************************/ 10 | 11 | /** 12 | \mainpage Geodesic routines implemented in C 13 | \author Charles F. F. Karney (charles@karney.com) 14 | \version @PROJECT_VERSION@ 15 | \date 2023-01-04 16 | 17 | \section abstract-c Abstract 18 | 19 | This is a C implementation of the geodesic algorithms from 20 | GeographicLib. This 21 | is a self-contained library (requiring only the standard C math library) 22 | which makes it easy to do geodesic computations for an ellipsoid of 23 | revolution in a C program. It is incorporated into 24 | PROJ and many people might 25 | prefer to use these routines via PROJ. 26 | 27 | This an implementation in C of the geodesic algorithms described in 28 | - C. F. F. Karney, 29 | 30 | Algorithms for geodesics, 31 | J. Geodesy 87, 43--55 (2013); 32 | 33 | addenda. 34 | . 35 | Other links 36 | - Library documentation (all versions): 37 | https://geographiclib.sourceforge.io/C 38 | - Git repository: https://github.com/geographiclib/geographiclib-c 39 | Releases are tagged in git as, e.g., v1.52, v2.0, etc. 40 | - Source distribution: 41 | https://sourceforge.net/projects/geographiclib/files/distrib-C 42 | - GeographicLib: 43 | https://geographiclib.sourceforge.io 44 | - The library has been implemented in a few other languages. 46 | 47 | \section contents Contents 48 | - \ref intro 49 | - \ref proj 50 | - \ref standalone 51 | - \ref library 52 | - \ref changes 53 | 54 | \page intro Introduction to the geodesic problem 55 | 56 | The shortest path between two points on the ellipsoid at (\e lat1, \e 57 | lon1) and (\e lat2, \e lon2) is called the geodesic. Its length is 58 | \e s12 and the geodesic from point 1 to point 2 has forward azimuths 59 | \e azi1 and \e azi2 at the two end points. 60 | 61 | Traditionally two geodesic problems are considered: 62 | - the direct problem -- given \e lat1, \e lon1, \e s12, and \e azi1, 63 | determine \e lat2, \e lon2, and \e azi2. This is solved by the function 64 | geod_direct(). 65 | - the inverse problem -- given \e lat1, \e lon1, and \e lat2, \e lon2, 66 | determine \e s12, \e azi1, and \e azi2. This is solved by the function 67 | geod_inverse(). 68 | 69 | The ellipsoid is specified by its equatorial radius \e a (typically in 70 | meters) and flattening \e f. The routines are accurate to round off with 71 | double precision arithmetic provided that |f| < 1/50; for the 72 | WGS84 ellipsoid, the errors are less than 15 nanometers. (Reasonably 73 | accurate results are obtained for |f| < 1/5.) For a prolate 74 | ellipsoid, specify \e f < 0. 75 | 76 | The routines also calculate several other quantities of interest 77 | - \e S12 is the area between the geodesic from point 1 to point 2 and the 78 | equator; i.e., it is the area, measured counter-clockwise, of the 79 | quadrilateral with corners (\e lat1,\e lon1), (0,\e lon1), (0,\e lon2), 80 | and (\e lat2,\e lon2). 81 | - \e m12, the reduced length of the geodesic is defined such that if 82 | the initial azimuth is perturbed by \e dazi1 (radians) then the 83 | second point is displaced by \e m12 \e dazi1 in the direction 84 | perpendicular to the geodesic. On a curved surface the reduced 85 | length obeys a symmetry relation, \e m12 + \e m21 = 0. On a flat 86 | surface, we have \e m12 = \e s12. 87 | - \e M12 and \e M21 are geodesic scales. If two geodesics are 88 | parallel at point 1 and separated by a small distance \e dt, then 89 | they are separated by a distance \e M12 \e dt at point 2. \e M21 90 | is defined similarly (with the geodesics being parallel to one 91 | another at point 2). On a flat surface, we have \e M12 = \e M21 92 | = 1. 93 | - \e a12 is the arc length on the auxiliary sphere. This is a 94 | construct for converting the problem to one in spherical 95 | trigonometry. \e a12 is measured in degrees. The spherical arc 96 | length from one equator crossing to the next is always 180°. 97 | 98 | If points 1, 2, and 3 lie on a single geodesic, then the following 99 | addition rules hold: 100 | - \e s13 = \e s12 + \e s23 101 | - \e a13 = \e a12 + \e a23 102 | - \e S13 = \e S12 + \e S23 103 | - \e m13 = \e m12 \e M23 + \e m23 \e M21 104 | - \e M13 = \e M12 \e M23 − (1 − \e M12 \e M21) \e 105 | m23 / \e m12 106 | - \e M31 = \e M32 \e M21 − (1 − \e M23 \e M32) \e 107 | m12 / \e m23 108 | 109 | The shortest distance returned by the solution of the inverse problem is 110 | (obviously) uniquely defined. However, in a few special cases there are 111 | multiple azimuths which yield the same shortest distance. Here is a 112 | catalog of those cases: 113 | - \e lat1 = −\e lat2 (with neither point at a pole). If \e azi1 = \e 114 | azi2, the geodesic is unique. Otherwise there are two geodesics and the 115 | second one is obtained by setting [\e azi1, \e azi2] → [\e azi2, \e 116 | azi1], [\e M12, \e M21] → [\e M21, \e M12], \e S12 → −\e 117 | S12. (This occurs when the longitude difference is near ±180° 118 | for oblate ellipsoids.) 119 | - \e lon2 = \e lon1 ± 180° (with neither point at a pole). If \e 120 | azi1 = 0° or ±180°, the geodesic is unique. Otherwise 121 | there are two geodesics and the second one is obtained by setting [\e 122 | azi1, \e azi2] → [−\e azi1, −\e azi2], \e S12 → 123 | −\e S12. (This occurs when \e lat2 is near −\e lat1 for 124 | prolate ellipsoids.) 125 | - Points 1 and 2 at opposite poles. There are infinitely many geodesics 126 | which can be generated by setting [\e azi1, \e azi2] → [\e azi1, \e 127 | azi2] + [\e d, −\e d], for arbitrary \e d. (For spheres, this 128 | prescription applies when points 1 and 2 are antipodal.) 129 | - \e s12 = 0 (coincident points). There are infinitely many geodesics which 130 | can be generated by setting [\e azi1, \e azi2] → [\e azi1, \e azi2] + 131 | [\e d, \e d], for arbitrary \e d. 132 | 133 | The area of a geodesic polygon can be determined by summing −\e 134 | S12 for successive edges of the polygon (\e S12 is negated so that 135 | clockwise traversal of a polygon gives a positive area). However, if 136 | the polygon encircles a pole, the sum must be adjusted by 137 | ±A/2, where \e A is the area of the full ellipsoid, with 138 | the sign chosen to place the result in (−A/2, A/2]. 139 | 140 | The principal advantages of these algorithms over previous ones (e.g., 141 | Vincenty, 1975) are 142 | - accurate to round off for |f| < 1/50; 143 | - the solution of the inverse problem is always found; 144 | - differential and integral properties of geodesics are computed. 145 | 146 | \page proj Information for PROJ user 147 | 148 | The library is included as part of PROJ 149 | starting with version 4.9.0 (released in 2015), where it is used as the 150 | computational backend for 151 | geod(1) and to implement 152 | some projections. 153 | 154 | If PROJ is installed on your system, that you can compiled your 155 | application with cmake. A skeleton 156 | CMakeLists.txt is 157 | \include proj-example/CMakeLists.txt 158 | 159 | This compiles and links the sample code proj-example.c. 160 | \include proj-example.c 161 | 162 | In this example, PROJ provides the following 163 | - access to the header file geodesic.h; 164 | - access to the geodesic code bundled into the PROJ library; 165 | - a mechanism for retrieving the parameters (equatorial radius and 166 | flattening) defining the ellipsoid. 167 | 168 | To compile and run this example, do 169 | \verbatim 170 | cmake -B BUILD -S . 171 | cd BUILD 172 | make 173 | ./proj-example 174 | \endverbatim 175 | which will print out 31 way points from Los Angeles to Christchurch 176 | starting with 177 | \verbatim 178 | latitude longitude azimuth 179 | 33.94000 -118.41000 -136.41544 180 | 31.49945 -121.08843 -137.86373 181 | 29.00508 -123.62942 -139.14437 182 | ... 183 | \endverbatim 184 | 185 | Other points to note for PROJ programmers: 186 | - The shape of the ellipsoid is given by the flattening *f*. *f* = 0 187 | denotes a sphere. *f* < 0 denotes a prolate ellipsoid. 188 | - The size of the ellipsoid is given by the equatorial radius *a*. 189 | This is usually given in meters. However, any units may be used and 190 | these are the units used for all distance-like quantites computed by 191 | the geodesic routines. 192 | - All angle like quantities are expressed in degrees, *not* radians. 193 | 194 | \page standalone Use as a standalone library 195 | 196 | The PROJ library (especially as provided as part of a Linux 197 | distribution) may lag behind the code documented here by about a year. 198 | 199 | Download the source code from as a tarball from 200 | 201 | - https://sourceforge.net/projects/geographiclib/files/distrib-C 202 | 203 | or check out the source code from 204 | 205 | - https://github.com/geographiclib/geographiclib-c 206 | 207 | Build the code with cmake 208 | \verbatim 209 | cmake -B BUILD -S . 210 | cd BUILD 211 | make 212 | make test 213 | \endverbatim 214 | possibly including some options via -D: 215 | - CONVERT_WARNINGS_TO_ERRORS warnings are fatal (default ON) 216 | - BUILD_DOCUMENTATION look for doxgen and build 217 | documentation (default ON) 218 | - BUILD_SHARED_LIBS make a shared (instead of static) 219 | library (default ON) 220 | 221 | CMake code to *install* the library is not provided. 222 | 223 | The library consists of two files geodesic.c and geodesic.h. 224 | 225 | Licensed under the 226 | MIT/X11 License; see 227 | LICENSE.txt. 228 | 229 | Also included are 3 small test programs: 230 | - direct.c is a simple command line utility for solving the 231 | direct geodesic problem; 232 | - inverse.c is a simple command line utility for solving the 233 | inverse geodesic problem; 234 | - planimeter.c is a simple command line utility for computing the 235 | area of a geodesic polygon given its vertices. 236 | . 237 | Here, for example, is inverse.c 238 | \include inverse.c 239 | Use inverse, for example, with 240 | \verbatim 241 | echo -30 0 29.5 179.5 | ./tools/inverse \endverbatim 242 | 243 | \page library Library documentation 244 | 245 | To use the library put @code{.c} 246 | #include "geodesic.h" 247 | @endcode 248 | in your source code. If you are using the library via PROJ, change 249 | this to @code{.c} 250 | #include 251 | @endcode 252 | 253 | The documention of the library is given in the header file, geodesic.h. 254 | Here is a brief summary of the functions. 255 | 256 | Simple geodesic problems: 257 | - Define a geod_geodesic object and initialize it with geod_init(). 258 | - Call geod_direct() and geod_inverse() to solve the direct and inverse 259 | geodesic problems. 260 | - geod_gendirect() and geod_geninverse() are more general versions of 261 | these routines which allow additional quantites, \e a12, \e m12, \e 262 | M12, \e M21, \e S12, to be computed. You can also specify the 263 | distance in the direct problem in terms of the arc length \e a12, 264 | instead of the distance \e s12 using the ::GEOD_ARCMODE flag. 265 | Additionally you can keep track of how many times a geodsic encircles 266 | the ellipsoid with the ::GEOD_LONG_UNROLL flag. 267 | 268 | The library allows multiple points on a single geodesic line (specified 269 | by \e lat1, \e lon1, and \e azi1) to be computed efficiently as follows: 270 | - Define a geod_geodesicline object and initialize it with 271 | geod_lineinit(). 272 | - Call geod_position() or geod_genposition() to compute the position and 273 | azimuth, \e lat2, \e lon2, and \e azi2, at a point a certain distance 274 | \e s12 from the starting point. 275 | - Optionally, you can register the position of a point 3 by specifying 276 | \e s13 or \e a13 using geod_setdistance() or geod_gensetdistance. 277 | - Alternatively, point 3 can be set up by initializing the 278 | geod_geodesicline object with geod_directline(), geod_gendirectline(), 279 | or geod_inverseline(). 280 | 281 | Finally, the library provides a way to compute the area of a polygon. 282 | The polygon is defined by adding successively either vertices or edges. 283 | This is done "progressively", to allow the area to be tracked as the 284 | polygon "grows". 285 | - Define a geod_polygon object and initialize it with 286 | geod_polygon_init(). You can declare that the object denotes a 287 | polyline instead of a polygon, in which case the area computation is 288 | skipped. 289 | - Add an initial point with geod_polygon_addpoint() and successively 290 | vertices or edges with geod_polygon_addpoint() or 291 | geod_polygon_addedge(). 292 | - Call geod_polygon_compute() to return the length and area so far (with 293 | polygons, but not polylines, being implicitly closed). Additional 294 | vertices or edges may be added subsequently. 295 | - Call geod_polygon_testpoint() or geod_polygon_testedge() to compute 296 | the results if a vertex or edge were tentatively added. This would 297 | allow a graphical user interface to continuously update the area and 298 | length as the cursor is moved which selecting the next vertex. 299 | - geod_polygon_clear() reinitializes the polygon. 300 | - geod_polygonarea() is a convenience function of computing the area 301 | directly using arrays latitudes and longitudes. 302 | 303 | You can check the version of the geodesic library with, e.g., 304 | @code{.c} 305 | #if GEODESIC_VERSION >= GEODESIC_VERSION_NUM(1,40,0) 306 | ... 307 | #endif 308 | @endcode 309 | 310 | \page changes Change log 311 | 312 | - Version 2.1 313 | (released 2023-01-04) 314 | - Minor improvement in the logic for the geodesic inverse problem. 315 | - Relax overly strict convergence test for inverse problem. 316 | - Allow for buggy remquo in geodsigntest.c. 317 | 318 | - Version 2.0 319 | (released 2022-04-21) 320 | - This is a major reorganization with the C library put into its own 321 | git repository, https://github.com/geographiclib/geographiclib-c. 322 | Despite this, there are only reasonably minor changes to the library 323 | itself. 324 | - Fix bug where the solution of the inverse geodesic problem with 325 | φ1 = 0 and φ2 = nan was treated as 326 | equatorial. 327 | - Updated \ref library to provide a better guide to the use of this 328 | library. 329 | - Add documentation and an example of using solving geodesic problems 330 | with PROJ. 331 | - More careful treatment of ±0° and ±180°. 332 | - These behave consistently with taking the limits 333 | - ±0 means ±ε as ε → 0+ 334 | - ±180 means ±(180 − ε) as ε 335 | → 0+ 336 | - As a consequence, azimuths of +0° and +180° are reckoned to 337 | be east-going, as far as tracking the longitude with ::GEOD_LONG_UNROLL 338 | and the area goes, while azimuths −0° and −180° 339 | are reckoned to be west-going. 340 | - When computing longitude differences, if λ2 341 | − λ1 = ±180° (mod 360°), 342 | then the sign is picked depending on the sign of the difference. 343 | - The normal range for returned longitudes and azimuths is 344 | [−180°, 180°]. 345 | - A separate test suite geodsigntest has been added to check this 346 | treatment. 347 | 348 | - Version 1.52 349 | (released 2021-03-13) 350 | - Be more aggressive in preventing negative s12 and m12 for short lines. 351 | - Initialize reference argument to remquo. 352 | - Work around inaccurate implementation of hypot with Visual Studio 353 | (win32). 354 | 355 | - Version 1.51 356 | (released 2020-11-22) 357 | - C99 is now required, so there's no need for private implementations 358 | of various routines now defined in math.h. 359 | 360 | - Version 1.50 361 | (released 2019-09-22) 362 | - Allow arbitrarily complex polygons in geod_polygon_*. In the case 363 | of self-intersecting polygons the area is accumulated 364 | "algebraically", e.g., the areas of the 2 loops in a figure-8 365 | polygon will partially cancel. 366 | - Workaround bugs in fmod and sin in Visual Studio 10, 11, and 12 and 367 | relax delta for GeodSolve59 in geodtest (tagged v1.49.1-c). 368 | - Fix bug in geod_polygon_addedge which caused the count of pole 369 | encirclings to be wrong, sometimes resulting in an incorrect area 370 | if a polygon vertex had longitude = 0 (tagged v1.49.2-c). 371 | 372 | - Version 1.49 373 | (released 2017-10-05) 374 | - Fix more warning messages from some compilers; add tests. 375 | 376 | - Version 1.48 377 | (released 2017-04-09) 378 | - This is the version slated for the version of proj.4 after 4.9.4 379 | (tagged v1.48.1-c). 380 | - Fix warnings messages from some compilers. 381 | - Change default range for longitude and azimuth to 382 | (−180°, 180°] (instead of 383 | [−180°, 180°)). 384 | 385 | - Version 1.47 386 | (released 2017-02-15) 387 | - This is the version incorporated into proj.4 version 4.9.3 (tagged 388 | v1.46.1-c). 389 | - Fix the order of declarations, incorporating the patches in version 390 | 1.46.1. 391 | - Improve accuracy of area calculation (fixing a flaw introduced in 392 | version 1.46). 393 | 394 | - Version 1.46 395 | (released 2016-02-15) 396 | - Add s13 and a13 to the geod_geodesicline struct. 397 | - Add geod_directline, geod_gendirectline, and geod_inverseline. 398 | - More accurate inverse solution when longitude difference is close 399 | to 180°. 400 | 401 | - Version 1.45 402 | (released 2015-09-30) 403 | - The solution of the inverse problem now correctly returns NaNs if 404 | one of the latitudes is a NaN. 405 | - Include a test suite that can be run with "make test" after 406 | configuring with cmake. 407 | - Add geod_polygon_clear(). 408 | 409 | - Version 1.44 410 | (released 2015-08-14) 411 | - This is the version incorporated into proj.4 version 4.9.2. 412 | - Improve accuracy of calculations by evaluating trigonometric 413 | functions more carefully and replacing the series for the reduced 414 | length with one with a smaller truncation error. 415 | - The allowed ranges for longitudes and azimuths is now unlimited; 416 | it used to be [−540°, 540°). 417 | - Enforce the restriction of latitude to [−90°, 90°] by 418 | returning NaNs if the latitude is outside this range. 419 | - The inverse calculation sets \e s12 to zero for coincident points 420 | at pole (instead of returning a tiny quantity). 421 | 422 | - Version 1.40 423 | (released 2014-12-18) 424 | - This is the version incorporated into proj.4 version 4.9.1. 425 | 426 | - Version 1.32 427 | (released 2013-07-12) 428 | - This is the version incorporated into proj.4 version 4.9.0. 429 | 430 | **********************************************************************/ 431 | -------------------------------------------------------------------------------- /proj-example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.13.0) 2 | project (proj-example C) 3 | 4 | find_package (PROJ REQUIRED) 5 | 6 | add_executable (proj-example proj-example.c) 7 | target_link_libraries (proj-example ${PROJ_LIBRARIES}) 8 | -------------------------------------------------------------------------------- /proj-example/proj-example.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proj-example.c 3 | * @brief An example of computing geodesics with the PROJ library 4 | **********************************************************************/ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int main (void) { 11 | /* Compute approximately equally spaced way points between LAX, 33.94N 12 | 118.41W, and CHC, 43.49S 172.53E. */ 13 | double a, invf; 14 | struct geod_geodesic g; 15 | struct geod_geodesicline l; 16 | const int n = 30; 17 | double lat[n+1], lon[n+1], azi[n+1]; 18 | int i; 19 | { 20 | /* Use PROJ to get the ellipsoid parameters */ 21 | PJ_CONTEXT* C = proj_context_create(); 22 | char* argv[3] = {"type=crs", "proj=longlat", "ellps=WGS84"}; 23 | PJ* P = proj_create_argv(C, 3, argv); 24 | if (P == 0) { 25 | fprintf(stderr, "Failed to create transformation object.\n"); 26 | return 1; 27 | } 28 | proj_ellipsoid_get_parameters(C, proj_get_ellipsoid(C, P), 29 | &a, 0, 0, &invf); 30 | proj_destroy(P); 31 | proj_context_destroy(C); 32 | } 33 | /* Initialize geodesic */ 34 | geod_init(&g, a, invf != 0 ? 1/invf : 0); 35 | /* Don't need GEOD_DISTANCE_IN if using GEOD_ARCMODE */ 36 | geod_inverseline(&l, &g, 33.94, -118.41, -43.49, 172.53, 37 | GEOD_LATITUDE | GEOD_LONGITUDE | GEOD_AZIMUTH); 38 | printf("latitude longitude azimuth\n"); 39 | for (i = 0; i <= n; ++i) { 40 | /* Use GEOD_LONG_UNROLL so longitude varies continuously */ 41 | geod_genposition(&l, GEOD_ARCMODE | GEOD_LONG_UNROLL, 42 | i * l.a13 / n, lat + i, lon + i, azi + i, 0, 0, 0, 0, 0); 43 | printf("%.5f %.5f %.5f\n", lat[i], lon[i], azi[i]); 44 | } 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (${LIBNAME} geodesic.c geodesic.h) 2 | 3 | target_include_directories (${LIBNAME} PUBLIC 4 | $ 5 | $) 6 | -------------------------------------------------------------------------------- /src/geodesic.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file geodesic.c 3 | * \brief Implementation of the geodesic routines in C 4 | * 5 | * For the full documentation see geodesic.h. 6 | **********************************************************************/ 7 | 8 | /** @cond SKIP */ 9 | 10 | /* 11 | * This is a C implementation of the geodesic algorithms described in 12 | * 13 | * C. F. F. Karney, 14 | * Algorithms for geodesics, 15 | * J. Geodesy 87, 43--55 (2013); 16 | * https://doi.org/10.1007/s00190-012-0578-z 17 | * Addenda: https://geographiclib.sourceforge.io/geod-addenda.html 18 | * 19 | * See the comments in geodesic.h for documentation. 20 | * 21 | * Copyright (c) Charles Karney (2012-2022) and licensed 22 | * under the MIT/X11 License. For more information, see 23 | * https://geographiclib.sourceforge.io/ 24 | */ 25 | 26 | #include "geodesic.h" 27 | #include 28 | #include 29 | 30 | #if !defined(__cplusplus) 31 | #define nullptr 0 32 | #endif 33 | 34 | #define GEOGRAPHICLIB_GEODESIC_ORDER 6 35 | #define nA1 GEOGRAPHICLIB_GEODESIC_ORDER 36 | #define nC1 GEOGRAPHICLIB_GEODESIC_ORDER 37 | #define nC1p GEOGRAPHICLIB_GEODESIC_ORDER 38 | #define nA2 GEOGRAPHICLIB_GEODESIC_ORDER 39 | #define nC2 GEOGRAPHICLIB_GEODESIC_ORDER 40 | #define nA3 GEOGRAPHICLIB_GEODESIC_ORDER 41 | #define nA3x nA3 42 | #define nC3 GEOGRAPHICLIB_GEODESIC_ORDER 43 | #define nC3x ((nC3 * (nC3 - 1)) / 2) 44 | #define nC4 GEOGRAPHICLIB_GEODESIC_ORDER 45 | #define nC4x ((nC4 * (nC4 + 1)) / 2) 46 | #define nC (GEOGRAPHICLIB_GEODESIC_ORDER + 1) 47 | 48 | typedef int boolx; 49 | enum booly { FALSE = 0, TRUE = 1 }; 50 | /* qd = quarter turn / degree 51 | * hd = half turn / degree 52 | * td = full turn / degree */ 53 | enum dms { qd = 90, hd = 2 * qd, td = 2 * hd }; 54 | 55 | static unsigned init = 0; 56 | static unsigned digits, maxit1, maxit2; 57 | static double epsilon, realmin, pi, degree, NaN, 58 | tiny, tol0, tol1, tol2, tolb, xthresh; 59 | 60 | static void Init(void) { 61 | if (!init) { 62 | digits = DBL_MANT_DIG; 63 | epsilon = DBL_EPSILON; 64 | realmin = DBL_MIN; 65 | #if defined(M_PI) 66 | pi = M_PI; 67 | #else 68 | pi = atan2(0.0, -1.0); 69 | #endif 70 | maxit1 = 20; 71 | maxit2 = maxit1 + digits + 10; 72 | tiny = sqrt(realmin); 73 | tol0 = epsilon; 74 | /* Increase multiplier in defn of tol1 from 100 to 200 to fix inverse case 75 | * 52.784459512564 0 -52.784459512563990912 179.634407464943777557 76 | * which otherwise failed for Visual Studio 10 (Release and Debug) */ 77 | tol1 = 200 * tol0; 78 | tol2 = sqrt(tol0); 79 | /* Check on bisection interval */ 80 | tolb = tol0; 81 | xthresh = 1000 * tol2; 82 | degree = pi/hd; 83 | NaN = nan("0"); 84 | init = 1; 85 | } 86 | } 87 | 88 | enum captype { 89 | CAP_NONE = 0U, 90 | CAP_C1 = 1U<<0, 91 | CAP_C1p = 1U<<1, 92 | CAP_C2 = 1U<<2, 93 | CAP_C3 = 1U<<3, 94 | CAP_C4 = 1U<<4, 95 | CAP_ALL = 0x1FU, 96 | OUT_ALL = 0x7F80U 97 | }; 98 | 99 | static double sq(double x) { return x * x; } 100 | 101 | static double sumx(double u, double v, double* t) { 102 | volatile double s = u + v; 103 | volatile double up = s - v; 104 | volatile double vpp = s - up; 105 | up -= u; 106 | vpp -= v; 107 | if (t) *t = s != 0 ? 0 - (up + vpp) : s; 108 | /* error-free sum: 109 | * u + v = s + t 110 | * = round(u + v) + t */ 111 | return s; 112 | } 113 | 114 | static double polyvalx(int N, const double p[], double x) { 115 | double y = N < 0 ? 0 : *p++; 116 | while (--N >= 0) y = y * x + *p++; 117 | return y; 118 | } 119 | 120 | static void swapx(double* x, double* y) 121 | { double t = *x; *x = *y; *y = t; } 122 | 123 | static void norm2(double* sinx, double* cosx) { 124 | #if defined(_MSC_VER) && defined(_M_IX86) 125 | /* hypot for Visual Studio (A=win32) fails monotonicity, e.g., with 126 | * x = 0.6102683302836215 127 | * y1 = 0.7906090004346522 128 | * y2 = y1 + 1e-16 129 | * the test 130 | * hypot(x, y2) >= hypot(x, y1) 131 | * fails. See also 132 | * https://bugs.python.org/issue43088 */ 133 | double r = sqrt(*sinx * *sinx + *cosx * *cosx); 134 | #else 135 | double r = hypot(*sinx, *cosx); 136 | #endif 137 | *sinx /= r; 138 | *cosx /= r; 139 | } 140 | 141 | static double AngNormalize(double x) { 142 | double y = remainder(x, (double)td); 143 | return fabs(y) == hd ? copysign((double)hd, x) : y; 144 | } 145 | 146 | static double LatFix(double x) 147 | { return fabs(x) > qd ? NaN : x; } 148 | 149 | static double AngDiff(double x, double y, double* e) { 150 | /* Use remainder instead of AngNormalize, since we treat boundary cases 151 | * later taking account of the error */ 152 | double t, d = sumx(remainder(-x, (double)td), remainder( y, (double)td), &t); 153 | /* This second sum can only change d if abs(d) < 128, so don't need to 154 | * apply remainder yet again. */ 155 | d = sumx(remainder(d, (double)td), t, &t); 156 | /* Fix the sign if d = -180, 0, 180. */ 157 | if (d == 0 || fabs(d) == hd) 158 | /* If t == 0, take sign from y - x 159 | * else (t != 0, implies d = +/-180), d and t must have opposite signs */ 160 | d = copysign(d, t == 0 ? y - x : -t); 161 | if (e) *e = t; 162 | return d; 163 | } 164 | 165 | static double AngRound(double x) { 166 | /* False positive in cppcheck requires "1.0" instead of "1" */ 167 | const double z = 1.0/16.0; 168 | volatile double y = fabs(x); 169 | volatile double w = z - y; 170 | /* The compiler mustn't "simplify" z - (z - y) to y */ 171 | y = w > 0 ? z - w : y; 172 | return copysign(y, x); 173 | } 174 | 175 | static void sincosdx(double x, double* sinx, double* cosx) { 176 | /* In order to minimize round-off errors, this function exactly reduces 177 | * the argument to the range [-45, 45] before converting it to radians. */ 178 | double r, s, c; int q = 0; 179 | r = remquo(x, (double)qd, &q); 180 | /* now abs(r) <= 45 */ 181 | r *= degree; 182 | /* Possibly could call the gnu extension sincos */ 183 | s = sin(r); c = cos(r); 184 | switch ((unsigned)q & 3U) { 185 | case 0U: *sinx = s; *cosx = c; break; 186 | case 1U: *sinx = c; *cosx = -s; break; 187 | case 2U: *sinx = -s; *cosx = -c; break; 188 | default: *sinx = -c; *cosx = s; break; /* case 3U */ 189 | } 190 | /* http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf */ 191 | *cosx += 0; /* special values from F.10.1.12 */ 192 | /* special values from F.10.1.13 */ 193 | if (*sinx == 0) *sinx = copysign(*sinx, x); 194 | } 195 | 196 | static void sincosde(double x, double t, double* sinx, double* cosx) { 197 | /* In order to minimize round-off errors, this function exactly reduces 198 | * the argument to the range [-45, 45] before converting it to radians. */ 199 | double r, s, c; int q = 0; 200 | r = AngRound(remquo(x, (double)qd, &q) + t); 201 | /* now abs(r) <= 45 */ 202 | r *= degree; 203 | /* Possibly could call the gnu extension sincos */ 204 | s = sin(r); c = cos(r); 205 | switch ((unsigned)q & 3U) { 206 | case 0U: *sinx = s; *cosx = c; break; 207 | case 1U: *sinx = c; *cosx = -s; break; 208 | case 2U: *sinx = -s; *cosx = -c; break; 209 | default: *sinx = -c; *cosx = s; break; /* case 3U */ 210 | } 211 | /* http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1950.pdf */ 212 | *cosx += 0; /* special values from F.10.1.12 */ 213 | /* special values from F.10.1.13 */ 214 | if (*sinx == 0) *sinx = copysign(*sinx, x); 215 | } 216 | 217 | static double atan2dx(double y, double x) { 218 | /* In order to minimize round-off errors, this function rearranges the 219 | * arguments so that result of atan2 is in the range [-pi/4, pi/4] before 220 | * converting it to degrees and mapping the result to the correct 221 | * quadrant. */ 222 | int q = 0; double ang; 223 | if (fabs(y) > fabs(x)) { swapx(&x, &y); q = 2; } 224 | if (signbit(x)) { x = -x; ++q; } 225 | /* here x >= 0 and x >= abs(y), so angle is in [-pi/4, pi/4] */ 226 | ang = atan2(y, x) / degree; 227 | switch (q) { 228 | case 1: ang = copysign((double)hd, y) - ang; break; 229 | case 2: ang = qd - ang; break; 230 | case 3: ang = -qd + ang; break; 231 | default: break; 232 | } 233 | return ang; 234 | } 235 | 236 | static void A3coeff(struct geod_geodesic* g); 237 | static void C3coeff(struct geod_geodesic* g); 238 | static void C4coeff(struct geod_geodesic* g); 239 | static double SinCosSeries(boolx sinp, 240 | double sinx, double cosx, 241 | const double c[], int n); 242 | static void Lengths(const struct geod_geodesic* g, 243 | double eps, double sig12, 244 | double ssig1, double csig1, double dn1, 245 | double ssig2, double csig2, double dn2, 246 | double cbet1, double cbet2, 247 | double* ps12b, double* pm12b, double* pm0, 248 | double* pM12, double* pM21, 249 | /* Scratch area of the right size */ 250 | double Ca[]); 251 | static double Astroid(double x, double y); 252 | static double InverseStart(const struct geod_geodesic* g, 253 | double sbet1, double cbet1, double dn1, 254 | double sbet2, double cbet2, double dn2, 255 | double lam12, double slam12, double clam12, 256 | double* psalp1, double* pcalp1, 257 | /* Only updated if return val >= 0 */ 258 | double* psalp2, double* pcalp2, 259 | /* Only updated for short lines */ 260 | double* pdnm, 261 | /* Scratch area of the right size */ 262 | double Ca[]); 263 | static double Lambda12(const struct geod_geodesic* g, 264 | double sbet1, double cbet1, double dn1, 265 | double sbet2, double cbet2, double dn2, 266 | double salp1, double calp1, 267 | double slam120, double clam120, 268 | double* psalp2, double* pcalp2, 269 | double* psig12, 270 | double* pssig1, double* pcsig1, 271 | double* pssig2, double* pcsig2, 272 | double* peps, 273 | double* pdomg12, 274 | boolx diffp, double* pdlam12, 275 | /* Scratch area of the right size */ 276 | double Ca[]); 277 | static double A3f(const struct geod_geodesic* g, double eps); 278 | static void C3f(const struct geod_geodesic* g, double eps, double c[]); 279 | static void C4f(const struct geod_geodesic* g, double eps, double c[]); 280 | static double A1m1f(double eps); 281 | static void C1f(double eps, double c[]); 282 | static void C1pf(double eps, double c[]); 283 | static double A2m1f(double eps); 284 | static void C2f(double eps, double c[]); 285 | static int transit(double lon1, double lon2); 286 | static int transitdirect(double lon1, double lon2); 287 | static void accini(double s[]); 288 | static void acccopy(const double s[], double t[]); 289 | static void accadd(double s[], double y); 290 | static double accsum(const double s[], double y); 291 | static void accneg(double s[]); 292 | static void accrem(double s[], double y); 293 | static double areareduceA(double area[], double area0, 294 | int crossings, boolx reverse, boolx sign); 295 | static double areareduceB(double area, double area0, 296 | int crossings, boolx reverse, boolx sign); 297 | 298 | void geod_init(struct geod_geodesic* g, double a, double f) { 299 | if (!init) Init(); 300 | g->a = a; 301 | g->f = f; 302 | g->f1 = 1 - g->f; 303 | g->e2 = g->f * (2 - g->f); 304 | g->ep2 = g->e2 / sq(g->f1); /* e2 / (1 - e2) */ 305 | g->n = g->f / ( 2 - g->f); 306 | g->b = g->a * g->f1; 307 | g->c2 = (sq(g->a) + sq(g->b) * 308 | (g->e2 == 0 ? 1 : 309 | (g->e2 > 0 ? atanh(sqrt(g->e2)) : atan(sqrt(-g->e2))) / 310 | sqrt(fabs(g->e2))))/2; /* authalic radius squared */ 311 | /* The sig12 threshold for "really short". Using the auxiliary sphere 312 | * solution with dnm computed at (bet1 + bet2) / 2, the relative error in the 313 | * azimuth consistency check is sig12^2 * abs(f) * min(1, 1-f/2) / 2. (Error 314 | * measured for 1/100 < b/a < 100 and abs(f) >= 1/1000. For a given f and 315 | * sig12, the max error occurs for lines near the pole. If the old rule for 316 | * computing dnm = (dn1 + dn2)/2 is used, then the error increases by a 317 | * factor of 2.) Setting this equal to epsilon gives sig12 = etol2. Here 318 | * 0.1 is a safety factor (error decreased by 100) and max(0.001, abs(f)) 319 | * stops etol2 getting too large in the nearly spherical case. */ 320 | g->etol2 = 0.1 * tol2 / 321 | sqrt( fmax(0.001, fabs(g->f)) * fmin(1.0, 1 - g->f/2) / 2 ); 322 | 323 | A3coeff(g); 324 | C3coeff(g); 325 | C4coeff(g); 326 | } 327 | 328 | static void geod_lineinit_int(struct geod_geodesicline* l, 329 | const struct geod_geodesic* g, 330 | double lat1, double lon1, 331 | double azi1, double salp1, double calp1, 332 | unsigned caps) { 333 | double cbet1, sbet1, eps; 334 | l->a = g->a; 335 | l->f = g->f; 336 | l->b = g->b; 337 | l->c2 = g->c2; 338 | l->f1 = g->f1; 339 | /* If caps is 0 assume the standard direct calculation */ 340 | l->caps = (caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE) | 341 | /* always allow latitude and azimuth and unrolling of longitude */ 342 | GEOD_LATITUDE | GEOD_AZIMUTH | GEOD_LONG_UNROLL; 343 | 344 | l->lat1 = LatFix(lat1); 345 | l->lon1 = lon1; 346 | l->azi1 = azi1; 347 | l->salp1 = salp1; 348 | l->calp1 = calp1; 349 | 350 | sincosdx(AngRound(l->lat1), &sbet1, &cbet1); sbet1 *= l->f1; 351 | /* Ensure cbet1 = +epsilon at poles */ 352 | norm2(&sbet1, &cbet1); cbet1 = fmax(tiny, cbet1); 353 | l->dn1 = sqrt(1 + g->ep2 * sq(sbet1)); 354 | 355 | /* Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), */ 356 | l->salp0 = l->salp1 * cbet1; /* alp0 in [0, pi/2 - |bet1|] */ 357 | /* Alt: calp0 = hypot(sbet1, calp1 * cbet1). The following 358 | * is slightly better (consider the case salp1 = 0). */ 359 | l->calp0 = hypot(l->calp1, l->salp1 * sbet1); 360 | /* Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1). 361 | * sig = 0 is nearest northward crossing of equator. 362 | * With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line). 363 | * With bet1 = pi/2, alp1 = -pi, sig1 = pi/2 364 | * With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2 365 | * Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1). 366 | * With alp0 in (0, pi/2], quadrants for sig and omg coincide. 367 | * No atan2(0,0) ambiguity at poles since cbet1 = +epsilon. 368 | * With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi. */ 369 | l->ssig1 = sbet1; l->somg1 = l->salp0 * sbet1; 370 | l->csig1 = l->comg1 = sbet1 != 0 || l->calp1 != 0 ? cbet1 * l->calp1 : 1; 371 | norm2(&l->ssig1, &l->csig1); /* sig1 in (-pi, pi] */ 372 | /* norm2(somg1, comg1); -- don't need to normalize! */ 373 | 374 | l->k2 = sq(l->calp0) * g->ep2; 375 | eps = l->k2 / (2 * (1 + sqrt(1 + l->k2)) + l->k2); 376 | 377 | if (l->caps & CAP_C1) { 378 | double s, c; 379 | l->A1m1 = A1m1f(eps); 380 | C1f(eps, l->C1a); 381 | l->B11 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C1a, nC1); 382 | s = sin(l->B11); c = cos(l->B11); 383 | /* tau1 = sig1 + B11 */ 384 | l->stau1 = l->ssig1 * c + l->csig1 * s; 385 | l->ctau1 = l->csig1 * c - l->ssig1 * s; 386 | /* Not necessary because C1pa reverts C1a 387 | * B11 = -SinCosSeries(TRUE, stau1, ctau1, C1pa, nC1p); */ 388 | } 389 | 390 | if (l->caps & CAP_C1p) 391 | C1pf(eps, l->C1pa); 392 | 393 | if (l->caps & CAP_C2) { 394 | l->A2m1 = A2m1f(eps); 395 | C2f(eps, l->C2a); 396 | l->B21 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C2a, nC2); 397 | } 398 | 399 | if (l->caps & CAP_C3) { 400 | C3f(g, eps, l->C3a); 401 | l->A3c = -l->f * l->salp0 * A3f(g, eps); 402 | l->B31 = SinCosSeries(TRUE, l->ssig1, l->csig1, l->C3a, nC3-1); 403 | } 404 | 405 | if (l->caps & CAP_C4) { 406 | C4f(g, eps, l->C4a); 407 | /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0) */ 408 | l->A4 = sq(l->a) * l->calp0 * l->salp0 * g->e2; 409 | l->B41 = SinCosSeries(FALSE, l->ssig1, l->csig1, l->C4a, nC4); 410 | } 411 | 412 | l->a13 = l->s13 = NaN; 413 | } 414 | 415 | void geod_lineinit(struct geod_geodesicline* l, 416 | const struct geod_geodesic* g, 417 | double lat1, double lon1, double azi1, unsigned caps) { 418 | double salp1, calp1; 419 | azi1 = AngNormalize(azi1); 420 | /* Guard against underflow in salp0 */ 421 | sincosdx(AngRound(azi1), &salp1, &calp1); 422 | geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); 423 | } 424 | 425 | void geod_gendirectline(struct geod_geodesicline* l, 426 | const struct geod_geodesic* g, 427 | double lat1, double lon1, double azi1, 428 | unsigned flags, double s12_a12, 429 | unsigned caps) { 430 | geod_lineinit(l, g, lat1, lon1, azi1, caps); 431 | geod_gensetdistance(l, flags, s12_a12); 432 | } 433 | 434 | void geod_directline(struct geod_geodesicline* l, 435 | const struct geod_geodesic* g, 436 | double lat1, double lon1, double azi1, 437 | double s12, unsigned caps) { 438 | geod_gendirectline(l, g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, caps); 439 | } 440 | 441 | double geod_genposition(const struct geod_geodesicline* l, 442 | unsigned flags, double s12_a12, 443 | double* plat2, double* plon2, double* pazi2, 444 | double* ps12, double* pm12, 445 | double* pM12, double* pM21, 446 | double* pS12) { 447 | double lat2 = 0, lon2 = 0, azi2 = 0, s12 = 0, 448 | m12 = 0, M12 = 0, M21 = 0, S12 = 0; 449 | /* Avoid warning about uninitialized B12. */ 450 | double sig12, ssig12, csig12, B12 = 0, AB1 = 0; 451 | double omg12, lam12, lon12; 452 | double ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2, dn2; 453 | unsigned outmask = 454 | (plat2 ? GEOD_LATITUDE : GEOD_NONE) | 455 | (plon2 ? GEOD_LONGITUDE : GEOD_NONE) | 456 | (pazi2 ? GEOD_AZIMUTH : GEOD_NONE) | 457 | (ps12 ? GEOD_DISTANCE : GEOD_NONE) | 458 | (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | 459 | (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | 460 | (pS12 ? GEOD_AREA : GEOD_NONE); 461 | 462 | outmask &= l->caps & OUT_ALL; 463 | if (!( (flags & GEOD_ARCMODE || (l->caps & (GEOD_DISTANCE_IN & OUT_ALL))) )) 464 | /* Impossible distance calculation requested */ 465 | return NaN; 466 | 467 | if (flags & GEOD_ARCMODE) { 468 | /* Interpret s12_a12 as spherical arc length */ 469 | sig12 = s12_a12 * degree; 470 | sincosdx(s12_a12, &ssig12, &csig12); 471 | } else { 472 | /* Interpret s12_a12 as distance */ 473 | double 474 | tau12 = s12_a12 / (l->b * (1 + l->A1m1)), 475 | s = sin(tau12), 476 | c = cos(tau12); 477 | /* tau2 = tau1 + tau12 */ 478 | B12 = - SinCosSeries(TRUE, 479 | l->stau1 * c + l->ctau1 * s, 480 | l->ctau1 * c - l->stau1 * s, 481 | l->C1pa, nC1p); 482 | sig12 = tau12 - (B12 - l->B11); 483 | ssig12 = sin(sig12); csig12 = cos(sig12); 484 | if (fabs(l->f) > 0.01) { 485 | /* Reverted distance series is inaccurate for |f| > 1/100, so correct 486 | * sig12 with 1 Newton iteration. The following table shows the 487 | * approximate maximum error for a = WGS_a() and various f relative to 488 | * GeodesicExact. 489 | * erri = the error in the inverse solution (nm) 490 | * errd = the error in the direct solution (series only) (nm) 491 | * errda = the error in the direct solution (series + 1 Newton) (nm) 492 | * 493 | * f erri errd errda 494 | * -1/5 12e6 1.2e9 69e6 495 | * -1/10 123e3 12e6 765e3 496 | * -1/20 1110 108e3 7155 497 | * -1/50 18.63 200.9 27.12 498 | * -1/100 18.63 23.78 23.37 499 | * -1/150 18.63 21.05 20.26 500 | * 1/150 22.35 24.73 25.83 501 | * 1/100 22.35 25.03 25.31 502 | * 1/50 29.80 231.9 30.44 503 | * 1/20 5376 146e3 10e3 504 | * 1/10 829e3 22e6 1.5e6 505 | * 1/5 157e6 3.8e9 280e6 */ 506 | double serr; 507 | ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; 508 | csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; 509 | B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); 510 | serr = (1 + l->A1m1) * (sig12 + (B12 - l->B11)) - s12_a12 / l->b; 511 | sig12 = sig12 - serr / sqrt(1 + l->k2 * sq(ssig2)); 512 | ssig12 = sin(sig12); csig12 = cos(sig12); 513 | /* Update B12 below */ 514 | } 515 | } 516 | 517 | /* sig2 = sig1 + sig12 */ 518 | ssig2 = l->ssig1 * csig12 + l->csig1 * ssig12; 519 | csig2 = l->csig1 * csig12 - l->ssig1 * ssig12; 520 | dn2 = sqrt(1 + l->k2 * sq(ssig2)); 521 | if (outmask & (GEOD_DISTANCE | GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { 522 | if (flags & GEOD_ARCMODE || fabs(l->f) > 0.01) 523 | B12 = SinCosSeries(TRUE, ssig2, csig2, l->C1a, nC1); 524 | AB1 = (1 + l->A1m1) * (B12 - l->B11); 525 | } 526 | /* sin(bet2) = cos(alp0) * sin(sig2) */ 527 | sbet2 = l->calp0 * ssig2; 528 | /* Alt: cbet2 = hypot(csig2, salp0 * ssig2); */ 529 | cbet2 = hypot(l->salp0, l->calp0 * csig2); 530 | if (cbet2 == 0) 531 | /* I.e., salp0 = 0, csig2 = 0. Break the degeneracy in this case */ 532 | cbet2 = csig2 = tiny; 533 | /* tan(alp0) = cos(sig2)*tan(alp2) */ 534 | salp2 = l->salp0; calp2 = l->calp0 * csig2; /* No need to normalize */ 535 | 536 | if (outmask & GEOD_DISTANCE) 537 | s12 = (flags & GEOD_ARCMODE) ? 538 | l->b * ((1 + l->A1m1) * sig12 + AB1) : 539 | s12_a12; 540 | 541 | if (outmask & GEOD_LONGITUDE) { 542 | double E = copysign(1, l->salp0); /* east or west going? */ 543 | /* tan(omg2) = sin(alp0) * tan(sig2) */ 544 | somg2 = l->salp0 * ssig2; comg2 = csig2; /* No need to normalize */ 545 | /* omg12 = omg2 - omg1 */ 546 | omg12 = (flags & GEOD_LONG_UNROLL) 547 | ? E * (sig12 548 | - (atan2( ssig2, csig2) - atan2( l->ssig1, l->csig1)) 549 | + (atan2(E * somg2, comg2) - atan2(E * l->somg1, l->comg1))) 550 | : atan2(somg2 * l->comg1 - comg2 * l->somg1, 551 | comg2 * l->comg1 + somg2 * l->somg1); 552 | lam12 = omg12 + l->A3c * 553 | ( sig12 + (SinCosSeries(TRUE, ssig2, csig2, l->C3a, nC3-1) 554 | - l->B31)); 555 | lon12 = lam12 / degree; 556 | lon2 = (flags & GEOD_LONG_UNROLL) ? l->lon1 + lon12 : 557 | AngNormalize(AngNormalize(l->lon1) + AngNormalize(lon12)); 558 | } 559 | 560 | if (outmask & GEOD_LATITUDE) 561 | lat2 = atan2dx(sbet2, l->f1 * cbet2); 562 | 563 | if (outmask & GEOD_AZIMUTH) 564 | azi2 = atan2dx(salp2, calp2); 565 | 566 | if (outmask & (GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE)) { 567 | double 568 | B22 = SinCosSeries(TRUE, ssig2, csig2, l->C2a, nC2), 569 | AB2 = (1 + l->A2m1) * (B22 - l->B21), 570 | J12 = (l->A1m1 - l->A2m1) * sig12 + (AB1 - AB2); 571 | if (outmask & GEOD_REDUCEDLENGTH) 572 | /* Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure 573 | * accurate cancellation in the case of coincident points. */ 574 | m12 = l->b * ((dn2 * (l->csig1 * ssig2) - l->dn1 * (l->ssig1 * csig2)) 575 | - l->csig1 * csig2 * J12); 576 | if (outmask & GEOD_GEODESICSCALE) { 577 | double t = l->k2 * (ssig2 - l->ssig1) * (ssig2 + l->ssig1) / 578 | (l->dn1 + dn2); 579 | M12 = csig12 + (t * ssig2 - csig2 * J12) * l->ssig1 / l->dn1; 580 | M21 = csig12 - (t * l->ssig1 - l->csig1 * J12) * ssig2 / dn2; 581 | } 582 | } 583 | 584 | if (outmask & GEOD_AREA) { 585 | double 586 | B42 = SinCosSeries(FALSE, ssig2, csig2, l->C4a, nC4); 587 | double salp12, calp12; 588 | if (l->calp0 == 0 || l->salp0 == 0) { 589 | /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ 590 | salp12 = salp2 * l->calp1 - calp2 * l->salp1; 591 | calp12 = calp2 * l->calp1 + salp2 * l->salp1; 592 | } else { 593 | /* tan(alp) = tan(alp0) * sec(sig) 594 | * tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1) 595 | * = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2) 596 | * If csig12 > 0, write 597 | * csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1) 598 | * else 599 | * csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1 600 | * No need to normalize */ 601 | salp12 = l->calp0 * l->salp0 * 602 | (csig12 <= 0 ? l->csig1 * (1 - csig12) + ssig12 * l->ssig1 : 603 | ssig12 * (l->csig1 * ssig12 / (1 + csig12) + l->ssig1)); 604 | calp12 = sq(l->salp0) + sq(l->calp0) * l->csig1 * csig2; 605 | } 606 | S12 = l->c2 * atan2(salp12, calp12) + l->A4 * (B42 - l->B41); 607 | } 608 | 609 | /* In the pattern 610 | * 611 | * if ((outmask & GEOD_XX) && pYY) 612 | * *pYY = YY; 613 | * 614 | * the second check "&& pYY" is redundant. It's there to make the CLang 615 | * static analyzer happy. 616 | */ 617 | if ((outmask & GEOD_LATITUDE) && plat2) 618 | *plat2 = lat2; 619 | if ((outmask & GEOD_LONGITUDE) && plon2) 620 | *plon2 = lon2; 621 | if ((outmask & GEOD_AZIMUTH) && pazi2) 622 | *pazi2 = azi2; 623 | if ((outmask & GEOD_DISTANCE) && ps12) 624 | *ps12 = s12; 625 | if ((outmask & GEOD_REDUCEDLENGTH) && pm12) 626 | *pm12 = m12; 627 | if (outmask & GEOD_GEODESICSCALE) { 628 | if (pM12) *pM12 = M12; 629 | if (pM21) *pM21 = M21; 630 | } 631 | if ((outmask & GEOD_AREA) && pS12) 632 | *pS12 = S12; 633 | 634 | return (flags & GEOD_ARCMODE) ? s12_a12 : sig12 / degree; 635 | } 636 | 637 | void geod_setdistance(struct geod_geodesicline* l, double s13) { 638 | l->s13 = s13; 639 | l->a13 = geod_genposition(l, GEOD_NOFLAGS, l->s13, nullptr, nullptr, nullptr, 640 | nullptr, nullptr, nullptr, nullptr, nullptr); 641 | } 642 | 643 | static void geod_setarc(struct geod_geodesicline* l, double a13) { 644 | l->a13 = a13; l->s13 = NaN; 645 | geod_genposition(l, GEOD_ARCMODE, l->a13, nullptr, nullptr, nullptr, &l->s13, 646 | nullptr, nullptr, nullptr, nullptr); 647 | } 648 | 649 | void geod_gensetdistance(struct geod_geodesicline* l, 650 | unsigned flags, double s13_a13) { 651 | (flags & GEOD_ARCMODE) ? 652 | geod_setarc(l, s13_a13) : 653 | geod_setdistance(l, s13_a13); 654 | } 655 | 656 | void geod_position(const struct geod_geodesicline* l, double s12, 657 | double* plat2, double* plon2, double* pazi2) { 658 | geod_genposition(l, FALSE, s12, plat2, plon2, pazi2, 659 | nullptr, nullptr, nullptr, nullptr, nullptr); 660 | } 661 | 662 | double geod_gendirect(const struct geod_geodesic* g, 663 | double lat1, double lon1, double azi1, 664 | unsigned flags, double s12_a12, 665 | double* plat2, double* plon2, double* pazi2, 666 | double* ps12, double* pm12, double* pM12, double* pM21, 667 | double* pS12) { 668 | struct geod_geodesicline l; 669 | unsigned outmask = 670 | (plat2 ? GEOD_LATITUDE : GEOD_NONE) | 671 | (plon2 ? GEOD_LONGITUDE : GEOD_NONE) | 672 | (pazi2 ? GEOD_AZIMUTH : GEOD_NONE) | 673 | (ps12 ? GEOD_DISTANCE : GEOD_NONE) | 674 | (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | 675 | (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | 676 | (pS12 ? GEOD_AREA : GEOD_NONE); 677 | 678 | geod_lineinit(&l, g, lat1, lon1, azi1, 679 | /* Automatically supply GEOD_DISTANCE_IN if necessary */ 680 | outmask | 681 | ((flags & GEOD_ARCMODE) ? GEOD_NONE : GEOD_DISTANCE_IN)); 682 | return geod_genposition(&l, flags, s12_a12, 683 | plat2, plon2, pazi2, ps12, pm12, pM12, pM21, pS12); 684 | } 685 | 686 | void geod_direct(const struct geod_geodesic* g, 687 | double lat1, double lon1, double azi1, 688 | double s12, 689 | double* plat2, double* plon2, double* pazi2) { 690 | geod_gendirect(g, lat1, lon1, azi1, GEOD_NOFLAGS, s12, plat2, plon2, pazi2, 691 | nullptr, nullptr, nullptr, nullptr, nullptr); 692 | } 693 | 694 | static double geod_geninverse_int(const struct geod_geodesic* g, 695 | double lat1, double lon1, 696 | double lat2, double lon2, 697 | double* ps12, 698 | double* psalp1, double* pcalp1, 699 | double* psalp2, double* pcalp2, 700 | double* pm12, double* pM12, double* pM21, 701 | double* pS12) { 702 | double s12 = 0, m12 = 0, M12 = 0, M21 = 0, S12 = 0; 703 | double lon12, lon12s; 704 | int latsign, lonsign, swapp; 705 | double sbet1, cbet1, sbet2, cbet2, s12x = 0, m12x = 0; 706 | double dn1, dn2, lam12, slam12, clam12; 707 | double a12 = 0, sig12, calp1 = 0, salp1 = 0, calp2 = 0, salp2 = 0; 708 | double Ca[nC]; 709 | boolx meridian; 710 | /* somg12 == 2 marks that it needs to be calculated */ 711 | double omg12 = 0, somg12 = 2, comg12 = 0; 712 | 713 | unsigned outmask = 714 | (ps12 ? GEOD_DISTANCE : GEOD_NONE) | 715 | (pm12 ? GEOD_REDUCEDLENGTH : GEOD_NONE) | 716 | (pM12 || pM21 ? GEOD_GEODESICSCALE : GEOD_NONE) | 717 | (pS12 ? GEOD_AREA : GEOD_NONE); 718 | 719 | outmask &= OUT_ALL; 720 | /* Compute longitude difference (AngDiff does this carefully). Result is 721 | * in [-180, 180] but -180 is only for west-going geodesics. 180 is for 722 | * east-going and meridional geodesics. */ 723 | lon12 = AngDiff(lon1, lon2, &lon12s); 724 | /* Make longitude difference positive. */ 725 | lonsign = signbit(lon12) ? -1 : 1; 726 | lon12 *= lonsign; lon12s *= lonsign; 727 | lam12 = lon12 * degree; 728 | /* Calculate sincos of lon12 + error (this applies AngRound internally). */ 729 | sincosde(lon12, lon12s, &slam12, &clam12); 730 | lon12s = (hd - lon12) - lon12s; /* the supplementary longitude difference */ 731 | 732 | /* If really close to the equator, treat as on equator. */ 733 | lat1 = AngRound(LatFix(lat1)); 734 | lat2 = AngRound(LatFix(lat2)); 735 | /* Swap points so that point with higher (abs) latitude is point 1 736 | * If one latitude is a nan, then it becomes lat1. */ 737 | swapp = fabs(lat1) < fabs(lat2) || lat2 != lat2 ? -1 : 1; 738 | if (swapp < 0) { 739 | lonsign *= -1; 740 | swapx(&lat1, &lat2); 741 | } 742 | /* Make lat1 <= -0 */ 743 | latsign = signbit(lat1) ? 1 : -1; 744 | lat1 *= latsign; 745 | lat2 *= latsign; 746 | /* Now we have 747 | * 748 | * 0 <= lon12 <= 180 749 | * -90 <= lat1 <= -0 750 | * lat1 <= lat2 <= -lat1 751 | * 752 | * longsign, swapp, latsign register the transformation to bring the 753 | * coordinates to this canonical form. In all cases, 1 means no change was 754 | * made. We make these transformations so that there are few cases to 755 | * check, e.g., on verifying quadrants in atan2. In addition, this 756 | * enforces some symmetries in the results returned. */ 757 | 758 | sincosdx(lat1, &sbet1, &cbet1); sbet1 *= g->f1; 759 | /* Ensure cbet1 = +epsilon at poles */ 760 | norm2(&sbet1, &cbet1); cbet1 = fmax(tiny, cbet1); 761 | 762 | sincosdx(lat2, &sbet2, &cbet2); sbet2 *= g->f1; 763 | /* Ensure cbet2 = +epsilon at poles */ 764 | norm2(&sbet2, &cbet2); cbet2 = fmax(tiny, cbet2); 765 | 766 | /* If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the 767 | * |bet1| - |bet2|. Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is 768 | * a better measure. This logic is used in assigning calp2 in Lambda12. 769 | * Sometimes these quantities vanish and in that case we force bet2 = +/- 770 | * bet1 exactly. An example where is is necessary is the inverse problem 771 | * 48.522876735459 0 -48.52287673545898293 179.599720456223079643 772 | * which failed with Visual Studio 10 (Release and Debug) */ 773 | 774 | if (cbet1 < -sbet1) { 775 | if (cbet2 == cbet1) 776 | sbet2 = copysign(sbet1, sbet2); 777 | } else { 778 | if (fabs(sbet2) == -sbet1) 779 | cbet2 = cbet1; 780 | } 781 | 782 | dn1 = sqrt(1 + g->ep2 * sq(sbet1)); 783 | dn2 = sqrt(1 + g->ep2 * sq(sbet2)); 784 | 785 | meridian = lat1 == -qd || slam12 == 0; 786 | 787 | if (meridian) { 788 | 789 | /* Endpoints are on a single full meridian, so the geodesic might lie on 790 | * a meridian. */ 791 | 792 | double ssig1, csig1, ssig2, csig2; 793 | calp1 = clam12; salp1 = slam12; /* Head to the target longitude */ 794 | calp2 = 1; salp2 = 0; /* At the target we're heading north */ 795 | 796 | /* tan(bet) = tan(sig) * cos(alp) */ 797 | ssig1 = sbet1; csig1 = calp1 * cbet1; 798 | ssig2 = sbet2; csig2 = calp2 * cbet2; 799 | 800 | /* sig12 = sig2 - sig1 */ 801 | sig12 = atan2(fmax(0.0, csig1 * ssig2 - ssig1 * csig2) + 0, 802 | csig1 * csig2 + ssig1 * ssig2); 803 | Lengths(g, g->n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, 804 | cbet1, cbet2, &s12x, &m12x, nullptr, 805 | (outmask & GEOD_GEODESICSCALE) ? &M12 : nullptr, 806 | (outmask & GEOD_GEODESICSCALE) ? &M21 : nullptr, 807 | Ca); 808 | /* Add the check for sig12 since zero length geodesics might yield m12 < 809 | * 0. Test case was 810 | * 811 | * echo 20.001 0 20.001 0 | GeodSolve -i 812 | * 813 | * In fact, we will have sig12 > pi/2 for meridional geodesic which is 814 | * not a shortest path. */ 815 | if (sig12 < 1 || m12x >= 0) { 816 | /* Need at least 2, to handle 90 0 90 180 */ 817 | if (sig12 < 3 * tiny || 818 | /* Prevent negative s12 or m12 for short lines */ 819 | (sig12 < tol0 && (s12x < 0 || m12x < 0))) 820 | sig12 = m12x = s12x = 0; 821 | m12x *= g->b; 822 | s12x *= g->b; 823 | a12 = sig12 / degree; 824 | } else 825 | /* m12 < 0, i.e., prolate and too close to anti-podal */ 826 | meridian = FALSE; 827 | } 828 | 829 | if (!meridian && 830 | sbet1 == 0 && /* and sbet2 == 0 */ 831 | /* Mimic the way Lambda12 works with calp1 = 0 */ 832 | (g->f <= 0 || lon12s >= g->f * hd)) { 833 | 834 | /* Geodesic runs along equator */ 835 | calp1 = calp2 = 0; salp1 = salp2 = 1; 836 | s12x = g->a * lam12; 837 | sig12 = omg12 = lam12 / g->f1; 838 | m12x = g->b * sin(sig12); 839 | if (outmask & GEOD_GEODESICSCALE) 840 | M12 = M21 = cos(sig12); 841 | a12 = lon12 / g->f1; 842 | 843 | } else if (!meridian) { 844 | 845 | /* Now point1 and point2 belong within a hemisphere bounded by a 846 | * meridian and geodesic is neither meridional or equatorial. */ 847 | 848 | /* Figure a starting point for Newton's method */ 849 | double dnm = 0; 850 | sig12 = InverseStart(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, 851 | lam12, slam12, clam12, 852 | &salp1, &calp1, &salp2, &calp2, &dnm, 853 | Ca); 854 | 855 | if (sig12 >= 0) { 856 | /* Short lines (InverseStart sets salp2, calp2, dnm) */ 857 | s12x = sig12 * g->b * dnm; 858 | m12x = sq(dnm) * g->b * sin(sig12 / dnm); 859 | if (outmask & GEOD_GEODESICSCALE) 860 | M12 = M21 = cos(sig12 / dnm); 861 | a12 = sig12 / degree; 862 | omg12 = lam12 / (g->f1 * dnm); 863 | } else { 864 | 865 | /* Newton's method. This is a straightforward solution of f(alp1) = 866 | * lambda12(alp1) - lam12 = 0 with one wrinkle. f(alp) has exactly one 867 | * root in the interval (0, pi) and its derivative is positive at the 868 | * root. Thus f(alp) is positive for alp > alp1 and negative for alp < 869 | * alp1. During the course of the iteration, a range (alp1a, alp1b) is 870 | * maintained which brackets the root and with each evaluation of 871 | * f(alp) the range is shrunk, if possible. Newton's method is 872 | * restarted whenever the derivative of f is negative (because the new 873 | * value of alp1 is then further from the solution) or if the new 874 | * estimate of alp1 lies outside (0,pi); in this case, the new starting 875 | * guess is taken to be (alp1a + alp1b) / 2. */ 876 | double ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, domg12 = 0; 877 | unsigned numit = 0; 878 | /* Bracketing range */ 879 | double salp1a = tiny, calp1a = 1, salp1b = tiny, calp1b = -1; 880 | boolx tripn = FALSE; 881 | boolx tripb = FALSE; 882 | for (;; ++numit) { 883 | /* the WGS84 test set: mean = 1.47, sd = 1.25, max = 16 884 | * WGS84 and random input: mean = 2.85, sd = 0.60 */ 885 | double dv = 0, 886 | v = Lambda12(g, sbet1, cbet1, dn1, sbet2, cbet2, dn2, salp1, calp1, 887 | slam12, clam12, 888 | &salp2, &calp2, &sig12, &ssig1, &csig1, &ssig2, &csig2, 889 | &eps, &domg12, numit < maxit1, &dv, Ca); 890 | if (tripb || 891 | /* Reversed test to allow escape with NaNs */ 892 | !(fabs(v) >= (tripn ? 8 : 1) * tol0) || 893 | /* Enough bisections to get accurate result */ 894 | numit == maxit2) 895 | break; 896 | /* Update bracketing values */ 897 | if (v > 0 && (numit > maxit1 || calp1/salp1 > calp1b/salp1b)) 898 | { salp1b = salp1; calp1b = calp1; } 899 | else if (v < 0 && (numit > maxit1 || calp1/salp1 < calp1a/salp1a)) 900 | { salp1a = salp1; calp1a = calp1; } 901 | if (numit < maxit1 && dv > 0) { 902 | double 903 | dalp1 = -v/dv; 904 | if (fabs(dalp1) < pi) { 905 | double 906 | sdalp1 = sin(dalp1), cdalp1 = cos(dalp1), 907 | nsalp1 = salp1 * cdalp1 + calp1 * sdalp1; 908 | if (nsalp1 > 0) { 909 | calp1 = calp1 * cdalp1 - salp1 * sdalp1; 910 | salp1 = nsalp1; 911 | norm2(&salp1, &calp1); 912 | /* In some regimes we don't get quadratic convergence because 913 | * slope -> 0. So use convergence conditions based on epsilon 914 | * instead of sqrt(epsilon). */ 915 | tripn = fabs(v) <= 16 * tol0; 916 | continue; 917 | } 918 | } 919 | } 920 | /* Either dv was not positive or updated value was outside legal 921 | * range. Use the midpoint of the bracket as the next estimate. 922 | * This mechanism is not needed for the WGS84 ellipsoid, but it does 923 | * catch problems with more eccentric ellipsoids. Its efficacy is 924 | * such for the WGS84 test set with the starting guess set to alp1 = 925 | * 90deg: 926 | * the WGS84 test set: mean = 5.21, sd = 3.93, max = 24 927 | * WGS84 and random input: mean = 4.74, sd = 0.99 */ 928 | salp1 = (salp1a + salp1b)/2; 929 | calp1 = (calp1a + calp1b)/2; 930 | norm2(&salp1, &calp1); 931 | tripn = FALSE; 932 | tripb = (fabs(salp1a - salp1) + (calp1a - calp1) < tolb || 933 | fabs(salp1 - salp1b) + (calp1 - calp1b) < tolb); 934 | } 935 | Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, 936 | cbet1, cbet2, &s12x, &m12x, nullptr, 937 | (outmask & GEOD_GEODESICSCALE) ? &M12 : nullptr, 938 | (outmask & GEOD_GEODESICSCALE) ? &M21 : nullptr, Ca); 939 | m12x *= g->b; 940 | s12x *= g->b; 941 | a12 = sig12 / degree; 942 | if (outmask & GEOD_AREA) { 943 | /* omg12 = lam12 - domg12 */ 944 | double sdomg12 = sin(domg12), cdomg12 = cos(domg12); 945 | somg12 = slam12 * cdomg12 - clam12 * sdomg12; 946 | comg12 = clam12 * cdomg12 + slam12 * sdomg12; 947 | } 948 | } 949 | } 950 | 951 | if (outmask & GEOD_DISTANCE) 952 | s12 = 0 + s12x; /* Convert -0 to 0 */ 953 | 954 | if (outmask & GEOD_REDUCEDLENGTH) 955 | m12 = 0 + m12x; /* Convert -0 to 0 */ 956 | 957 | if (outmask & GEOD_AREA) { 958 | double 959 | /* From Lambda12: sin(alp1) * cos(bet1) = sin(alp0) */ 960 | salp0 = salp1 * cbet1, 961 | calp0 = hypot(calp1, salp1 * sbet1); /* calp0 > 0 */ 962 | double alp12; 963 | if (calp0 != 0 && salp0 != 0) { 964 | double 965 | /* From Lambda12: tan(bet) = tan(sig) * cos(alp) */ 966 | ssig1 = sbet1, csig1 = calp1 * cbet1, 967 | ssig2 = sbet2, csig2 = calp2 * cbet2, 968 | k2 = sq(calp0) * g->ep2, 969 | eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2), 970 | /* Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0). */ 971 | A4 = sq(g->a) * calp0 * salp0 * g->e2; 972 | double B41, B42; 973 | norm2(&ssig1, &csig1); 974 | norm2(&ssig2, &csig2); 975 | C4f(g, eps, Ca); 976 | B41 = SinCosSeries(FALSE, ssig1, csig1, Ca, nC4); 977 | B42 = SinCosSeries(FALSE, ssig2, csig2, Ca, nC4); 978 | S12 = A4 * (B42 - B41); 979 | } else 980 | /* Avoid problems with indeterminate sig1, sig2 on equator */ 981 | S12 = 0; 982 | 983 | if (!meridian && somg12 == 2) { 984 | somg12 = sin(omg12); comg12 = cos(omg12); 985 | } 986 | 987 | if (!meridian && 988 | /* omg12 < 3/4 * pi */ 989 | comg12 > -0.7071 && /* Long difference not too big */ 990 | sbet2 - sbet1 < 1.75) { /* Lat difference not too big */ 991 | /* Use tan(Gamma/2) = tan(omg12/2) 992 | * * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2)) 993 | * with tan(x/2) = sin(x)/(1+cos(x)) */ 994 | double 995 | domg12 = 1 + comg12, dbet1 = 1 + cbet1, dbet2 = 1 + cbet2; 996 | alp12 = 2 * atan2( somg12 * ( sbet1 * dbet2 + sbet2 * dbet1 ), 997 | domg12 * ( sbet1 * sbet2 + dbet1 * dbet2 ) ); 998 | } else { 999 | /* alp12 = alp2 - alp1, used in atan2 so no need to normalize */ 1000 | double 1001 | salp12 = salp2 * calp1 - calp2 * salp1, 1002 | calp12 = calp2 * calp1 + salp2 * salp1; 1003 | /* The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz 1004 | * salp12 = -0 and alp12 = -180. However this depends on the sign 1005 | * being attached to 0 correctly. The following ensures the correct 1006 | * behavior. */ 1007 | if (salp12 == 0 && calp12 < 0) { 1008 | salp12 = tiny * calp1; 1009 | calp12 = -1; 1010 | } 1011 | alp12 = atan2(salp12, calp12); 1012 | } 1013 | S12 += g->c2 * alp12; 1014 | S12 *= swapp * lonsign * latsign; 1015 | /* Convert -0 to 0 */ 1016 | S12 += 0; 1017 | } 1018 | 1019 | /* Convert calp, salp to azimuth accounting for lonsign, swapp, latsign. */ 1020 | if (swapp < 0) { 1021 | swapx(&salp1, &salp2); 1022 | swapx(&calp1, &calp2); 1023 | if (outmask & GEOD_GEODESICSCALE) 1024 | swapx(&M12, &M21); 1025 | } 1026 | 1027 | salp1 *= swapp * lonsign; calp1 *= swapp * latsign; 1028 | salp2 *= swapp * lonsign; calp2 *= swapp * latsign; 1029 | 1030 | if (psalp1) *psalp1 = salp1; 1031 | if (pcalp1) *pcalp1 = calp1; 1032 | if (psalp2) *psalp2 = salp2; 1033 | if (pcalp2) *pcalp2 = calp2; 1034 | 1035 | if (outmask & GEOD_DISTANCE) 1036 | *ps12 = s12; 1037 | if (outmask & GEOD_REDUCEDLENGTH) 1038 | *pm12 = m12; 1039 | if (outmask & GEOD_GEODESICSCALE) { 1040 | if (pM12) *pM12 = M12; 1041 | if (pM21) *pM21 = M21; 1042 | } 1043 | if (outmask & GEOD_AREA) 1044 | *pS12 = S12; 1045 | 1046 | /* Returned value in [0, 180] */ 1047 | return a12; 1048 | } 1049 | 1050 | double geod_geninverse(const struct geod_geodesic* g, 1051 | double lat1, double lon1, double lat2, double lon2, 1052 | double* ps12, double* pazi1, double* pazi2, 1053 | double* pm12, double* pM12, double* pM21, 1054 | double* pS12) { 1055 | double salp1, calp1, salp2, calp2, 1056 | a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, ps12, 1057 | &salp1, &calp1, &salp2, &calp2, 1058 | pm12, pM12, pM21, pS12); 1059 | if (pazi1) *pazi1 = atan2dx(salp1, calp1); 1060 | if (pazi2) *pazi2 = atan2dx(salp2, calp2); 1061 | return a12; 1062 | } 1063 | 1064 | void geod_inverseline(struct geod_geodesicline* l, 1065 | const struct geod_geodesic* g, 1066 | double lat1, double lon1, double lat2, double lon2, 1067 | unsigned caps) { 1068 | double salp1, calp1, 1069 | a12 = geod_geninverse_int(g, lat1, lon1, lat2, lon2, nullptr, 1070 | &salp1, &calp1, nullptr, nullptr, 1071 | nullptr, nullptr, nullptr, nullptr), 1072 | azi1 = atan2dx(salp1, calp1); 1073 | caps = caps ? caps : GEOD_DISTANCE_IN | GEOD_LONGITUDE; 1074 | /* Ensure that a12 can be converted to a distance */ 1075 | if (caps & (OUT_ALL & GEOD_DISTANCE_IN)) caps |= GEOD_DISTANCE; 1076 | geod_lineinit_int(l, g, lat1, lon1, azi1, salp1, calp1, caps); 1077 | geod_setarc(l, a12); 1078 | } 1079 | 1080 | void geod_inverse(const struct geod_geodesic* g, 1081 | double lat1, double lon1, double lat2, double lon2, 1082 | double* ps12, double* pazi1, double* pazi2) { 1083 | geod_geninverse(g, lat1, lon1, lat2, lon2, ps12, pazi1, pazi2, 1084 | nullptr, nullptr, nullptr, nullptr); 1085 | } 1086 | 1087 | double SinCosSeries(boolx sinp, double sinx, double cosx, 1088 | const double c[], int n) { 1089 | /* Evaluate 1090 | * y = sinp ? sum(c[i] * sin( 2*i * x), i, 1, n) : 1091 | * sum(c[i] * cos((2*i+1) * x), i, 0, n-1) 1092 | * using Clenshaw summation. N.B. c[0] is unused for sin series 1093 | * Approx operation count = (n + 5) mult and (2 * n + 2) add */ 1094 | double ar, y0, y1; 1095 | c += (n + sinp); /* Point to one beyond last element */ 1096 | ar = 2 * (cosx - sinx) * (cosx + sinx); /* 2 * cos(2 * x) */ 1097 | y0 = (n & 1) ? *--c : 0; y1 = 0; /* accumulators for sum */ 1098 | /* Now n is even */ 1099 | n /= 2; 1100 | while (n--) { 1101 | /* Unroll loop x 2, so accumulators return to their original role */ 1102 | y1 = ar * y0 - y1 + *--c; 1103 | y0 = ar * y1 - y0 + *--c; 1104 | } 1105 | return sinp 1106 | ? 2 * sinx * cosx * y0 /* sin(2 * x) * y0 */ 1107 | : cosx * (y0 - y1); /* cos(x) * (y0 - y1) */ 1108 | } 1109 | 1110 | void Lengths(const struct geod_geodesic* g, 1111 | double eps, double sig12, 1112 | double ssig1, double csig1, double dn1, 1113 | double ssig2, double csig2, double dn2, 1114 | double cbet1, double cbet2, 1115 | double* ps12b, double* pm12b, double* pm0, 1116 | double* pM12, double* pM21, 1117 | /* Scratch area of the right size */ 1118 | double Ca[]) { 1119 | double m0 = 0, J12 = 0, A1 = 0, A2 = 0; 1120 | double Cb[nC]; 1121 | 1122 | /* Return m12b = (reduced length)/b; also calculate s12b = distance/b, 1123 | * and m0 = coefficient of secular term in expression for reduced length. */ 1124 | boolx redlp = pm12b || pm0 || pM12 || pM21; 1125 | if (ps12b || redlp) { 1126 | A1 = A1m1f(eps); 1127 | C1f(eps, Ca); 1128 | if (redlp) { 1129 | A2 = A2m1f(eps); 1130 | C2f(eps, Cb); 1131 | m0 = A1 - A2; 1132 | A2 = 1 + A2; 1133 | } 1134 | A1 = 1 + A1; 1135 | } 1136 | if (ps12b) { 1137 | double B1 = SinCosSeries(TRUE, ssig2, csig2, Ca, nC1) - 1138 | SinCosSeries(TRUE, ssig1, csig1, Ca, nC1); 1139 | /* Missing a factor of b */ 1140 | *ps12b = A1 * (sig12 + B1); 1141 | if (redlp) { 1142 | double B2 = SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - 1143 | SinCosSeries(TRUE, ssig1, csig1, Cb, nC2); 1144 | J12 = m0 * sig12 + (A1 * B1 - A2 * B2); 1145 | } 1146 | } else if (redlp) { 1147 | /* Assume here that nC1 >= nC2 */ 1148 | int l; 1149 | for (l = 1; l <= nC2; ++l) 1150 | Cb[l] = A1 * Ca[l] - A2 * Cb[l]; 1151 | J12 = m0 * sig12 + (SinCosSeries(TRUE, ssig2, csig2, Cb, nC2) - 1152 | SinCosSeries(TRUE, ssig1, csig1, Cb, nC2)); 1153 | } 1154 | if (pm0) *pm0 = m0; 1155 | if (pm12b) 1156 | /* Missing a factor of b. 1157 | * Add parens around (csig1 * ssig2) and (ssig1 * csig2) to ensure 1158 | * accurate cancellation in the case of coincident points. */ 1159 | *pm12b = dn2 * (csig1 * ssig2) - dn1 * (ssig1 * csig2) - 1160 | csig1 * csig2 * J12; 1161 | if (pM12 || pM21) { 1162 | double csig12 = csig1 * csig2 + ssig1 * ssig2; 1163 | double t = g->ep2 * (cbet1 - cbet2) * (cbet1 + cbet2) / (dn1 + dn2); 1164 | if (pM12) 1165 | *pM12 = csig12 + (t * ssig2 - csig2 * J12) * ssig1 / dn1; 1166 | if (pM21) 1167 | *pM21 = csig12 - (t * ssig1 - csig1 * J12) * ssig2 / dn2; 1168 | } 1169 | } 1170 | 1171 | double Astroid(double x, double y) { 1172 | /* Solve k^4+2*k^3-(x^2+y^2-1)*k^2-2*y^2*k-y^2 = 0 for positive root k. 1173 | * This solution is adapted from Geocentric::Reverse. */ 1174 | double k; 1175 | double 1176 | p = sq(x), 1177 | q = sq(y), 1178 | r = (p + q - 1) / 6; 1179 | if ( !(q == 0 && r <= 0) ) { 1180 | double 1181 | /* Avoid possible division by zero when r = 0 by multiplying equations 1182 | * for s and t by r^3 and r, resp. */ 1183 | S = p * q / 4, /* S = r^3 * s */ 1184 | r2 = sq(r), 1185 | r3 = r * r2, 1186 | /* The discriminant of the quadratic equation for T3. This is zero on 1187 | * the evolute curve p^(1/3)+q^(1/3) = 1 */ 1188 | disc = S * (S + 2 * r3); 1189 | double u = r; 1190 | double v, uv, w; 1191 | if (disc >= 0) { 1192 | double T3 = S + r3, T; 1193 | /* Pick the sign on the sqrt to maximize abs(T3). This minimizes loss 1194 | * of precision due to cancellation. The result is unchanged because 1195 | * of the way the T is used in definition of u. */ 1196 | T3 += T3 < 0 ? -sqrt(disc) : sqrt(disc); /* T3 = (r * t)^3 */ 1197 | /* N.B. cbrt always returns the double root. cbrt(-8) = -2. */ 1198 | T = cbrt(T3); /* T = r * t */ 1199 | /* T can be zero; but then r2 / T -> 0. */ 1200 | u += T + (T != 0 ? r2 / T : 0); 1201 | } else { 1202 | /* T is complex, but the way u is defined the result is double. */ 1203 | double ang = atan2(sqrt(-disc), -(S + r3)); 1204 | /* There are three possible cube roots. We choose the root which 1205 | * avoids cancellation. Note that disc < 0 implies that r < 0. */ 1206 | u += 2 * r * cos(ang / 3); 1207 | } 1208 | v = sqrt(sq(u) + q); /* guaranteed positive */ 1209 | /* Avoid loss of accuracy when u < 0. */ 1210 | uv = u < 0 ? q / (v - u) : u + v; /* u+v, guaranteed positive */ 1211 | w = (uv - q) / (2 * v); /* positive? */ 1212 | /* Rearrange expression for k to avoid loss of accuracy due to 1213 | * subtraction. Division by 0 not possible because uv > 0, w >= 0. */ 1214 | k = uv / (sqrt(uv + sq(w)) + w); /* guaranteed positive */ 1215 | } else { /* q == 0 && r <= 0 */ 1216 | /* y = 0 with |x| <= 1. Handle this case directly. 1217 | * for y small, positive root is k = abs(y)/sqrt(1-x^2) */ 1218 | k = 0; 1219 | } 1220 | return k; 1221 | } 1222 | 1223 | double InverseStart(const struct geod_geodesic* g, 1224 | double sbet1, double cbet1, double dn1, 1225 | double sbet2, double cbet2, double dn2, 1226 | double lam12, double slam12, double clam12, 1227 | double* psalp1, double* pcalp1, 1228 | /* Only updated if return val >= 0 */ 1229 | double* psalp2, double* pcalp2, 1230 | /* Only updated for short lines */ 1231 | double* pdnm, 1232 | /* Scratch area of the right size */ 1233 | double Ca[]) { 1234 | double salp1 = 0, calp1 = 0, salp2 = 0, calp2 = 0, dnm = 0; 1235 | 1236 | /* Return a starting point for Newton's method in salp1 and calp1 (function 1237 | * value is -1). If Newton's method doesn't need to be used, return also 1238 | * salp2 and calp2 and function value is sig12. */ 1239 | double 1240 | sig12 = -1, /* Return value */ 1241 | /* bet12 = bet2 - bet1 in [0, pi); bet12a = bet2 + bet1 in (-pi, 0] */ 1242 | sbet12 = sbet2 * cbet1 - cbet2 * sbet1, 1243 | cbet12 = cbet2 * cbet1 + sbet2 * sbet1; 1244 | double sbet12a; 1245 | boolx shortline = cbet12 >= 0 && sbet12 < 0.5 && cbet2 * lam12 < 0.5; 1246 | double somg12, comg12, ssig12, csig12; 1247 | sbet12a = sbet2 * cbet1 + cbet2 * sbet1; 1248 | if (shortline) { 1249 | double sbetm2 = sq(sbet1 + sbet2), omg12; 1250 | /* sin((bet1+bet2)/2)^2 1251 | * = (sbet1 + sbet2)^2 / ((sbet1 + sbet2)^2 + (cbet1 + cbet2)^2) */ 1252 | sbetm2 /= sbetm2 + sq(cbet1 + cbet2); 1253 | dnm = sqrt(1 + g->ep2 * sbetm2); 1254 | omg12 = lam12 / (g->f1 * dnm); 1255 | somg12 = sin(omg12); comg12 = cos(omg12); 1256 | } else { 1257 | somg12 = slam12; comg12 = clam12; 1258 | } 1259 | 1260 | salp1 = cbet2 * somg12; 1261 | calp1 = comg12 >= 0 ? 1262 | sbet12 + cbet2 * sbet1 * sq(somg12) / (1 + comg12) : 1263 | sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); 1264 | 1265 | ssig12 = hypot(salp1, calp1); 1266 | csig12 = sbet1 * sbet2 + cbet1 * cbet2 * comg12; 1267 | 1268 | if (shortline && ssig12 < g->etol2) { 1269 | /* really short lines */ 1270 | salp2 = cbet1 * somg12; 1271 | calp2 = sbet12 - cbet1 * sbet2 * 1272 | (comg12 >= 0 ? sq(somg12) / (1 + comg12) : 1 - comg12); 1273 | norm2(&salp2, &calp2); 1274 | /* Set return value */ 1275 | sig12 = atan2(ssig12, csig12); 1276 | } else if (fabs(g->n) > 0.1 || /* No astroid calc if too eccentric */ 1277 | csig12 >= 0 || 1278 | ssig12 >= 6 * fabs(g->n) * pi * sq(cbet1)) { 1279 | /* Nothing to do, zeroth order spherical approximation is OK */ 1280 | } else { 1281 | /* Scale lam12 and bet2 to x, y coordinate system where antipodal point 1282 | * is at origin and singular point is at y = 0, x = -1. */ 1283 | double x, y, lamscale, betscale; 1284 | double lam12x = atan2(-slam12, -clam12); /* lam12 - pi */ 1285 | if (g->f >= 0) { /* In fact f == 0 does not get here */ 1286 | /* x = dlong, y = dlat */ 1287 | { 1288 | double 1289 | k2 = sq(sbet1) * g->ep2, 1290 | eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); 1291 | lamscale = g->f * cbet1 * A3f(g, eps) * pi; 1292 | } 1293 | betscale = lamscale * cbet1; 1294 | 1295 | x = lam12x / lamscale; 1296 | y = sbet12a / betscale; 1297 | } else { /* f < 0 */ 1298 | /* x = dlat, y = dlong */ 1299 | double 1300 | cbet12a = cbet2 * cbet1 - sbet2 * sbet1, 1301 | bet12a = atan2(sbet12a, cbet12a); 1302 | double m12b, m0; 1303 | /* In the case of lon12 = 180, this repeats a calculation made in 1304 | * Inverse. */ 1305 | Lengths(g, g->n, pi + bet12a, 1306 | sbet1, -cbet1, dn1, sbet2, cbet2, dn2, 1307 | cbet1, cbet2, nullptr, &m12b, &m0, nullptr, nullptr, Ca); 1308 | x = -1 + m12b / (cbet1 * cbet2 * m0 * pi); 1309 | betscale = x < -0.01 ? sbet12a / x : 1310 | -g->f * sq(cbet1) * pi; 1311 | lamscale = betscale / cbet1; 1312 | y = lam12x / lamscale; 1313 | } 1314 | 1315 | if (y > -tol1 && x > -1 - xthresh) { 1316 | /* strip near cut */ 1317 | if (g->f >= 0) { 1318 | salp1 = fmin(1.0, -x); calp1 = - sqrt(1 - sq(salp1)); 1319 | } else { 1320 | calp1 = fmax(x > -tol1 ? 0.0 : -1.0, x); 1321 | salp1 = sqrt(1 - sq(calp1)); 1322 | } 1323 | } else { 1324 | /* Estimate alp1, by solving the astroid problem. 1325 | * 1326 | * Could estimate alpha1 = theta + pi/2, directly, i.e., 1327 | * calp1 = y/k; salp1 = -x/(1+k); for f >= 0 1328 | * calp1 = x/(1+k); salp1 = -y/k; for f < 0 (need to check) 1329 | * 1330 | * However, it's better to estimate omg12 from astroid and use 1331 | * spherical formula to compute alp1. This reduces the mean number of 1332 | * Newton iterations for astroid cases from 2.24 (min 0, max 6) to 2.12 1333 | * (min 0 max 5). The changes in the number of iterations are as 1334 | * follows: 1335 | * 1336 | * change percent 1337 | * 1 5 1338 | * 0 78 1339 | * -1 16 1340 | * -2 0.6 1341 | * -3 0.04 1342 | * -4 0.002 1343 | * 1344 | * The histogram of iterations is (m = number of iterations estimating 1345 | * alp1 directly, n = number of iterations estimating via omg12, total 1346 | * number of trials = 148605): 1347 | * 1348 | * iter m n 1349 | * 0 148 186 1350 | * 1 13046 13845 1351 | * 2 93315 102225 1352 | * 3 36189 32341 1353 | * 4 5396 7 1354 | * 5 455 1 1355 | * 6 56 0 1356 | * 1357 | * Because omg12 is near pi, estimate work with omg12a = pi - omg12 */ 1358 | double k = Astroid(x, y); 1359 | double 1360 | omg12a = lamscale * ( g->f >= 0 ? -x * k/(1 + k) : -y * (1 + k)/k ); 1361 | somg12 = sin(omg12a); comg12 = -cos(omg12a); 1362 | /* Update spherical estimate of alp1 using omg12 instead of lam12 */ 1363 | salp1 = cbet2 * somg12; 1364 | calp1 = sbet12a - cbet2 * sbet1 * sq(somg12) / (1 - comg12); 1365 | } 1366 | } 1367 | /* Sanity check on starting guess. Backwards check allows NaN through. */ 1368 | if (!(salp1 <= 0)) 1369 | norm2(&salp1, &calp1); 1370 | else { 1371 | salp1 = 1; calp1 = 0; 1372 | } 1373 | 1374 | *psalp1 = salp1; 1375 | *pcalp1 = calp1; 1376 | if (shortline) 1377 | *pdnm = dnm; 1378 | if (sig12 >= 0) { 1379 | *psalp2 = salp2; 1380 | *pcalp2 = calp2; 1381 | } 1382 | return sig12; 1383 | } 1384 | 1385 | double Lambda12(const struct geod_geodesic* g, 1386 | double sbet1, double cbet1, double dn1, 1387 | double sbet2, double cbet2, double dn2, 1388 | double salp1, double calp1, 1389 | double slam120, double clam120, 1390 | double* psalp2, double* pcalp2, 1391 | double* psig12, 1392 | double* pssig1, double* pcsig1, 1393 | double* pssig2, double* pcsig2, 1394 | double* peps, 1395 | double* pdomg12, 1396 | boolx diffp, double* pdlam12, 1397 | /* Scratch area of the right size */ 1398 | double Ca[]) { 1399 | double salp2 = 0, calp2 = 0, sig12 = 0, 1400 | ssig1 = 0, csig1 = 0, ssig2 = 0, csig2 = 0, eps = 0, 1401 | domg12 = 0, dlam12 = 0; 1402 | double salp0, calp0; 1403 | double somg1, comg1, somg2, comg2, somg12, comg12, lam12; 1404 | double B312, eta, k2; 1405 | 1406 | if (sbet1 == 0 && calp1 == 0) 1407 | /* Break degeneracy of equatorial line. This case has already been 1408 | * handled. */ 1409 | calp1 = -tiny; 1410 | 1411 | /* sin(alp1) * cos(bet1) = sin(alp0) */ 1412 | salp0 = salp1 * cbet1; 1413 | calp0 = hypot(calp1, salp1 * sbet1); /* calp0 > 0 */ 1414 | 1415 | /* tan(bet1) = tan(sig1) * cos(alp1) 1416 | * tan(omg1) = sin(alp0) * tan(sig1) = tan(omg1)=tan(alp1)*sin(bet1) */ 1417 | ssig1 = sbet1; somg1 = salp0 * sbet1; 1418 | csig1 = comg1 = calp1 * cbet1; 1419 | norm2(&ssig1, &csig1); 1420 | /* norm2(&somg1, &comg1); -- don't need to normalize! */ 1421 | 1422 | /* Enforce symmetries in the case abs(bet2) = -bet1. Need to be careful 1423 | * about this case, since this can yield singularities in the Newton 1424 | * iteration. 1425 | * sin(alp2) * cos(bet2) = sin(alp0) */ 1426 | salp2 = cbet2 != cbet1 ? salp0 / cbet2 : salp1; 1427 | /* calp2 = sqrt(1 - sq(salp2)) 1428 | * = sqrt(sq(calp0) - sq(sbet2)) / cbet2 1429 | * and subst for calp0 and rearrange to give (choose positive sqrt 1430 | * to give alp2 in [0, pi/2]). */ 1431 | calp2 = cbet2 != cbet1 || fabs(sbet2) != -sbet1 ? 1432 | sqrt(sq(calp1 * cbet1) + 1433 | (cbet1 < -sbet1 ? 1434 | (cbet2 - cbet1) * (cbet1 + cbet2) : 1435 | (sbet1 - sbet2) * (sbet1 + sbet2))) / cbet2 : 1436 | fabs(calp1); 1437 | /* tan(bet2) = tan(sig2) * cos(alp2) 1438 | * tan(omg2) = sin(alp0) * tan(sig2). */ 1439 | ssig2 = sbet2; somg2 = salp0 * sbet2; 1440 | csig2 = comg2 = calp2 * cbet2; 1441 | norm2(&ssig2, &csig2); 1442 | /* norm2(&somg2, &comg2); -- don't need to normalize! */ 1443 | 1444 | /* sig12 = sig2 - sig1, limit to [0, pi] */ 1445 | sig12 = atan2(fmax(0.0, csig1 * ssig2 - ssig1 * csig2) + 0, 1446 | csig1 * csig2 + ssig1 * ssig2); 1447 | 1448 | /* omg12 = omg2 - omg1, limit to [0, pi] */ 1449 | somg12 = fmax(0.0, comg1 * somg2 - somg1 * comg2) + 0; 1450 | comg12 = comg1 * comg2 + somg1 * somg2; 1451 | /* eta = omg12 - lam120 */ 1452 | eta = atan2(somg12 * clam120 - comg12 * slam120, 1453 | comg12 * clam120 + somg12 * slam120); 1454 | k2 = sq(calp0) * g->ep2; 1455 | eps = k2 / (2 * (1 + sqrt(1 + k2)) + k2); 1456 | C3f(g, eps, Ca); 1457 | B312 = (SinCosSeries(TRUE, ssig2, csig2, Ca, nC3-1) - 1458 | SinCosSeries(TRUE, ssig1, csig1, Ca, nC3-1)); 1459 | domg12 = -g->f * A3f(g, eps) * salp0 * (sig12 + B312); 1460 | lam12 = eta + domg12; 1461 | 1462 | if (diffp) { 1463 | if (calp2 == 0) 1464 | dlam12 = - 2 * g->f1 * dn1 / sbet1; 1465 | else { 1466 | Lengths(g, eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, 1467 | cbet1, cbet2, nullptr, &dlam12, nullptr, nullptr, nullptr, Ca); 1468 | dlam12 *= g->f1 / (calp2 * cbet2); 1469 | } 1470 | } 1471 | 1472 | *psalp2 = salp2; 1473 | *pcalp2 = calp2; 1474 | *psig12 = sig12; 1475 | *pssig1 = ssig1; 1476 | *pcsig1 = csig1; 1477 | *pssig2 = ssig2; 1478 | *pcsig2 = csig2; 1479 | *peps = eps; 1480 | *pdomg12 = domg12; 1481 | if (diffp) 1482 | *pdlam12 = dlam12; 1483 | 1484 | return lam12; 1485 | } 1486 | 1487 | double A3f(const struct geod_geodesic* g, double eps) { 1488 | /* Evaluate A3 */ 1489 | return polyvalx(nA3 - 1, g->A3x, eps); 1490 | } 1491 | 1492 | void C3f(const struct geod_geodesic* g, double eps, double c[]) { 1493 | /* Evaluate C3 coeffs 1494 | * Elements c[1] through c[nC3 - 1] are set */ 1495 | double mult = 1; 1496 | int o = 0, l; 1497 | for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ 1498 | int m = nC3 - l - 1; /* order of polynomial in eps */ 1499 | mult *= eps; 1500 | c[l] = mult * polyvalx(m, g->C3x + o, eps); 1501 | o += m + 1; 1502 | } 1503 | } 1504 | 1505 | void C4f(const struct geod_geodesic* g, double eps, double c[]) { 1506 | /* Evaluate C4 coeffs 1507 | * Elements c[0] through c[nC4 - 1] are set */ 1508 | double mult = 1; 1509 | int o = 0, l; 1510 | for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ 1511 | int m = nC4 - l - 1; /* order of polynomial in eps */ 1512 | c[l] = mult * polyvalx(m, g->C4x + o, eps); 1513 | o += m + 1; 1514 | mult *= eps; 1515 | } 1516 | } 1517 | 1518 | /* The scale factor A1-1 = mean value of (d/dsigma)I1 - 1 */ 1519 | double A1m1f(double eps) { 1520 | static const double coeff[] = { 1521 | /* (1-eps)*A1-1, polynomial in eps2 of order 3 */ 1522 | 1, 4, 64, 0, 256, 1523 | }; 1524 | int m = nA1/2; 1525 | double t = polyvalx(m, coeff, sq(eps)) / coeff[m + 1]; 1526 | return (t + eps) / (1 - eps); 1527 | } 1528 | 1529 | /* The coefficients C1[l] in the Fourier expansion of B1 */ 1530 | void C1f(double eps, double c[]) { 1531 | static const double coeff[] = { 1532 | /* C1[1]/eps^1, polynomial in eps2 of order 2 */ 1533 | -1, 6, -16, 32, 1534 | /* C1[2]/eps^2, polynomial in eps2 of order 2 */ 1535 | -9, 64, -128, 2048, 1536 | /* C1[3]/eps^3, polynomial in eps2 of order 1 */ 1537 | 9, -16, 768, 1538 | /* C1[4]/eps^4, polynomial in eps2 of order 1 */ 1539 | 3, -5, 512, 1540 | /* C1[5]/eps^5, polynomial in eps2 of order 0 */ 1541 | -7, 1280, 1542 | /* C1[6]/eps^6, polynomial in eps2 of order 0 */ 1543 | -7, 2048, 1544 | }; 1545 | double 1546 | eps2 = sq(eps), 1547 | d = eps; 1548 | int o = 0, l; 1549 | for (l = 1; l <= nC1; ++l) { /* l is index of C1p[l] */ 1550 | int m = (nC1 - l) / 2; /* order of polynomial in eps^2 */ 1551 | c[l] = d * polyvalx(m, coeff + o, eps2) / coeff[o + m + 1]; 1552 | o += m + 2; 1553 | d *= eps; 1554 | } 1555 | } 1556 | 1557 | /* The coefficients C1p[l] in the Fourier expansion of B1p */ 1558 | void C1pf(double eps, double c[]) { 1559 | static const double coeff[] = { 1560 | /* C1p[1]/eps^1, polynomial in eps2 of order 2 */ 1561 | 205, -432, 768, 1536, 1562 | /* C1p[2]/eps^2, polynomial in eps2 of order 2 */ 1563 | 4005, -4736, 3840, 12288, 1564 | /* C1p[3]/eps^3, polynomial in eps2 of order 1 */ 1565 | -225, 116, 384, 1566 | /* C1p[4]/eps^4, polynomial in eps2 of order 1 */ 1567 | -7173, 2695, 7680, 1568 | /* C1p[5]/eps^5, polynomial in eps2 of order 0 */ 1569 | 3467, 7680, 1570 | /* C1p[6]/eps^6, polynomial in eps2 of order 0 */ 1571 | 38081, 61440, 1572 | }; 1573 | double 1574 | eps2 = sq(eps), 1575 | d = eps; 1576 | int o = 0, l; 1577 | for (l = 1; l <= nC1p; ++l) { /* l is index of C1p[l] */ 1578 | int m = (nC1p - l) / 2; /* order of polynomial in eps^2 */ 1579 | c[l] = d * polyvalx(m, coeff + o, eps2) / coeff[o + m + 1]; 1580 | o += m + 2; 1581 | d *= eps; 1582 | } 1583 | } 1584 | 1585 | /* The scale factor A2-1 = mean value of (d/dsigma)I2 - 1 */ 1586 | double A2m1f(double eps) { 1587 | static const double coeff[] = { 1588 | /* (eps+1)*A2-1, polynomial in eps2 of order 3 */ 1589 | -11, -28, -192, 0, 256, 1590 | }; 1591 | int m = nA2/2; 1592 | double t = polyvalx(m, coeff, sq(eps)) / coeff[m + 1]; 1593 | return (t - eps) / (1 + eps); 1594 | } 1595 | 1596 | /* The coefficients C2[l] in the Fourier expansion of B2 */ 1597 | void C2f(double eps, double c[]) { 1598 | static const double coeff[] = { 1599 | /* C2[1]/eps^1, polynomial in eps2 of order 2 */ 1600 | 1, 2, 16, 32, 1601 | /* C2[2]/eps^2, polynomial in eps2 of order 2 */ 1602 | 35, 64, 384, 2048, 1603 | /* C2[3]/eps^3, polynomial in eps2 of order 1 */ 1604 | 15, 80, 768, 1605 | /* C2[4]/eps^4, polynomial in eps2 of order 1 */ 1606 | 7, 35, 512, 1607 | /* C2[5]/eps^5, polynomial in eps2 of order 0 */ 1608 | 63, 1280, 1609 | /* C2[6]/eps^6, polynomial in eps2 of order 0 */ 1610 | 77, 2048, 1611 | }; 1612 | double 1613 | eps2 = sq(eps), 1614 | d = eps; 1615 | int o = 0, l; 1616 | for (l = 1; l <= nC2; ++l) { /* l is index of C2[l] */ 1617 | int m = (nC2 - l) / 2; /* order of polynomial in eps^2 */ 1618 | c[l] = d * polyvalx(m, coeff + o, eps2) / coeff[o + m + 1]; 1619 | o += m + 2; 1620 | d *= eps; 1621 | } 1622 | } 1623 | 1624 | /* The scale factor A3 = mean value of (d/dsigma)I3 */ 1625 | void A3coeff(struct geod_geodesic* g) { 1626 | static const double coeff[] = { 1627 | /* A3, coeff of eps^5, polynomial in n of order 0 */ 1628 | -3, 128, 1629 | /* A3, coeff of eps^4, polynomial in n of order 1 */ 1630 | -2, -3, 64, 1631 | /* A3, coeff of eps^3, polynomial in n of order 2 */ 1632 | -1, -3, -1, 16, 1633 | /* A3, coeff of eps^2, polynomial in n of order 2 */ 1634 | 3, -1, -2, 8, 1635 | /* A3, coeff of eps^1, polynomial in n of order 1 */ 1636 | 1, -1, 2, 1637 | /* A3, coeff of eps^0, polynomial in n of order 0 */ 1638 | 1, 1, 1639 | }; 1640 | int o = 0, k = 0, j; 1641 | for (j = nA3 - 1; j >= 0; --j) { /* coeff of eps^j */ 1642 | int m = nA3 - j - 1 < j ? nA3 - j - 1 : j; /* order of polynomial in n */ 1643 | g->A3x[k++] = polyvalx(m, coeff + o, g->n) / coeff[o + m + 1]; 1644 | o += m + 2; 1645 | } 1646 | } 1647 | 1648 | /* The coefficients C3[l] in the Fourier expansion of B3 */ 1649 | void C3coeff(struct geod_geodesic* g) { 1650 | static const double coeff[] = { 1651 | /* C3[1], coeff of eps^5, polynomial in n of order 0 */ 1652 | 3, 128, 1653 | /* C3[1], coeff of eps^4, polynomial in n of order 1 */ 1654 | 2, 5, 128, 1655 | /* C3[1], coeff of eps^3, polynomial in n of order 2 */ 1656 | -1, 3, 3, 64, 1657 | /* C3[1], coeff of eps^2, polynomial in n of order 2 */ 1658 | -1, 0, 1, 8, 1659 | /* C3[1], coeff of eps^1, polynomial in n of order 1 */ 1660 | -1, 1, 4, 1661 | /* C3[2], coeff of eps^5, polynomial in n of order 0 */ 1662 | 5, 256, 1663 | /* C3[2], coeff of eps^4, polynomial in n of order 1 */ 1664 | 1, 3, 128, 1665 | /* C3[2], coeff of eps^3, polynomial in n of order 2 */ 1666 | -3, -2, 3, 64, 1667 | /* C3[2], coeff of eps^2, polynomial in n of order 2 */ 1668 | 1, -3, 2, 32, 1669 | /* C3[3], coeff of eps^5, polynomial in n of order 0 */ 1670 | 7, 512, 1671 | /* C3[3], coeff of eps^4, polynomial in n of order 1 */ 1672 | -10, 9, 384, 1673 | /* C3[3], coeff of eps^3, polynomial in n of order 2 */ 1674 | 5, -9, 5, 192, 1675 | /* C3[4], coeff of eps^5, polynomial in n of order 0 */ 1676 | 7, 512, 1677 | /* C3[4], coeff of eps^4, polynomial in n of order 1 */ 1678 | -14, 7, 512, 1679 | /* C3[5], coeff of eps^5, polynomial in n of order 0 */ 1680 | 21, 2560, 1681 | }; 1682 | int o = 0, k = 0, l, j; 1683 | for (l = 1; l < nC3; ++l) { /* l is index of C3[l] */ 1684 | for (j = nC3 - 1; j >= l; --j) { /* coeff of eps^j */ 1685 | int m = nC3 - j - 1 < j ? nC3 - j - 1 : j; /* order of polynomial in n */ 1686 | g->C3x[k++] = polyvalx(m, coeff + o, g->n) / coeff[o + m + 1]; 1687 | o += m + 2; 1688 | } 1689 | } 1690 | } 1691 | 1692 | /* The coefficients C4[l] in the Fourier expansion of I4 */ 1693 | void C4coeff(struct geod_geodesic* g) { 1694 | static const double coeff[] = { 1695 | /* C4[0], coeff of eps^5, polynomial in n of order 0 */ 1696 | 97, 15015, 1697 | /* C4[0], coeff of eps^4, polynomial in n of order 1 */ 1698 | 1088, 156, 45045, 1699 | /* C4[0], coeff of eps^3, polynomial in n of order 2 */ 1700 | -224, -4784, 1573, 45045, 1701 | /* C4[0], coeff of eps^2, polynomial in n of order 3 */ 1702 | -10656, 14144, -4576, -858, 45045, 1703 | /* C4[0], coeff of eps^1, polynomial in n of order 4 */ 1704 | 64, 624, -4576, 6864, -3003, 15015, 1705 | /* C4[0], coeff of eps^0, polynomial in n of order 5 */ 1706 | 100, 208, 572, 3432, -12012, 30030, 45045, 1707 | /* C4[1], coeff of eps^5, polynomial in n of order 0 */ 1708 | 1, 9009, 1709 | /* C4[1], coeff of eps^4, polynomial in n of order 1 */ 1710 | -2944, 468, 135135, 1711 | /* C4[1], coeff of eps^3, polynomial in n of order 2 */ 1712 | 5792, 1040, -1287, 135135, 1713 | /* C4[1], coeff of eps^2, polynomial in n of order 3 */ 1714 | 5952, -11648, 9152, -2574, 135135, 1715 | /* C4[1], coeff of eps^1, polynomial in n of order 4 */ 1716 | -64, -624, 4576, -6864, 3003, 135135, 1717 | /* C4[2], coeff of eps^5, polynomial in n of order 0 */ 1718 | 8, 10725, 1719 | /* C4[2], coeff of eps^4, polynomial in n of order 1 */ 1720 | 1856, -936, 225225, 1721 | /* C4[2], coeff of eps^3, polynomial in n of order 2 */ 1722 | -8448, 4992, -1144, 225225, 1723 | /* C4[2], coeff of eps^2, polynomial in n of order 3 */ 1724 | -1440, 4160, -4576, 1716, 225225, 1725 | /* C4[3], coeff of eps^5, polynomial in n of order 0 */ 1726 | -136, 63063, 1727 | /* C4[3], coeff of eps^4, polynomial in n of order 1 */ 1728 | 1024, -208, 105105, 1729 | /* C4[3], coeff of eps^3, polynomial in n of order 2 */ 1730 | 3584, -3328, 1144, 315315, 1731 | /* C4[4], coeff of eps^5, polynomial in n of order 0 */ 1732 | -128, 135135, 1733 | /* C4[4], coeff of eps^4, polynomial in n of order 1 */ 1734 | -2560, 832, 405405, 1735 | /* C4[5], coeff of eps^5, polynomial in n of order 0 */ 1736 | 128, 99099, 1737 | }; 1738 | int o = 0, k = 0, l, j; 1739 | for (l = 0; l < nC4; ++l) { /* l is index of C4[l] */ 1740 | for (j = nC4 - 1; j >= l; --j) { /* coeff of eps^j */ 1741 | int m = nC4 - j - 1; /* order of polynomial in n */ 1742 | g->C4x[k++] = polyvalx(m, coeff + o, g->n) / coeff[o + m + 1]; 1743 | o += m + 2; 1744 | } 1745 | } 1746 | } 1747 | 1748 | int transit(double lon1, double lon2) { 1749 | double lon12; 1750 | /* Return 1 or -1 if crossing prime meridian in east or west direction. 1751 | * Otherwise return zero. */ 1752 | /* Compute lon12 the same way as Geodesic::Inverse. */ 1753 | lon12 = AngDiff(lon1, lon2, nullptr); 1754 | lon1 = AngNormalize(lon1); 1755 | lon2 = AngNormalize(lon2); 1756 | return 1757 | lon12 > 0 && ((lon1 < 0 && lon2 >= 0) || 1758 | (lon1 > 0 && lon2 == 0)) ? 1 : 1759 | (lon12 < 0 && lon1 >= 0 && lon2 < 0 ? -1 : 0); 1760 | } 1761 | 1762 | int transitdirect(double lon1, double lon2) { 1763 | /* Compute exactly the parity of 1764 | * int(floor(lon2 / 360)) - int(floor(lon1 / 360)) */ 1765 | lon1 = remainder(lon1, 2.0 * td); lon2 = remainder(lon2, 2.0 * td); 1766 | return ( (lon2 >= 0 && lon2 < td ? 0 : 1) - 1767 | (lon1 >= 0 && lon1 < td ? 0 : 1) ); 1768 | } 1769 | 1770 | void accini(double s[]) { 1771 | /* Initialize an accumulator; this is an array with two elements. */ 1772 | s[0] = s[1] = 0; 1773 | } 1774 | 1775 | void acccopy(const double s[], double t[]) { 1776 | /* Copy an accumulator; t = s. */ 1777 | t[0] = s[0]; t[1] = s[1]; 1778 | } 1779 | 1780 | void accadd(double s[], double y) { 1781 | /* Add y to an accumulator. */ 1782 | double u, z = sumx(y, s[1], &u); 1783 | s[0] = sumx(z, s[0], &s[1]); 1784 | if (s[0] == 0) 1785 | s[0] = u; 1786 | else 1787 | s[1] = s[1] + u; 1788 | } 1789 | 1790 | double accsum(const double s[], double y) { 1791 | /* Return accumulator + y (but don't add to accumulator). */ 1792 | double t[2]; 1793 | acccopy(s, t); 1794 | accadd(t, y); 1795 | return t[0]; 1796 | } 1797 | 1798 | void accneg(double s[]) { 1799 | /* Negate an accumulator. */ 1800 | s[0] = -s[0]; s[1] = -s[1]; 1801 | } 1802 | 1803 | void accrem(double s[], double y) { 1804 | /* Reduce to [-y/2, y/2]. */ 1805 | s[0] = remainder(s[0], y); 1806 | accadd(s, 0.0); 1807 | } 1808 | 1809 | void geod_polygon_init(struct geod_polygon* p, boolx polylinep) { 1810 | p->polyline = (polylinep != 0); 1811 | geod_polygon_clear(p); 1812 | } 1813 | 1814 | void geod_polygon_clear(struct geod_polygon* p) { 1815 | p->lat0 = p->lon0 = p->lat = p->lon = NaN; 1816 | accini(p->P); 1817 | accini(p->A); 1818 | p->num = p->crossings = 0; 1819 | } 1820 | 1821 | void geod_polygon_addpoint(const struct geod_geodesic* g, 1822 | struct geod_polygon* p, 1823 | double lat, double lon) { 1824 | if (p->num == 0) { 1825 | p->lat0 = p->lat = lat; 1826 | p->lon0 = p->lon = lon; 1827 | } else { 1828 | double s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ 1829 | geod_geninverse(g, p->lat, p->lon, lat, lon, 1830 | &s12, nullptr, nullptr, nullptr, nullptr, nullptr, 1831 | p->polyline ? nullptr : &S12); 1832 | accadd(p->P, s12); 1833 | if (!p->polyline) { 1834 | accadd(p->A, S12); 1835 | p->crossings += transit(p->lon, lon); 1836 | } 1837 | p->lat = lat; p->lon = lon; 1838 | } 1839 | ++p->num; 1840 | } 1841 | 1842 | void geod_polygon_addedge(const struct geod_geodesic* g, 1843 | struct geod_polygon* p, 1844 | double azi, double s) { 1845 | if (p->num) { /* Do nothing is num is zero */ 1846 | /* Initialize S12 to stop Visual Studio warning. Initialization of lat and 1847 | * lon is to make CLang static analyzer happy. */ 1848 | double lat = 0, lon = 0, S12 = 0; 1849 | geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, 1850 | &lat, &lon, nullptr, 1851 | nullptr, nullptr, nullptr, nullptr, 1852 | p->polyline ? nullptr : &S12); 1853 | accadd(p->P, s); 1854 | if (!p->polyline) { 1855 | accadd(p->A, S12); 1856 | p->crossings += transitdirect(p->lon, lon); 1857 | } 1858 | p->lat = lat; p->lon = lon; 1859 | ++p->num; 1860 | } 1861 | } 1862 | 1863 | unsigned geod_polygon_compute(const struct geod_geodesic* g, 1864 | const struct geod_polygon* p, 1865 | boolx reverse, boolx sign, 1866 | double* pA, double* pP) { 1867 | double s12, S12, t[2]; 1868 | if (p->num < 2) { 1869 | if (pP) *pP = 0; 1870 | if (!p->polyline && pA) *pA = 0; 1871 | return p->num; 1872 | } 1873 | if (p->polyline) { 1874 | if (pP) *pP = p->P[0]; 1875 | return p->num; 1876 | } 1877 | geod_geninverse(g, p->lat, p->lon, p->lat0, p->lon0, 1878 | &s12, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); 1879 | if (pP) *pP = accsum(p->P, s12); 1880 | acccopy(p->A, t); 1881 | accadd(t, S12); 1882 | if (pA) *pA = areareduceA(t, 4 * pi * g->c2, 1883 | p->crossings + transit(p->lon, p->lon0), 1884 | reverse, sign); 1885 | return p->num; 1886 | } 1887 | 1888 | unsigned geod_polygon_testpoint(const struct geod_geodesic* g, 1889 | const struct geod_polygon* p, 1890 | double lat, double lon, 1891 | boolx reverse, boolx sign, 1892 | double* pA, double* pP) { 1893 | double perimeter, tempsum; 1894 | int crossings, i; 1895 | unsigned num = p->num + 1; 1896 | if (num == 1) { 1897 | if (pP) *pP = 0; 1898 | if (!p->polyline && pA) *pA = 0; 1899 | return num; 1900 | } 1901 | perimeter = p->P[0]; 1902 | tempsum = p->polyline ? 0 : p->A[0]; 1903 | crossings = p->crossings; 1904 | for (i = 0; i < (p->polyline ? 1 : 2); ++i) { 1905 | double s12, S12 = 0; /* Initialize S12 to stop Visual Studio warning */ 1906 | geod_geninverse(g, 1907 | i == 0 ? p->lat : lat, i == 0 ? p->lon : lon, 1908 | i != 0 ? p->lat0 : lat, i != 0 ? p->lon0 : lon, 1909 | &s12, nullptr, nullptr, nullptr, nullptr, nullptr, 1910 | p->polyline ? nullptr : &S12); 1911 | perimeter += s12; 1912 | if (!p->polyline) { 1913 | tempsum += S12; 1914 | crossings += transit(i == 0 ? p->lon : lon, 1915 | i != 0 ? p->lon0 : lon); 1916 | } 1917 | } 1918 | 1919 | if (pP) *pP = perimeter; 1920 | if (p->polyline) 1921 | return num; 1922 | 1923 | if (pA) *pA = areareduceB(tempsum, 4 * pi * g->c2, crossings, reverse, sign); 1924 | return num; 1925 | } 1926 | 1927 | unsigned geod_polygon_testedge(const struct geod_geodesic* g, 1928 | const struct geod_polygon* p, 1929 | double azi, double s, 1930 | boolx reverse, boolx sign, 1931 | double* pA, double* pP) { 1932 | double perimeter, tempsum; 1933 | int crossings; 1934 | unsigned num = p->num + 1; 1935 | if (num == 1) { /* we don't have a starting point! */ 1936 | if (pP) *pP = NaN; 1937 | if (!p->polyline && pA) *pA = NaN; 1938 | return 0; 1939 | } 1940 | perimeter = p->P[0] + s; 1941 | if (p->polyline) { 1942 | if (pP) *pP = perimeter; 1943 | return num; 1944 | } 1945 | 1946 | tempsum = p->A[0]; 1947 | crossings = p->crossings; 1948 | { 1949 | /* Initialization of lat, lon, and S12 is to make CLang static analyzer 1950 | * happy. */ 1951 | double lat = 0, lon = 0, s12, S12 = 0; 1952 | geod_gendirect(g, p->lat, p->lon, azi, GEOD_LONG_UNROLL, s, 1953 | &lat, &lon, nullptr, 1954 | nullptr, nullptr, nullptr, nullptr, &S12); 1955 | tempsum += S12; 1956 | crossings += transitdirect(p->lon, lon); 1957 | geod_geninverse(g, lat, lon, p->lat0, p->lon0, 1958 | &s12, nullptr, nullptr, nullptr, nullptr, nullptr, &S12); 1959 | perimeter += s12; 1960 | tempsum += S12; 1961 | crossings += transit(lon, p->lon0); 1962 | } 1963 | 1964 | if (pP) *pP = perimeter; 1965 | if (pA) *pA = areareduceB(tempsum, 4 * pi * g->c2, crossings, reverse, sign); 1966 | return num; 1967 | } 1968 | 1969 | void geod_polygonarea(const struct geod_geodesic* g, 1970 | double lats[], double lons[], int n, 1971 | double* pA, double* pP) { 1972 | int i; 1973 | struct geod_polygon p; 1974 | geod_polygon_init(&p, FALSE); 1975 | for (i = 0; i < n; ++i) 1976 | geod_polygon_addpoint(g, &p, lats[i], lons[i]); 1977 | geod_polygon_compute(g, &p, FALSE, TRUE, pA, pP); 1978 | } 1979 | 1980 | double areareduceA(double area[], double area0, 1981 | int crossings, boolx reverse, boolx sign) { 1982 | accrem(area, area0); 1983 | if (crossings & 1) 1984 | accadd(area, (area[0] < 0 ? 1 : -1) * area0/2); 1985 | /* area is with the clockwise sense. If !reverse convert to 1986 | * counter-clockwise convention. */ 1987 | if (!reverse) 1988 | accneg(area); 1989 | /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ 1990 | if (sign) { 1991 | if (area[0] > area0/2) 1992 | accadd(area, -area0); 1993 | else if (area[0] <= -area0/2) 1994 | accadd(area, +area0); 1995 | } else { 1996 | if (area[0] >= area0) 1997 | accadd(area, -area0); 1998 | else if (area[0] < 0) 1999 | accadd(area, +area0); 2000 | } 2001 | return 0 + area[0]; 2002 | } 2003 | 2004 | double areareduceB(double area, double area0, 2005 | int crossings, boolx reverse, boolx sign) { 2006 | area = remainder(area, area0); 2007 | if (crossings & 1) 2008 | area += (area < 0 ? 1 : -1) * area0/2; 2009 | /* area is with the clockwise sense. If !reverse convert to 2010 | * counter-clockwise convention. */ 2011 | if (!reverse) 2012 | area *= -1; 2013 | /* If sign put area in (-area0/2, area0/2], else put area in [0, area0) */ 2014 | if (sign) { 2015 | if (area > area0/2) 2016 | area -= area0; 2017 | else if (area <= -area0/2) 2018 | area += area0; 2019 | } else { 2020 | if (area >= area0) 2021 | area -= area0; 2022 | else if (area < 0) 2023 | area += area0; 2024 | } 2025 | return 0 + area; 2026 | } 2027 | 2028 | /** @endcond */ 2029 | -------------------------------------------------------------------------------- /src/geodesic.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file geodesic.h 3 | * \brief API for the geodesic routines in C 4 | * 5 | * These routines are a simple transcription of the corresponding C++ classes 6 | * in GeographicLib. The 7 | * "class data" is represented by the structs geod_geodesic, geod_geodesicline, 8 | * geod_polygon and pointers to these objects are passed as initial arguments 9 | * to the member functions. Most of the internal comments have been retained. 10 | * However, in the process of transcription some documentation has been lost 11 | * and the documentation for the C++ classes, GeographicLib::Geodesic, 12 | * GeographicLib::GeodesicLine, and GeographicLib::PolygonAreaT, should be 13 | * consulted. The C++ code remains the "reference implementation". Think 14 | * twice about restructuring the internals of the C code since this may make 15 | * porting fixes from the C++ code more difficult. 16 | * 17 | * Copyright (c) Charles Karney (2012-2022) and licensed 18 | * under the MIT/X11 License. For more information, see 19 | * https://geographiclib.sourceforge.io/ 20 | **********************************************************************/ 21 | 22 | #if !defined(GEODESIC_H) 23 | #define GEODESIC_H 1 24 | 25 | /** 26 | * The major version of the geodesic library. (This tracks the version of 27 | * GeographicLib.) 28 | **********************************************************************/ 29 | #define GEODESIC_VERSION_MAJOR 2 30 | /** 31 | * The minor version of the geodesic library. (This tracks the version of 32 | * GeographicLib.) 33 | **********************************************************************/ 34 | #define GEODESIC_VERSION_MINOR 1 35 | /** 36 | * The patch level of the geodesic library. (This tracks the version of 37 | * GeographicLib.) 38 | **********************************************************************/ 39 | #define GEODESIC_VERSION_PATCH 0 40 | 41 | /** 42 | * Pack the version components into a single integer. Users should not rely on 43 | * this particular packing of the components of the version number; see the 44 | * documentation for ::GEODESIC_VERSION, below. 45 | **********************************************************************/ 46 | #define GEODESIC_VERSION_NUM(a,b,c) ((((a) * 10000 + (b)) * 100) + (c)) 47 | 48 | /** 49 | * The version of the geodesic library as a single integer, packed as MMmmmmpp 50 | * where MM is the major version, mmmm is the minor version, and pp is the 51 | * patch level. Users should not rely on this particular packing of the 52 | * components of the version number. Instead they should use a test such as 53 | * @code{.c} 54 | #if GEODESIC_VERSION >= GEODESIC_VERSION_NUM(1,40,0) 55 | ... 56 | #endif 57 | * @endcode 58 | **********************************************************************/ 59 | #define GEODESIC_VERSION \ 60 | GEODESIC_VERSION_NUM(GEODESIC_VERSION_MAJOR, \ 61 | GEODESIC_VERSION_MINOR, \ 62 | GEODESIC_VERSION_PATCH) 63 | 64 | #if !defined(GEOD_DLL) 65 | #if defined(_MSC_VER) && defined(PROJ_MSVC_DLL_EXPORT) 66 | #define GEOD_DLL __declspec(dllexport) 67 | #elif defined(__GNUC__) 68 | #define GEOD_DLL __attribute__ ((visibility("default"))) 69 | #else 70 | #define GEOD_DLL 71 | #endif 72 | #endif 73 | 74 | #if defined(PROJ_RENAME_SYMBOLS) 75 | #include "proj_symbol_rename.h" 76 | #endif 77 | 78 | #if defined(__cplusplus) 79 | extern "C" { 80 | #endif 81 | 82 | /** 83 | * The struct containing information about the ellipsoid. This must be 84 | * initialized by geod_init() before use. 85 | **********************************************************************/ 86 | struct geod_geodesic { 87 | double a; /**< the equatorial radius */ 88 | double f; /**< the flattening */ 89 | /**< @cond SKIP */ 90 | double f1, e2, ep2, n, b, c2, etol2; 91 | double A3x[6], C3x[15], C4x[21]; 92 | /**< @endcond */ 93 | }; 94 | 95 | /** 96 | * The struct containing information about a single geodesic. This must be 97 | * initialized by geod_lineinit(), geod_directline(), geod_gendirectline(), 98 | * or geod_inverseline() before use. 99 | **********************************************************************/ 100 | struct geod_geodesicline { 101 | double lat1; /**< the starting latitude */ 102 | double lon1; /**< the starting longitude */ 103 | double azi1; /**< the starting azimuth */ 104 | double a; /**< the equatorial radius */ 105 | double f; /**< the flattening */ 106 | double salp1; /**< sine of \e azi1 */ 107 | double calp1; /**< cosine of \e azi1 */ 108 | double a13; /**< arc length to reference point */ 109 | double s13; /**< distance to reference point */ 110 | /**< @cond SKIP */ 111 | double b, c2, f1, salp0, calp0, k2, 112 | ssig1, csig1, dn1, stau1, ctau1, somg1, comg1, 113 | A1m1, A2m1, A3c, B11, B21, B31, A4, B41; 114 | double C1a[6+1], C1pa[6+1], C2a[6+1], C3a[6], C4a[6]; 115 | /**< @endcond */ 116 | unsigned caps; /**< the capabilities */ 117 | }; 118 | 119 | /** 120 | * The struct for accumulating information about a geodesic polygon. This is 121 | * used for computing the perimeter and area of a polygon. This must be 122 | * initialized by geod_polygon_init() before use. 123 | **********************************************************************/ 124 | struct geod_polygon { 125 | double lat; /**< the current latitude */ 126 | double lon; /**< the current longitude */ 127 | /**< @cond SKIP */ 128 | double lat0; 129 | double lon0; 130 | double A[2]; 131 | double P[2]; 132 | int polyline; 133 | int crossings; 134 | /**< @endcond */ 135 | unsigned num; /**< the number of points so far */ 136 | }; 137 | 138 | /** 139 | * Initialize a geod_geodesic object. 140 | * 141 | * @param[out] g a pointer to the object to be initialized. 142 | * @param[in] a the equatorial radius (meters). 143 | * @param[in] f the flattening. 144 | **********************************************************************/ 145 | void GEOD_DLL geod_init(struct geod_geodesic* g, double a, double f); 146 | 147 | /** 148 | * Solve the direct geodesic problem. 149 | * 150 | * @param[in] g a pointer to the geod_geodesic object specifying the 151 | * ellipsoid. 152 | * @param[in] lat1 latitude of point 1 (degrees). 153 | * @param[in] lon1 longitude of point 1 (degrees). 154 | * @param[in] azi1 azimuth at point 1 (degrees). 155 | * @param[in] s12 distance from point 1 to point 2 (meters); it can be 156 | * negative. 157 | * @param[out] plat2 pointer to the latitude of point 2 (degrees). 158 | * @param[out] plon2 pointer to the longitude of point 2 (degrees). 159 | * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). 160 | * 161 | * \e g must have been initialized with a call to geod_init(). \e lat1 162 | * should be in the range [−90°, 90°]. The values of \e lon2 163 | * and \e azi2 returned are in the range [−180°, 180°]. Any of 164 | * the "return" arguments \e plat2, etc., may be replaced by 0, if you do not 165 | * need some quantities computed. 166 | * 167 | * If either point is at a pole, the azimuth is defined by keeping the 168 | * longitude fixed, writing \e lat = ±(90° − ε), and 169 | * taking the limit ε → 0+. An arc length greater that 180° 170 | * signifies a geodesic which is not a shortest path. (For a prolate 171 | * ellipsoid, an additional condition is necessary for a shortest path: the 172 | * longitudinal extent must not exceed of 180°.) 173 | * 174 | * Example, determine the point 10000 km NE of JFK: 175 | @code{.c} 176 | struct geod_geodesic g; 177 | double lat, lon; 178 | geod_init(&g, 6378137, 1/298.257223563); 179 | geod_direct(&g, 40.64, -73.78, 45.0, 10e6, &lat, &lon, 0); 180 | printf("%.5f %.5f\n", lat, lon); 181 | @endcode 182 | **********************************************************************/ 183 | void GEOD_DLL geod_direct(const struct geod_geodesic* g, 184 | double lat1, double lon1, double azi1, double s12, 185 | double* plat2, double* plon2, double* pazi2); 186 | 187 | /** 188 | * The general direct geodesic problem. 189 | * 190 | * @param[in] g a pointer to the geod_geodesic object specifying the 191 | * ellipsoid. 192 | * @param[in] lat1 latitude of point 1 (degrees). 193 | * @param[in] lon1 longitude of point 1 (degrees). 194 | * @param[in] azi1 azimuth at point 1 (degrees). 195 | * @param[in] flags bitor'ed combination of ::geod_flags; \e flags & 196 | * ::GEOD_ARCMODE determines the meaning of \e s12_a12 and \e flags & 197 | * ::GEOD_LONG_UNROLL "unrolls" \e lon2. 198 | * @param[in] s12_a12 if \e flags & ::GEOD_ARCMODE is 0, this is the distance 199 | * from point 1 to point 2 (meters); otherwise it is the arc length 200 | * from point 1 to point 2 (degrees); it can be negative. 201 | * @param[out] plat2 pointer to the latitude of point 2 (degrees). 202 | * @param[out] plon2 pointer to the longitude of point 2 (degrees). 203 | * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). 204 | * @param[out] ps12 pointer to the distance from point 1 to point 2 205 | * (meters). 206 | * @param[out] pm12 pointer to the reduced length of geodesic (meters). 207 | * @param[out] pM12 pointer to the geodesic scale of point 2 relative to 208 | * point 1 (dimensionless). 209 | * @param[out] pM21 pointer to the geodesic scale of point 1 relative to 210 | * point 2 (dimensionless). 211 | * @param[out] pS12 pointer to the area under the geodesic 212 | * (meters2). 213 | * @return \e a12 arc length from point 1 to point 2 (degrees). 214 | * 215 | * \e g must have been initialized with a call to geod_init(). \e lat1 216 | * should be in the range [−90°, 90°]. The function value \e 217 | * a12 equals \e s12_a12 if \e flags & ::GEOD_ARCMODE. Any of the "return" 218 | * arguments, \e plat2, etc., may be replaced by 0, if you do not need some 219 | * quantities computed. 220 | * 221 | * With \e flags & ::GEOD_LONG_UNROLL bit set, the longitude is "unrolled" so 222 | * that the quantity \e lon2 − \e lon1 indicates how many times and in 223 | * what sense the geodesic encircles the ellipsoid. 224 | **********************************************************************/ 225 | double GEOD_DLL geod_gendirect(const struct geod_geodesic* g, 226 | double lat1, double lon1, double azi1, 227 | unsigned flags, double s12_a12, 228 | double* plat2, double* plon2, double* pazi2, 229 | double* ps12, double* pm12, 230 | double* pM12, double* pM21, 231 | double* pS12); 232 | 233 | /** 234 | * Solve the inverse geodesic problem. 235 | * 236 | * @param[in] g a pointer to the geod_geodesic object specifying the 237 | * ellipsoid. 238 | * @param[in] lat1 latitude of point 1 (degrees). 239 | * @param[in] lon1 longitude of point 1 (degrees). 240 | * @param[in] lat2 latitude of point 2 (degrees). 241 | * @param[in] lon2 longitude of point 2 (degrees). 242 | * @param[out] ps12 pointer to the distance from point 1 to point 2 243 | * (meters). 244 | * @param[out] pazi1 pointer to the azimuth at point 1 (degrees). 245 | * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). 246 | * 247 | * \e g must have been initialized with a call to geod_init(). \e lat1 and 248 | * \e lat2 should be in the range [−90°, 90°]. The values of 249 | * \e azi1 and \e azi2 returned are in the range [−180°, 180°]. 250 | * Any of the "return" arguments, \e ps12, etc., may be replaced by 0, if you 251 | * do not need some quantities computed. 252 | * 253 | * If either point is at a pole, the azimuth is defined by keeping the 254 | * longitude fixed, writing \e lat = ±(90° − ε), and 255 | * taking the limit ε → 0+. 256 | * 257 | * The solution to the inverse problem is found using Newton's method. If 258 | * this fails to converge (this is very unlikely in geodetic applications 259 | * but does occur for very eccentric ellipsoids), then the bisection method 260 | * is used to refine the solution. 261 | * 262 | * Example, determine the distance between JFK and Singapore Changi Airport: 263 | @code{.c} 264 | struct geod_geodesic g; 265 | double s12; 266 | geod_init(&g, 6378137, 1/298.257223563); 267 | geod_inverse(&g, 40.64, -73.78, 1.36, 103.99, &s12, 0, 0); 268 | printf("%.3f\n", s12); 269 | @endcode 270 | **********************************************************************/ 271 | void GEOD_DLL geod_inverse(const struct geod_geodesic* g, 272 | double lat1, double lon1, 273 | double lat2, double lon2, 274 | double* ps12, double* pazi1, double* pazi2); 275 | 276 | /** 277 | * The general inverse geodesic calculation. 278 | * 279 | * @param[in] g a pointer to the geod_geodesic object specifying the 280 | * ellipsoid. 281 | * @param[in] lat1 latitude of point 1 (degrees). 282 | * @param[in] lon1 longitude of point 1 (degrees). 283 | * @param[in] lat2 latitude of point 2 (degrees). 284 | * @param[in] lon2 longitude of point 2 (degrees). 285 | * @param[out] ps12 pointer to the distance from point 1 to point 2 286 | * (meters). 287 | * @param[out] pazi1 pointer to the azimuth at point 1 (degrees). 288 | * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). 289 | * @param[out] pm12 pointer to the reduced length of geodesic (meters). 290 | * @param[out] pM12 pointer to the geodesic scale of point 2 relative to 291 | * point 1 (dimensionless). 292 | * @param[out] pM21 pointer to the geodesic scale of point 1 relative to 293 | * point 2 (dimensionless). 294 | * @param[out] pS12 pointer to the area under the geodesic 295 | * (meters2). 296 | * @return \e a12 arc length from point 1 to point 2 (degrees). 297 | * 298 | * \e g must have been initialized with a call to geod_init(). \e lat1 and 299 | * \e lat2 should be in the range [−90°, 90°]. Any of the 300 | * "return" arguments \e ps12, etc., may be replaced by 0, if you do not need 301 | * some quantities computed. 302 | **********************************************************************/ 303 | double GEOD_DLL geod_geninverse(const struct geod_geodesic* g, 304 | double lat1, double lon1, 305 | double lat2, double lon2, 306 | double* ps12, double* pazi1, double* pazi2, 307 | double* pm12, double* pM12, double* pM21, 308 | double* pS12); 309 | 310 | /** 311 | * Initialize a geod_geodesicline object. 312 | * 313 | * @param[out] l a pointer to the object to be initialized. 314 | * @param[in] g a pointer to the geod_geodesic object specifying the 315 | * ellipsoid. 316 | * @param[in] lat1 latitude of point 1 (degrees). 317 | * @param[in] lon1 longitude of point 1 (degrees). 318 | * @param[in] azi1 azimuth at point 1 (degrees). 319 | * @param[in] caps bitor'ed combination of ::geod_mask values specifying the 320 | * capabilities the geod_geodesicline object should possess, i.e., which 321 | * quantities can be returned in calls to geod_position() and 322 | * geod_genposition(). 323 | * 324 | * \e g must have been initialized with a call to geod_init(). \e lat1 325 | * should be in the range [−90°, 90°]. 326 | * 327 | * The ::geod_mask values are: 328 | * - \e caps |= ::GEOD_LATITUDE for the latitude \e lat2; this is 329 | * added automatically, 330 | * - \e caps |= ::GEOD_LONGITUDE for the latitude \e lon2, 331 | * - \e caps |= ::GEOD_AZIMUTH for the latitude \e azi2; this is 332 | * added automatically, 333 | * - \e caps |= ::GEOD_DISTANCE for the distance \e s12, 334 | * - \e caps |= ::GEOD_REDUCEDLENGTH for the reduced length \e m12, 335 | * - \e caps |= ::GEOD_GEODESICSCALE for the geodesic scales \e M12 336 | * and \e M21, 337 | * - \e caps |= ::GEOD_AREA for the area \e S12, 338 | * - \e caps |= ::GEOD_DISTANCE_IN permits the length of the 339 | * geodesic to be given in terms of \e s12; without this capability the 340 | * length can only be specified in terms of arc length. 341 | * . 342 | * A value of \e caps = 0 is treated as ::GEOD_LATITUDE | ::GEOD_LONGITUDE | 343 | * ::GEOD_AZIMUTH | ::GEOD_DISTANCE_IN (to support the solution of the 344 | * "standard" direct problem). 345 | * 346 | * When initialized by this function, point 3 is undefined (l->s13 = l->a13 = 347 | * NaN). 348 | **********************************************************************/ 349 | void GEOD_DLL geod_lineinit(struct geod_geodesicline* l, 350 | const struct geod_geodesic* g, 351 | double lat1, double lon1, double azi1, 352 | unsigned caps); 353 | 354 | /** 355 | * Initialize a geod_geodesicline object in terms of the direct geodesic 356 | * problem. 357 | * 358 | * @param[out] l a pointer to the object to be initialized. 359 | * @param[in] g a pointer to the geod_geodesic object specifying the 360 | * ellipsoid. 361 | * @param[in] lat1 latitude of point 1 (degrees). 362 | * @param[in] lon1 longitude of point 1 (degrees). 363 | * @param[in] azi1 azimuth at point 1 (degrees). 364 | * @param[in] s12 distance from point 1 to point 2 (meters); it can be 365 | * negative. 366 | * @param[in] caps bitor'ed combination of ::geod_mask values specifying the 367 | * capabilities the geod_geodesicline object should possess, i.e., which 368 | * quantities can be returned in calls to geod_position() and 369 | * geod_genposition(). 370 | * 371 | * This function sets point 3 of the geod_geodesicline to correspond to point 372 | * 2 of the direct geodesic problem. See geod_lineinit() for more 373 | * information. 374 | **********************************************************************/ 375 | void GEOD_DLL geod_directline(struct geod_geodesicline* l, 376 | const struct geod_geodesic* g, 377 | double lat1, double lon1, 378 | double azi1, double s12, 379 | unsigned caps); 380 | 381 | /** 382 | * Initialize a geod_geodesicline object in terms of the direct geodesic 383 | * problem specified in terms of either distance or arc length. 384 | * 385 | * @param[out] l a pointer to the object to be initialized. 386 | * @param[in] g a pointer to the geod_geodesic object specifying the 387 | * ellipsoid. 388 | * @param[in] lat1 latitude of point 1 (degrees). 389 | * @param[in] lon1 longitude of point 1 (degrees). 390 | * @param[in] azi1 azimuth at point 1 (degrees). 391 | * @param[in] flags either ::GEOD_NOFLAGS or ::GEOD_ARCMODE to determining 392 | * the meaning of the \e s12_a12. 393 | * @param[in] s12_a12 if \e flags = ::GEOD_NOFLAGS, this is the distance 394 | * from point 1 to point 2 (meters); if \e flags = ::GEOD_ARCMODE, it is 395 | * the arc length from point 1 to point 2 (degrees); it can be 396 | * negative. 397 | * @param[in] caps bitor'ed combination of ::geod_mask values specifying the 398 | * capabilities the geod_geodesicline object should possess, i.e., which 399 | * quantities can be returned in calls to geod_position() and 400 | * geod_genposition(). 401 | * 402 | * This function sets point 3 of the geod_geodesicline to correspond to point 403 | * 2 of the direct geodesic problem. See geod_lineinit() for more 404 | * information. 405 | **********************************************************************/ 406 | void GEOD_DLL geod_gendirectline(struct geod_geodesicline* l, 407 | const struct geod_geodesic* g, 408 | double lat1, double lon1, double azi1, 409 | unsigned flags, double s12_a12, 410 | unsigned caps); 411 | 412 | /** 413 | * Initialize a geod_geodesicline object in terms of the inverse geodesic 414 | * problem. 415 | * 416 | * @param[out] l a pointer to the object to be initialized. 417 | * @param[in] g a pointer to the geod_geodesic object specifying the 418 | * ellipsoid. 419 | * @param[in] lat1 latitude of point 1 (degrees). 420 | * @param[in] lon1 longitude of point 1 (degrees). 421 | * @param[in] lat2 latitude of point 2 (degrees). 422 | * @param[in] lon2 longitude of point 2 (degrees). 423 | * @param[in] caps bitor'ed combination of ::geod_mask values specifying the 424 | * capabilities the geod_geodesicline object should possess, i.e., which 425 | * quantities can be returned in calls to geod_position() and 426 | * geod_genposition(). 427 | * 428 | * This function sets point 3 of the geod_geodesicline to correspond to point 429 | * 2 of the inverse geodesic problem. See geod_lineinit() for more 430 | * information. 431 | **********************************************************************/ 432 | void GEOD_DLL geod_inverseline(struct geod_geodesicline* l, 433 | const struct geod_geodesic* g, 434 | double lat1, double lon1, 435 | double lat2, double lon2, 436 | unsigned caps); 437 | 438 | /** 439 | * Compute the position along a geod_geodesicline. 440 | * 441 | * @param[in] l a pointer to the geod_geodesicline object specifying the 442 | * geodesic line. 443 | * @param[in] s12 distance from point 1 to point 2 (meters); it can be 444 | * negative. 445 | * @param[out] plat2 pointer to the latitude of point 2 (degrees). 446 | * @param[out] plon2 pointer to the longitude of point 2 (degrees); requires 447 | * that \e l was initialized with \e caps |= ::GEOD_LONGITUDE. 448 | * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). 449 | * 450 | * \e l must have been initialized with a call, e.g., to geod_lineinit(), 451 | * with \e caps |= ::GEOD_DISTANCE_IN (or \e caps = 0). The values of \e 452 | * lon2 and \e azi2 returned are in the range [−180°, 180°]. 453 | * Any of the "return" arguments \e plat2, etc., may be replaced by 0, if you 454 | * do not need some quantities computed. 455 | * 456 | * Example, compute way points between JFK and Singapore Changi Airport 457 | * the "obvious" way using geod_direct(): 458 | @code{.c} 459 | struct geod_geodesic g; 460 | double s12, azi1, lat[101], lon[101]; 461 | int i; 462 | geod_init(&g, 6378137, 1/298.257223563); 463 | geod_inverse(&g, 40.64, -73.78, 1.36, 103.99, &s12, &azi1, 0); 464 | for (i = 0; i < 101; ++i) { 465 | geod_direct(&g, 40.64, -73.78, azi1, i * s12 * 0.01, lat + i, lon + i, 0); 466 | printf("%.5f %.5f\n", lat[i], lon[i]); 467 | } 468 | @endcode 469 | * A faster way using geod_position(): 470 | @code{.c} 471 | struct geod_geodesic g; 472 | struct geod_geodesicline l; 473 | double lat[101], lon[101]; 474 | int i; 475 | geod_init(&g, 6378137, 1/298.257223563); 476 | geod_inverseline(&l, &g, 40.64, -73.78, 1.36, 103.99, 0); 477 | for (i = 0; i <= 100; ++i) { 478 | geod_position(&l, i * l.s13 * 0.01, lat + i, lon + i, 0); 479 | printf("%.5f %.5f\n", lat[i], lon[i]); 480 | } 481 | @endcode 482 | **********************************************************************/ 483 | void GEOD_DLL geod_position(const struct geod_geodesicline* l, double s12, 484 | double* plat2, double* plon2, double* pazi2); 485 | 486 | /** 487 | * The general position function. 488 | * 489 | * @param[in] l a pointer to the geod_geodesicline object specifying the 490 | * geodesic line. 491 | * @param[in] flags bitor'ed combination of ::geod_flags; \e flags & 492 | * ::GEOD_ARCMODE determines the meaning of \e s12_a12 and \e flags & 493 | * ::GEOD_LONG_UNROLL "unrolls" \e lon2; if \e flags & ::GEOD_ARCMODE is 0, 494 | * then \e l must have been initialized with \e caps |= ::GEOD_DISTANCE_IN. 495 | * @param[in] s12_a12 if \e flags & ::GEOD_ARCMODE is 0, this is the 496 | * distance from point 1 to point 2 (meters); otherwise it is the 497 | * arc length from point 1 to point 2 (degrees); it can be 498 | * negative. 499 | * @param[out] plat2 pointer to the latitude of point 2 (degrees). 500 | * @param[out] plon2 pointer to the longitude of point 2 (degrees); requires 501 | * that \e l was initialized with \e caps |= ::GEOD_LONGITUDE. 502 | * @param[out] pazi2 pointer to the (forward) azimuth at point 2 (degrees). 503 | * @param[out] ps12 pointer to the distance from point 1 to point 2 504 | * (meters); requires that \e l was initialized with \e caps |= 505 | * ::GEOD_DISTANCE. 506 | * @param[out] pm12 pointer to the reduced length of geodesic (meters); 507 | * requires that \e l was initialized with \e caps |= ::GEOD_REDUCEDLENGTH. 508 | * @param[out] pM12 pointer to the geodesic scale of point 2 relative to 509 | * point 1 (dimensionless); requires that \e l was initialized with \e caps 510 | * |= ::GEOD_GEODESICSCALE. 511 | * @param[out] pM21 pointer to the geodesic scale of point 1 relative to 512 | * point 2 (dimensionless); requires that \e l was initialized with \e caps 513 | * |= ::GEOD_GEODESICSCALE. 514 | * @param[out] pS12 pointer to the area under the geodesic 515 | * (meters2); requires that \e l was initialized with \e caps |= 516 | * ::GEOD_AREA. 517 | * @return \e a12 arc length from point 1 to point 2 (degrees). 518 | * 519 | * \e l must have been initialized with a call to geod_lineinit() with \e 520 | * caps |= ::GEOD_DISTANCE_IN. The value \e azi2 returned is in the range 521 | * [−180°, 180°]. Any of the "return" arguments \e plat2, 522 | * etc., may be replaced by 0, if you do not need some quantities 523 | * computed. Requesting a value which \e l is not capable of computing 524 | * is not an error; the corresponding argument will not be altered. 525 | * 526 | * With \e flags & ::GEOD_LONG_UNROLL bit set, the longitude is "unrolled" so 527 | * that the quantity \e lon2 − \e lon1 indicates how many times and in 528 | * what sense the geodesic encircles the ellipsoid. 529 | * 530 | * Example, compute way points between JFK and Singapore Changi Airport using 531 | * geod_genposition(). In this example, the points are evenly spaced in arc 532 | * length (and so only approximately equally spaced in distance). This is 533 | * faster than using geod_position() and would be appropriate if drawing the 534 | * path on a map. 535 | @code{.c} 536 | struct geod_geodesic g; 537 | struct geod_geodesicline l; 538 | double lat[101], lon[101]; 539 | int i; 540 | geod_init(&g, 6378137, 1/298.257223563); 541 | geod_inverseline(&l, &g, 40.64, -73.78, 1.36, 103.99, 542 | GEOD_LATITUDE | GEOD_LONGITUDE); 543 | for (i = 0; i <= 100; ++i) { 544 | geod_genposition(&l, GEOD_ARCMODE, i * l.a13 * 0.01, 545 | lat + i, lon + i, 0, 0, 0, 0, 0, 0); 546 | printf("%.5f %.5f\n", lat[i], lon[i]); 547 | } 548 | @endcode 549 | **********************************************************************/ 550 | double GEOD_DLL geod_genposition(const struct geod_geodesicline* l, 551 | unsigned flags, double s12_a12, 552 | double* plat2, double* plon2, double* pazi2, 553 | double* ps12, double* pm12, 554 | double* pM12, double* pM21, 555 | double* pS12); 556 | 557 | /** 558 | * Specify position of point 3 in terms of distance. 559 | * 560 | * @param[in,out] l a pointer to the geod_geodesicline object. 561 | * @param[in] s13 the distance from point 1 to point 3 (meters); it 562 | * can be negative. 563 | * 564 | * This is only useful if the geod_geodesicline object has been constructed 565 | * with \e caps |= ::GEOD_DISTANCE_IN. 566 | **********************************************************************/ 567 | void GEOD_DLL geod_setdistance(struct geod_geodesicline* l, double s13); 568 | 569 | /** 570 | * Specify position of point 3 in terms of either distance or arc length. 571 | * 572 | * @param[in,out] l a pointer to the geod_geodesicline object. 573 | * @param[in] flags either ::GEOD_NOFLAGS or ::GEOD_ARCMODE to determining 574 | * the meaning of the \e s13_a13. 575 | * @param[in] s13_a13 if \e flags = ::GEOD_NOFLAGS, this is the distance 576 | * from point 1 to point 3 (meters); if \e flags = ::GEOD_ARCMODE, it is 577 | * the arc length from point 1 to point 3 (degrees); it can be 578 | * negative. 579 | * 580 | * If flags = ::GEOD_NOFLAGS, this calls geod_setdistance(). If flags = 581 | * ::GEOD_ARCMODE, the \e s13 is only set if the geod_geodesicline object has 582 | * been constructed with \e caps |= ::GEOD_DISTANCE. 583 | **********************************************************************/ 584 | void GEOD_DLL geod_gensetdistance(struct geod_geodesicline* l, 585 | unsigned flags, double s13_a13); 586 | 587 | /** 588 | * Initialize a geod_polygon object. 589 | * 590 | * @param[out] p a pointer to the object to be initialized. 591 | * @param[in] polylinep non-zero if a polyline instead of a polygon. 592 | * 593 | * If \e polylinep is zero, then the sequence of vertices and edges added by 594 | * geod_polygon_addpoint() and geod_polygon_addedge() define a polygon and 595 | * the perimeter and area are returned by geod_polygon_compute(). If \e 596 | * polylinep is non-zero, then the vertices and edges define a polyline and 597 | * only the perimeter is returned by geod_polygon_compute(). 598 | * 599 | * The area and perimeter are accumulated at two times the standard floating 600 | * point precision to guard against the loss of accuracy with many-sided 601 | * polygons. At any point you can ask for the perimeter and area so far. 602 | * 603 | * An example of the use of this function is given in the documentation for 604 | * geod_polygon_compute(). 605 | **********************************************************************/ 606 | void GEOD_DLL geod_polygon_init(struct geod_polygon* p, int polylinep); 607 | 608 | /** 609 | * Clear the polygon, allowing a new polygon to be started. 610 | * 611 | * @param[in,out] p a pointer to the object to be cleared. 612 | **********************************************************************/ 613 | void GEOD_DLL geod_polygon_clear(struct geod_polygon* p); 614 | 615 | /** 616 | * Add a point to the polygon or polyline. 617 | * 618 | * @param[in] g a pointer to the geod_geodesic object specifying the 619 | * ellipsoid. 620 | * @param[in,out] p a pointer to the geod_polygon object specifying the 621 | * polygon. 622 | * @param[in] lat the latitude of the point (degrees). 623 | * @param[in] lon the longitude of the point (degrees). 624 | * 625 | * \e g and \e p must have been initialized with calls to geod_init() and 626 | * geod_polygon_init(), respectively. The same \e g must be used for all the 627 | * points and edges in a polygon. \e lat should be in the range 628 | * [−90°, 90°]. 629 | * 630 | * An example of the use of this function is given in the documentation for 631 | * geod_polygon_compute(). 632 | **********************************************************************/ 633 | void GEOD_DLL geod_polygon_addpoint(const struct geod_geodesic* g, 634 | struct geod_polygon* p, 635 | double lat, double lon); 636 | 637 | /** 638 | * Add an edge to the polygon or polyline. 639 | * 640 | * @param[in] g a pointer to the geod_geodesic object specifying the 641 | * ellipsoid. 642 | * @param[in,out] p a pointer to the geod_polygon object specifying the 643 | * polygon. 644 | * @param[in] azi azimuth at current point (degrees). 645 | * @param[in] s distance from current point to next point (meters). 646 | * 647 | * \e g and \e p must have been initialized with calls to geod_init() and 648 | * geod_polygon_init(), respectively. The same \e g must be used for all the 649 | * points and edges in a polygon. This does nothing if no points have been 650 | * added yet. The \e lat and \e lon fields of \e p give the location of the 651 | * new vertex. 652 | **********************************************************************/ 653 | void GEOD_DLL geod_polygon_addedge(const struct geod_geodesic* g, 654 | struct geod_polygon* p, 655 | double azi, double s); 656 | 657 | /** 658 | * Return the results for a polygon. 659 | * 660 | * @param[in] g a pointer to the geod_geodesic object specifying the 661 | * ellipsoid. 662 | * @param[in] p a pointer to the geod_polygon object specifying the polygon. 663 | * @param[in] reverse if non-zero then clockwise (instead of 664 | * counter-clockwise) traversal counts as a positive area. 665 | * @param[in] sign if non-zero then return a signed result for the area if 666 | * the polygon is traversed in the "wrong" direction instead of returning 667 | * the area for the rest of the earth. 668 | * @param[out] pA pointer to the area of the polygon (meters2); 669 | * only set if \e polyline is non-zero in the call to geod_polygon_init(). 670 | * @param[out] pP pointer to the perimeter of the polygon or length of the 671 | * polyline (meters). 672 | * @return the number of points. 673 | * 674 | * The area and perimeter are accumulated at two times the standard floating 675 | * point precision to guard against the loss of accuracy with many-sided 676 | * polygons. Arbitrarily complex polygons are allowed. In the case of 677 | * self-intersecting polygons the area is accumulated "algebraically", e.g., 678 | * the areas of the 2 loops in a figure-8 polygon will partially cancel. 679 | * There's no need to "close" the polygon by repeating the first vertex. Set 680 | * \e pA or \e pP to zero, if you do not want the corresponding quantity 681 | * returned. 682 | * 683 | * More points can be added to the polygon after this call. 684 | * 685 | * Example, compute the perimeter and area of the geodesic triangle with 686 | * vertices (0°N,0°E), (0°N,90°E), (90°N,0°E). 687 | @code{.c} 688 | double A, P; 689 | int n; 690 | struct geod_geodesic g; 691 | struct geod_polygon p; 692 | geod_init(&g, 6378137, 1/298.257223563); 693 | geod_polygon_init(&p, 0); 694 | 695 | geod_polygon_addpoint(&g, &p, 0, 0); 696 | geod_polygon_addpoint(&g, &p, 0, 90); 697 | geod_polygon_addpoint(&g, &p, 90, 0); 698 | n = geod_polygon_compute(&g, &p, 0, 1, &A, &P); 699 | printf("%d %.8f %.3f\n", n, P, A); 700 | @endcode 701 | **********************************************************************/ 702 | unsigned GEOD_DLL geod_polygon_compute(const struct geod_geodesic* g, 703 | const struct geod_polygon* p, 704 | int reverse, int sign, 705 | double* pA, double* pP); 706 | 707 | /** 708 | * Return the results assuming a tentative final test point is added; 709 | * however, the data for the test point is not saved. This lets you report a 710 | * running result for the perimeter and area as the user moves the mouse 711 | * cursor. Ordinary floating point arithmetic is used to accumulate the data 712 | * for the test point; thus the area and perimeter returned are less accurate 713 | * than if geod_polygon_addpoint() and geod_polygon_compute() are used. 714 | * 715 | * @param[in] g a pointer to the geod_geodesic object specifying the 716 | * ellipsoid. 717 | * @param[in] p a pointer to the geod_polygon object specifying the polygon. 718 | * @param[in] lat the latitude of the test point (degrees). 719 | * @param[in] lon the longitude of the test point (degrees). 720 | * @param[in] reverse if non-zero then clockwise (instead of 721 | * counter-clockwise) traversal counts as a positive area. 722 | * @param[in] sign if non-zero then return a signed result for the area if 723 | * the polygon is traversed in the "wrong" direction instead of returning 724 | * the area for the rest of the earth. 725 | * @param[out] pA pointer to the area of the polygon (meters2); 726 | * only set if \e polyline is non-zero in the call to geod_polygon_init(). 727 | * @param[out] pP pointer to the perimeter of the polygon or length of the 728 | * polyline (meters). 729 | * @return the number of points. 730 | * 731 | * \e lat should be in the range [−90°, 90°]. 732 | **********************************************************************/ 733 | unsigned GEOD_DLL geod_polygon_testpoint(const struct geod_geodesic* g, 734 | const struct geod_polygon* p, 735 | double lat, double lon, 736 | int reverse, int sign, 737 | double* pA, double* pP); 738 | 739 | /** 740 | * Return the results assuming a tentative final test point is added via an 741 | * azimuth and distance; however, the data for the test point is not saved. 742 | * This lets you report a running result for the perimeter and area as the 743 | * user moves the mouse cursor. Ordinary floating point arithmetic is used 744 | * to accumulate the data for the test point; thus the area and perimeter 745 | * returned are less accurate than if geod_polygon_addedge() and 746 | * geod_polygon_compute() are used. 747 | * 748 | * @param[in] g a pointer to the geod_geodesic object specifying the 749 | * ellipsoid. 750 | * @param[in] p a pointer to the geod_polygon object specifying the polygon. 751 | * @param[in] azi azimuth at current point (degrees). 752 | * @param[in] s distance from current point to final test point (meters). 753 | * @param[in] reverse if non-zero then clockwise (instead of 754 | * counter-clockwise) traversal counts as a positive area. 755 | * @param[in] sign if non-zero then return a signed result for the area if 756 | * the polygon is traversed in the "wrong" direction instead of returning 757 | * the area for the rest of the earth. 758 | * @param[out] pA pointer to the area of the polygon (meters2); 759 | * only set if \e polyline is non-zero in the call to geod_polygon_init(). 760 | * @param[out] pP pointer to the perimeter of the polygon or length of the 761 | * polyline (meters). 762 | * @return the number of points. 763 | **********************************************************************/ 764 | unsigned GEOD_DLL geod_polygon_testedge(const struct geod_geodesic* g, 765 | const struct geod_polygon* p, 766 | double azi, double s, 767 | int reverse, int sign, 768 | double* pA, double* pP); 769 | 770 | /** 771 | * A simple interface for computing the area of a geodesic polygon. 772 | * 773 | * @param[in] g a pointer to the geod_geodesic object specifying the 774 | * ellipsoid. 775 | * @param[in] lats an array of latitudes of the polygon vertices (degrees). 776 | * @param[in] lons an array of longitudes of the polygon vertices (degrees). 777 | * @param[in] n the number of vertices. 778 | * @param[out] pA pointer to the area of the polygon (meters2). 779 | * @param[out] pP pointer to the perimeter of the polygon (meters). 780 | * 781 | * \e lats should be in the range [−90°, 90°]. 782 | * 783 | * Arbitrarily complex polygons are allowed. In the case self-intersecting 784 | * of polygons the area is accumulated "algebraically", e.g., the areas of 785 | * the 2 loops in a figure-8 polygon will partially cancel. There's no need 786 | * to "close" the polygon by repeating the first vertex. The area returned 787 | * is signed with counter-clockwise traversal being treated as positive. 788 | * 789 | * Example, compute the area of Antarctica: 790 | @code{.c} 791 | double 792 | lats[] = {-72.9, -71.9, -74.9, -74.3, -77.5, -77.4, -71.7, -65.9, -65.7, 793 | -66.6, -66.9, -69.8, -70.0, -71.0, -77.3, -77.9, -74.7}, 794 | lons[] = {-74, -102, -102, -131, -163, 163, 172, 140, 113, 795 | 88, 59, 25, -4, -14, -33, -46, -61}; 796 | struct geod_geodesic g; 797 | double A, P; 798 | geod_init(&g, 6378137, 1/298.257223563); 799 | geod_polygonarea(&g, lats, lons, (sizeof lats) / (sizeof lats[0]), &A, &P); 800 | printf("%.0f %.2f\n", A, P); 801 | @endcode 802 | **********************************************************************/ 803 | void GEOD_DLL geod_polygonarea(const struct geod_geodesic* g, 804 | double lats[], double lons[], int n, 805 | double* pA, double* pP); 806 | 807 | /** 808 | * mask values for the \e caps argument to geod_lineinit(). 809 | **********************************************************************/ 810 | enum geod_mask { 811 | GEOD_NONE = 0U, /**< Calculate nothing */ 812 | GEOD_LATITUDE = 1U<<7 | 0U, /**< Calculate latitude */ 813 | GEOD_LONGITUDE = 1U<<8 | 1U<<3, /**< Calculate longitude */ 814 | GEOD_AZIMUTH = 1U<<9 | 0U, /**< Calculate azimuth */ 815 | GEOD_DISTANCE = 1U<<10 | 1U<<0, /**< Calculate distance */ 816 | GEOD_DISTANCE_IN = 1U<<11 | 1U<<0 | 1U<<1,/**< Allow distance as input */ 817 | GEOD_REDUCEDLENGTH= 1U<<12 | 1U<<0 | 1U<<2,/**< Calculate reduced length */ 818 | GEOD_GEODESICSCALE= 1U<<13 | 1U<<0 | 1U<<2,/**< Calculate geodesic scale */ 819 | GEOD_AREA = 1U<<14 | 1U<<4, /**< Calculate reduced length */ 820 | GEOD_ALL = 0x7F80U| 0x1FU /**< Calculate everything */ 821 | }; 822 | 823 | /** 824 | * flag values for the \e flags argument to geod_gendirect() and 825 | * geod_genposition() 826 | **********************************************************************/ 827 | enum geod_flags { 828 | GEOD_NOFLAGS = 0U, /**< No flags */ 829 | GEOD_ARCMODE = 1U<<0, /**< Position given in terms of arc distance */ 830 | GEOD_LONG_UNROLL = 1U<<15 /**< Unroll the longitude */ 831 | }; 832 | 833 | #if defined(__cplusplus) 834 | } 835 | #endif 836 | 837 | #endif 838 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (TESTS geodtest geodsigntest) 2 | 3 | if (NOT MSVC) 4 | set (MATH_LIBS m) 5 | endif () 6 | 7 | foreach (TEST geodtest) 8 | add_executable (${TEST} ${TEST}.c) 9 | target_link_libraries (${TEST} ${LIBNAME} ${MATH_LIBS}) 10 | endforeach () 11 | 12 | foreach (TEST geodsigntest) 13 | add_executable (${TEST} ${TEST}.c) 14 | # geodsigntest includes geodesic.c directly so it doesn't need to link 15 | # against the library. 16 | target_link_libraries (${TEST} ${MATH_LIBS}) 17 | target_include_directories (${TEST} PRIVATE ../src) 18 | endforeach () 19 | -------------------------------------------------------------------------------- /tests/geodsigntest.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file geodsigntest.c 3 | * \brief Test treatment of +/-0 and +/-180 4 | * 5 | * Copyright (c) Charles Karney (2022) and licensed 6 | * under the MIT/X11 License. For more information, see 7 | * https://geographiclib.sourceforge.io/ 8 | **********************************************************************/ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* Include the source file for the library directly so we can access the 15 | * internal (static) functions. */ 16 | #include "geodesic.c" 17 | 18 | /* Define function names with the "geod_" prefix. */ 19 | #define geod_Init Init 20 | #define geod_sum sumx 21 | #define geod_AngNormalize AngNormalize 22 | #define geod_AngDiff AngDiff 23 | #define geod_AngRound AngRound 24 | #define geod_sincosd sincosdx 25 | #define geod_atan2d atan2dx 26 | 27 | typedef double T; 28 | 29 | #if !defined(__cplusplus) 30 | #define nullptr 0 31 | #endif 32 | 33 | #if !defined(OLD_BUGGY_REMQUO) 34 | /* 35 | * glibc prior to version 2.22 had a bug in remquo. This was reported in 2014 36 | * and fixed in 2015. See 37 | * https://sourceware.org/bugzilla/show_bug.cgi?id=17569 38 | * 39 | * The bug causes some of the tests here to fail. The failures aren't terribly 40 | * serious (just a loss of accuracy). If you're still using the buggy glibc, 41 | * then define OLD_BUGGY_REMQUO to be 1. 42 | */ 43 | #define OLD_BUGGY_REMQUO 0 44 | #endif 45 | 46 | static const T wgs84_a = 6378137, wgs84_f = 1/298.257223563; /* WGS84 */ 47 | 48 | static int equiv(T x, T y) { 49 | return ( (isnan(x) && isnan(y)) || (x == y && signbit(x) == signbit(y)) ) ? 50 | 0 : 1; 51 | } 52 | 53 | static int checkEquals(T x, T y, T d) { 54 | if (fabs(x - y) <= d) 55 | return 0; 56 | printf("checkEquals fails: %.7g != %.7g +/- %.7g\n", x, y, d); 57 | return 1; 58 | } 59 | 60 | /* use "do { } while (false)" idiom so it can be punctuated like a 61 | * statement. */ 62 | 63 | #define check(expr, r) do { \ 64 | T s = (T)(r), t = expr; \ 65 | if (equiv(s, t)) { \ 66 | printf("Line %d : %s != %s (%g)\n", \ 67 | __LINE__, #expr, #r, t); \ 68 | ++n; \ 69 | } \ 70 | } while (0) 71 | 72 | #define checksincosd(x, s, c) do { \ 73 | T sx, cx; \ 74 | geod_sincosd(x, &sx, &cx); \ 75 | if (equiv(s, sx)) { \ 76 | printf("Line %d: sin(%g) != %g (%g)\n", \ 77 | __LINE__, x, s, sx); \ 78 | ++n; \ 79 | } \ 80 | if (equiv(c, cx)) { \ 81 | printf("Line %d: cos(%g) != %g (%g)\n", \ 82 | __LINE__, x, c, cx); \ 83 | ++n; \ 84 | } \ 85 | } while (0) 86 | 87 | int main() { 88 | T inf = INFINITY, 89 | nan = NAN, 90 | eps = DBL_EPSILON, 91 | e; 92 | int n = 0; 93 | geod_Init(); 94 | 95 | check( geod_AngRound(-eps/32), -eps/32); 96 | check( geod_AngRound(-eps/64), -0.0 ); 97 | check( geod_AngRound(- 0.0 ), -0.0 ); 98 | check( geod_AngRound( 0.0 ), +0.0 ); 99 | check( geod_AngRound( eps/64), +0.0 ); 100 | check( geod_AngRound( eps/32), +eps/32); 101 | check( geod_AngRound((1-2*eps)/64), (1-2*eps)/64); 102 | check( geod_AngRound((1-eps )/64), 1.0 /64); 103 | check( geod_AngRound((1-eps/2)/64), 1.0 /64); 104 | check( geod_AngRound((1-eps/4)/64), 1.0 /64); 105 | check( geod_AngRound( 1.0 /64), 1.0 /64); 106 | check( geod_AngRound((1+eps/2)/64), 1.0 /64); 107 | check( geod_AngRound((1+eps )/64), 1.0 /64); 108 | check( geod_AngRound((1+2*eps)/64), (1+2*eps)/64); 109 | check( geod_AngRound((1-eps )/32), (1-eps )/32); 110 | check( geod_AngRound((1-eps/2)/32), 1.0 /32); 111 | check( geod_AngRound((1-eps/4)/32), 1.0 /32); 112 | check( geod_AngRound( 1.0 /32), 1.0 /32); 113 | check( geod_AngRound((1+eps/2)/32), 1.0 /32); 114 | check( geod_AngRound((1+eps )/32), (1+eps )/32); 115 | check( geod_AngRound((1-eps )/16), (1-eps )/16); 116 | check( geod_AngRound((1-eps/2)/16), (1-eps/2)/16); 117 | check( geod_AngRound((1-eps/4)/16), 1.0 /16); 118 | check( geod_AngRound( 1.0 /16), 1.0 /16); 119 | check( geod_AngRound((1+eps/4)/16), 1.0 /16); 120 | check( geod_AngRound((1+eps/2)/16), 1.0 /16); 121 | check( geod_AngRound((1+eps )/16), (1+eps )/16); 122 | check( geod_AngRound((1-eps )/ 8), (1-eps )/ 8); 123 | check( geod_AngRound((1-eps/2)/ 8), (1-eps/2)/ 8); 124 | check( geod_AngRound((1-eps/4)/ 8), 1.0 / 8); 125 | check( geod_AngRound((1+eps/2)/ 8), 1.0 / 8); 126 | check( geod_AngRound((1+eps )/ 8), (1+eps )/ 8); 127 | check( geod_AngRound( 1-eps ), 1-eps ); 128 | check( geod_AngRound( 1-eps/2 ), 1-eps/2 ); 129 | check( geod_AngRound( 1-eps/4 ), 1 ); 130 | check( geod_AngRound( 1.0 ), 1 ); 131 | check( geod_AngRound( 1+eps/4 ), 1 ); 132 | check( geod_AngRound( 1+eps/2 ), 1 ); 133 | check( geod_AngRound( 1+eps ), 1+ eps ); 134 | check( geod_AngRound( 90.0-64*eps), 90-64*eps ); 135 | check( geod_AngRound( 90.0-32*eps), 90 ); 136 | check( geod_AngRound( 90.0 ), 90 ); 137 | 138 | checksincosd(- inf, nan, nan); 139 | #if !OLD_BUGGY_REMQUO 140 | checksincosd(-810.0, -1.0, +0.0); 141 | #endif 142 | checksincosd(-720.0, -0.0, +1.0); 143 | checksincosd(-630.0, +1.0, +0.0); 144 | checksincosd(-540.0, -0.0, -1.0); 145 | checksincosd(-450.0, -1.0, +0.0); 146 | checksincosd(-360.0, -0.0, +1.0); 147 | checksincosd(-270.0, +1.0, +0.0); 148 | checksincosd(-180.0, -0.0, -1.0); 149 | checksincosd(- 90.0, -1.0, +0.0); 150 | checksincosd(- 0.0, -0.0, +1.0); 151 | checksincosd(+ 0.0, +0.0, +1.0); 152 | checksincosd(+ 90.0, +1.0, +0.0); 153 | checksincosd(+180.0, +0.0, -1.0); 154 | checksincosd(+270.0, -1.0, +0.0); 155 | checksincosd(+360.0, +0.0, +1.0); 156 | checksincosd(+450.0, +1.0, +0.0); 157 | checksincosd(+540.0, +0.0, -1.0); 158 | checksincosd(+630.0, -1.0, +0.0); 159 | checksincosd(+720.0, +0.0, +1.0); 160 | #if !OLD_BUGGY_REMQUO 161 | checksincosd(+810.0, +1.0, +0.0); 162 | #endif 163 | checksincosd(+ inf, nan, nan); 164 | checksincosd( nan, nan, nan); 165 | 166 | #if !OLD_BUGGY_REMQUO 167 | { 168 | T s1, c1, s2, c2, s3, c3; 169 | geod_sincosd( 9.0, &s1, &c1); 170 | geod_sincosd( 81.0, &s2, &c2); 171 | geod_sincosd(-123456789.0, &s3, &c3); 172 | if ( equiv(s1, c2) + equiv(s1, s3) + equiv(c1, s2) + equiv(c1, -c3) ) { 173 | printf("Line %d : sincos accuracy fail\n", __LINE__); 174 | ++n; 175 | } 176 | } 177 | #endif 178 | 179 | check( geod_atan2d(+0.0 , -0.0 ), +180 ); 180 | check( geod_atan2d(-0.0 , -0.0 ), -180 ); 181 | check( geod_atan2d(+0.0 , +0.0 ), +0.0 ); 182 | check( geod_atan2d(-0.0 , +0.0 ), -0.0 ); 183 | check( geod_atan2d(+0.0 , -1.0 ), +180 ); 184 | check( geod_atan2d(-0.0 , -1.0 ), -180 ); 185 | check( geod_atan2d(+0.0 , +1.0 ), +0.0 ); 186 | check( geod_atan2d(-0.0 , +1.0 ), -0.0 ); 187 | check( geod_atan2d(-1.0 , +0.0 ), -90 ); 188 | check( geod_atan2d(-1.0 , -0.0 ), -90 ); 189 | check( geod_atan2d(+1.0 , +0.0 ), +90 ); 190 | check( geod_atan2d(+1.0 , -0.0 ), +90 ); 191 | check( geod_atan2d(+1.0 , -inf), +180 ); 192 | check( geod_atan2d(-1.0 , -inf), -180 ); 193 | check( geod_atan2d(+1.0 , +inf), +0.0 ); 194 | check( geod_atan2d(-1.0 , +inf), -0.0 ); 195 | check( geod_atan2d( +inf, +1.0 ), +90 ); 196 | check( geod_atan2d( +inf, -1.0 ), +90 ); 197 | check( geod_atan2d( -inf, +1.0 ), -90 ); 198 | check( geod_atan2d( -inf, -1.0 ), -90 ); 199 | check( geod_atan2d( +inf, -inf), +135 ); 200 | check( geod_atan2d( -inf, -inf), -135 ); 201 | check( geod_atan2d( +inf, +inf), +45 ); 202 | check( geod_atan2d( -inf, +inf), -45 ); 203 | check( geod_atan2d( nan, +1.0 ), nan ); 204 | check( geod_atan2d(+1.0 , nan), nan ); 205 | 206 | { 207 | T s = 7e-16; 208 | if ( equiv( geod_atan2d(s, -1.0), 180 - geod_atan2d(s, 1.0) ) ) { 209 | printf("Line %d : atan2d accuracy fail\n", __LINE__); 210 | ++n; 211 | } 212 | } 213 | 214 | check( geod_sum(+9.0, -9.0, &e), +0.0 ); 215 | check( geod_sum(-9.0, +9.0, &e), +0.0 ); 216 | check( geod_sum(-0.0, +0.0, &e), +0.0 ); 217 | check( geod_sum(+0.0, -0.0, &e), +0.0 ); 218 | check( geod_sum(-0.0, -0.0, &e), -0.0 ); 219 | check( geod_sum(+0.0, +0.0, &e), +0.0 ); 220 | 221 | check( geod_AngNormalize(-900.0), -180 ); 222 | check( geod_AngNormalize(-720.0), -0.0 ); 223 | check( geod_AngNormalize(-540.0), -180 ); 224 | check( geod_AngNormalize(-360.0), -0.0 ); 225 | check( geod_AngNormalize(-180.0), -180 ); 226 | check( geod_AngNormalize( -0.0), -0.0 ); 227 | check( geod_AngNormalize( +0.0), +0.0 ); 228 | check( geod_AngNormalize( 180.0), +180 ); 229 | check( geod_AngNormalize( 360.0), +0.0 ); 230 | check( geod_AngNormalize( 540.0), +180 ); 231 | check( geod_AngNormalize( 720.0), +0.0 ); 232 | check( geod_AngNormalize( 900.0), +180 ); 233 | 234 | check( geod_AngDiff(+ 0.0, + 0.0, &e), +0.0 ); 235 | check( geod_AngDiff(+ 0.0, - 0.0, &e), -0.0 ); 236 | check( geod_AngDiff(- 0.0, + 0.0, &e), +0.0 ); 237 | check( geod_AngDiff(- 0.0, - 0.0, &e), +0.0 ); 238 | check( geod_AngDiff(+ 5.0, +365.0, &e), +0.0 ); 239 | check( geod_AngDiff(+365.0, + 5.0, &e), -0.0 ); 240 | check( geod_AngDiff(+ 5.0, +185.0, &e), +180.0 ); 241 | check( geod_AngDiff(+185.0, + 5.0, &e), -180.0 ); 242 | check( geod_AngDiff( +eps , +180.0, &e), +180.0 ); 243 | check( geod_AngDiff( -eps , +180.0, &e), -180.0 ); 244 | check( geod_AngDiff( +eps , -180.0, &e), +180.0 ); 245 | check( geod_AngDiff( -eps , -180.0, &e), -180.0 ); 246 | 247 | { 248 | T x = 138 + 128 * eps, y = -164; 249 | if ( equiv( geod_AngDiff(x, y, &e), 58 - 128 * eps ) ) { 250 | printf("Line %d : AngDiff accuracy fail\n", __LINE__); 251 | ++n; 252 | } 253 | } 254 | 255 | { 256 | /* azimuth of geodesic line with points on equator determined by signs of 257 | * latitude 258 | * lat1 lat2 azi1/2 */ 259 | T C[2][3] = { 260 | { +0.0, -0.0, 180 }, 261 | { -0.0, +0.0, 0 } 262 | }; 263 | struct geod_geodesic g; 264 | geod_init(&g, wgs84_a, wgs84_f); 265 | T azi1, azi2; 266 | int i = 0; 267 | for (int k = 0; k < 2; ++k) { 268 | geod_inverse(&g, C[k][0], 0.0, C[k][1], 0.0, nullptr, &azi1, &azi2); 269 | if ( equiv(azi1, C[k][2]) + equiv(azi2, C[k][2]) ) ++i; 270 | } 271 | if (i) { 272 | printf("Line %d: inverse coincident points on equator fail\n", __LINE__); 273 | ++n; 274 | } 275 | } 276 | 277 | { 278 | /* Does the nearly antipodal equatorial solution go north or south? 279 | * lat1 lat2 azi1 azi2 */ 280 | T C[2][4] = { 281 | { +0.0, +0.0, 56, 124}, 282 | { -0.0, -0.0, 124, 56} 283 | }; 284 | struct geod_geodesic g; 285 | geod_init(&g, wgs84_a, wgs84_f); 286 | T azi1, azi2; 287 | int i = 0; 288 | for (int k = 0; k < 2; ++k) { 289 | geod_inverse(&g, C[k][0], 0.0, C[k][1], 179.5, nullptr, &azi1, &azi2); 290 | i += checkEquals(azi1, C[k][2], 1) + checkEquals(azi2, C[k][3], 1); 291 | } 292 | if (i) { 293 | printf("Line %d: inverse nearly antipodal points on equator fail\n", 294 | __LINE__);; 295 | ++n; 296 | } 297 | } 298 | 299 | { 300 | /* How does the exact antipodal equatorial path go N/S + E/W 301 | * lat1 lat2 lon2 azi1 azi2 */ 302 | T C[4][5] = { 303 | { +0.0, +0.0, +180, +0.0, +180}, 304 | { -0.0, -0.0, +180, +180, +0.0}, 305 | { +0.0, +0.0, -180, -0.0, -180}, 306 | { -0.0, -0.0, -180, -180, -0.0} 307 | }; 308 | struct geod_geodesic g; 309 | geod_init(&g, wgs84_a, wgs84_f); 310 | T azi1, azi2; 311 | int i = 0; 312 | for (int k = 0; k < 4; ++k) { 313 | geod_inverse(&g, C[k][0], 0.0, C[k][1], C[k][2], nullptr, &azi1, &azi2); 314 | if ( equiv(azi1, C[k][3]) + equiv(azi2, C[k][4]) ) ++i; 315 | } 316 | if (i) { 317 | printf("Line %d: inverse antipodal points on equator fail\n", 318 | __LINE__); 319 | ++n; 320 | } 321 | } 322 | 323 | { 324 | /* Antipodal points on the equator with prolate ellipsoid 325 | * lon2 azi1/2 */ 326 | T C[2][2] = { 327 | { +180, +90 }, 328 | { -180, -90 } 329 | }; 330 | struct geod_geodesic g; 331 | geod_init(&g, 6.4e6, -1/300.0); 332 | T azi1, azi2; 333 | int i = 0; 334 | for (int k = 0; k < 2; ++k) { 335 | geod_inverse(&g, 0.0, 0.0, 0.0, C[k][0], nullptr, &azi1, &azi2); 336 | if ( equiv(azi1, C[k][1]) + equiv(azi2, C[k][1]) ) ++i; 337 | } 338 | if (i) { 339 | printf("Line %d: inverse antipodal points on equator, prolate, fail\n", 340 | __LINE__); 341 | ++n; 342 | } 343 | } 344 | 345 | { 346 | /* azimuths = +/-0 and +/-180 for the direct problem 347 | * azi1, lon2, azi2 */ 348 | T C[4][3] = { 349 | { +0.0, +180, +180 }, 350 | { -0.0, -180, -180 }, 351 | { +180 , +180, +0.0 }, 352 | { -180 , -180, -0.0 } 353 | }; 354 | struct geod_geodesic g; 355 | geod_init(&g, wgs84_a, wgs84_f); 356 | T lon2, azi2; 357 | int i = 0; 358 | for (int k = 0; k < 4; ++k) { 359 | geod_gendirect(&g, 0.0, 0.0, C[k][0], GEOD_LONG_UNROLL, 15e6, 360 | nullptr, &lon2, &azi2, 361 | nullptr, nullptr, nullptr, nullptr, nullptr); 362 | if ( equiv(lon2, C[k][1]) + equiv(azi2, C[k][2]) ) ++i; 363 | } 364 | if (i) { 365 | printf("Line %d: direct azi1 = +/-0 +/-180, fail\n", __LINE__); 366 | ++n; 367 | } 368 | } 369 | 370 | if (n) { 371 | printf("%d %s%s\n", n, "failure", (n > 1 ? "s" : "")); 372 | return 1; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /tests/geodtest.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file geodtest.c 3 | * \brief Test suite for the geodesic routines in C 4 | * 5 | * Run these tests by configuring with cmake and running "make test". 6 | * 7 | * Copyright (c) Charles Karney (2015-2022) and licensed 8 | * under the MIT/X11 License. For more information, see 9 | * https://geographiclib.sourceforge.io/ 10 | **********************************************************************/ 11 | 12 | #include "geodesic.h" 13 | #include 14 | #include 15 | 16 | #if defined(_MSC_VER) 17 | /* Squelch warnings about assignment within conditional expression */ 18 | # pragma warning (disable: 4706) 19 | #endif 20 | 21 | #if !defined(__cplusplus) 22 | #define nullptr 0 23 | #endif 24 | 25 | static const double wgs84_a = 6378137, wgs84_f = 1/298.257223563; /* WGS84 */ 26 | 27 | static int checkEquals(double x, double y, double d) { 28 | if (fabs(x - y) <= d) 29 | return 0; 30 | printf("checkEquals fails: %.7g != %.7g +/- %.7g\n", x, y, d); 31 | return 1; 32 | } 33 | 34 | static int checkNaN(double x) { 35 | /* cppcheck-suppress duplicateExpression */ 36 | if (isnan(x)) 37 | return 0; 38 | printf("checkNaN fails: %.7g\n", x); 39 | return 1; 40 | } 41 | 42 | static const int ncases = 20; 43 | static const double testcases[20][12] = { 44 | {35.60777, -139.44815, 111.098748429560326, 45 | -11.17491, -69.95921, 129.289270889708762, 46 | 8935244.5604818305, 80.50729714281974, 6273170.2055303837, 47 | 0.16606318447386067, 0.16479116945612937, 12841384694976.432}, 48 | {55.52454, 106.05087, 22.020059880982801, 49 | 77.03196, 197.18234, 109.112041110671519, 50 | 4105086.1713924406, 36.892740690445894, 3828869.3344387607, 51 | 0.80076349608092607, 0.80101006984201008, 61674961290615.615}, 52 | {-21.97856, 142.59065, -32.44456876433189, 53 | 41.84138, 98.56635, -41.84359951440466, 54 | 8394328.894657671, 75.62930491011522, 6161154.5773110616, 55 | 0.24816339233950381, 0.24930251203627892, -6637997720646.717}, 56 | {-66.99028, 112.2363, 173.73491240878403, 57 | -12.70631, 285.90344, 2.512956620913668, 58 | 11150344.2312080241, 100.278634181155759, 6289939.5670446687, 59 | -0.17199490274700385, -0.17722569526345708, -121287239862139.744}, 60 | {-17.42761, 173.34268, -159.033557661192928, 61 | -15.84784, 5.93557, -20.787484651536988, 62 | 16076603.1631180673, 144.640108810286253, 3732902.1583877189, 63 | -0.81273638700070476, -0.81299800519154474, 97825992354058.708}, 64 | {32.84994, 48.28919, 150.492927788121982, 65 | -56.28556, 202.29132, 48.113449399816759, 66 | 16727068.9438164461, 150.565799985466607, 3147838.1910180939, 67 | -0.87334918086923126, -0.86505036767110637, -72445258525585.010}, 68 | {6.96833, 52.74123, 92.581585386317712, 69 | -7.39675, 206.17291, 90.721692165923907, 70 | 17102477.2496958388, 154.147366239113561, 2772035.6169917581, 71 | -0.89991282520302447, -0.89986892177110739, -1311796973197.995}, 72 | {-50.56724, -16.30485, -105.439679907590164, 73 | -33.56571, -94.97412, -47.348547835650331, 74 | 6455670.5118668696, 58.083719495371259, 5409150.7979815838, 75 | 0.53053508035997263, 0.52988722644436602, 41071447902810.047}, 76 | {-58.93002, -8.90775, 140.965397902500679, 77 | -8.91104, 133.13503, 19.255429433416599, 78 | 11756066.0219864627, 105.755691241406877, 6151101.2270708536, 79 | -0.26548622269867183, -0.27068483874510741, -86143460552774.735}, 80 | {-68.82867, -74.28391, 93.774347763114881, 81 | -50.63005, -8.36685, 34.65564085411343, 82 | 3956936.926063544, 35.572254987389284, 3708890.9544062657, 83 | 0.81443963736383502, 0.81420859815358342, -41845309450093.787}, 84 | {-10.62672, -32.0898, -86.426713286747751, 85 | 5.883, -134.31681, -80.473780971034875, 86 | 11470869.3864563009, 103.387395634504061, 6184411.6622659713, 87 | -0.23138683500430237, -0.23155097622286792, 4198803992123.548}, 88 | {-21.76221, 166.90563, 29.319421206936428, 89 | 48.72884, 213.97627, 43.508671946410168, 90 | 9098627.3986554915, 81.963476716121964, 6299240.9166992283, 91 | 0.13965943368590333, 0.14152969707656796, 10024709850277.476}, 92 | {-19.79938, -174.47484, 71.167275780171533, 93 | -11.99349, -154.35109, 65.589099775199228, 94 | 2319004.8601169389, 20.896611684802389, 2267960.8703918325, 95 | 0.93427001867125849, 0.93424887135032789, -3935477535005.785}, 96 | {-11.95887, -116.94513, 92.712619830452549, 97 | 4.57352, 7.16501, 78.64960934409585, 98 | 13834722.5801401374, 124.688684161089762, 5228093.177931598, 99 | -0.56879356755666463, -0.56918731952397221, -9919582785894.853}, 100 | {-87.85331, 85.66836, -65.120313040242748, 101 | 66.48646, 16.09921, -4.888658719272296, 102 | 17286615.3147144645, 155.58592449699137, 2635887.4729110181, 103 | -0.90697975771398578, -0.91095608883042767, 42667211366919.534}, 104 | {1.74708, 128.32011, -101.584843631173858, 105 | -11.16617, 11.87109, -86.325793296437476, 106 | 12942901.1241347408, 116.650512484301857, 5682744.8413270572, 107 | -0.44857868222697644, -0.44824490340007729, 10763055294345.653}, 108 | {-25.72959, -144.90758, -153.647468693117198, 109 | -57.70581, -269.17879, -48.343983158876487, 110 | 9413446.7452453107, 84.664533838404295, 6356176.6898881281, 111 | 0.09492245755254703, 0.09737058264766572, 74515122850712.444}, 112 | {-41.22777, 122.32875, 14.285113402275739, 113 | -7.57291, 130.37946, 10.805303085187369, 114 | 3812686.035106021, 34.34330804743883, 3588703.8812128856, 115 | 0.82605222593217889, 0.82572158200920196, -2456961531057.857}, 116 | {11.01307, 138.25278, 79.43682622782374, 117 | 6.62726, 247.05981, 103.708090215522657, 118 | 11911190.819018408, 107.341669954114577, 6070904.722786735, 119 | -0.29767608923657404, -0.29785143390252321, 17121631423099.696}, 120 | {-29.47124, 95.14681, -163.779130441688382, 121 | -27.46601, -69.15955, -15.909335945554969, 122 | 13487015.8381145492, 121.294026715742277, 5481428.9945736388, 123 | -0.51527225545373252, -0.51556587964721788, 104679964020340.318}}; 124 | 125 | static int testinverse() { 126 | double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; 127 | double azi1a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; 128 | struct geod_geodesic g; 129 | int i, result = 0; 130 | geod_init(&g, wgs84_a, wgs84_f); 131 | for (i = 0; i < ncases; ++i) { 132 | lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; 133 | lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; 134 | s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; 135 | M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; 136 | a12a = geod_geninverse(&g, lat1, lon1, lat2, lon2, &s12a, &azi1a, &azi2a, 137 | &m12a, &M12a, &M21a, &S12a); 138 | result += checkEquals(azi1, azi1a, 1e-13); 139 | result += checkEquals(azi2, azi2a, 1e-13); 140 | result += checkEquals(s12, s12a, 1e-8); 141 | result += checkEquals(a12, a12a, 1e-13); 142 | result += checkEquals(m12, m12a, 1e-8); 143 | result += checkEquals(M12, M12a, 1e-15); 144 | result += checkEquals(M21, M21a, 1e-15); 145 | result += checkEquals(S12, S12a, 0.1); 146 | } 147 | return result; 148 | } 149 | 150 | static int testdirect() { 151 | double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; 152 | double lat2a, lon2a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; 153 | struct geod_geodesic g; 154 | int i, result = 0; 155 | unsigned flags = GEOD_LONG_UNROLL; 156 | geod_init(&g, wgs84_a, wgs84_f); 157 | for (i = 0; i < ncases; ++i) { 158 | lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; 159 | lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; 160 | s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; 161 | M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; 162 | a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, s12, 163 | &lat2a, &lon2a, &azi2a, &s12a, &m12a, &M12a, &M21a, &S12a); 164 | result += checkEquals(lat2, lat2a, 1e-13); 165 | result += checkEquals(lon2, lon2a, 1e-13); 166 | result += checkEquals(azi2, azi2a, 1e-13); 167 | result += checkEquals(s12, s12a, 0); 168 | result += checkEquals(a12, a12a, 1e-13); 169 | result += checkEquals(m12, m12a, 1e-8); 170 | result += checkEquals(M12, M12a, 1e-15); 171 | result += checkEquals(M21, M21a, 1e-15); 172 | result += checkEquals(S12, S12a, 0.1); 173 | } 174 | return result; 175 | } 176 | 177 | static int testarcdirect() { 178 | double lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; 179 | double lat2a, lon2a, azi2a, s12a, a12a, m12a, M12a, M21a, S12a; 180 | struct geod_geodesic g; 181 | int i, result = 0; 182 | unsigned flags = GEOD_ARCMODE | GEOD_LONG_UNROLL; 183 | geod_init(&g, wgs84_a, wgs84_f); 184 | for (i = 0; i < ncases; ++i) { 185 | lat1 = testcases[i][0]; lon1 = testcases[i][1]; azi1 = testcases[i][2]; 186 | lat2 = testcases[i][3]; lon2 = testcases[i][4]; azi2 = testcases[i][5]; 187 | s12 = testcases[i][6]; a12 = testcases[i][7]; m12 = testcases[i][8]; 188 | M12 = testcases[i][9]; M21 = testcases[i][10]; S12 = testcases[i][11]; 189 | a12a = geod_gendirect(&g, lat1, lon1, azi1, flags, a12, &lat2a, &lon2a, 190 | &azi2a, &s12a, &m12a, &M12a, &M21a, &S12a); 191 | result += checkEquals(lat2, lat2a, 1e-13); 192 | result += checkEquals(lon2, lon2a, 1e-13); 193 | result += checkEquals(azi2, azi2a, 1e-13); 194 | result += checkEquals(s12, s12a, 1e-8); 195 | result += checkEquals(a12, a12a, 0); 196 | result += checkEquals(s12, s12a, 1e-8); 197 | result += checkEquals(m12, m12a, 1e-8); 198 | result += checkEquals(M12, M12a, 1e-15); 199 | result += checkEquals(M21, M21a, 1e-15); 200 | result += checkEquals(S12, S12a, 0.1); 201 | } 202 | return result; 203 | } 204 | 205 | static int GeodSolve0() { 206 | double azi1, azi2, s12; 207 | struct geod_geodesic g; 208 | int result = 0; 209 | geod_init(&g, wgs84_a, wgs84_f); 210 | geod_inverse(&g, 40.6, -73.8, 49.01666667, 2.55, &s12, &azi1, &azi2); 211 | result += checkEquals(azi1, 53.47022, 0.5e-5); 212 | result += checkEquals(azi2, 111.59367, 0.5e-5); 213 | result += checkEquals(s12, 5853226, 0.5); 214 | return result; 215 | } 216 | 217 | static int GeodSolve1() { 218 | double lat2, lon2, azi2; 219 | struct geod_geodesic g; 220 | int result = 0; 221 | geod_init(&g, wgs84_a, wgs84_f); 222 | geod_direct(&g, 40.63972222, -73.77888889, 53.5, 5850e3, 223 | &lat2, &lon2, &azi2); 224 | result += checkEquals(lat2, 49.01467, 0.5e-5); 225 | result += checkEquals(lon2, 2.56106, 0.5e-5); 226 | result += checkEquals(azi2, 111.62947, 0.5e-5); 227 | return result; 228 | } 229 | 230 | static int GeodSolve2() { 231 | /* Check fix for antipodal prolate bug found 2010-09-04 */ 232 | double azi1, azi2, s12; 233 | struct geod_geodesic g; 234 | int result = 0; 235 | geod_init(&g, 6.4e6, -1/150.0); 236 | geod_inverse(&g, 0.07476, 0, -0.07476, 180, &s12, &azi1, &azi2); 237 | result += checkEquals(azi1, 90.00078, 0.5e-5); 238 | result += checkEquals(azi2, 90.00078, 0.5e-5); 239 | result += checkEquals(s12, 20106193, 0.5); 240 | geod_inverse(&g, 0.1, 0, -0.1, 180, &s12, &azi1, &azi2); 241 | result += checkEquals(azi1, 90.00105, 0.5e-5); 242 | result += checkEquals(azi2, 90.00105, 0.5e-5); 243 | result += checkEquals(s12, 20106193, 0.5); 244 | return result; 245 | } 246 | 247 | static int GeodSolve4() { 248 | /* Check fix for short line bug found 2010-05-21 */ 249 | double s12; 250 | struct geod_geodesic g; 251 | int result = 0; 252 | geod_init(&g, wgs84_a, wgs84_f); 253 | geod_inverse(&g, 36.493349428792, 0, 36.49334942879201, .0000008, 254 | &s12, nullptr, nullptr); 255 | result += checkEquals(s12, 0.072, 0.5e-3); 256 | return result; 257 | } 258 | 259 | static int GeodSolve5() { 260 | /* Check fix for point2=pole bug found 2010-05-03 */ 261 | double lat2, lon2, azi2; 262 | struct geod_geodesic g; 263 | int result = 0; 264 | geod_init(&g, wgs84_a, wgs84_f); 265 | geod_direct(&g, 0.01777745589997, 30, 0, 10e6, &lat2, &lon2, &azi2); 266 | result += checkEquals(lat2, 90, 0.5e-5); 267 | if (lon2 < 0) { 268 | result += checkEquals(lon2, -150, 0.5e-5); 269 | result += checkEquals(fabs(azi2), 180, 0.5e-5); 270 | } else { 271 | result += checkEquals(lon2, 30, 0.5e-5); 272 | result += checkEquals(azi2, 0, 0.5e-5); 273 | } 274 | return result; 275 | } 276 | 277 | static int GeodSolve6() { 278 | /* Check fix for volatile sbet12a bug found 2011-06-25 (gcc 4.4.4 279 | * x86 -O3). Found again on 2012-03-27 with tdm-mingw32 (g++ 4.6.1). */ 280 | double s12; 281 | struct geod_geodesic g; 282 | int result = 0; 283 | geod_init(&g, wgs84_a, wgs84_f); 284 | geod_inverse(&g, 88.202499451857, 0, 285 | -88.202499451857, 179.981022032992859592, 286 | &s12, nullptr, nullptr); 287 | result += checkEquals(s12, 20003898.214, 0.5e-3); 288 | geod_inverse(&g, 89.262080389218, 0, 289 | -89.262080389218, 179.992207982775375662, 290 | &s12, nullptr, nullptr); 291 | result += checkEquals(s12, 20003925.854, 0.5e-3); 292 | geod_inverse(&g, 89.333123580033, 0, 293 | -89.333123580032997687, 179.99295812360148422, 294 | &s12, nullptr, nullptr); 295 | result += checkEquals(s12, 20003926.881, 0.5e-3); 296 | return result; 297 | } 298 | 299 | static int GeodSolve9() { 300 | /* Check fix for volatile x bug found 2011-06-25 (gcc 4.4.4 x86 -O3) */ 301 | double s12; 302 | struct geod_geodesic g; 303 | int result = 0; 304 | geod_init(&g, wgs84_a, wgs84_f); 305 | geod_inverse(&g, 56.320923501171, 0, 306 | -56.320923501171, 179.664747671772880215, 307 | &s12, nullptr, nullptr); 308 | result += checkEquals(s12, 19993558.287, 0.5e-3); 309 | return result; 310 | } 311 | 312 | static int GeodSolve10() { 313 | /* Check fix for adjust tol1_ bug found 2011-06-25 (Visual Studio 314 | * 10 rel + debug) */ 315 | double s12; 316 | struct geod_geodesic g; 317 | int result = 0; 318 | geod_init(&g, wgs84_a, wgs84_f); 319 | geod_inverse(&g, 52.784459512564, 0, 320 | -52.784459512563990912, 179.634407464943777557, 321 | &s12, nullptr, nullptr); 322 | result += checkEquals(s12, 19991596.095, 0.5e-3); 323 | return result; 324 | } 325 | 326 | static int GeodSolve11() { 327 | /* Check fix for bet2 = -bet1 bug found 2011-06-25 (Visual Studio 328 | * 10 rel + debug) */ 329 | double s12; 330 | struct geod_geodesic g; 331 | int result = 0; 332 | geod_init(&g, wgs84_a, wgs84_f); 333 | geod_inverse(&g, 48.522876735459, 0, 334 | -48.52287673545898293, 179.599720456223079643, 335 | &s12, nullptr, nullptr); 336 | result += checkEquals(s12, 19989144.774, 0.5e-3); 337 | return result; 338 | } 339 | 340 | static int GeodSolve12() { 341 | /* Check fix for inverse geodesics on extreme prolate/oblate 342 | * ellipsoids Reported 2012-08-29 Stefan Guenther 343 | * ; fixed 2012-10-07 */ 344 | double azi1, azi2, s12; 345 | struct geod_geodesic g; 346 | int result = 0; 347 | geod_init(&g, 89.8, -1.83); 348 | geod_inverse(&g, 0, 0, -10, 160, &s12, &azi1, &azi2); 349 | result += checkEquals(azi1, 120.27, 1e-2); 350 | result += checkEquals(azi2, 105.15, 1e-2); 351 | result += checkEquals(s12, 266.7, 1e-1); 352 | return result; 353 | } 354 | 355 | static int GeodSolve14() { 356 | /* Check fix for inverse ignoring lon12 = nan */ 357 | double azi1, azi2, s12; 358 | struct geod_geodesic g; 359 | int result = 0; 360 | geod_init(&g, wgs84_a, wgs84_f); 361 | geod_inverse(&g, 0, 0, 1, nan("0"), &s12, &azi1, &azi2); 362 | result += checkNaN(azi1); 363 | result += checkNaN(azi2); 364 | result += checkNaN(s12); 365 | return result; 366 | } 367 | 368 | static int GeodSolve15() { 369 | /* Initial implementation of Math::eatanhe was wrong for e^2 < 0. This 370 | * checks that this is fixed. */ 371 | double S12; 372 | struct geod_geodesic g; 373 | int result = 0; 374 | geod_init(&g, 6.4e6, -1/150.0); 375 | geod_gendirect(&g, 1, 2, 3, 0, 4, nullptr, nullptr, nullptr, 376 | nullptr, nullptr, nullptr, nullptr, &S12); 377 | result += checkEquals(S12, 23700, 0.5); 378 | return result; 379 | } 380 | 381 | static int GeodSolve17() { 382 | /* Check fix for LONG_UNROLL bug found on 2015-05-07 */ 383 | double lat2, lon2, azi2; 384 | struct geod_geodesic g; 385 | struct geod_geodesicline l; 386 | int result = 0; 387 | unsigned flags = GEOD_LONG_UNROLL; 388 | geod_init(&g, wgs84_a, wgs84_f); 389 | geod_gendirect(&g, 40, -75, -10, flags, 2e7, &lat2, &lon2, &azi2, 390 | nullptr, nullptr, nullptr, nullptr, nullptr); 391 | result += checkEquals(lat2, -39, 1); 392 | result += checkEquals(lon2, -254, 1); 393 | result += checkEquals(azi2, -170, 1); 394 | geod_lineinit(&l, &g, 40, -75, -10, 0); 395 | geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 396 | nullptr, nullptr, nullptr, nullptr, nullptr); 397 | result += checkEquals(lat2, -39, 1); 398 | result += checkEquals(lon2, -254, 1); 399 | result += checkEquals(azi2, -170, 1); 400 | geod_direct(&g, 40, -75, -10, 2e7, &lat2, &lon2, &azi2); 401 | result += checkEquals(lat2, -39, 1); 402 | result += checkEquals(lon2, 105, 1); 403 | result += checkEquals(azi2, -170, 1); 404 | geod_position(&l, 2e7, &lat2, &lon2, &azi2); 405 | result += checkEquals(lat2, -39, 1); 406 | result += checkEquals(lon2, 105, 1); 407 | result += checkEquals(azi2, -170, 1); 408 | return result; 409 | } 410 | 411 | static int GeodSolve26() { 412 | /* Check 0/0 problem with area calculation on sphere 2015-09-08 */ 413 | double S12; 414 | struct geod_geodesic g; 415 | int result = 0; 416 | geod_init(&g, 6.4e6, 0); 417 | geod_geninverse(&g, 1, 2, 3, 4, nullptr, nullptr, nullptr, 418 | nullptr, nullptr, nullptr, &S12); 419 | result += checkEquals(S12, 49911046115.0, 0.5); 420 | return result; 421 | } 422 | 423 | static int GeodSolve28() { 424 | /* Check for bad placement of assignment of r.a12 with |f| > 0.01 (bug in 425 | * Java implementation fixed on 2015-05-19). */ 426 | double a12; 427 | struct geod_geodesic g; 428 | int result = 0; 429 | geod_init(&g, 6.4e6, 0.1); 430 | a12 = geod_gendirect(&g, 1, 2, 10, 0, 5e6, nullptr, nullptr, nullptr, 431 | nullptr, nullptr, nullptr, nullptr, nullptr); 432 | result += checkEquals(a12, 48.55570690, 0.5e-8); 433 | return result; 434 | } 435 | 436 | static int GeodSolve33() { 437 | /* Check max(-0.0,+0.0) issues 2015-08-22 (triggered by bugs in Octave -- 438 | * sind(-0.0) = +0.0 -- and in some version of Visual Studio -- 439 | * fmod(-0.0, 360.0) = +0.0. */ 440 | double azi1, azi2, s12; 441 | struct geod_geodesic g; 442 | int result = 0; 443 | geod_init(&g, wgs84_a, wgs84_f); 444 | geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); 445 | result += checkEquals(azi1, 90.00000, 0.5e-5); 446 | result += checkEquals(azi2, 90.00000, 0.5e-5); 447 | result += checkEquals(s12, 19926189, 0.5); 448 | geod_inverse(&g, 0, 0, 0, 179.5, &s12, &azi1, &azi2); 449 | result += checkEquals(azi1, 55.96650, 0.5e-5); 450 | result += checkEquals(azi2, 124.03350, 0.5e-5); 451 | result += checkEquals(s12, 19980862, 0.5); 452 | geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); 453 | result += checkEquals(azi1, 0.00000, 0.5e-5); 454 | result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); 455 | result += checkEquals(s12, 20003931, 0.5); 456 | geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); 457 | result += checkEquals(azi1, 0.00000, 0.5e-5); 458 | result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); 459 | result += checkEquals(s12, 19893357, 0.5); 460 | geod_init(&g, 6.4e6, 0); 461 | geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); 462 | result += checkEquals(azi1, 90.00000, 0.5e-5); 463 | result += checkEquals(azi2, 90.00000, 0.5e-5); 464 | result += checkEquals(s12, 19994492, 0.5); 465 | geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); 466 | result += checkEquals(azi1, 0.00000, 0.5e-5); 467 | result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); 468 | result += checkEquals(s12, 20106193, 0.5); 469 | geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); 470 | result += checkEquals(azi1, 0.00000, 0.5e-5); 471 | result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); 472 | result += checkEquals(s12, 19994492, 0.5); 473 | geod_init(&g, 6.4e6, -1/300.0); 474 | geod_inverse(&g, 0, 0, 0, 179, &s12, &azi1, &azi2); 475 | result += checkEquals(azi1, 90.00000, 0.5e-5); 476 | result += checkEquals(azi2, 90.00000, 0.5e-5); 477 | result += checkEquals(s12, 19994492, 0.5); 478 | geod_inverse(&g, 0, 0, 0, 180, &s12, &azi1, &azi2); 479 | result += checkEquals(azi1, 90.00000, 0.5e-5); 480 | result += checkEquals(azi2, 90.00000, 0.5e-5); 481 | result += checkEquals(s12, 20106193, 0.5); 482 | geod_inverse(&g, 0, 0, 0.5, 180, &s12, &azi1, &azi2); 483 | result += checkEquals(azi1, 33.02493, 0.5e-5); 484 | result += checkEquals(azi2, 146.97364, 0.5e-5); 485 | result += checkEquals(s12, 20082617, 0.5); 486 | geod_inverse(&g, 0, 0, 1, 180, &s12, &azi1, &azi2); 487 | result += checkEquals(azi1, 0.00000, 0.5e-5); 488 | result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); 489 | result += checkEquals(s12, 20027270, 0.5); 490 | 491 | return result; 492 | } 493 | 494 | static int GeodSolve55() { 495 | /* Check fix for nan + point on equator or pole not returning all nans in 496 | * Geodesic::Inverse, found 2015-09-23. */ 497 | double azi1, azi2, s12; 498 | struct geod_geodesic g; 499 | int result = 0; 500 | geod_init(&g, wgs84_a, wgs84_f); 501 | geod_inverse(&g, nan("0"), 0, 0, 90, &s12, &azi1, &azi2); 502 | result += checkNaN(azi1); 503 | result += checkNaN(azi2); 504 | result += checkNaN(s12); 505 | geod_inverse(&g, nan("0"), 0, 90, 9, &s12, &azi1, &azi2); 506 | result += checkNaN(azi1); 507 | result += checkNaN(azi2); 508 | result += checkNaN(s12); 509 | return result; 510 | } 511 | 512 | static int GeodSolve59() { 513 | /* Check for points close with longitudes close to 180 deg apart. */ 514 | double azi1, azi2, s12; 515 | struct geod_geodesic g; 516 | int result = 0; 517 | geod_init(&g, wgs84_a, wgs84_f); 518 | geod_inverse(&g, 5, 0.00000000000001, 10, 180, &s12, &azi1, &azi2); 519 | result += checkEquals(azi1, 0.000000000000035, 1.5e-14); 520 | result += checkEquals(azi2, 179.99999999999996, 1.5e-14); 521 | result += checkEquals(s12, 18345191.174332713, 5e-9); 522 | return result; 523 | } 524 | 525 | static int GeodSolve61() { 526 | /* Make sure small negative azimuths are west-going */ 527 | double lat2, lon2, azi2; 528 | struct geod_geodesic g; 529 | struct geod_geodesicline l; 530 | int result = 0; 531 | unsigned flags = GEOD_LONG_UNROLL; 532 | geod_init(&g, wgs84_a, wgs84_f); 533 | geod_gendirect(&g, 45, 0, -0.000000000000000003, flags, 1e7, 534 | &lat2, &lon2, &azi2, 535 | nullptr, nullptr, nullptr, nullptr, nullptr); 536 | result += checkEquals(lat2, 45.30632, 0.5e-5); 537 | result += checkEquals(lon2, -180, 0.5e-5); 538 | result += checkEquals(fabs(azi2), 180, 0.5e-5); 539 | geod_inverseline(&l, &g, 45, 0, 80, -0.000000000000000003, 0); 540 | geod_genposition(&l, flags, 1e7, &lat2, &lon2, &azi2, 541 | nullptr, nullptr, nullptr, nullptr, nullptr); 542 | result += checkEquals(lat2, 45.30632, 0.5e-5); 543 | result += checkEquals(lon2, -180, 0.5e-5); 544 | result += checkEquals(fabs(azi2), 180, 0.5e-5); 545 | return result; 546 | } 547 | 548 | static int GeodSolve65() { 549 | /* Check for bug in east-going check in GeodesicLine (needed to check for 550 | * sign of 0) and sign error in area calculation due to a bogus override of 551 | * the code for alp12. Found/fixed on 2015-12-19. */ 552 | double lat2, lon2, azi2, s12, a12, m12, M12, M21, S12; 553 | struct geod_geodesic g; 554 | struct geod_geodesicline l; 555 | int result = 0; 556 | unsigned flags = GEOD_LONG_UNROLL, caps = GEOD_ALL; 557 | geod_init(&g, wgs84_a, wgs84_f); 558 | geod_inverseline(&l, &g, 30, -0.000000000000000001, -31, 180, caps); 559 | a12 = geod_genposition(&l, flags, 1e7, 560 | &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); 561 | result += checkEquals(lat2, -60.23169, 0.5e-5); 562 | result += checkEquals(lon2, -0.00000, 0.5e-5); 563 | result += checkEquals(fabs(azi2), 180.00000, 0.5e-5); 564 | result += checkEquals(s12, 10000000, 0.5); 565 | result += checkEquals(a12, 90.06544, 0.5e-5); 566 | result += checkEquals(m12, 6363636, 0.5); 567 | result += checkEquals(M12, -0.0012834, 0.5e-7); 568 | result += checkEquals(M21, 0.0013749, 0.5e-7); 569 | result += checkEquals(S12, 0, 0.5); 570 | a12 = geod_genposition(&l, flags, 2e7, 571 | &lat2, &lon2, &azi2, &s12, &m12, &M12, &M21, &S12); 572 | result += checkEquals(lat2, -30.03547, 0.5e-5); 573 | result += checkEquals(lon2, -180.00000, 0.5e-5); 574 | result += checkEquals(azi2, -0.00000, 0.5e-5); 575 | result += checkEquals(s12, 20000000, 0.5); 576 | result += checkEquals(a12, 179.96459, 0.5e-5); 577 | result += checkEquals(m12, 54342, 0.5); 578 | result += checkEquals(M12, -1.0045592, 0.5e-7); 579 | result += checkEquals(M21, -0.9954339, 0.5e-7); 580 | result += checkEquals(S12, 127516405431022.0, 0.5); 581 | return result; 582 | } 583 | 584 | static int GeodSolve67() { 585 | /* Check for InverseLine if line is slightly west of S and that s13 is 586 | * correctly set. */ 587 | double lat2, lon2, azi2; 588 | struct geod_geodesic g; 589 | struct geod_geodesicline l; 590 | int result = 0; 591 | unsigned flags = GEOD_LONG_UNROLL; 592 | geod_init(&g, wgs84_a, wgs84_f); 593 | geod_inverseline(&l, &g, -5, -0.000000000000002, -10, 180, 0); 594 | geod_genposition(&l, flags, 2e7, &lat2, &lon2, &azi2, 595 | nullptr, nullptr, nullptr, nullptr, nullptr); 596 | result += checkEquals(lat2, 4.96445, 0.5e-5); 597 | result += checkEquals(lon2, -180.00000, 0.5e-5); 598 | result += checkEquals(azi2, -0.00000, 0.5e-5); 599 | geod_genposition(&l, flags, 0.5 * l.s13, &lat2, &lon2, &azi2, 600 | nullptr, nullptr, nullptr, nullptr, nullptr); 601 | result += checkEquals(lat2, -87.52461, 0.5e-5); 602 | result += checkEquals(lon2, -0.00000, 0.5e-5); 603 | result += checkEquals(azi2, -180.00000, 0.5e-5); 604 | return result; 605 | } 606 | 607 | static int GeodSolve71() { 608 | /* Check that DirectLine sets s13. */ 609 | double lat2, lon2, azi2; 610 | struct geod_geodesic g; 611 | struct geod_geodesicline l; 612 | int result = 0; 613 | geod_init(&g, wgs84_a, wgs84_f); 614 | geod_directline(&l, &g, 1, 2, 45, 1e7, 0); 615 | geod_position(&l, 0.5 * l.s13, &lat2, &lon2, &azi2); 616 | result += checkEquals(lat2, 30.92625, 0.5e-5); 617 | result += checkEquals(lon2, 37.54640, 0.5e-5); 618 | result += checkEquals(azi2, 55.43104, 0.5e-5); 619 | return result; 620 | } 621 | 622 | static int GeodSolve73() { 623 | /* Check for backwards from the pole bug reported by Anon on 2016-02-13. 624 | * This only affected the Java implementation. It was introduced in Java 625 | * version 1.44 and fixed in 1.46-SNAPSHOT on 2016-01-17. 626 | * Also the + sign on azi2 is a check on the normalizing of azimuths 627 | * (converting -0.0 to +0.0). */ 628 | double lat2, lon2, azi2; 629 | struct geod_geodesic g; 630 | int result = 0; 631 | geod_init(&g, wgs84_a, wgs84_f); 632 | geod_direct(&g, 90, 10, 180, -1e6, 633 | &lat2, &lon2, &azi2); 634 | result += checkEquals(lat2, 81.04623, 0.5e-5); 635 | result += checkEquals(lon2, -170, 0.5e-5); 636 | result += azi2 == 0 ? 0 : 1; 637 | result += 1/azi2 > 0 ? 0 : 1; /* Check that azi2 = +0.0 not -0.0 */ 638 | return result; 639 | } 640 | 641 | static void planimeter(const struct geod_geodesic* g, 642 | double points[][2], int N, 643 | double* perimeter, double* area) { 644 | struct geod_polygon p; 645 | int i; 646 | geod_polygon_init(&p, 0); 647 | for (i = 0; i < N; ++i) 648 | geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); 649 | geod_polygon_compute(g, &p, 0, 1, area, perimeter); 650 | } 651 | 652 | static void polylength(const struct geod_geodesic* g, 653 | double points[][2], int N, 654 | double* perimeter) { 655 | struct geod_polygon p; 656 | int i; 657 | geod_polygon_init(&p, 1); 658 | for (i = 0; i < N; ++i) 659 | geod_polygon_addpoint(g, &p, points[i][0], points[i][1]); 660 | geod_polygon_compute(g, &p, 0, 1, nullptr, perimeter); 661 | } 662 | 663 | static int GeodSolve74() { 664 | /* Check fix for inaccurate areas, bug introduced in v1.46, fixed 665 | * 2015-10-16. */ 666 | double a12, s12, azi1, azi2, m12, M12, M21, S12; 667 | struct geod_geodesic g; 668 | int result = 0; 669 | geod_init(&g, wgs84_a, wgs84_f); 670 | a12 = geod_geninverse(&g, 54.1589, 15.3872, 54.1591, 15.3877, 671 | &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); 672 | result += checkEquals(azi1, 55.723110355, 5e-9); 673 | result += checkEquals(azi2, 55.723515675, 5e-9); 674 | result += checkEquals(s12, 39.527686385, 5e-9); 675 | result += checkEquals(a12, 0.000355495, 5e-9); 676 | result += checkEquals(m12, 39.527686385, 5e-9); 677 | result += checkEquals(M12, 0.999999995, 5e-9); 678 | result += checkEquals(M21, 0.999999995, 5e-9); 679 | result += checkEquals(S12, 286698586.30197, 5e-4); 680 | return result; 681 | } 682 | 683 | static int GeodSolve76() { 684 | /* The distance from Wellington and Salamanca (a classic failure of 685 | * Vincenty) */ 686 | double azi1, azi2, s12; 687 | struct geod_geodesic g; 688 | int result = 0; 689 | geod_init(&g, wgs84_a, wgs84_f); 690 | geod_inverse(&g, -(41+19/60.0), 174+49/60.0, 40+58/60.0, -(5+30/60.0), 691 | &s12, &azi1, &azi2); 692 | result += checkEquals(azi1, 160.39137649664, 0.5e-11); 693 | result += checkEquals(azi2, 19.50042925176, 0.5e-11); 694 | result += checkEquals(s12, 19960543.857179, 0.5e-6); 695 | return result; 696 | } 697 | 698 | static int GeodSolve78() { 699 | /* An example where the NGS calculator fails to converge */ 700 | double azi1, azi2, s12; 701 | struct geod_geodesic g; 702 | int result = 0; 703 | geod_init(&g, wgs84_a, wgs84_f); 704 | geod_inverse(&g, 27.2, 0.0, -27.1, 179.5, &s12, &azi1, &azi2); 705 | result += checkEquals(azi1, 45.82468716758, 0.5e-11); 706 | result += checkEquals(azi2, 134.22776532670, 0.5e-11); 707 | result += checkEquals(s12, 19974354.765767, 0.5e-6); 708 | return result; 709 | } 710 | 711 | static int GeodSolve80() { 712 | /* Some tests to add code coverage: computing scale in special cases + zero 713 | * length geodesic (includes GeodSolve80 - GeodSolve83) + using an incapable 714 | * line. */ 715 | double a12, s12, azi1, azi2, m12, M12, M21, S12; 716 | struct geod_geodesic g; 717 | struct geod_geodesicline l; 718 | int result = 0; 719 | geod_init(&g, wgs84_a, wgs84_f); 720 | 721 | geod_geninverse(&g, 0, 0, 0, 90, nullptr, nullptr, nullptr, 722 | nullptr, &M12, &M21, nullptr); 723 | result += checkEquals(M12, -0.00528427534, 0.5e-10); 724 | result += checkEquals(M21, -0.00528427534, 0.5e-10); 725 | 726 | geod_geninverse(&g, 0, 0, 1e-6, 1e-6, nullptr, nullptr, nullptr, 727 | nullptr, &M12, &M21, nullptr); 728 | result += checkEquals(M12, 1, 0.5e-10); 729 | result += checkEquals(M21, 1, 0.5e-10); 730 | 731 | a12 = geod_geninverse(&g, 20.001, 0, 20.001, 0, 732 | &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); 733 | result += checkEquals(a12, 0, 1e-13); 734 | result += checkEquals(s12, 0, 1e-8); 735 | result += checkEquals(azi1, 180, 1e-13); 736 | result += checkEquals(azi2, 180, 1e-13); 737 | result += checkEquals(m12, 0, 1e-8); 738 | result += checkEquals(M12, 1, 1e-15); 739 | result += checkEquals(M21, 1, 1e-15); 740 | result += checkEquals(S12, 0, 1e-10); 741 | result += 1/a12 > 0 ? 0 : 1; 742 | result += 1/s12 > 0 ? 0 : 1; 743 | result += 1/m12 > 0 ? 0 : 1; 744 | 745 | a12 = geod_geninverse(&g, 90, 0, 90, 180, 746 | &s12, &azi1, &azi2, &m12, &M12, &M21, &S12); 747 | result += checkEquals(a12, 0, 1e-13); 748 | result += checkEquals(s12, 0, 1e-8); 749 | result += checkEquals(azi1, 0, 1e-13); 750 | result += checkEquals(azi2, 180, 1e-13); 751 | result += checkEquals(m12, 0, 1e-8); 752 | result += checkEquals(M12, 1, 1e-15); 753 | result += checkEquals(M21, 1, 1e-15); 754 | result += checkEquals(S12, 127516405431022.0, 0.5); 755 | 756 | /* An incapable line which can't take distance as input */ 757 | geod_lineinit(&l, &g, 1, 2, 90, GEOD_LATITUDE); 758 | a12 = geod_genposition(&l, 0, 1000, nullptr, nullptr, nullptr, 759 | nullptr, nullptr, nullptr, nullptr, nullptr); 760 | result += checkNaN(a12); 761 | return result; 762 | } 763 | 764 | static int GeodSolve84() { 765 | /* Tests for python implementation to check fix for range errors with 766 | * {fmod,sin,cos}(inf) (includes GeodSolve84 - GeodSolve86). */ 767 | 768 | double lat2, lon2, azi2, inf; 769 | struct geod_geodesic g; 770 | int result = 0; 771 | geod_init(&g, wgs84_a, wgs84_f); 772 | { 773 | /* a round about way to set inf = 0 */ 774 | geod_direct(&g, 0, 0, 90, 0, &inf, nullptr, nullptr); 775 | /* so that this doesn't give a compiler time error on Windows */ 776 | inf = 1.0/inf; 777 | } 778 | geod_direct(&g, 0, 0, 90, inf, &lat2, &lon2, &azi2); 779 | result += checkNaN(lat2); 780 | result += checkNaN(lon2); 781 | result += checkNaN(azi2); 782 | geod_direct(&g, 0, 0, 90, nan("0"), &lat2, &lon2, &azi2); 783 | result += checkNaN(lat2); 784 | result += checkNaN(lon2); 785 | result += checkNaN(azi2); 786 | geod_direct(&g, 0, 0, inf, 1000, &lat2, &lon2, &azi2); 787 | result += checkNaN(lat2); 788 | result += checkNaN(lon2); 789 | result += checkNaN(azi2); 790 | geod_direct(&g, 0, 0, nan("0"), 1000, &lat2, &lon2, &azi2); 791 | result += checkNaN(lat2); 792 | result += checkNaN(lon2); 793 | result += checkNaN(azi2); 794 | geod_direct(&g, 0, inf, 90, 1000, &lat2, &lon2, &azi2); 795 | result += lat2 == 0 ? 0 : 1; 796 | result += checkNaN(lon2); 797 | result += azi2 == 90 ? 0 : 1; 798 | geod_direct(&g, 0, nan("0"), 90, 1000, &lat2, &lon2, &azi2); 799 | result += lat2 == 0 ? 0 : 1; 800 | result += checkNaN(lon2); 801 | result += azi2 == 90 ? 0 : 1; 802 | geod_direct(&g, inf, 0, 90, 1000, &lat2, &lon2, &azi2); 803 | result += checkNaN(lat2); 804 | result += checkNaN(lon2); 805 | result += checkNaN(azi2); 806 | geod_direct(&g, nan("0"), 0, 90, 1000, &lat2, &lon2, &azi2); 807 | result += checkNaN(lat2); 808 | result += checkNaN(lon2); 809 | result += checkNaN(azi2); 810 | return result; 811 | } 812 | 813 | static int GeodSolve92() { 814 | /* Check fix for inaccurate hypot with python 3.[89]. Problem reported 815 | * by agdhruv https://github.com/geopy/geopy/issues/466 ; see 816 | * https://bugs.python.org/issue43088 */ 817 | double azi1, azi2, s12; 818 | struct geod_geodesic g; 819 | int result = 0; 820 | geod_init(&g, wgs84_a, wgs84_f); 821 | geod_inverse(&g, 822 | 37.757540000000006, -122.47018, 823 | 37.75754, -122.470177, 824 | &s12, &azi1, &azi2); 825 | result += checkEquals(azi1, 89.99999923, 1e-7 ); 826 | result += checkEquals(azi2, 90.00000106, 1e-7 ); 827 | result += checkEquals(s12, 0.264, 0.5e-3); 828 | return result; 829 | } 830 | 831 | static int GeodSolve94() { 832 | /* Check fix for lat2 = nan being treated as lat2 = 0 (bug found 833 | * 2021-07-26) */ 834 | double azi1, azi2, s12; 835 | struct geod_geodesic g; 836 | int result = 0; 837 | geod_init(&g, wgs84_a, wgs84_f); 838 | geod_inverse(&g, 0, 0, nan("0"), 90, &s12, &azi1, &azi2); 839 | result += checkNaN(azi1); 840 | result += checkNaN(azi2); 841 | result += checkNaN(s12); 842 | return result; 843 | } 844 | 845 | static int GeodSolve96() { 846 | /* Failure with long doubles found with test case from Nowak + Nowak Da 847 | * Costa (2022). Problem was using somg12 > 1 as a test that it needed 848 | * to be set when roundoff could result in somg12 slightly bigger that 1. 849 | * Found + fixed 2022-03-30. */ 850 | double S12; 851 | struct geod_geodesic g; 852 | int result = 0; 853 | geod_init(&g, 6378137, 1/298.257222101); 854 | geod_geninverse(&g, 0, 0, 60.0832522871723, 89.8492185074635, 855 | nullptr, nullptr, nullptr, 856 | nullptr, nullptr, nullptr, &S12); 857 | result += checkEquals(S12, 42426932221845, 0.5); 858 | return result; 859 | } 860 | 861 | static int Planimeter0() { 862 | /* Check fix for pole-encircling bug found 2011-03-16 */ 863 | double pa[4][2] = {{89, 0}, {89, 90}, {89, 180}, {89, 270}}; 864 | double pb[4][2] = {{-89, 0}, {-89, 90}, {-89, 180}, {-89, 270}}; 865 | double pc[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; 866 | double pd[3][2] = {{90, 0}, {0, 0}, {0, 90}}; 867 | struct geod_geodesic g; 868 | double perimeter, area; 869 | int result = 0; 870 | geod_init(&g, wgs84_a, wgs84_f); 871 | 872 | planimeter(&g, pa, 4, &perimeter, &area); 873 | result += checkEquals(perimeter, 631819.8745, 1e-4); 874 | result += checkEquals(area, 24952305678.0, 1); 875 | 876 | planimeter(&g, pb, 4, &perimeter, &area); 877 | result += checkEquals(perimeter, 631819.8745, 1e-4); 878 | result += checkEquals(area, -24952305678.0, 1); 879 | 880 | planimeter(&g, pc, 4, &perimeter, &area); 881 | result += checkEquals(perimeter, 627598.2731, 1e-4); 882 | result += checkEquals(area, 24619419146.0, 1); 883 | 884 | planimeter(&g, pd, 3, &perimeter, &area); 885 | result += checkEquals(perimeter, 30022685, 1); 886 | result += checkEquals(area, 63758202715511.0, 1); 887 | 888 | polylength(&g, pd, 3, &perimeter); 889 | result += checkEquals(perimeter, 20020719, 1); 890 | 891 | return result; 892 | } 893 | 894 | static int Planimeter5() { 895 | /* Check fix for Planimeter pole crossing bug found 2011-06-24 */ 896 | double points[3][2] = {{89, 0.1}, {89, 90.1}, {89, -179.9}}; 897 | struct geod_geodesic g; 898 | double perimeter, area; 899 | int result = 0; 900 | geod_init(&g, wgs84_a, wgs84_f); 901 | planimeter(&g, points, 3, &perimeter, &area); 902 | result += checkEquals(perimeter, 539297, 1); 903 | result += checkEquals(area, 12476152838.5, 1); 904 | return result; 905 | } 906 | 907 | static int Planimeter6() { 908 | /* Check fix for Planimeter lon12 rounding bug found 2012-12-03 */ 909 | double pa[3][2] = {{9, -0.00000000000001}, {9, 180}, {9, 0}}; 910 | double pb[3][2] = {{9, 0.00000000000001}, {9, 0}, {9, 180}}; 911 | double pc[3][2] = {{9, 0.00000000000001}, {9, 180}, {9, 0}}; 912 | double pd[3][2] = {{9, -0.00000000000001}, {9, 0}, {9, 180}}; 913 | struct geod_geodesic g; 914 | double perimeter, area; 915 | int result = 0; 916 | geod_init(&g, wgs84_a, wgs84_f); 917 | 918 | planimeter(&g, pa, 3, &perimeter, &area); 919 | result += checkEquals(perimeter, 36026861, 1); 920 | result += checkEquals(area, 0, 1); 921 | planimeter(&g, pb, 3, &perimeter, &area); 922 | result += checkEquals(perimeter, 36026861, 1); 923 | result += checkEquals(area, 0, 1); 924 | planimeter(&g, pc, 3, &perimeter, &area); 925 | result += checkEquals(perimeter, 36026861, 1); 926 | result += checkEquals(area, 0, 1); 927 | planimeter(&g, pd, 3, &perimeter, &area); 928 | result += checkEquals(perimeter, 36026861, 1); 929 | result += checkEquals(area, 0, 1); 930 | return result; 931 | } 932 | 933 | static int Planimeter12() { 934 | /* Area of arctic circle (not really -- adjunct to rhumb-area test) */ 935 | double points[3][2] = {{66.562222222, 0}, 936 | {66.562222222, 180}, 937 | {66.562222222, 360}}; 938 | struct geod_geodesic g; 939 | double perimeter, area; 940 | int result = 0; 941 | geod_init(&g, wgs84_a, wgs84_f); 942 | planimeter(&g, points, 3, &perimeter, &area); 943 | result += checkEquals(perimeter, 10465729, 1); 944 | result += checkEquals(area, 0, 1); 945 | return result; 946 | } 947 | 948 | static int Planimeter12r() { 949 | /* Area of arctic circle (not really -- adjunct to rhumb-area test) */ 950 | double points[3][2] = {{66.562222222, -0}, 951 | {66.562222222, -180}, 952 | {66.562222222, -360}}; 953 | struct geod_geodesic g; 954 | double perimeter, area; 955 | int result = 0; 956 | geod_init(&g, wgs84_a, wgs84_f); 957 | planimeter(&g, points, 3, &perimeter, &area); 958 | result += checkEquals(perimeter, 10465729, 1); 959 | result += checkEquals(area, 0, 1); 960 | return result; 961 | } 962 | 963 | static int Planimeter13() { 964 | /* Check encircling pole twice */ 965 | double points[6][2] = {{89,-360}, {89,-240}, {89,-120}, 966 | {89,0}, {89,120}, {89,240}}; 967 | struct geod_geodesic g; 968 | double perimeter, area; 969 | int result = 0; 970 | geod_init(&g, wgs84_a, wgs84_f); 971 | planimeter(&g, points, 6, &perimeter, &area); 972 | result += checkEquals(perimeter, 1160741, 1); 973 | result += checkEquals(area, 32415230256.0, 1); 974 | return result; 975 | } 976 | 977 | static int Planimeter15() { 978 | /* Coverage tests, includes Planimeter15 - Planimeter18 (combinations of 979 | * reverse and sign) + calls to testpoint, testedge, geod_polygonarea. */ 980 | struct geod_geodesic g; 981 | struct geod_polygon p; 982 | double lat[] = {2, 1, 3}, lon[] = {1, 2, 3}; 983 | double area, s12, azi1; 984 | double r = 18454562325.45119, 985 | a0 = 510065621724088.5093; /* ellipsoid area */ 986 | int result = 0; 987 | geod_init(&g, wgs84_a, wgs84_f); 988 | geod_polygon_init(&p, 0); 989 | geod_polygon_addpoint(&g, &p, lat[0], lon[0]); 990 | geod_polygon_addpoint(&g, &p, lat[1], lon[1]); 991 | geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 1, &area, nullptr); 992 | result += checkEquals(area, r, 0.5); 993 | geod_polygon_testpoint(&g, &p, lat[2], lon[2], 0, 0, &area, nullptr); 994 | result += checkEquals(area, r, 0.5); 995 | geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 1, &area, nullptr); 996 | result += checkEquals(area, -r, 0.5); 997 | geod_polygon_testpoint(&g, &p, lat[2], lon[2], 1, 0, &area, nullptr); 998 | result += checkEquals(area, a0-r, 0.5); 999 | geod_inverse(&g, lat[1], lon[1], lat[2], lon[2], &s12, &azi1, nullptr); 1000 | geod_polygon_testedge(&g, &p, azi1, s12, 0, 1, &area, nullptr); 1001 | result += checkEquals(area, r, 0.5); 1002 | geod_polygon_testedge(&g, &p, azi1, s12, 0, 0, &area, nullptr); 1003 | result += checkEquals(area, r, 0.5); 1004 | geod_polygon_testedge(&g, &p, azi1, s12, 1, 1, &area, nullptr); 1005 | result += checkEquals(area, -r, 0.5); 1006 | geod_polygon_testedge(&g, &p, azi1, s12, 1, 0, &area, nullptr); 1007 | result += checkEquals(area, a0-r, 0.5); 1008 | geod_polygon_addpoint(&g, &p, lat[2], lon[2]); 1009 | geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); 1010 | result += checkEquals(area, r, 0.5); 1011 | geod_polygon_compute(&g, &p, 0, 0, &area, nullptr); 1012 | result += checkEquals(area, r, 0.5); 1013 | geod_polygon_compute(&g, &p, 1, 1, &area, nullptr); 1014 | result += checkEquals(area, -r, 0.5); 1015 | geod_polygon_compute(&g, &p, 1, 0, &area, nullptr); 1016 | result += checkEquals(area, a0-r, 0.5); 1017 | geod_polygonarea(&g, lat, lon, 3, &area, nullptr); 1018 | result += checkEquals(area, r, 0.5); 1019 | return result; 1020 | } 1021 | 1022 | static int Planimeter19() { 1023 | /* Coverage tests, includes Planimeter19 - Planimeter20 (degenerate 1024 | * polygons) + extra cases. */ 1025 | struct geod_geodesic g; 1026 | struct geod_polygon p; 1027 | double area, perim; 1028 | int result = 0; 1029 | geod_init(&g, wgs84_a, wgs84_f); 1030 | geod_polygon_init(&p, 0); 1031 | geod_polygon_compute(&g, &p, 0, 1, &area, &perim); 1032 | result += area == 0 ? 0 : 1; 1033 | result += perim == 0 ? 0 : 1; 1034 | geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, &area, &perim); 1035 | result += area == 0 ? 0 : 1; 1036 | result += perim == 0 ? 0 : 1; 1037 | geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, &area, &perim); 1038 | result += checkNaN(area); 1039 | result += checkNaN(perim); 1040 | geod_polygon_addpoint(&g, &p, 1, 1); 1041 | geod_polygon_compute(&g, &p, 0, 1, &area, &perim); 1042 | result += area == 0 ? 0 : 1; 1043 | result += perim == 0 ? 0 : 1; 1044 | geod_polygon_init(&p, 1); 1045 | geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); 1046 | result += perim == 0 ? 0 : 1; 1047 | geod_polygon_testpoint(&g, &p, 1, 1, 0, 1, nullptr, &perim); 1048 | result += perim == 0 ? 0 : 1; 1049 | geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); 1050 | result += checkNaN(perim); 1051 | geod_polygon_addpoint(&g, &p, 1, 1); 1052 | geod_polygon_compute(&g, &p, 0, 1, nullptr, &perim); 1053 | result += perim == 0 ? 0 : 1; 1054 | geod_polygon_addpoint(&g, &p, 1, 1); 1055 | geod_polygon_testedge(&g, &p, 90, 1000, 0, 1, nullptr, &perim); 1056 | result += checkEquals(perim, 1000, 1e-10); 1057 | geod_polygon_testpoint(&g, &p, 2, 2, 0, 1, nullptr, &perim); 1058 | result += checkEquals(perim, 156876.149, 0.5e-3); 1059 | return result; 1060 | } 1061 | 1062 | static int Planimeter21() { 1063 | /* Some tests to add code coverage: multiple circlings of pole (includes 1064 | * Planimeter21 - Planimeter28) + invocations via testpoint and testedge. */ 1065 | struct geod_geodesic g; 1066 | struct geod_polygon p; 1067 | double area, lat = 45, 1068 | a = 39.2144607176828184218, s = 8420705.40957178156285, 1069 | r = 39433884866571.4277, /* Area for one circuit */ 1070 | a0 = 510065621724088.5093; /* Ellipsoid area */ 1071 | int result = 0, i; 1072 | geod_init(&g, wgs84_a, wgs84_f); 1073 | geod_polygon_init(&p, 0); 1074 | geod_polygon_addpoint(&g, &p, lat, 60); 1075 | geod_polygon_addpoint(&g, &p, lat, 180); 1076 | geod_polygon_addpoint(&g, &p, lat, -60); 1077 | geod_polygon_addpoint(&g, &p, lat, 60); 1078 | geod_polygon_addpoint(&g, &p, lat, 180); 1079 | geod_polygon_addpoint(&g, &p, lat, -60); 1080 | for (i = 3; i <= 4; ++i) { 1081 | geod_polygon_addpoint(&g, &p, lat, 60); 1082 | geod_polygon_addpoint(&g, &p, lat, 180); 1083 | geod_polygon_testpoint(&g, &p, lat, -60, 0, 1, &area, nullptr); 1084 | result += checkEquals(area, i*r, 0.5); 1085 | geod_polygon_testpoint(&g, &p, lat, -60, 0, 0, &area, nullptr); 1086 | result += checkEquals(area, i*r, 0.5); 1087 | geod_polygon_testpoint(&g, &p, lat, -60, 1, 1, &area, nullptr); 1088 | result += checkEquals(area, -i*r, 0.5); 1089 | geod_polygon_testpoint(&g, &p, lat, -60, 1, 0, &area, nullptr); 1090 | result += checkEquals(area, -i*r + a0, 0.5); 1091 | geod_polygon_testedge(&g, &p, a, s, 0, 1, &area, nullptr); 1092 | result += checkEquals(area, i*r, 0.5); 1093 | geod_polygon_testedge(&g, &p, a, s, 0, 0, &area, nullptr); 1094 | result += checkEquals(area, i*r, 0.5); 1095 | geod_polygon_testedge(&g, &p, a, s, 1, 1, &area, nullptr); 1096 | result += checkEquals(area, -i*r, 0.5); 1097 | geod_polygon_testedge(&g, &p, a, s, 1, 0, &area, nullptr); 1098 | result += checkEquals(area, -i*r + a0, 0.5); 1099 | geod_polygon_addpoint(&g, &p, lat, -60); 1100 | geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); 1101 | result += checkEquals(area, i*r, 0.5); 1102 | geod_polygon_compute(&g, &p, 0, 0, &area, nullptr); 1103 | result += checkEquals(area, i*r, 0.5); 1104 | geod_polygon_compute(&g, &p, 1, 1, &area, nullptr); 1105 | result += checkEquals(area, -i*r, 0.5); 1106 | geod_polygon_compute(&g, &p, 1, 0, &area, nullptr); 1107 | result += checkEquals(area, -i*r + a0, 0.5); 1108 | } 1109 | return result; 1110 | } 1111 | 1112 | static int Planimeter29() { 1113 | /* Check fix to transitdirect vs transit zero handling inconsistency */ 1114 | struct geod_geodesic g; 1115 | struct geod_polygon p; 1116 | double area; 1117 | int result = 0; 1118 | geod_init(&g, wgs84_a, wgs84_f); 1119 | geod_polygon_init(&p, 0); 1120 | geod_polygon_addpoint(&g, &p, 0, 0); 1121 | geod_polygon_addedge(&g, &p, 90, 1000); 1122 | geod_polygon_addedge(&g, &p, 0, 1000); 1123 | geod_polygon_addedge(&g, &p, -90, 1000); 1124 | geod_polygon_compute(&g, &p, 0, 1, &area, nullptr); 1125 | /* The area should be 1e6. Prior to the fix it was 1e6 - A/2, where 1126 | * A = ellipsoid area. */ 1127 | result += checkEquals(area, 1000000.0, 0.01); 1128 | return result; 1129 | } 1130 | 1131 | int main() { 1132 | int n = 0, i; 1133 | if ((i = testinverse())) {++n; printf("testinverse fail: %d\n", i);} 1134 | if ((i = testdirect())) {++n; printf("testdirect fail: %d\n", i);} 1135 | if ((i = testarcdirect())) {++n; printf("testarcdirect fail: %d\n", i);} 1136 | if ((i = GeodSolve0())) {++n; printf("GeodSolve0 fail: %d\n", i);} 1137 | if ((i = GeodSolve1())) {++n; printf("GeodSolve1 fail: %d\n", i);} 1138 | if ((i = GeodSolve2())) {++n; printf("GeodSolve2 fail: %d\n", i);} 1139 | if ((i = GeodSolve4())) {++n; printf("GeodSolve4 fail: %d\n", i);} 1140 | if ((i = GeodSolve5())) {++n; printf("GeodSolve5 fail: %d\n", i);} 1141 | if ((i = GeodSolve6())) {++n; printf("GeodSolve6 fail: %d\n", i);} 1142 | if ((i = GeodSolve9())) {++n; printf("GeodSolve9 fail: %d\n", i);} 1143 | if ((i = GeodSolve10())) {++n; printf("GeodSolve10 fail: %d\n", i);} 1144 | if ((i = GeodSolve11())) {++n; printf("GeodSolve11 fail: %d\n", i);} 1145 | if ((i = GeodSolve12())) {++n; printf("GeodSolve12 fail: %d\n", i);} 1146 | if ((i = GeodSolve14())) {++n; printf("GeodSolve14 fail: %d\n", i);} 1147 | if ((i = GeodSolve15())) {++n; printf("GeodSolve15 fail: %d\n", i);} 1148 | if ((i = GeodSolve17())) {++n; printf("GeodSolve17 fail: %d\n", i);} 1149 | if ((i = GeodSolve26())) {++n; printf("GeodSolve26 fail: %d\n", i);} 1150 | if ((i = GeodSolve28())) {++n; printf("GeodSolve28 fail: %d\n", i);} 1151 | if ((i = GeodSolve33())) {++n; printf("GeodSolve33 fail: %d\n", i);} 1152 | if ((i = GeodSolve55())) {++n; printf("GeodSolve55 fail: %d\n", i);} 1153 | if ((i = GeodSolve59())) {++n; printf("GeodSolve59 fail: %d\n", i);} 1154 | if ((i = GeodSolve61())) {++n; printf("GeodSolve61 fail: %d\n", i);} 1155 | if ((i = GeodSolve65())) {++n; printf("GeodSolve65 fail: %d\n", i);} 1156 | if ((i = GeodSolve67())) {++n; printf("GeodSolve67 fail: %d\n", i);} 1157 | if ((i = GeodSolve71())) {++n; printf("GeodSolve71 fail: %d\n", i);} 1158 | if ((i = GeodSolve73())) {++n; printf("GeodSolve73 fail: %d\n", i);} 1159 | if ((i = GeodSolve74())) {++n; printf("GeodSolve74 fail: %d\n", i);} 1160 | if ((i = GeodSolve76())) {++n; printf("GeodSolve76 fail: %d\n", i);} 1161 | if ((i = GeodSolve78())) {++n; printf("GeodSolve78 fail: %d\n", i);} 1162 | if ((i = GeodSolve80())) {++n; printf("GeodSolve80 fail: %d\n", i);} 1163 | if ((i = GeodSolve84())) {++n; printf("GeodSolve84 fail: %d\n", i);} 1164 | if ((i = GeodSolve92())) {++n; printf("GeodSolve92 fail: %d\n", i);} 1165 | if ((i = GeodSolve94())) {++n; printf("GeodSolve94 fail: %d\n", i);} 1166 | if ((i = GeodSolve96())) {++n; printf("GeodSolve96 fail: %d\n", i);} 1167 | if ((i = Planimeter0())) {++n; printf("Planimeter0 fail: %d\n", i);} 1168 | if ((i = Planimeter5())) {++n; printf("Planimeter5 fail: %d\n", i);} 1169 | if ((i = Planimeter6())) {++n; printf("Planimeter6 fail: %d\n", i);} 1170 | if ((i = Planimeter12())) {++n; printf("Planimeter12 fail: %d\n", i);} 1171 | if ((i = Planimeter12r())) {++n; printf("Planimeter12r fail: %d\n", i);} 1172 | if ((i = Planimeter13())) {++n; printf("Planimeter13 fail: %d\n", i);} 1173 | if ((i = Planimeter15())) {++n; printf("Planimeter15 fail: %d\n", i);} 1174 | if ((i = Planimeter19())) {++n; printf("Planimeter19 fail: %d\n", i);} 1175 | if ((i = Planimeter21())) {++n; printf("Planimeter21 fail: %d\n", i);} 1176 | if ((i = Planimeter29())) {++n; printf("Planimeter29 fail: %d\n", i);} 1177 | return n; 1178 | } 1179 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (NOT MSVC) 2 | set (MATH_LIBS m) 3 | endif () 4 | 5 | foreach (TOOL ${TOOLS}) 6 | add_executable (${TOOL} ${TOOL}.c) 7 | target_link_libraries (${TOOL} ${LIBNAME} ${MATH_LIBS}) 8 | endforeach () 9 | 10 | if (MSVC OR CMAKE_CONFIGURATION_TYPES) 11 | # Add _d suffix for your debug versions of the tools 12 | set_target_properties (${TOOLS} PROPERTIES 13 | DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}") 14 | endif () 15 | -------------------------------------------------------------------------------- /tools/direct.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file direct.c 3 | * @brief A test program for geod_direct() 4 | **********************************************************************/ 5 | 6 | #include 7 | #include "geodesic.h" 8 | 9 | #if defined(_MSC_VER) 10 | /* Squelch warnings about scanf */ 11 | # pragma warning (disable: 4996) 12 | #endif 13 | 14 | /** 15 | * A simple program to solve the direct geodesic problem. 16 | * 17 | * This program reads in lines with lat1, lon1, azi1, s12 and prints out lines 18 | * with lat2, lon2, azi2 (for the WGS84 ellipsoid). 19 | **********************************************************************/ 20 | 21 | int main() { 22 | double a = 6378137, f = 1/298.257223563; /* WGS84 */ 23 | double lat1, lon1, azi1, lat2, lon2, azi2, s12; 24 | struct geod_geodesic g; 25 | 26 | geod_init(&g, a, f); 27 | while (scanf("%lf %lf %lf %lf", &lat1, &lon1, &azi1, &s12) == 4) { 28 | geod_direct(&g, lat1, lon1, azi1, s12, &lat2, &lon2, &azi2); 29 | printf("%.15f %.15f %.15f\n", lat2, lon2, azi2); 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /tools/inverse.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file inverse.c 3 | * @brief A test program for geod_inverse() 4 | **********************************************************************/ 5 | 6 | #include 7 | #include "geodesic.h" 8 | 9 | #if defined(_MSC_VER) 10 | /* Squelch warnings about scanf */ 11 | # pragma warning (disable: 4996) 12 | #endif 13 | 14 | /** 15 | * A simple program to solve the inverse geodesic problem. 16 | * 17 | * This program reads in lines with lat1, lon1, lat2, lon2 and prints out lines 18 | * with azi1, azi2, s12 (for the WGS84 ellipsoid). 19 | **********************************************************************/ 20 | 21 | int main() { 22 | double a = 6378137, f = 1/298.257223563; /* WGS84 */ 23 | double lat1, lon1, azi1, lat2, lon2, azi2, s12; 24 | struct geod_geodesic g; 25 | 26 | geod_init(&g, a, f); 27 | while (scanf("%lf %lf %lf %lf", &lat1, &lon1, &lat2, &lon2) == 4) { 28 | geod_inverse(&g, lat1, lon1, lat2, lon2, &s12, &azi1, &azi2); 29 | printf("%.15f %.15f %.10f\n", azi1, azi2, s12); 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /tools/planimeter.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file planimeter.c 3 | * @brief A test program for geod_polygon_compute() 4 | **********************************************************************/ 5 | 6 | #include 7 | #include "geodesic.h" 8 | 9 | #if defined(_MSC_VER) 10 | /* Squelch warnings about scanf */ 11 | # pragma warning (disable: 4996) 12 | #endif 13 | 14 | /** 15 | * A simple program to compute the area of a geodesic polygon. 16 | * 17 | * This program reads in lines with lat, lon for each vertex 18 | * of a polygon. At the end of input, the program prints the number of 19 | * vertices, the perimeter of the polygon and its area (for the WGS84 20 | * ellipsoid). 21 | **********************************************************************/ 22 | 23 | int main() { 24 | double a = 6378137, f = 1/298.257223563; /* WGS84 */ 25 | double lat, lon, A, P; 26 | int n; 27 | struct geod_geodesic g; 28 | struct geod_polygon p; 29 | geod_init(&g, a, f); 30 | geod_polygon_init(&p, 0); 31 | 32 | while (scanf("%lf %lf", &lat, &lon) == 2) 33 | geod_polygon_addpoint(&g, &p, lat, lon); 34 | n = geod_polygon_compute(&g, &p, 0, 1, &A, &P); 35 | printf("%d %.8f %.3f\n", n, P, A); 36 | return 0; 37 | } 38 | --------------------------------------------------------------------------------