├── tests ├── packages.config ├── SphericalUtil │ ├── computeDistanceBetween.hpp │ ├── computeSignedArea.hpp │ ├── computeArea.hpp │ ├── computeLength.hpp │ ├── computeOffsetOrigin.hpp │ ├── computeHeading.hpp │ ├── computeAngleBetween.hpp │ ├── computeOffset.hpp │ └── interpolate.hpp ├── Tests.cpp ├── PolyUtil │ ├── distanceToLine.hpp │ ├── containsLocation.hpp │ ├── isLocationOnEdge.hpp │ └── isLocationOnPath.hpp ├── tests.vcxproj.filters └── tests.vcxproj ├── Makefile ├── samples ├── samples.vcxproj.filters ├── Samples.cpp ├── samples.cpp └── samples.vcxproj ├── include ├── geometry-library.vcxproj.filters ├── LatLng.hpp ├── MathUtil.hpp ├── geometry-library.vcxproj ├── SphericalUtil.hpp └── PolyUtil.hpp ├── .gitignore ├── .circleci └── config.yml ├── geometry-library.sln ├── LICENSE └── README.md /tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | sample : 2 | g++ samples/Samples.cpp \ 3 | -std=c++14 -Iinclude/ \ 4 | -o Sample 5 | 6 | gtests : 7 | g++ tests/Tests.cpp \ 8 | -std=c++14 -Iinclude/ \ 9 | -lgtest -pthread \ 10 | -o GTests 11 | -------------------------------------------------------------------------------- /samples/samples.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeDistanceBetween.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | TEST(SphericalUtil, computeDistanceBetween) { 7 | LatLng up = { 90.0, 0.0}; 8 | LatLng down = {-90.0, 0.0}; 9 | 10 | EXPECT_NEAR(SphericalUtil::computeDistanceBetween(up, down), M_PI * MathUtil::EARTH_RADIUS, 1e-6); 11 | } 12 | -------------------------------------------------------------------------------- /include/geometry-library.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeSignedArea.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | TEST(SphericalUtil, computeSignedArea) { 7 | LatLng up = { 90.0, 0.0 }; 8 | LatLng down = {-90.0, 0.0 }; 9 | LatLng front = { 0.0, 0.0 }; 10 | LatLng right = { 0.0, 90.0 }; 11 | 12 | std::vector path = { right, up, front, down, right }; 13 | std::vector pathReversed = { right, down, front, up, right }; 14 | 15 | EXPECT_NEAR(-SphericalUtil::computeSignedArea(path), SphericalUtil::computeSignedArea(pathReversed), 0); 16 | } 17 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeArea.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | TEST(SphericalUtil, computeArea) { 7 | LatLng up = { 90.0, 0.0 }; 8 | LatLng down = {-90.0, 0.0 }; 9 | LatLng front = { 0.0, 0.0 }; 10 | LatLng right = { 0.0, 90.0 }; 11 | 12 | std::vector first = { right, up, front, down, right }; 13 | EXPECT_NEAR(SphericalUtil::computeArea(first), M_PI * MathUtil::EARTH_RADIUS * MathUtil::EARTH_RADIUS, .4); 14 | 15 | std::vector second = { right, down, front, up, right }; 16 | EXPECT_NEAR(SphericalUtil::computeArea(second), M_PI * MathUtil::EARTH_RADIUS * MathUtil::EARTH_RADIUS, .4); 17 | } 18 | -------------------------------------------------------------------------------- /tests/Tests.cpp: -------------------------------------------------------------------------------- 1 | /** Including all tests */ 2 | #include "SphericalUtil/interpolate.hpp" 3 | #include "SphericalUtil/computeAngleBetween.hpp" 4 | #include "SphericalUtil/computeSignedArea.hpp" 5 | #include "SphericalUtil/computeArea.hpp" 6 | #include "SphericalUtil/computeLength.hpp" 7 | #include "SphericalUtil/computeOffset.hpp" 8 | #include "SphericalUtil/computeHeading.hpp" 9 | #include "SphericalUtil/computeOffsetOrigin.hpp" 10 | #include "SphericalUtil/computeDistanceBetween.hpp" 11 | 12 | #include "PolyUtil/containsLocation.hpp" 13 | #include "PolyUtil/isLocationOnEdge.hpp" 14 | #include "PolyUtil/isLocationOnPath.hpp" 15 | #include "PolyUtil/distanceToLine.hpp" 16 | 17 | 18 | int main(int argc, char** argv) { 19 | testing::InitGoogleTest(&argc, argv); 20 | return RUN_ALL_TESTS(); 21 | } 22 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeLength.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | TEST(SphericalUtil, computeLength) { 7 | // List without points 8 | std::vector latLngs; 9 | EXPECT_NEAR(SphericalUtil::computeLength(latLngs), 0, 1e-6); 10 | 11 | // List with one point 12 | latLngs.push_back(LatLng(0, 0)); 13 | EXPECT_NEAR(SphericalUtil::computeLength(latLngs), 0, 1e-6); 14 | 15 | // List with two points 16 | latLngs.push_back(LatLng(0.1, 0.1)); 17 | EXPECT_NEAR(SphericalUtil::computeLength(latLngs), deg2rad(0.1) * sqrt(2) * MathUtil::EARTH_RADIUS, 1); 18 | 19 | // List with three points 20 | std::vector latLngs2 = { {0, 0}, {90, 0}, {0, 90} }; 21 | EXPECT_NEAR(SphericalUtil::computeLength(latLngs2), M_PI * MathUtil::EARTH_RADIUS, 1e-6); 22 | } 23 | -------------------------------------------------------------------------------- /tests/PolyUtil/distanceToLine.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "PolyUtil.hpp" 5 | #include "SphericalUtil.hpp" 6 | 7 | 8 | TEST(PolyUtil, distanceToLine) { 9 | LatLng startLine(28.05359, -82.41632); 10 | LatLng endLine(28.05310, -82.41634); 11 | LatLng point(28.05342, -82.41594); 12 | 13 | double distance = PolyUtil::distanceToLine(point, startLine, endLine); 14 | 15 | EXPECT_NEAR(37.947946, distance, 1e-6); 16 | } 17 | 18 | TEST(PolyUtil, distanceToLine_LessThanDistanceToExtrems) { 19 | LatLng startLine(28.05359, -82.41632); 20 | LatLng endLine(28.05310, -82.41634); 21 | LatLng point(28.05342, -82.41594); 22 | 23 | double distance = PolyUtil::distanceToLine(point, startLine, endLine); 24 | double distanceToStart = SphericalUtil::computeDistanceBetween(point, startLine); 25 | double distanceToEnd = SphericalUtil::computeDistanceBetween(point, endLine); 26 | 27 | EXPECT_TRUE(distance <= distanceToStart && distance <= distanceToEnd); 28 | } 29 | -------------------------------------------------------------------------------- /samples/Samples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "SphericalUtil.hpp" 5 | 6 | 7 | int main() { 8 | LatLng up = { 90.0, 0.0 }; 9 | LatLng down = {-90.0, 0.0 }; 10 | LatLng front = { 0.0, 0.0 }; 11 | LatLng right = { 0.0, 90.0 }; 12 | 13 | auto angle = SphericalUtil::computeAngleBetween(up, right); 14 | std::cout << "The angle between up and right is " << rad2deg(angle) << " degrees" << std::endl; 15 | 16 | auto distance = SphericalUtil::computeDistanceBetween(up, down); 17 | std::cout << "The distance between up and down is " << distance << " meters" << std::endl; 18 | 19 | std::vector points = { front, up, right }; 20 | 21 | auto length = SphericalUtil::computeLength(points); 22 | std::cout << "The length between front, up and right is " << length << " meters" << std::endl; 23 | 24 | auto area = SphericalUtil::computeArea(points); 25 | std::cout << "The area between front, up and right is " << area << " square meters" << std::endl; 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /samples/samples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "SphericalUtil.hpp" 5 | 6 | 7 | int main() { 8 | LatLng up = { 90.0, 0.0 }; 9 | LatLng down = {-90.0, 0.0 }; 10 | LatLng front = { 0.0, 0.0 }; 11 | LatLng right = { 0.0, 90.0 }; 12 | 13 | auto angle = SphericalUtil::computeAngleBetween(up, right); 14 | std::cout << "The angle between up and right is " << rad2deg(angle) << " degrees" << std::endl; 15 | 16 | auto distance = SphericalUtil::computeDistanceBetween(up, down); 17 | std::cout << "The distance between up and down is " << distance << " meters" << std::endl; 18 | 19 | std::vector points = { front, up, right }; 20 | 21 | auto length = SphericalUtil::computeLength(points); 22 | std::cout << "The length between front, up and right is " << length << " meters" << std::endl; 23 | 24 | auto area = SphericalUtil::computeArea(points); 25 | std::cout << "The area between front, up and right is " << area << " square meters" << std::endl; 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gtests 2 | Sample 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # Packages 15 | packages/ 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | [Aa][Rr][Mm]/ 25 | [Aa][Rr][Mm]64/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | [Ll]ogs/ 31 | 32 | # Visual Studio 2015/2017 cache/options directory 33 | .vs/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # Prerequisites 39 | *.d 40 | 41 | # Compiled Object files 42 | *.slo 43 | *.lo 44 | *.o 45 | *.obj 46 | 47 | # Precompiled Headers 48 | *.gch 49 | *.pch 50 | 51 | # Compiled Dynamic libraries 52 | *.so 53 | *.dylib 54 | *.dll 55 | 56 | # Fortran module files 57 | *.mod 58 | *.smod 59 | 60 | # Compiled Static libraries 61 | *.lai 62 | *.la 63 | *.a 64 | *.lib 65 | 66 | # Executables 67 | *.exe 68 | *.out 69 | *.app 70 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | gtest: 4 | working_directory: ~/root 5 | docker: 6 | - image: gcc:latest 7 | steps: 8 | - run: 9 | name: Downloading dependencies 10 | command: | 11 | apt-get update 12 | apt-get install -y cmake 13 | apt-get install -y libgtest-dev 14 | - run: 15 | name: Installing dependencies 16 | working_directory: /usr/src/gtest/ 17 | command: | 18 | cmake CMakeLists.txt 19 | make 20 | cp /usr/src/gtest/*.a /usr/lib 21 | - checkout 22 | - run: 23 | name: Building tests 24 | command: make gtests 25 | - run: 26 | name: Running tests 27 | command: ./GTests --gtest_filter=* 28 | samples: 29 | working_directory: ~/root 30 | docker: 31 | - image: gcc:latest 32 | steps: 33 | - checkout 34 | - run: 35 | name: Building sample 36 | command: make sample 37 | - run: 38 | name: Running sample 39 | command: ./Sample 40 | workflows: 41 | version: 2 42 | build_and_test: 43 | jobs: 44 | - gtest 45 | - samples -------------------------------------------------------------------------------- /tests/SphericalUtil/computeOffsetOrigin.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | #define EXPECT_NEAR_LatLan(actual, expected) \ 7 | EXPECT_NEAR(actual.lat, expected.lat, 1e-6); 8 | // Issue #2 9 | // Account for the convergence of longitude lines at the poles 10 | // double cosLat = cos(deg2rad(actual.lat)); 11 | // EXPECT_NEAR(cosLat * actual.lng, cosLat * expected.lng, 1e-6); 12 | 13 | TEST(SphericalUtil, computeOffsetOrigin) { 14 | LatLng front = { 0.0, 0.0 }; 15 | 16 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(front, 0, 0)); 17 | 18 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng( 0, 45), M_PI * MathUtil::EARTH_RADIUS / 4, 90)); 19 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng( 0, -45), M_PI * MathUtil::EARTH_RADIUS / 4, -90)); 20 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng( 45, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 0)); 21 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng(-45, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 180)); 22 | 23 | // Issue #3 24 | // Situations with no solution, should return null. 25 | // 26 | // First 'over' the pole. 27 | // EXPECT_NULL(SphericalUtil::computeOffsetOrigin(LatLng(80, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 180)); 28 | 29 | // Second a distance that doesn't fit on the earth. 30 | // EXPECT_NULL(SphericalUtil::computeOffsetOrigin(LatLng(80, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 90)); 31 | } 32 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeHeading.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | TEST(SphericalUtil, computeHeading) { 7 | LatLng up = { 90.0, 0.0 }; 8 | LatLng down = {-90.0, 0.0 }; 9 | LatLng front = { 0.0, 0.0 }; 10 | LatLng right = { 0.0, 90.0 }; 11 | LatLng back = { 0.0, -180.0 }; 12 | LatLng left = { 0.0, -90.0 }; 13 | 14 | // Opposing vertices for which there is a result 15 | EXPECT_NEAR(SphericalUtil::computeHeading(up, down), -180, 1e-6); 16 | EXPECT_NEAR(SphericalUtil::computeHeading(down, up), 0, 1e-6); 17 | 18 | // Adjacent vertices for which there is a result 19 | EXPECT_NEAR(SphericalUtil::computeHeading(front, up), 0, 1e-6); 20 | EXPECT_NEAR(SphericalUtil::computeHeading(right, up), 0, 1e-6); 21 | EXPECT_NEAR(SphericalUtil::computeHeading(back, up), 0, 1e-6); 22 | EXPECT_NEAR(SphericalUtil::computeHeading(down, up), 0, 1e-6); 23 | 24 | EXPECT_NEAR(SphericalUtil::computeHeading(front, down), -180, 1e-6); 25 | EXPECT_NEAR(SphericalUtil::computeHeading(right, down), -180, 1e-6); 26 | EXPECT_NEAR(SphericalUtil::computeHeading(back, down), -180, 1e-6); 27 | EXPECT_NEAR(SphericalUtil::computeHeading(left, down), -180, 1e-6); 28 | 29 | EXPECT_NEAR(SphericalUtil::computeHeading(right, front), -90, 1e-6); 30 | EXPECT_NEAR(SphericalUtil::computeHeading(left, front), 90, 1e-6); 31 | 32 | EXPECT_NEAR(SphericalUtil::computeHeading(front, right), 90, 1e-6); 33 | EXPECT_NEAR(SphericalUtil::computeHeading(back, right), -90, 1e-6); 34 | } 35 | -------------------------------------------------------------------------------- /include/LatLng.hpp: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | // Copyright 2013 Google Inc. 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // This software and the related documents are provided as is, with no express 10 | // or implied warranties, other than those that are expressly stated in the 11 | // License. 12 | //****************************************************************************** 13 | 14 | #ifndef GEOMETRY_LIBRARY_LATLNG 15 | #define GEOMETRY_LIBRARY_LATLNG 16 | 17 | #include 18 | 19 | class LatLng { 20 | public: 21 | double lat; // The latitude of this location 22 | double lng; // The longitude of this location 23 | 24 | /** 25 | * Constructs a location with a latitude/longitude pair. 26 | * 27 | * @param lat The latitude of this location. 28 | * @param lng The longitude of this location. 29 | */ 30 | LatLng(double lat, double lng) 31 | : lat(lat), lng(lng) {} 32 | 33 | LatLng(const LatLng & point) = default; 34 | 35 | LatLng& operator=(const LatLng & other) = default; 36 | 37 | bool operator==(const LatLng & other) const { 38 | return isCoordinateEqual(lat, other.lat) && 39 | isCoordinateEqual(lng, other.lng); 40 | } 41 | 42 | 43 | private: 44 | bool isCoordinateEqual(double first, double second) const { 45 | return std::fabs(first - second) < 1e-12; 46 | } 47 | }; 48 | 49 | #endif // GEOMETRY_LIBRARY_LATLNG 50 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeAngleBetween.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | TEST(SphericalUtil, computeAngleBetween) { 7 | LatLng up = { 90.0, 0.0 }; 8 | LatLng down = {-90.0, 0.0 }; 9 | LatLng front = { 0.0, 0.0 }; 10 | LatLng right = { 0.0, 90.0 }; 11 | LatLng back = { 0.0, -180.0 }; 12 | LatLng left = { 0.0, -90.0 }; 13 | 14 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, up), 0, 1e-6); 15 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(down, down), 0, 1e-6); 16 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(left, left), 0, 1e-6); 17 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(right, right), 0, 1e-6); 18 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(front, front), 0, 1e-6); 19 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(back, back), 0, 1e-6); 20 | 21 | // Adjacent vertices 22 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, front), M_PI / 2, 1e-6); 23 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, right), M_PI / 2, 1e-6); 24 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, back), M_PI / 2, 1e-6); 25 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, left), M_PI / 2, 1e-6); 26 | 27 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(down, front), M_PI / 2, 1e-6); 28 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(down, right), M_PI / 2, 1e-6); 29 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(down, back), M_PI / 2, 1e-6); 30 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(down, left), M_PI / 2, 1e-6); 31 | 32 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(back, up), M_PI / 2, 1e-6); 33 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(back, right), M_PI / 2, 1e-6); 34 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(back, down), M_PI / 2, 1e-6); 35 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(back, left), M_PI / 2, 1e-6); 36 | 37 | // Opposite vertices 38 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, down), M_PI, 1e-6); 39 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(front, back), M_PI, 1e-6); 40 | EXPECT_NEAR(SphericalUtil::computeAngleBetween(left, right), M_PI, 1e-6); 41 | } 42 | -------------------------------------------------------------------------------- /tests/tests.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4fbd4f21-7e3a-4fd2-9bfa-29dc154dc6d7} 6 | 7 | 8 | {1498a4c7-50db-40b7-a717-9cf9f748a7fb} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | SphericalUtil 20 | 21 | 22 | SphericalUtil 23 | 24 | 25 | SphericalUtil 26 | 27 | 28 | SphericalUtil 29 | 30 | 31 | SphericalUtil 32 | 33 | 34 | SphericalUtil 35 | 36 | 37 | SphericalUtil 38 | 39 | 40 | SphericalUtil 41 | 42 | 43 | SphericalUtil 44 | 45 | 46 | PolyUtil 47 | 48 | 49 | PolyUtil 50 | 51 | 52 | PolyUtil 53 | 54 | 55 | PolyUtil 56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/SphericalUtil/computeOffset.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | #define EXPECT_NEAR_LatLan(actual, expected) \ 6 | EXPECT_NEAR(actual.lat, expected.lat, 1e-6); 7 | // Issue #2 8 | // Account for the convergence of longitude lines at the poles 9 | // double cosLat = cos(deg2rad(actual.lat)); 10 | // EXPECT_NEAR(cosLat * actual.lng, cosLat * expected.lng, 1e-6); 11 | 12 | TEST(SphericalUtil, computeOffset) { 13 | LatLng up = { 90.0, 0.0 }; 14 | LatLng down = {-90.0, 0.0 }; 15 | LatLng front = { 0.0, 0.0 }; 16 | LatLng right = { 0.0, 90.0 }; 17 | LatLng back = { 0.0, -180.0 }; 18 | LatLng left = { 0.0, -90.0 }; 19 | 20 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffset(front, 0, 0)); 21 | EXPECT_NEAR_LatLan(up, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 0)); 22 | EXPECT_NEAR_LatLan(down, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 180)); 23 | EXPECT_NEAR_LatLan(left, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, -90)); 24 | EXPECT_NEAR_LatLan(right, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 90)); 25 | EXPECT_NEAR_LatLan(back, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS, 0)); 26 | EXPECT_NEAR_LatLan(back, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS, 90)); 27 | 28 | // From left 29 | EXPECT_NEAR_LatLan(left, SphericalUtil::computeOffset(left, 0, 0)); 30 | EXPECT_NEAR_LatLan(up, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, 0)); 31 | EXPECT_NEAR_LatLan(down, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, 180)); 32 | EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, 90)); 33 | EXPECT_NEAR_LatLan(back, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, -90)); 34 | EXPECT_NEAR_LatLan(right, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS, 0)); 35 | EXPECT_NEAR_LatLan(right, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS, 90)); 36 | 37 | // NOTE: Heading is undefined at the poles, so we do not test from up/down. 38 | } 39 | -------------------------------------------------------------------------------- /geometry-library.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.106 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "geometry-library", "include\geometry-library.vcxproj", "{6FF667F1-262A-4779-81CF-FD346FE2AF62}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests\tests.vcxproj", "{96B63797-1621-492E-88C0-72751561874D}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "samples", "samples\samples.vcxproj", "{1587DECE-892D-47F5-863A-D7568D4D2167}" 11 | ProjectSection(ProjectDependencies) = postProject 12 | {6FF667F1-262A-4779-81CF-FD346FE2AF62} = {6FF667F1-262A-4779-81CF-FD346FE2AF62} 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|x64 = Debug|x64 18 | Debug|x86 = Debug|x86 19 | Release|x64 = Release|x64 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {6FF667F1-262A-4779-81CF-FD346FE2AF62}.Debug|x64.ActiveCfg = Debug|x64 24 | {6FF667F1-262A-4779-81CF-FD346FE2AF62}.Debug|x86.ActiveCfg = Debug|Win32 25 | {6FF667F1-262A-4779-81CF-FD346FE2AF62}.Release|x64.ActiveCfg = Debug|x64 26 | {6FF667F1-262A-4779-81CF-FD346FE2AF62}.Release|x86.ActiveCfg = Release|Win32 27 | {96B63797-1621-492E-88C0-72751561874D}.Debug|x64.ActiveCfg = Debug|x64 28 | {96B63797-1621-492E-88C0-72751561874D}.Debug|x64.Build.0 = Debug|x64 29 | {96B63797-1621-492E-88C0-72751561874D}.Debug|x86.ActiveCfg = Debug|Win32 30 | {96B63797-1621-492E-88C0-72751561874D}.Debug|x86.Build.0 = Debug|Win32 31 | {96B63797-1621-492E-88C0-72751561874D}.Release|x64.ActiveCfg = Debug|x64 32 | {96B63797-1621-492E-88C0-72751561874D}.Release|x64.Build.0 = Debug|x64 33 | {96B63797-1621-492E-88C0-72751561874D}.Release|x86.ActiveCfg = Release|Win32 34 | {96B63797-1621-492E-88C0-72751561874D}.Release|x86.Build.0 = Release|Win32 35 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Debug|x64.ActiveCfg = Debug|x64 36 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Debug|x64.Build.0 = Debug|x64 37 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Debug|x86.ActiveCfg = Debug|Win32 38 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Debug|x86.Build.0 = Debug|Win32 39 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Release|x64.ActiveCfg = Debug|x64 40 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Release|x64.Build.0 = Debug|x64 41 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Release|x86.ActiveCfg = Release|Win32 42 | {1587DECE-892D-47F5-863A-D7568D4D2167}.Release|x86.Build.0 = Release|Win32 43 | EndGlobalSection 44 | GlobalSection(SolutionProperties) = preSolution 45 | HideSolutionNode = FALSE 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {16C2AA62-9846-4234-962E-007CD4A92124} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /tests/SphericalUtil/interpolate.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "SphericalUtil.hpp" 4 | 5 | 6 | #define EXPECT_NEAR_LatLan(actual, expected) \ 7 | EXPECT_NEAR(actual.lat, expected.lat, 1e-6); 8 | // Issue #2 9 | // Account for the convergence of longitude lines at the poles 10 | // double cosLat = cos(deg2rad(actual.lat)); 11 | // EXPECT_NEAR(cosLat * actual.lng, cosLat * expected.lng, 1e-6); 12 | 13 | 14 | TEST(SphericalUtil, interpolate) { 15 | LatLng up = { 90.0, 0.0 }; 16 | LatLng down = {-90.0, 0.0 }; 17 | LatLng front = { 0.0, 0.0 }; 18 | LatLng back = { 0.0, -180.0 }; 19 | LatLng left = { 0.0, -90.0 }; 20 | 21 | EXPECT_NEAR_LatLan(up, SphericalUtil::interpolate(up, up, 1 / 2.0)); 22 | EXPECT_NEAR_LatLan(down, SphericalUtil::interpolate(down, down, 1 / 2.0)); 23 | EXPECT_NEAR_LatLan(left, SphericalUtil::interpolate(left, left, 1 / 2.0)); 24 | 25 | // Between front and up 26 | EXPECT_NEAR_LatLan(LatLng(1, 0), SphericalUtil::interpolate(front, up, 1 / 90.0)); 27 | EXPECT_NEAR_LatLan(LatLng(1, 0), SphericalUtil::interpolate(up, front, 89 / 90.0)); 28 | EXPECT_NEAR_LatLan(LatLng(89, 0), SphericalUtil::interpolate(front, up, 89 / 90.0)); 29 | EXPECT_NEAR_LatLan(LatLng(89, 0), SphericalUtil::interpolate(up, front, 1 / 90.0)); 30 | 31 | // Between front and down 32 | EXPECT_NEAR_LatLan(LatLng(-1, 0), SphericalUtil::interpolate(front, down, 1 / 90.0)); 33 | EXPECT_NEAR_LatLan(LatLng(-1, 0), SphericalUtil::interpolate(down, front, 89 / 90.0)); 34 | EXPECT_NEAR_LatLan(LatLng(-89, 0), SphericalUtil::interpolate(front, down, 89 / 90.0)); 35 | EXPECT_NEAR_LatLan(LatLng(-89, 0), SphericalUtil::interpolate(down, front, 1 / 90.0)); 36 | 37 | // Between left and back 38 | EXPECT_NEAR_LatLan(LatLng(0, -91), SphericalUtil::interpolate(left, back, 1 / 90.0)); 39 | EXPECT_NEAR_LatLan(LatLng(0, -91), SphericalUtil::interpolate(back, left, 89 / 90.0)); 40 | EXPECT_NEAR_LatLan(LatLng(0, -179), SphericalUtil::interpolate(left, back, 89 / 90.0)); 41 | EXPECT_NEAR_LatLan(LatLng(0, -179), SphericalUtil::interpolate(back, left, 1 / 90.0)); 42 | 43 | // geodesic crosses pole 44 | EXPECT_NEAR_LatLan(up, SphericalUtil::interpolate(LatLng(45, 0), LatLng(45, 180), 1 / 2.0)); 45 | EXPECT_NEAR_LatLan(down, SphericalUtil::interpolate(LatLng(-45, 0), LatLng(-45, 180), 1 / 2.0)); 46 | 47 | // boundary values for fraction, between left and back 48 | EXPECT_NEAR_LatLan(left, SphericalUtil::interpolate(left, back, 0.0)); 49 | EXPECT_NEAR_LatLan(back, SphericalUtil::interpolate(left, back, 1.0)); 50 | 51 | // two nearby points, separated by ~4m, for which the Slerp algorithm is not stable and we 52 | // have to fall back to linear interpolation. 53 | LatLng interpolateResult = SphericalUtil::interpolate(LatLng(-37.756891, 175.325262), LatLng(-37.756853, 175.325242), 0.5); 54 | LatLng goldenResult(-37.756872, 175.325252); 55 | 56 | EXPECT_NEAR(interpolateResult.lat, goldenResult.lat, 2e-5); 57 | } 58 | -------------------------------------------------------------------------------- /tests/PolyUtil/containsLocation.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "PolyUtil.hpp" 5 | 6 | 7 | TEST(PolyUtil, containsLocation) { 8 | // Empty. 9 | std::vector empty; 10 | EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), empty, true)); 11 | EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), empty, false)); 12 | 13 | 14 | // One point. 15 | std::vector one = { {1, 2} }; 16 | EXPECT_TRUE(PolyUtil::containsLocation(LatLng(1, 2), one, true)); 17 | EXPECT_TRUE(PolyUtil::containsLocation(LatLng(1, 2), one, false)); 18 | 19 | EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), one, true)); 20 | EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), one, false)); 21 | 22 | 23 | // Two points. 24 | std::vector two = { {1, 2}, {3, 5} }; 25 | for (const auto & point : { LatLng(1, 2), LatLng(3, 5) }) { 26 | EXPECT_TRUE(PolyUtil::containsLocation(point, two, true)); 27 | EXPECT_TRUE(PolyUtil::containsLocation(point, two, false)); 28 | } 29 | for (const auto & point : { LatLng(0, 0), LatLng(40, 4) }) { 30 | EXPECT_FALSE(PolyUtil::containsLocation(point, two, true)); 31 | EXPECT_FALSE(PolyUtil::containsLocation(point, two, false)); 32 | } 33 | 34 | 35 | // Some arbitrary triangle. 36 | std::vector triangle = { {0, 0}, {10, 12}, {20, 5} }; 37 | for (const auto & point : { LatLng(10, 12), LatLng(10, 11), LatLng(19, 5) }) { 38 | EXPECT_TRUE(PolyUtil::containsLocation(point, triangle, true)); 39 | EXPECT_TRUE(PolyUtil::containsLocation(point, triangle, false)); 40 | } 41 | for (const auto & point : { LatLng(0, 1), LatLng(11, 12), LatLng(30, 5), LatLng(0, -180), LatLng(0, 90) }) { 42 | EXPECT_FALSE(PolyUtil::containsLocation(point, triangle, true)); 43 | EXPECT_FALSE(PolyUtil::containsLocation(point, triangle, false)); 44 | } 45 | 46 | 47 | // Around North Pole. 48 | std::vector northPole = { {89, 0}, {89, 120}, {89, -120} }; 49 | for (const auto & point : { LatLng(90, 0), /* LatLng(90, 180), */ LatLng(90, -90) }) { 50 | EXPECT_TRUE(PolyUtil::containsLocation(point, northPole, true)); 51 | EXPECT_TRUE(PolyUtil::containsLocation(point, northPole, false)); 52 | } 53 | for (const auto & point : { LatLng(-90, 0), LatLng(0, 0) }) { 54 | EXPECT_FALSE(PolyUtil::containsLocation(point, northPole, true)); 55 | EXPECT_FALSE(PolyUtil::containsLocation(point, northPole, false)); 56 | } 57 | 58 | // Around South Pole. 59 | std::vector southPole = { {-89, 0}, {-89, 120}, {-89, -120} }; 60 | for (const auto & point : { LatLng(90, 0), /* LatLng(90, 180), */ LatLng(90, -90), LatLng(0, 0) }) { 61 | EXPECT_TRUE(PolyUtil::containsLocation(point, southPole, true)); 62 | EXPECT_TRUE(PolyUtil::containsLocation(point, southPole, false)); 63 | } 64 | for (const auto & point : { LatLng(-90, 0), LatLng(-90, 90) }) { 65 | EXPECT_FALSE(PolyUtil::containsLocation(point, southPole, true)); 66 | EXPECT_FALSE(PolyUtil::containsLocation(point, southPole, false)); 67 | } 68 | 69 | // Over/under segment on meridian and equator. 70 | std::vector poly = { {5, 10}, {10, 10}, {0, 20}, {0, -10} }; 71 | for (const auto & point : { LatLng(2.5, 10), LatLng(1, 0) }) { 72 | EXPECT_TRUE(PolyUtil::containsLocation(point, poly, true)); 73 | EXPECT_TRUE(PolyUtil::containsLocation(point, poly, false)); 74 | } 75 | for (const auto & point : { LatLng(15, 10), LatLng(0, -15), LatLng(0, 25), LatLng(-1, 0) }) { 76 | EXPECT_FALSE(PolyUtil::containsLocation(point, poly, true)); 77 | EXPECT_FALSE(PolyUtil::containsLocation(point, poly, false)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /include/MathUtil.hpp: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | // Copyright 2013 Google Inc. 3 | // https://github.com/googlemaps/android-maps-utils/blob/master/library/src/main/java/com/google/maps/android/MathUtil.java 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // This software and the related documents are provided as is, with no express 11 | // or implied warranties, other than those that are expressly stated in the 12 | // License. 13 | //****************************************************************************** 14 | 15 | #ifndef GEOMETRY_LIBRARY_MATH_UTIL 16 | #define GEOMETRY_LIBRARY_MATH_UTIL 17 | 18 | #include 19 | #include 20 | 21 | #define M_PI 3.14159265358979323846 22 | 23 | inline double deg2rad(double degrees) { 24 | return degrees * M_PI / 180.0; 25 | } 26 | 27 | inline double rad2deg(double angle) { 28 | return angle * 180.0 / M_PI; 29 | } 30 | 31 | class MathUtil { 32 | public: 33 | /** 34 | * The earth's radius, in meters. 35 | * Mean radius as defined by IUGG. 36 | */ 37 | static constexpr double EARTH_RADIUS = 6371009.0; 38 | 39 | /** 40 | * Restrict x to the range [low, high]. 41 | */ 42 | static inline double clamp(double x, double low, double high) { 43 | return x < low ? low : (x > high ? high : x); 44 | } 45 | 46 | /** 47 | * Wraps the given value into the inclusive-exclusive interval between min and max. 48 | * @param n The value to wrap. 49 | * @param min The minimum. 50 | * @param max The maximum. 51 | */ 52 | static inline double wrap(double n, double min, double max) { 53 | return (n >= min && n < max) ? n : (MathUtil::mod(n - min, max - min) + min); 54 | } 55 | 56 | /** 57 | * Returns the non-negative remainder of x / m. 58 | * @param x The operand. 59 | * @param m The modulus. 60 | */ 61 | static inline double mod(double x, double m) { 62 | return remainder(remainder(x, m) + m, m); 63 | } 64 | 65 | /** 66 | * Returns mercator Y corresponding to latitude. 67 | * See http://en.wikipedia.org/wiki/Mercator_projection . 68 | */ 69 | static inline double mercator(double lat) { 70 | return log(tan(lat * 0.5 + M_PI / 4.0)); 71 | } 72 | 73 | /** 74 | * Returns latitude from mercator Y. 75 | */ 76 | static inline double inverseMercator(double y) { 77 | return 2.0 * atan(exp(y)) - M_PI / 2.0; 78 | } 79 | 80 | /** 81 | * Returns haversine(angle-in-radians). 82 | * hav(x) == (1 - cos(x)) / 2 == sin(x / 2)^2. 83 | */ 84 | static inline double hav(double x) { 85 | double sinHalf = sin(x * 0.5); 86 | return sinHalf * sinHalf; 87 | } 88 | 89 | /** 90 | * Computes inverse haversine. Has good numerical stability around 0. 91 | * arcHav(x) == acos(1 - 2 * x) == 2 * asin(sqrt(x)). 92 | * The argument must be in [0, 1], and the result is positive. 93 | */ 94 | static inline double arcHav(double x) { 95 | return 2.0 * asin(sqrt(x)); 96 | } 97 | 98 | // Given h==hav(x), returns sin(abs(x)). 99 | static inline double sinFromHav(double h) { 100 | return 2.0 * sqrt(h * (1.0 - h)); 101 | } 102 | 103 | // Returns hav(asin(x)). 104 | static inline double havFromSin(double x) { 105 | double x2 = x * x; 106 | return x2 / (1.0 + sqrt(1.0 - x2)) * 0.5; 107 | } 108 | 109 | // Returns sin(arcHav(x) + arcHav(y)). 110 | static inline double sinSumFromHav(double x, double y) { 111 | double a = sqrt(x * (1 - x)); 112 | double b = sqrt(y * (1 - y)); 113 | return 2.0 * (a + b - 2 * (a * y + b * x)); 114 | } 115 | 116 | /** 117 | * Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit sphere. 118 | */ 119 | static inline double havDistance(double lat1, double lat2, double dLng) { 120 | return MathUtil::hav(lat1 - lat2) + MathUtil::hav(dLng) * cos(lat1) * cos(lat2); 121 | } 122 | }; 123 | 124 | #endif // GEOMETRY_LIBRARY_MATH_UTIL 125 | -------------------------------------------------------------------------------- /tests/PolyUtil/isLocationOnEdge.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "PolyUtil.hpp" 5 | 6 | 7 | TEST(PolyUtil, isLocationOnEdge) { 8 | // Empty 9 | std::vector empty; 10 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), empty, true)); 11 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), empty, false)); 12 | 13 | // One point. 14 | std::vector one = { {1, 2} }; 15 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(LatLng(1, 2), one, true)); 16 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(LatLng(1, 2), one, false)); 17 | 18 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(3, 5), one, true)); 19 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(3, 5), one, false)); 20 | 21 | // Endpoints 22 | std::vector endpoints = { {1, 2}, {3, 5} }; 23 | for (const auto & point : { LatLng(1, 2), LatLng(3, 5) }) { 24 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, endpoints, true)); 25 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, endpoints, false)); 26 | } 27 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), endpoints, true)); 28 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), endpoints, false)); 29 | 30 | double small = 5e-7; // About 5cm on equator, half the default tolerance. 31 | double big = 2e-6; // About 10cm on equator, double the default tolerance. 32 | 33 | // On equator. 34 | std::vector equator = { {0, 90}, {0, 180} }; 35 | for (const auto & point : { LatLng(0, 90-small), LatLng(0, 90+small), LatLng(0-small, 90), LatLng(0, 135), LatLng(small, 135) }) { 36 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, equator, true)); 37 | } 38 | for (const auto & point : { LatLng(0, 90 - big), LatLng(0, 0), LatLng(0, -90), LatLng(big, 135) }) { 39 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, equator, false)); 40 | } 41 | 42 | // Ends on same latitude. 43 | std::vector sameLatitude = { {-45, -180}, {-45, -small} }; 44 | for (const auto & point : { LatLng(-45, 180+small), LatLng(-45, 180-small), LatLng(-45-small, 180-small), LatLng(-45, 0) }) { 45 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, sameLatitude, true)); 46 | } 47 | for (const auto & point : { LatLng(-45, big), LatLng(-45, 180-big), LatLng(-45+big, -90), LatLng(-45, 90) }) { 48 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, sameLatitude, false)); 49 | } 50 | 51 | // Meridian. 52 | std::vector meridian = { {-10, 30}, {45, 30} }; 53 | for (const auto & point : { LatLng(10, 30 - small), LatLng(20, 30 + small), LatLng(-10 - small, 30 + small) }) { 54 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, meridian, true)); 55 | } 56 | for (const auto & point : { LatLng(-10 - big, 30), LatLng(10, -150), LatLng(0, 30 - big) }) { 57 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, meridian, false)); 58 | } 59 | 60 | // Slanted close to meridian, close to North pole. 61 | std::vector northPole = { {0, 0}, {90 - small, 0 + big} }; 62 | for (const auto & point : { LatLng(1, 0 + small), LatLng(2, 0 - small), LatLng(90 - small, -90), LatLng(90 - small, 10) }) { 63 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, northPole, true)); 64 | } 65 | for (const auto & point : { LatLng(-big, 0), LatLng(90 - big, 180), LatLng(10, big) }) { 66 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, northPole, false)); 67 | } 68 | 69 | // Arc > 120 deg. 70 | std::vector poly = { {0, 0}, {0, 179.999} }; 71 | for (const auto & point : { LatLng(0, 90), LatLng(0, small), LatLng(0, 179), LatLng(small, 90) }) { 72 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, poly, true)); 73 | } 74 | for (const auto & point : { LatLng(0, -90), LatLng(small, -100), LatLng(0, 180), LatLng(0, -big), LatLng(90, 0), LatLng(-90, 180) }) { 75 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, poly, false)); 76 | } 77 | 78 | std::vector poly2 = { {10, 5}, {30, 15} }; 79 | for (const auto & point : { LatLng(10+2*big, 5+big), LatLng(10+big, 5+big/2), LatLng(30-2*big, 15-big) }) { 80 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, poly2, true)); 81 | } 82 | for (const auto & point : { LatLng(20, 10), LatLng(10-big, 5-big/2), LatLng(30+2*big, 15+big), LatLng(10+2*big, 5), LatLng(10, 5+big) }) { 83 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, poly2, false)); 84 | } 85 | 86 | std::vector poly3 = { {90 - small, 0}, {0, 180 - small / 2} }; 87 | for (const auto & point : { LatLng(big, -180 + small / 2), LatLng(big, 180 - small / 4), LatLng(big, 180 - small) }) { 88 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, poly3, true)); 89 | } 90 | for (const auto & point : { LatLng(-big, -180 + small / 2), LatLng(-big, 180), LatLng(-big, 180 - small) }) { 91 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, poly3, false)); 92 | } 93 | 94 | // Reaching close to North pole. 95 | std::vector closeToNorthPole = { {80, 0}, {80, 180 - small} }; 96 | 97 | for (const auto & point : { LatLng(90 - small, -90), LatLng(90, -135), LatLng(80 - small, 0), LatLng(80 + small, 0) }) { 98 | EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, true)); 99 | } 100 | for (const auto & point : { LatLng(80, 90), LatLng(79, big) }) { 101 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, true)); 102 | } 103 | 104 | for (const auto & point : { LatLng(80 - small, 0), LatLng(80 + small, 0), LatLng(80, 90) }) { 105 | // EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, false)); 106 | } 107 | for (const auto & point : { LatLng(79, big), LatLng(90 - small, -90), LatLng(90, -135) }) { 108 | EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, false)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/PolyUtil/isLocationOnPath.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "PolyUtil.hpp" 5 | 6 | 7 | TEST(PolyUtil, isLocationOnPath) { 8 | // Empty 9 | std::vector empty; 10 | EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), empty, true)); 11 | EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), empty, false)); 12 | 13 | // One point. 14 | std::vector one = { {1, 2} }; 15 | EXPECT_TRUE(PolyUtil::isLocationOnPath(LatLng(1, 2), one, true)); 16 | EXPECT_TRUE(PolyUtil::isLocationOnPath(LatLng(1, 2), one, false)); 17 | 18 | EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(3, 5), one, true)); 19 | EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(3, 5), one, false)); 20 | 21 | // Endpoints 22 | std::vector endpoints = { {1, 2}, {3, 5} }; 23 | for (const auto & point : { LatLng(1, 2), LatLng(3, 5) }) { 24 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, endpoints, true)); 25 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, endpoints, false)); 26 | } 27 | EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), endpoints, true)); 28 | EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), endpoints, false)); 29 | 30 | double small = 5e-7; // About 5cm on equator, half the default tolerance. 31 | double big = 2e-6; // About 10cm on equator, double the default tolerance. 32 | 33 | // On equator. 34 | std::vector equator = { {0, 90}, {0, 180} }; 35 | for (const auto & point : { LatLng(0, 90-small), LatLng(0, 90+small), LatLng(0-small, 90), LatLng(0, 135), LatLng(small, 135) }) { 36 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, equator, true)); 37 | } 38 | for (const auto & point : { LatLng(0, 90 - big), LatLng(0, 0), LatLng(0, -90), LatLng(big, 135) }) { 39 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, equator, false)); 40 | } 41 | 42 | // Ends on same latitude. 43 | std::vector sameLatitude = { {-45, -180}, {-45, -small} }; 44 | for (const auto & point : { LatLng(-45, 180+small), LatLng(-45, 180-small), LatLng(-45-small, 180-small), LatLng(-45, 0) }) { 45 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, sameLatitude, true)); 46 | } 47 | for (const auto & point : { LatLng(-45, big), LatLng(-45, 180-big), LatLng(-45+big, -90), LatLng(-45, 90) }) { 48 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, sameLatitude, false)); 49 | } 50 | 51 | // Meridian. 52 | std::vector meridian = { {-10, 30}, {45, 30} }; 53 | for (const auto & point : { LatLng(10, 30 - small), LatLng(20, 30 + small), LatLng(-10 - small, 30 + small) }) { 54 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, meridian, true)); 55 | } 56 | for (const auto & point : { LatLng(-10 - big, 30), LatLng(10, -150), LatLng(0, 30 - big) }) { 57 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, meridian, false)); 58 | } 59 | 60 | // Slanted close to meridian, close to North pole. 61 | std::vector northPole = { {0, 0}, {90 - small, 0 + big} }; 62 | for (const auto & point : { LatLng(1, 0 + small), LatLng(2, 0 - small), LatLng(90 - small, -90), LatLng(90 - small, 10) }) { 63 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, northPole, true)); 64 | } 65 | for (const auto & point : { LatLng(-big, 0), LatLng(90 - big, 180), LatLng(10, big) }) { 66 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, northPole, false)); 67 | } 68 | 69 | // Arc > 120 deg. 70 | std::vector poly = { {0, 0}, {0, 179.999} }; 71 | for (const auto & point : { LatLng(0, 90), LatLng(0, small), LatLng(0, 179), LatLng(small, 90) }) { 72 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, poly, true)); 73 | } 74 | for (const auto & point : { LatLng(0, -90), LatLng(small, -100), LatLng(0, 180), LatLng(0, -big), LatLng(90, 0), LatLng(-90, 180) }) { 75 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, poly, false)); 76 | } 77 | 78 | std::vector poly2 = { {10, 5}, {30, 15} }; 79 | for (const auto & point : { LatLng(10+2*big, 5+big), LatLng(10+big, 5+big/2), LatLng(30-2*big, 15-big) }) { 80 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, poly2, true)); 81 | } 82 | for (const auto & point : { LatLng(20, 10), LatLng(10-big, 5-big/2), LatLng(30+2*big, 15+big), LatLng(10+2*big, 5), LatLng(10, 5+big) }) { 83 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, poly2, false)); 84 | } 85 | 86 | std::vector poly3 = { {90 - small, 0}, {0, 180 - small / 2} }; 87 | for (const auto & point : { LatLng(big, -180 + small / 2), LatLng(big, 180 - small / 4), LatLng(big, 180 - small) }) { 88 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, poly3, true)); 89 | } 90 | for (const auto & point : { LatLng(-big, -180 + small / 2), LatLng(-big, 180), LatLng(-big, 180 - small) }) { 91 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, poly3, false)); 92 | } 93 | 94 | // Reaching close to North pole. 95 | std::vector closeToNorthPole = { {80, 0}, {80, 180 - small} }; 96 | 97 | for (const auto & point : { LatLng(90 - small, -90), LatLng(90, -135), LatLng(80 - small, 0), LatLng(80 + small, 0) }) { 98 | EXPECT_TRUE(PolyUtil::isLocationOnPath(point, closeToNorthPole, true)); 99 | } 100 | for (const auto & point : { LatLng(80, 90), LatLng(79, big) }) { 101 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, closeToNorthPole, true)); 102 | } 103 | 104 | for (const auto & point : { LatLng(80 - small, 0), LatLng(80 + small, 0), LatLng(80, 90) }) { 105 | // EXPECT_TRUE(PolyUtil::isLocationOnPath(point, closeToNorthPole, false)); 106 | } 107 | for (const auto & point : { LatLng(79, big), LatLng(90 - small, -90), LatLng(90, -135) }) { 108 | EXPECT_FALSE(PolyUtil::isLocationOnPath(point, closeToNorthPole, false)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /include/geometry-library.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {6FF667F1-262A-4779-81CF-FD346FE2AF62} 24 | geometrylibrary 25 | 10.0.17763.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v142 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v141 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | Disabled 77 | true 78 | true 79 | 80 | 81 | Console 82 | 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | 91 | 92 | Console 93 | 94 | 95 | 96 | 97 | Level3 98 | MaxSpeed 99 | true 100 | true 101 | true 102 | true 103 | 104 | 105 | Console 106 | true 107 | true 108 | 109 | 110 | 111 | 112 | Level3 113 | MaxSpeed 114 | true 115 | true 116 | true 117 | true 118 | 119 | 120 | Console 121 | true 122 | true 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /samples/samples.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {1587DECE-892D-47F5-863A-D7568D4D2167} 24 | samples 25 | 10.0.17763.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v141 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | EnableAllWarnings 76 | MaxSpeed 77 | true 78 | true 79 | true 80 | true 81 | $(SolutionDir)/include/ 82 | 83 | 84 | true 85 | true 86 | Console 87 | 88 | 89 | 90 | 91 | Level3 92 | Disabled 93 | true 94 | true 95 | $(SolutionDir)/include/ 96 | 97 | 98 | 99 | 100 | EnableAllWarnings 101 | Disabled 102 | true 103 | true 104 | $(SolutionDir)/include/ 105 | 106 | 107 | Console 108 | 109 | 110 | 111 | 112 | Level3 113 | MaxSpeed 114 | true 115 | true 116 | true 117 | true 118 | $(SolutionDir)/include/ 119 | 120 | 121 | true 122 | true 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /tests/tests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {96b63797-1621-492e-88c0-72751561874d} 23 | Win32Proj 24 | 10.0.17763.0 25 | Application 26 | v141 27 | Unicode 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | $(VC_IncludePath);$(WindowsSDK_IncludePath);../include/ 37 | true 38 | 39 | 40 | $(VC_IncludePath);$(WindowsSDK_IncludePath);../include/ 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {6ff667f1-262a-4779-81cf-fd346fe2af62} 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | NotUsing 75 | pch.h 76 | Disabled 77 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | true 79 | EnableFastChecks 80 | MultiThreadedDebugDLL 81 | Level3 82 | $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ 83 | 84 | 85 | true 86 | Console 87 | 88 | 89 | 90 | 91 | NotUsing 92 | 93 | 94 | Disabled 95 | X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 96 | true 97 | EnableFastChecks 98 | MultiThreadedDebugDLL 99 | Level3 100 | $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ 101 | 102 | 103 | 104 | true 105 | Console 106 | 107 | 108 | 109 | 110 | NotUsing 111 | pch.h 112 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 113 | MultiThreadedDLL 114 | Level3 115 | ProgramDatabase 116 | $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ 117 | 118 | 119 | true 120 | Console 121 | true 122 | true 123 | 124 | 125 | 126 | 127 | NotUsing 128 | 129 | 130 | X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 131 | MultiThreadedDLL 132 | Level3 133 | ProgramDatabase 134 | $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ 135 | 136 | 137 | 138 | true 139 | Console 140 | true 141 | true 142 | 143 | 144 | 145 | 146 | Данный проект ссылается на пакеты NuGet, отсутствующие на этом компьютере. Используйте восстановление пакетов NuGet, чтобы скачать их. Дополнительную информацию см. по адресу: http://go.microsoft.com/fwlink/?LinkID=322105. Отсутствует следующий файл: {0}. 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /include/SphericalUtil.hpp: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | // Copyright 2013 Google Inc. 3 | // https://github.com/googlemaps/android-maps-utils/blob/master/library/src/main/java/com/google/maps/android/SphericalUtil.java 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // This software and the related documents are provided as is, with no express 11 | // or implied warranties, other than those that are expressly stated in the 12 | // License. 13 | //****************************************************************************** 14 | 15 | #ifndef GEOMETRY_LIBRARY_SPHERICAL_UTIL 16 | #define GEOMETRY_LIBRARY_SPHERICAL_UTIL 17 | 18 | #include "MathUtil.hpp" 19 | #include "LatLng.hpp" 20 | 21 | class SphericalUtil { 22 | public: 23 | /** 24 | * Returns the heading from one LatLng to another LatLng. Headings are 25 | * expressed in degrees clockwise from North within the range [-180,180). 26 | * 27 | * @return The heading in degrees clockwise from north. 28 | */ 29 | inline static double computeHeading(const LatLng& from, const LatLng& to) { 30 | // http://williams.best.vwh.net/avform.htm#Crs 31 | double fromLat = deg2rad(from.lat); 32 | double fromLng = deg2rad(from.lng); 33 | double toLat = deg2rad(to.lat); 34 | double toLng = deg2rad(to.lng); 35 | double dLng = toLng - fromLng; 36 | double heading = atan2( 37 | sin(dLng) * cos(toLat), 38 | cos(fromLat) * sin(toLat) - sin(fromLat) * cos(toLat) * cos(dLng)); 39 | 40 | return MathUtil::wrap(rad2deg(heading), -180, 180); 41 | } 42 | 43 | 44 | /** 45 | * Returns the LatLng resulting from moving a distance from an origin 46 | * in the specified heading (expressed in degrees clockwise from north). 47 | * 48 | * @param from The LatLng from which to start. 49 | * @param distance The distance to travel. 50 | * @param heading The heading in degrees clockwise from north. 51 | */ 52 | inline static LatLng computeOffset(const LatLng& from, double distance, double heading) { 53 | distance /= MathUtil::EARTH_RADIUS; 54 | heading = deg2rad(heading); 55 | // http://williams.best.vwh.net/avform.htm#LL 56 | double fromLat = deg2rad(from.lat); 57 | double fromLng = deg2rad(from.lng); 58 | double cosDistance = cos(distance); 59 | double sinDistance = sin(distance); 60 | double sinFromLat = sin(fromLat); 61 | double cosFromLat = cos(fromLat); 62 | double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading); 63 | double dLng = atan2( 64 | sinDistance * cosFromLat * sin(heading), 65 | cosDistance - sinFromLat * sinLat); 66 | 67 | return LatLng(rad2deg(asin(sinLat)), rad2deg(fromLng + dLng)); 68 | } 69 | 70 | 71 | 72 | /** 73 | * Returns the location of origin when provided with a LatLng destination, 74 | * meters travelled and original heading. Headings are expressed in degrees 75 | * clockwise from North. This function returns null when no solution is 76 | * available. 77 | * 78 | * @param to The destination LatLng. 79 | * @param distance The distance travelled, in meters. 80 | * @param heading The heading in degrees clockwise from north. 81 | */ 82 | inline static LatLng computeOffsetOrigin(const LatLng& to, double distance, double heading) { 83 | heading = deg2rad(heading); 84 | distance /= MathUtil::EARTH_RADIUS; 85 | // http://lists.maptools.org/pipermail/proj/2008-October/003939.html 86 | double n1 = cos(distance); 87 | double n2 = sin(distance) * cos(heading); 88 | double n3 = sin(distance) * sin(heading); 89 | double n4 = sin(deg2rad(to.lat)); 90 | // There are two solutions for b. b = n2 * n4 +/- sqrt(), one solution results 91 | // in the latitude outside the [-90, 90] range. We first try one solution and 92 | // back off to the other if we are outside that range. 93 | double n12 = n1 * n1; 94 | double discriminant = n2 * n2 * n12 + n12 * n12 - n12 * n4 * n4; 95 | 96 | // TODO: No real solution which would make sense in LatLng-space. 97 | // if (discriminant < 0) return null; 98 | 99 | double b = n2 * n4 + sqrt(discriminant); 100 | b /= n1 * n1 + n2 * n2; 101 | double a = (n4 - n2 * b) / n1; 102 | double fromLatRadians = atan2(a, b); 103 | if (fromLatRadians < -M_PI / 2 || fromLatRadians > M_PI / 2) { 104 | b = n2 * n4 - sqrt(discriminant); 105 | b /= n1 * n1 + n2 * n2; 106 | fromLatRadians = atan2(a, b); 107 | } 108 | 109 | // TODO: No solution which would make sense in LatLng-space. 110 | // if (fromLatRadians < -M_PI / 2 || fromLatRadians > M_PI / 2) return null; 111 | 112 | double fromLngRadians = rad2deg(to.lng) - atan2(n3, n1 * cos(fromLatRadians) - n2 * sin(fromLatRadians)); 113 | return LatLng(rad2deg(fromLatRadians), rad2deg(fromLngRadians)); 114 | } 115 | 116 | 117 | 118 | /** 119 | * Returns the LatLng which lies the given fraction of the way between the 120 | * origin LatLng and the destination LatLng. 121 | * 122 | * @param from The LatLng from which to start. 123 | * @param to The LatLng toward which to travel. 124 | * @param fraction A fraction of the distance to travel. 125 | * @return The interpolated LatLng. 126 | */ 127 | inline static LatLng interpolate(const LatLng& from, const LatLng& to, double fraction) { 128 | // http://en.wikipedia.org/wiki/Slerp 129 | double fromLat = deg2rad(from.lat); 130 | double fromLng = deg2rad(from.lng); 131 | double toLat = deg2rad(to.lat); 132 | double toLng = deg2rad(to.lng); 133 | double cosFromLat = cos(fromLat); 134 | double cosToLat = cos(toLat); 135 | // Computes Spherical interpolation coefficients. 136 | double angle = SphericalUtil::computeAngleBetween(from, to); 137 | double sinAngle = sin(angle); 138 | if (sinAngle < 1e-6) { 139 | return from; 140 | } 141 | double a = sin((1 - fraction) * angle) / sinAngle; 142 | double b = sin(fraction * angle) / sinAngle; 143 | // Converts from polar to vector and interpolate. 144 | double x = a * cosFromLat * cos(fromLng) + b * cosToLat * cos(toLng); 145 | double y = a * cosFromLat * sin(fromLng) + b * cosToLat * sin(toLng); 146 | double z = a * sin(fromLat) + b * sin(toLat); 147 | // Converts interpolated vector back to polar. 148 | double lat = atan2(z, sqrt(x * x + y * y)); 149 | double lng = atan2(y, x); 150 | return LatLng(rad2deg(lat), rad2deg(lng)); 151 | } 152 | 153 | /** 154 | * Returns the angle between two LatLngs, in radians. This is the same as the distance 155 | * on the unit sphere. 156 | */ 157 | inline static double computeAngleBetween(const LatLng& from, const LatLng& to) { 158 | return SphericalUtil::distanceRadians(deg2rad(from.lat), deg2rad(from.lng), deg2rad(to.lat), deg2rad(to.lng)); 159 | } 160 | 161 | /** 162 | * Returns the distance between two LatLngs, in meters. 163 | */ 164 | inline static double computeDistanceBetween(const LatLng& from, const LatLng& to) { 165 | return SphericalUtil::computeAngleBetween(from, to) * MathUtil::EARTH_RADIUS; 166 | } 167 | 168 | /** 169 | * Returns the length of the given path, in meters, on Earth. 170 | */ 171 | template 172 | inline static double computeLength(const LatLngList& path) { 173 | if (path.size() < 2U) { 174 | return 0; 175 | } 176 | double length = 0; 177 | LatLng prev = path[0]; 178 | double prevLat = deg2rad(prev.lat); 179 | double prevLng = deg2rad(prev.lng); 180 | for (auto point : path) { 181 | double lat = deg2rad(point.lat); 182 | double lng = deg2rad(point.lng); 183 | length += SphericalUtil::distanceRadians(prevLat, prevLng, lat, lng); 184 | prevLat = lat; 185 | prevLng = lng; 186 | } 187 | return length * MathUtil::EARTH_RADIUS; 188 | } 189 | 190 | /** 191 | * Returns the area of a closed path on Earth. 192 | * 193 | * @param path A closed path. 194 | * @return The path's area in square meters. 195 | */ 196 | template 197 | inline static double computeArea(const LatLngList& path) { 198 | return abs(SphericalUtil::computeSignedArea(path)); 199 | } 200 | 201 | /** 202 | * Returns the signed area of a closed path on Earth. The sign of the area may be used to 203 | * determine the orientation of the path. 204 | * "inside" is the surface that does not contain the South Pole. 205 | * 206 | * @param path A closed path. 207 | * @return The loop's area in square meters. 208 | */ 209 | template 210 | inline static double computeSignedArea(const LatLngList& path) { 211 | return SphericalUtil::computeSignedAreaP(path, MathUtil::EARTH_RADIUS); 212 | } 213 | 214 | 215 | private: 216 | /** 217 | * Returns distance on the unit sphere; the arguments are in radians. 218 | */ 219 | inline static double distanceRadians(double lat1, double lng1, double lat2, double lng2) { 220 | return MathUtil::arcHav(MathUtil::havDistance(lat1, lat2, lng1 - lng2)); 221 | } 222 | 223 | /** 224 | * Returns the signed area of a closed path on a sphere of given radius. 225 | * The computed area uses the same units as the radius squared. 226 | * Used by SphericalUtilTest. 227 | */ 228 | template 229 | inline static double computeSignedAreaP(const LatLngList& path, double radius) { 230 | size_t size = path.size(); 231 | if (size < 3U) { return 0; } 232 | double total = 0; 233 | LatLng prev = path[size - 1]; 234 | double prevTanLat = tan((M_PI / 2 - deg2rad(prev.lat)) / 2); 235 | double prevLng = deg2rad(prev.lng); 236 | // For each edge, accumulate the signed area of the triangle formed by the North Pole 237 | // and that edge ("polar triangle"). 238 | for (auto point : path) { 239 | double tanLat = tan((M_PI / 2 - deg2rad(point.lat)) / 2); 240 | double lng = deg2rad(point.lng); 241 | total += SphericalUtil::polarTriangleArea(tanLat, lng, prevTanLat, prevLng); 242 | prevTanLat = tanLat; 243 | prevLng = lng; 244 | } 245 | return total * (radius * radius); 246 | } 247 | 248 | /** 249 | * Returns the signed area of a triangle which has North Pole as a vertex. 250 | * Formula derived from "Area of a spherical triangle given two edges and the included angle" 251 | * as per "Spherical Trigonometry" by Todhunter, page 71, section 103, point 2. 252 | * See http://books.google.com/books?id=3uBHAAAAIAAJ&pg=PA71 253 | * The arguments named "tan" are tan((pi/2 - latitude)/2). 254 | */ 255 | inline static double polarTriangleArea(double tan1, double lng1, double tan2, double lng2) { 256 | double deltaLng = lng1 - lng2; 257 | double t = tan1 * tan2; 258 | return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng)); 259 | } 260 | }; 261 | 262 | #endif // GEOMETRY_LIBRARY_SPHERICAL_UTIL 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /include/PolyUtil.hpp: -------------------------------------------------------------------------------- 1 | //****************************************************************************** 2 | // Copyright 2013 Google Inc. 3 | // https://github.com/googlemaps/android-maps-utils/blob/master/library/src/main/java/com/google/maps/android/PolyUtil.java 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // This software and the related documents are provided as is, with no express 11 | // or implied warranties, other than those that are expressly stated in the 12 | // License. 13 | //****************************************************************************** 14 | 15 | #ifndef GEOMETRY_LIBRARY_POLY_UTIL 16 | #define GEOMETRY_LIBRARY_POLY_UTIL 17 | 18 | #include "MathUtil.hpp" 19 | #include "SphericalUtil.hpp" 20 | 21 | 22 | class PolyUtil { 23 | public: 24 | static constexpr double DEFAULT_TOLERANCE = 0.1; // meters 25 | 26 | /** 27 | * Computes whether the given point lies inside the specified polygon. 28 | * The polygon is always cosidered closed, regardless of whether the last point equals 29 | * the first or not. 30 | * Inside is defined as not containing the South Pole -- the South Pole is always outside. 31 | * The polygon is formed of great circle segments if geodesic is true, and of rhumb 32 | * (loxodromic) segments otherwise. 33 | */ 34 | template 35 | static inline bool containsLocation(const LatLng& point, const LatLngList& polygon, bool geodesic = false) { 36 | size_t size = polygon.size(); 37 | 38 | if (size == 0) { 39 | return false; 40 | } 41 | double lat3 = deg2rad(point.lat); 42 | double lng3 = deg2rad(point.lng); 43 | LatLng prev = polygon[size - 1]; 44 | double lat1 = deg2rad(prev.lat); 45 | double lng1 = deg2rad(prev.lng); 46 | 47 | size_t nIntersect = 0; 48 | 49 | for (auto val : polygon) { 50 | double dLng3 = MathUtil::wrap(lng3 - lng1, -M_PI, M_PI); 51 | // Special case: point equal to vertex is inside. 52 | if (lat3 == lat1 && dLng3 == 0) { 53 | return true; 54 | } 55 | 56 | double lat2 = deg2rad(val.lat); 57 | double lng2 = deg2rad(val.lng); 58 | 59 | // Offset longitudes by -lng1. 60 | if (PolyUtil::intersects(lat1, lat2, MathUtil::wrap(lng2 - lng1, -M_PI, M_PI), lat3, dLng3, geodesic)) { 61 | ++nIntersect; 62 | } 63 | lat1 = lat2; 64 | lng1 = lng2; 65 | } 66 | return (nIntersect & 1) != 0; 67 | } 68 | 69 | 70 | /** 71 | * Computes whether the given point lies on or near the edge of a polygon, within a specified 72 | * tolerance in meters. The polygon edge is composed of great circle segments if geodesic 73 | * is true, and of Rhumb segments otherwise. The polygon edge is implicitly closed -- the 74 | * closing segment between the first point and the last point is included. 75 | */ 76 | template 77 | static inline bool isLocationOnEdge(const LatLng& point, const LatLngList& polygon, double tolerance = PolyUtil::DEFAULT_TOLERANCE, bool geodesic = true) { 78 | return PolyUtil::isLocationOnEdgeOrPath(point, polygon, true, geodesic, tolerance); 79 | } 80 | 81 | 82 | /** 83 | * Computes whether the given point lies on or near a polyline, within a specified 84 | * tolerance in meters. The polyline is composed of great circle segments if geodesic 85 | * is true, and of Rhumb segments otherwise. The polyline is not closed -- the closing 86 | * segment between the first point and the last point is not included. 87 | */ 88 | template 89 | static inline bool isLocationOnPath(const LatLng& point, const LatLngList& polyline, double tolerance = PolyUtil::DEFAULT_TOLERANCE, bool geodesic = true) { 90 | return PolyUtil::isLocationOnEdgeOrPath(point, polyline, false, geodesic, tolerance); 91 | } 92 | 93 | /** 94 | * Computes whether (and where) a given point lies on or near a polyline, within a specified tolerance. 95 | * If closed, the closing segment between the last and first points of the polyline is not considered. 96 | * 97 | * @param point our needle 98 | * @param poly our haystack 99 | * @param closed whether the polyline should be considered closed by a segment connecting the last point back to the first one 100 | * @param geodesic the polyline is composed of great circle segments if geodesic 101 | * is true, and of Rhumb segments otherwise 102 | * @param toleranceEarth tolerance (in meters) 103 | * @return -1 if point does not lie on or near the polyline. 104 | * 0 if point is between poly[0] and poly[1] (inclusive), 105 | * 1 if between poly[1] and poly[2], 106 | * ..., 107 | * poly.size()-2 if between poly[poly.size() - 2] and poly[poly.size() - 1] 108 | */ 109 | template 110 | static inline bool isLocationOnEdgeOrPath(const LatLng& point, const LatLngList& poly, bool closed, bool geodesic, double toleranceEarth) { 111 | size_t size = poly.size(); 112 | 113 | if (size == 0U) { 114 | return false; 115 | } 116 | 117 | double tolerance = toleranceEarth / MathUtil::EARTH_RADIUS; 118 | double havTolerance = MathUtil::hav(tolerance); 119 | double lat3 = deg2rad(point.lat); 120 | double lng3 = deg2rad(point.lng); 121 | LatLng prev = poly[closed ? size - 1 : 0]; 122 | double lat1 = deg2rad(prev.lat); 123 | double lng1 = deg2rad(prev.lng); 124 | 125 | if (geodesic) { 126 | for (auto val : poly) { 127 | double lat2 = deg2rad(val.lat); 128 | double lng2 = deg2rad(val.lng); 129 | if (PolyUtil::isOnSegmentGC(lat1, lng1, lat2, lng2, lat3, lng3, havTolerance)) { 130 | return true; 131 | } 132 | lat1 = lat2; 133 | lng1 = lng2; 134 | } 135 | }else { 136 | // We project the points to mercator space, where the Rhumb segment is a straight line, 137 | // and compute the geodesic distance between point3 and the closest point on the 138 | // segment. This method is an approximation, because it uses "closest" in mercator 139 | // space which is not "closest" on the sphere -- but the error is small because 140 | // "tolerance" is small. 141 | double minAcceptable = lat3 - tolerance; 142 | double maxAcceptable = lat3 + tolerance; 143 | double y1 = MathUtil::mercator(lat1); 144 | double y3 = MathUtil::mercator(lat3); 145 | double xTry[3]; 146 | for (auto val : poly) { 147 | double lat2 = deg2rad(val.lat); 148 | double y2 = MathUtil::mercator(lat2); 149 | double lng2 = deg2rad(val.lng); 150 | if (std::max(lat1, lat2) >= minAcceptable && std::min(lat1, lat2) <= maxAcceptable) { 151 | // We offset longitudes by -lng1; the implicit x1 is 0. 152 | double x2 = MathUtil::wrap(lng2 - lng1, -M_PI, M_PI); 153 | double x3Base = MathUtil::wrap(lng3 - lng1, -M_PI, M_PI); 154 | xTry[0] = x3Base; 155 | // Also explore wrapping of x3Base around the world in both directions. 156 | xTry[1] = x3Base + 2 * M_PI; 157 | xTry[2] = x3Base - 2 * M_PI; 158 | 159 | for (auto x3 : xTry) { 160 | double dy = y2 - y1; 161 | double len2 = x2 * x2 + dy * dy; 162 | double t = len2 <= 0 ? 0 : MathUtil::clamp((x3 * x2 + (y3 - y1) * dy) / len2, 0, 1); 163 | double xClosest = t * x2; 164 | double yClosest = y1 + t * dy; 165 | double latClosest = MathUtil::inverseMercator(yClosest); 166 | double havDist = MathUtil::havDistance(lat3, latClosest, x3 - xClosest); 167 | if (havDist < havTolerance) { 168 | return true; 169 | } 170 | } 171 | } 172 | lat1 = lat2; 173 | lng1 = lng2; 174 | y1 = y2; 175 | } 176 | } 177 | return false; 178 | } 179 | 180 | /** 181 | * Computes the distance on the sphere between the point p and the line segment start to end. 182 | * 183 | * @param p the point to be measured 184 | * @param start the beginning of the line segment 185 | * @param end the end of the line segment 186 | * @return the distance in meters (assuming spherical earth) 187 | */ 188 | static inline double distanceToLine(const LatLng& p, const LatLng& start, const LatLng& end) { 189 | if (start == end) { 190 | return SphericalUtil::computeDistanceBetween(end, p); 191 | } 192 | double s0lat = deg2rad(p.lat); 193 | double s0lng = deg2rad(p.lng); 194 | double s1lat = deg2rad(start.lat); 195 | double s1lng = deg2rad(start.lng); 196 | double s2lat = deg2rad(end.lat); 197 | double s2lng = deg2rad(end.lng); 198 | double s2s1lat = s2lat - s1lat; 199 | double s2s1lng = s2lng - s1lng; 200 | double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng) 201 | / (s2s1lat * s2s1lat + s2s1lng * s2s1lng); 202 | if (u <= 0) { 203 | return SphericalUtil::computeDistanceBetween(p, start); 204 | } 205 | if (u >= 1) { 206 | return SphericalUtil::computeDistanceBetween(p, end); 207 | } 208 | LatLng su(start.lat + u * (end.lat - start.lat), start.lng + u * (end.lng - start.lng)); 209 | return SphericalUtil::computeDistanceBetween(p, su); 210 | } 211 | 212 | 213 | private: 214 | /** 215 | * Returns tan(latitude-at-lng3) on the great circle (lat1, lng1) to (lat2, lng2). lng1==0. 216 | * See http://williams.best.vwh.net/avform.htm . 217 | */ 218 | static inline double tanLatGC(double lat1, double lat2, double lng2, double lng3) { 219 | return (tan(lat1) * sin(lng2 - lng3) + tan(lat2) * sin(lng3)) / sin(lng2); 220 | } 221 | 222 | /** 223 | * Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0. 224 | */ 225 | static inline double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) { 226 | return (MathUtil::mercator(lat1) * (lng2 - lng3) + MathUtil::mercator(lat2) * lng3) / lng2; 227 | } 228 | 229 | /** 230 | * Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment 231 | * (lat1, lng1) to (lat2, lng2). 232 | * Longitudes are offset by -lng1; the implicit lng1 becomes 0. 233 | */ 234 | static inline double intersects(double lat1, double lat2, double lng2, double lat3, double lng3, bool geodesic) { 235 | // Both ends on the same side of lng3. 236 | if ((lng3 >= 0 && lng3 >= lng2) || (lng3 < 0 && lng3 < lng2)) { 237 | return false; 238 | } 239 | // Point is South Pole. 240 | if (lat3 <= -M_PI / 2) { 241 | return false; 242 | } 243 | // Any segment end is a pole. 244 | if (lat1 <= -M_PI / 2 || lat2 <= -M_PI / 2 || lat1 >= M_PI / 2 || lat2 >= M_PI / 2) { 245 | return false; 246 | } 247 | if (lng2 <= -M_PI) { 248 | return false; 249 | } 250 | double linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2; 251 | // Northern hemisphere and point under lat-lng line. 252 | if (lat1 >= 0 && lat2 >= 0 && lat3 < linearLat) { 253 | return false; 254 | } 255 | // Southern hemisphere and point above lat-lng line. 256 | if (lat1 <= 0 && lat2 <= 0 && lat3 >= linearLat) { 257 | return true; 258 | } 259 | // North Pole. 260 | if (lat3 >= M_PI / 2) { 261 | return true; 262 | } 263 | // Compare lat3 with latitude on the GC/Rhumb segment corresponding to lng3. 264 | // Compare through a strictly-increasing function (tan() or mercator()) as convenient. 265 | return geodesic ? 266 | tan(lat3) >= PolyUtil::tanLatGC(lat1, lat2, lng2, lng3) : 267 | MathUtil::mercator(lat3) >= PolyUtil::mercatorLatRhumb(lat1, lat2, lng2, lng3); 268 | } 269 | 270 | /** 271 | * Returns sin(initial bearing from (lat1,lng1) to (lat3,lng3) minus initial bearing 272 | * from (lat1, lng1) to (lat2,lng2)). 273 | */ 274 | static inline double sinDeltaBearing(double lat1, double lng1, double lat2, double lng2, double lat3, double lng3) { 275 | double sinLat1 = sin(lat1); 276 | double cosLat2 = cos(lat2); 277 | double cosLat3 = cos(lat3); 278 | double lat31 = lat3 - lat1; 279 | double lng31 = lng3 - lng1; 280 | double lat21 = lat2 - lat1; 281 | double lng21 = lng2 - lng1; 282 | double a = sin(lng31) * cosLat3; 283 | double c = sin(lng21) * cosLat2; 284 | double b = sin(lat31) + 2 * sinLat1 * cosLat3 * MathUtil::hav(lng31); 285 | double d = sin(lat21) + 2 * sinLat1 * cosLat2 * MathUtil::hav(lng21); 286 | double denom = (a * a + b * b) * (c * c + d * d); 287 | return denom <= 0 ? 1 : (a * d - b * c) / sqrt(denom); 288 | } 289 | 290 | static inline bool isOnSegmentGC(double lat1, double lng1, double lat2, double lng2, double lat3, double lng3, double havTolerance) { 291 | double havDist13 = MathUtil::havDistance(lat1, lat3, lng1 - lng3); 292 | if (havDist13 <= havTolerance) { 293 | return true; 294 | } 295 | double havDist23 = MathUtil::havDistance(lat2, lat3, lng2 - lng3); 296 | if (havDist23 <= havTolerance) { 297 | return true; 298 | } 299 | double sinBearing = PolyUtil::sinDeltaBearing(lat1, lng1, lat2, lng2, lat3, lng3); 300 | double sinDist13 = MathUtil::sinFromHav(havDist13); 301 | double havCrossTrack = MathUtil::havFromSin(sinDist13 * sinBearing); 302 | if (havCrossTrack > havTolerance) { 303 | return false; 304 | } 305 | double havDist12 = MathUtil::havDistance(lat1, lat2, lng1 - lng2); 306 | double term = havDist12 + havCrossTrack * (1 - 2 * havDist12); 307 | if (havDist13 > term || havDist23 > term) { 308 | return false; 309 | } 310 | if (havDist12 < 0.74) { 311 | return true; 312 | } 313 | double cosCrossTrack = 1 - 2 * havCrossTrack; 314 | double havAlongTrack13 = (havDist13 - havCrossTrack) / cosCrossTrack; 315 | double havAlongTrack23 = (havDist23 - havCrossTrack) / cosCrossTrack; 316 | double sinSumAlongTrack = MathUtil::sinSumFromHav(havAlongTrack13, havAlongTrack23); 317 | return sinSumAlongTrack > 0; // Compare with half-circle == PI using sign of sin(). 318 | } 319 | }; 320 | 321 | #endif // GEOMETRY_LIBRARY_POLY_UTIL 322 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Geometry Library 2 | 3 |

4 | 5 | Build status 6 | 7 | 8 | Code quality 9 | 10 | 11 | Release 12 | 13 | 14 | Supported platforms 15 | 16 | 17 | License 18 | 19 |

20 | 21 | C++ Geometry Library provides utility functions for the computation of geometric data on the surface of the Earth. Code ported from Google [Maps Android API](https://github.com/googlemaps/android-maps-utils/). 22 | 23 | ## Features 24 | 25 | * [Spherical](https://developers.google.com/maps/documentation/javascript/reference#spherical) contains spherical geometry utilities allowing you to compute angles, distances and areas from latitudes and longitudes. 26 | * [Poly](https://developers.google.com/maps/documentation/javascript/reference#poly) utility functions for computations involving polygons and polylines. 27 | 28 | ## Usage 29 | 30 | You just need to include `SphericalUtil.hpp` or `PolyUtil.hpp` 31 | 32 | Here is an example of using this library: 33 | 34 | ```c++ 35 | #include 36 | #include 37 | 38 | #include "SphericalUtil.hpp" 39 | 40 | int main() { 41 | LatLng up = { 90.0, 0.0 }; 42 | LatLng down = {-90.0, 0.0 }; 43 | LatLng front = { 0.0, 0.0 }; 44 | LatLng right = { 0.0, 90.0 }; 45 | 46 | auto angle = SphericalUtil::computeAngleBetween(up, right); // 90 47 | std::cout << "The angle between up and right is " << rad2deg(angle) << " degrees" << std::endl; 48 | 49 | auto distance = SphericalUtil::computeDistanceBetween(up, down); // 2.00151e+07 50 | std::cout << "The distance between up and down is " << distance << " meters" << std::endl; 51 | 52 | std::vector points = { front, up, right }; 53 | 54 | auto length = SphericalUtil::computeLength(points); // 2.00151e+07 55 | std::cout << "The length between front, up and right is " << length << " meters" << std::endl; 56 | 57 | auto area = SphericalUtil::computeArea(points); // 6.37582e+13 58 | std::cout << "The area between front, up and right is " << area << " square meters" << std::endl; 59 | 60 | return 0; 61 | } 62 | ``` 63 | 64 | ## Available methods 65 | 66 | ### PolyUtil class 67 | 68 | * [`containsLocation(LatLng point, LatLngList polygon, bool geodesic)`](#containsLocation) 69 | * [`isLocationOnEdge(LatLng point, LatLngList polygon, double tolerance, bool geodesic)`](#isLocationOnEdge) 70 | * [`isLocationOnPath(LatLng point, LatLngList polyline, double tolerance, bool geodesic)`](#isLocationOnPath) 71 | * [`distanceToLine(LatLng point, LatLng start, LatLng end)`](#distanceToLine) 72 | 73 | ### SphericalUtil class 74 | 75 | * [`computeHeading(LatLng from, LatLng to)`](#computeHeading) 76 | * [`computeOffset(LatLng from, double distance, double heading)`](#computeOffset) 77 | * [`computeOffsetOrigin(LatLng to, double distance, double heading)`](#computeOffsetOrigin) 78 | * [`interpolate(LatLng from, LatLng to, double fraction)`](#interpolate) 79 | * [`computeDistanceBetween(LatLng from, LatLng to)`](#computeDistanceBetween) 80 | * [`computeLength(LatLngList path)`](#computeLength) 81 | * [`computeArea(LatLngList path)`](#computeArea) 82 | * [`computeSignedArea(LatLngList path)`](#computeSignedArea) 83 | 84 | ## Classes description 85 | 86 | `LatLng` - a point in geographical coordinates: latitude and longitude. 87 | 88 | * Latitude ranges between `-90` and `90` degrees, inclusive 89 | * Longitude ranges between `-180` and `180` degrees, inclusive 90 | 91 | Usage example: 92 | 93 | ```c++ 94 | LatLng northPole = {90, 0}; 95 | 96 | LatLng otherPoint = northPole; 97 | ``` 98 | 99 | --- 100 | 101 | `LatLngList` - a series of connected coordinates in an ordered sequence. Any iterable containers. 102 | 103 | Usage example: 104 | 105 | ```c++ 106 | std::vector aroundNorthPole = { {89, 0}, {89, 120}, {89, -120} }; 107 | 108 | std::array northPole = { {90, 0} }; 109 | ``` 110 | 111 | ## Functions description 112 | 113 | ### PolyUtil functions 114 | 115 | 116 | **`PolyUtil::containsLocation(const LatLng& point, const LatLngList& polygon, bool geodesic = false)`** - Computes whether the given point lies inside the specified polygon 117 | 118 | * `point` - a point in geographical coordinates: latitude and longitude 119 | * `polygon` - a series of connected coordinates in an ordered sequence 120 | * `geodesic` - the polyline is composed of great circle segments if geodesic is true, and of Rhumb segments otherwise 121 | 122 | Return value: `bool` - whether the given point lies inside the specified polygon 123 | 124 | ```c++ 125 | // Around the north pole. 126 | std::vector aroundNorthPole = { {89, 0}, {89, 120}, {89, -120} }; 127 | 128 | std::cout << PolyUtil::containsLocation(LatLng(90, 0), aroundNorthPole); // true 129 | std::cout << PolyUtil::containsLocation(LatLng(-90, 0), aroundNorthPole); // false 130 | ``` 131 | 132 | --- 133 | 134 | 135 | **`PolyUtil::isLocationOnEdge(const LatLng& point, const LatLngList& polygon, double tolerance = PolyUtil::DEFAULT_TOLERANCE, bool geodesic = true)`** - Computes whether the given point lies on or near to a polyline, or the edge of a polygon, within a specified tolerance. Returns true when the difference between the latitude and longitude of the supplied point, and the closest point on the edge, is less than the tolerance. The tolerance defaults to `0.1` meters. 136 | 137 | * `point` - a point in geographical coordinates: latitude and longitude 138 | * `polygon` - a series of connected coordinates in an ordered sequence 139 | * `tolerance` - tolerance value in meters 140 | * `geodesic` - the polyline is composed of great circle segments if geodesic is true, and of Rhumb segments otherwise 141 | 142 | Return value: `bool` - whether the given point lies on or near the edge of a polygon 143 | 144 | ```c++ 145 | // On equator. 146 | std::vector equator = { {0, 90}, {0, 180} }; 147 | 148 | double small = 5e-7; // Half the default tolerance. 149 | double big = 2e-6; // Double the default tolerance. 150 | 151 | std::cout << PolyUtil::isLocationOnEdge(LatLng(0, 90 - small), equator); // true 152 | std::cout << PolyUtil::isLocationOnEdge(LatLng(0, 90 - big), equator); // false 153 | ``` 154 | 155 | --- 156 | 157 | 158 | **`PolyUtil::isLocationOnPath(const LatLng& point, const LatLngList& polyline, double tolerance = PolyUtil::DEFAULT_TOLERANCE, bool geodesic = true)`** - Computes whether the given point lies on or near a polyline, within a specified tolerance in meters. The polyline is composed of great circle segments if geodesic is true, and of Rhumb segments otherwise. The polyline is not closed -- the closing segment between the first point and the last point is not included. 159 | 160 | * `point` - a point in geographical coordinates: latitude and longitude 161 | * `polygon` - a series of connected coordinates in an ordered sequence 162 | * `tolerance` - tolerance value in meters 163 | * `geodesic` - the polyline is composed of great circle segments if geodesic is true, and of Rhumb segments otherwise 164 | 165 | Return value: `bool` - whether the point lies on or near a polyline 166 | 167 | ```c++ 168 | // On equator. 169 | std::vector equator = { {0, 90}, {0, 180} }; 170 | 171 | double small = 5e-7; // Half the default tolerance. 172 | double big = 2e-6; // Double the default tolerance. 173 | 174 | std::cout << PolyUtil::isLocationOnPath(LatLng(0, 90 - small), equator); // true 175 | std::cout << PolyUtil::isLocationOnPath(LatLng(0, 90 - big), equator); // false 176 | ``` 177 | 178 | --- 179 | 180 | 181 | **`PolyUtil::distanceToLine(const LatLng& p, const LatLng& start, const LatLng& end)`** - Computes the distance on the sphere between the point p and the line segment start to end. 182 | 183 | * `point` - the point to be measured 184 | * `start` - the beginning of the line segment 185 | * `end` - the end of the line segment 186 | 187 | Return value: `double` - the distance in meters (assuming spherical earth) 188 | 189 | ```c++ 190 | LatLng startLine(28.05359, -82.41632); 191 | LatLng endLine(28.05310, -82.41634); 192 | LatLng point(28.05342, -82.41594); 193 | 194 | std::cout << PolyUtil::distanceToLine(point, startLine, endLine); // 37.947946 195 | ``` 196 | 197 | ### SphericalUtil functions 198 | 199 | 200 | **`SphericalUtil::computeHeading(const LatLng& from, const LatLng& to)`** - Returns the heading from one LatLng to another LatLng. Headings are expressed in degrees clockwise from North within the range [-180,180). 201 | 202 | * `from` - a point in geographical coordinates: latitude and longitude 203 | * `to` - a point in geographical coordinates: latitude and longitude 204 | 205 | Return value: `double` - the heading in degrees clockwise from north 206 | 207 | ```c++ 208 | LatLng front(0, 0); 209 | LatLng right(0, 90); 210 | 211 | std::cout << SphericalUtil::computeHeading(right, front); // -90 212 | std::cout << SphericalUtil::computeHeading(front, right); // +90 213 | ``` 214 | 215 | --- 216 | 217 | 218 | **`SphericalUtil::computeOffset(const LatLng& from, double distance, double heading)`** - Returns the LatLng resulting from moving a distance from an origin in the specified heading (expressed in degrees clockwise from north). 219 | 220 | * `from` - the LatLng from which to start. 221 | * `distance` - the distance to travel. 222 | * `heading` - the heading in degrees clockwise from north. 223 | 224 | Return value: `LatLng` - resulting from moving a distance from an origin in the specified heading (expressed in degrees clockwise from north) 225 | 226 | ```c++ 227 | LatLng front(0, 0); 228 | 229 | auto up = SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 0); // LatLng( 90, 0) 230 | auto down = SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 180); // LatLng(-90, 0) 231 | auto left = SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, -90); // LatLng( 0, -90) 232 | auto right = SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 90); // LatLng( 0, 90) 233 | auto back = SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS, 90); // LatLng( 0, -180) 234 | ``` 235 | 236 | --- 237 | 238 | 239 | **`SphericalUtil::computeOffsetOrigin(const LatLng& to, double distance, double heading)`** - Returns the location of origin when provided with a LatLng destination, meters travelled and original heading. Headings are expressed in degrees clockwise from North. 240 | 241 | * `from` - the destination LatLng 242 | * `distance` - the distance travelled, in meters. 243 | * `heading` - the heading in degrees clockwise from north 244 | 245 | Return value: `LatLng` - the location of origin when provided with a LatLng destination, meters travelled and original heading. Headings are expressed in degrees clockwise from North 246 | 247 | ```c++ 248 | LatLng front(0, 0); 249 | 250 | assert(front == SphericalUtil::computeOffsetOrigin(front, 0, 0)); 251 | 252 | assert(front == SphericalUtil::computeOffsetOrigin(LatLng( 0, 45), M_PI * MathUtil::EARTH_RADIUS / 4, 90)); 253 | assert(front == SphericalUtil::computeOffsetOrigin(LatLng( 0, -45), M_PI * MathUtil::EARTH_RADIUS / 4, -90)); 254 | assert(front == SphericalUtil::computeOffsetOrigin(LatLng( 45, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 0)); 255 | assert(front == SphericalUtil::computeOffsetOrigin(LatLng(-45, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 190)); 256 | ``` 257 | 258 | --- 259 | 260 | 261 | **`SphericalUtil::interpolate(const LatLng& from, const LatLng& to, double fraction)`** - Returns the LatLng which lies the given fraction of the way between the origin LatLng and the destination LatLng. 262 | 263 | * `from` - the LatLng from which to start. 264 | * `to` - the LatLng toward which to travel. 265 | * `fraction` - a fraction of the distance to travel. 266 | 267 | Return value: `LatLng` - point which lies the given fraction of the way between the origin LatLng and the destination LatLng 268 | 269 | ```c++ 270 | LatLng up(90, 0); 271 | LatLng front(0, 0); 272 | 273 | assert(LatLng(1, 0) == SphericalUtil::interpolate(front, up, 1 / 90.0)); 274 | assert(LatLng(1, 0) == SphericalUtil::interpolate(up, front, 89 / 90.0)); 275 | assert(LatLng(89, 0) == SphericalUtil::interpolate(front, up, 89 / 90.0)); 276 | assert(LatLng(89, 0) == SphericalUtil::interpolate(up, front, 1 / 90.0)); 277 | 278 | ``` 279 | 280 | --- 281 | 282 | 283 | **`SphericalUtil::computeDistanceBetween(const LatLng& from, const LatLng& to)`** - Returns the distance, in meters, between two LatLngs. 284 | 285 | * `from` - the first point 286 | * `to` - the second point 287 | 288 | Return value: `double` - the distance, in meters, between two LatLngs 289 | 290 | ```c++ 291 | LatLng up(90, 0); 292 | LatLng down(-90, 0); 293 | 294 | std:cout << SphericalUtil::computeDistanceBetween(up, down); // MathUtil::EARTH_RADIUS 295 | ``` 296 | 297 | --- 298 | 299 | 300 | **`SphericalUtil::computeLength(const LatLngList& path)`** - Returns the length of the given path, in meters, on Earth 301 | 302 | * `path` - a series of connected coordinates in an ordered sequence. Any iterable containers. 303 | 304 | Return valuse: `double` - the length of the given path, in meters, on Earth 305 | 306 | ```c++ 307 | // List with three points 308 | std::vector latLngs2 = { {0, 0}, {90, 0}, {0, 90} }; 309 | 310 | std::cout << SphericalUtil::computeLength(latLngs2); // M_PI * MathUtil::EARTH_RADIUS 311 | ``` 312 | 313 | --- 314 | 315 | 316 | **`SphericalUtil::computeArea(const LatLngList& path)`** - Returns the area of a closed path on Earth. 317 | 318 | * `path` - a closed path. Any iterable containers. 319 | 320 | Return value: `double` - the area of a closed path on Earth 321 | 322 | ```c++ 323 | LatLng up = { 90.0, 0.0 }; 324 | LatLng down = {-90.0, 0.0 }; 325 | LatLng front = { 0.0, 0.0 }; 326 | LatLng right = { 0.0, 90.0 }; 327 | 328 | std::vector path = { right, down, front, up, right }; 329 | 330 | std::cout << SphericalUtil::computeArea(second); // M_PI * MathUtil::EARTH_RADIUS * MathUtil::EARTH_RADIUS 331 | ``` 332 | 333 | --- 334 | 335 | 336 | **`SphericalUtil::computeSignedArea(const LatLngList& path)`** - Returns the signed area of a closed path on Earth. The sign of the area may be used to determine the orientation of the path. "inside" is the surface that does not contain the South Pole. 337 | 338 | * `path` - a closed path. Any iterable containers. 339 | 340 | Return value: `double` - the loop's area in square meters 341 | 342 | ```c++ 343 | LatLng up = { 90.0, 0.0 }; 344 | LatLng down = {-90.0, 0.0 }; 345 | LatLng front = { 0.0, 0.0 }; 346 | LatLng right = { 0.0, 90.0 }; 347 | 348 | std::vector path = { right, up, front, down, right }; 349 | std::vector pathReversed = { right, down, front, up, right }; 350 | 351 | assert(SphericalUtil::computeSignedArea(path) == -SphericalUtil::computeSignedArea(pathReversed)); 352 | ``` 353 | 354 | --- 355 | 356 | ## Support 357 | 358 | [Please open an issue on GitHub](https://github.com/gistrec/cpp-geometry-library/issues) 359 | 360 | ## License 361 | 362 | Geometry Library Google Maps API V3 is released under the MIT License. 363 | See the bundled [LICENSE](https://github.com/alexpechkarev/geometry-library/blob/master/LICENSE) file for details. --------------------------------------------------------------------------------