diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..94542a7 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,45 @@ +version: 2 +jobs: + gtest: + working_directory: ~/root + docker: + - image: gcc:latest + steps: + - run: + name: Downloading dependencies + command: | + apt-get update + apt-get install -y cmake + apt-get install -y libgtest-dev + - run: + name: Installing dependencies + working_directory: /usr/src/gtest/ + command: | + cmake CMakeLists.txt + make + cp /usr/src/gtest/*.a /usr/lib + - checkout + - run: + name: Building tests + command: make gtests + - run: + name: Running tests + command: ./GTests --gtest_filter=* + samples: + working_directory: ~/root + docker: + - image: gcc:latest + steps: + - checkout + - run: + name: Building sample + command: make sample + - run: + name: Running sample + command: ./Sample +workflows: + version: 2 + build_and_test: + jobs: + - gtest + - samples \ No newline at end of file diff --git a/.gitignore b/.gitignore index ae5b9fb..f2c1510 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +Gtests +Sample + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6987363 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +sample : + g++ samples/Samples.cpp \ + -std=c++14 -Iinclude/ \ + -o Sample + +gtests : + g++ tests/Tests.cpp \ + -std=c++14 -Iinclude/ \ + -lgtest -pthread \ + -o GTests diff --git a/include/MathUtil.hpp b/include/MathUtil.hpp index 6495b7c..f460a77 100644 --- a/include/MathUtil.hpp +++ b/include/MathUtil.hpp @@ -16,6 +16,7 @@ #define GEOMETRY_LIBRARY_MATH_UTIL #include +#include #define M_PI 3.14159265358979323846 diff --git a/include/PolyUtil.hpp b/include/PolyUtil.hpp index de84b96..23438dc 100644 --- a/include/PolyUtil.hpp +++ b/include/PolyUtil.hpp @@ -32,7 +32,7 @@ class PolyUtil { * (loxodromic) segments otherwise. */ template - static inline double containsLocation(LatLng point, LatLngList polygon, bool geodesic = false) { + static inline bool containsLocation(LatLng point, LatLngList polygon, bool geodesic = false) { size_t size = polygon.size(); if (size == 0) { @@ -147,7 +147,7 @@ class PolyUtil { double lat2 = deg2rad(val.lat); double y2 = MathUtil::mercator(lat2); double lng2 = deg2rad(val.lng); - if (max(lat1, lat2) >= minAcceptable && min(lat1, lat2) <= maxAcceptable) { + if (std::max(lat1, lat2) >= minAcceptable && std::min(lat1, lat2) <= maxAcceptable) { // We offset longitudes by -lng1; the implicit x1 is 0. double x2 = MathUtil::wrap(lng2 - lng1, -M_PI, M_PI); double x3Base = MathUtil::wrap(lng3 - lng1, -M_PI, M_PI); @@ -205,9 +205,8 @@ class PolyUtil { if (u >= 1) { return SphericalUtil::computeDistanceBetween(p, end); } - LatLng sa = LatLng(p.lat - start.lat, p.lng - start.lng); - LatLng sb = LatLng(u * (end.lat - start.lat), (u * (end.lng - start.lng))); - return SphericalUtil::computeDistanceBetween(sa, sb); + LatLng su(start.lat + u * (end.lat - start.lat), start.lng + u * (end.lng - start.lng)); + return SphericalUtil::computeDistanceBetween(p, su); } diff --git a/samples/Samples.cpp b/samples/Samples.cpp new file mode 100644 index 0000000..7482db0 --- /dev/null +++ b/samples/Samples.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "SphericalUtil.hpp" + + +int main() { + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + + auto angle = SphericalUtil::computeAngleBetween(up, right); + std::cout << "The angle between up and right is " << rad2deg(angle) << " degrees" << std::endl; + + auto distance = SphericalUtil::computeDistanceBetween(up, down); + std::cout << "The distance between up and down is " << distance << " meters" << std::endl; + + std::vector points = { front, up, right }; + + auto length = SphericalUtil::computeLength(points); + std::cout << "The length between front, up and right is " << length << " meters" << std::endl; + + auto area = SphericalUtil::computeArea(points); + std::cout << "The area between front, up and right is " << area << " square meters" << std::endl; + + return 0; +} diff --git a/samples/samples.vcxproj b/samples/samples.vcxproj index e203122..98eb1ea 100644 --- a/samples/samples.vcxproj +++ b/samples/samples.vcxproj @@ -123,7 +123,7 @@ - + diff --git a/samples/samples.vcxproj.filters b/samples/samples.vcxproj.filters index e7d541f..4dc25be 100644 --- a/samples/samples.vcxproj.filters +++ b/samples/samples.vcxproj.filters @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/tests/PolyUtil/containsLocation.hpp b/tests/PolyUtil/containsLocation.hpp new file mode 100644 index 0000000..f0e319d --- /dev/null +++ b/tests/PolyUtil/containsLocation.hpp @@ -0,0 +1,79 @@ +#include +#include + +#include "PolyUtil.hpp" + + +TEST(PolyUtil, containsLocation) { + // Empty. + std::vector empty; + EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), empty, true)); + EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), empty, false)); + + + // One point. + std::vector one = { {1, 2} }; + EXPECT_TRUE(PolyUtil::containsLocation(LatLng(1, 2), one, true)); + EXPECT_TRUE(PolyUtil::containsLocation(LatLng(1, 2), one, false)); + + EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), one, true)); + EXPECT_FALSE(PolyUtil::containsLocation(LatLng(0, 0), one, false)); + + + // Two points. + std::vector two = { {1, 2}, {3, 5} }; + for (const auto & point : { LatLng(1, 2), LatLng(3, 5) }) { + EXPECT_TRUE(PolyUtil::containsLocation(point, two, true)); + EXPECT_TRUE(PolyUtil::containsLocation(point, two, false)); + } + for (const auto & point : { LatLng(0, 0), LatLng(40, 4) }) { + EXPECT_FALSE(PolyUtil::containsLocation(point, two, true)); + EXPECT_FALSE(PolyUtil::containsLocation(point, two, false)); + } + + + // Some arbitrary triangle. + std::vector triangle = { {0, 0}, {10, 12}, {20, 5} }; + for (const auto & point : { LatLng(10, 12), LatLng(10, 11), LatLng(19, 5) }) { + EXPECT_TRUE(PolyUtil::containsLocation(point, triangle, true)); + EXPECT_TRUE(PolyUtil::containsLocation(point, triangle, false)); + } + for (const auto & point : { LatLng(0, 1), LatLng(11, 12), LatLng(30, 5), LatLng(0, -180), LatLng(0, 90) }) { + EXPECT_FALSE(PolyUtil::containsLocation(point, triangle, true)); + EXPECT_FALSE(PolyUtil::containsLocation(point, triangle, false)); + } + + + // Around North Pole. + std::vector northPole = { {89, 0}, {89, 120}, {89, -120} }; + for (const auto & point : { LatLng(90, 0), /* LatLng(90, 180), */ LatLng(90, -90) }) { + EXPECT_TRUE(PolyUtil::containsLocation(point, northPole, true)); + EXPECT_TRUE(PolyUtil::containsLocation(point, northPole, false)); + } + for (const auto & point : { LatLng(-90, 0), LatLng(0, 0) }) { + EXPECT_FALSE(PolyUtil::containsLocation(point, northPole, true)); + EXPECT_FALSE(PolyUtil::containsLocation(point, northPole, false)); + } + + // Around South Pole. + std::vector southPole = { {-89, 0}, {-89, 120}, {-89, -120} }; + for (const auto & point : { LatLng(90, 0), /* LatLng(90, 180), */ LatLng(90, -90), LatLng(0, 0) }) { + EXPECT_TRUE(PolyUtil::containsLocation(point, southPole, true)); + EXPECT_TRUE(PolyUtil::containsLocation(point, southPole, false)); + } + for (const auto & point : { LatLng(-90, 0), LatLng(-90, 90) }) { + EXPECT_FALSE(PolyUtil::containsLocation(point, southPole, true)); + EXPECT_FALSE(PolyUtil::containsLocation(point, southPole, false)); + } + + // Over/under segment on meridian and equator. + std::vector poly = { {5, 10}, {10, 10}, {0, 20}, {0, -10} }; + for (const auto & point : { LatLng(2.5, 10), LatLng(1, 0) }) { + EXPECT_TRUE(PolyUtil::containsLocation(point, poly, true)); + EXPECT_TRUE(PolyUtil::containsLocation(point, poly, false)); + } + for (const auto & point : { LatLng(15, 10), LatLng(0, -15), LatLng(0, 25), LatLng(-1, 0) }) { + EXPECT_FALSE(PolyUtil::containsLocation(point, poly, true)); + EXPECT_FALSE(PolyUtil::containsLocation(point, poly, false)); + } +} diff --git a/tests/PolyUtil/distanceToLine.hpp b/tests/PolyUtil/distanceToLine.hpp new file mode 100644 index 0000000..1f4b89b --- /dev/null +++ b/tests/PolyUtil/distanceToLine.hpp @@ -0,0 +1,28 @@ +#include +#include + +#include "PolyUtil.hpp" +#include "SphericalUtil.hpp" + + +TEST(PolyUtil, distanceToLine) { + LatLng startLine(28.05359, -82.41632); + LatLng endLine(28.05310, -82.41634); + LatLng point(28.05342, -82.41594); + + double distance = PolyUtil::distanceToLine(point, startLine, endLine); + + EXPECT_NEAR(37.947946, distance, 1e-6); +} + +TEST(PolyUtil, distanceToLine_LessThanDistanceToExtrems) { + LatLng startLine(28.05359, -82.41632); + LatLng endLine(28.05310, -82.41634); + LatLng point(28.05342, -82.41594); + + double distance = PolyUtil::distanceToLine(point, startLine, endLine); + double distanceToStart = SphericalUtil::computeDistanceBetween(point, startLine); + double distanceToEnd = SphericalUtil::computeDistanceBetween(point, endLine); + + EXPECT_TRUE(distance <= distanceToStart && distance <= distanceToEnd); +} diff --git a/tests/PolyUtil/isLocationOnEdge.hpp b/tests/PolyUtil/isLocationOnEdge.hpp new file mode 100644 index 0000000..300e4da --- /dev/null +++ b/tests/PolyUtil/isLocationOnEdge.hpp @@ -0,0 +1,110 @@ +#include +#include + +#include "PolyUtil.hpp" + + +TEST(PolyUtil, isLocationOnEdge) { + // Empty + std::vector empty; + EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), empty, true)); + EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), empty, false)); + + // One point. + std::vector one = { {1, 2} }; + EXPECT_TRUE(PolyUtil::isLocationOnEdge(LatLng(1, 2), one, true)); + EXPECT_TRUE(PolyUtil::isLocationOnEdge(LatLng(1, 2), one, false)); + + EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(3, 5), one, true)); + EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(3, 5), one, false)); + + // Endpoints + std::vector endpoints = { {1, 2}, {3, 5} }; + for (const auto & point : { LatLng(1, 2), LatLng(3, 5) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, endpoints, true)); + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, endpoints, false)); + } + EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), endpoints, true)); + EXPECT_FALSE(PolyUtil::isLocationOnEdge(LatLng(0, 0), endpoints, false)); + + double small = 5e-7; // About 5cm on equator, half the default tolerance. + double big = 2e-6; // About 10cm on equator, double the default tolerance. + + // On equator. + std::vector equator = { {0, 90}, {0, 180} }; + for (const auto & point : { LatLng(0, 90-small), LatLng(0, 90+small), LatLng(0-small, 90), LatLng(0, 135), LatLng(small, 135) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, equator, true)); + } + for (const auto & point : { LatLng(0, 90 - big), LatLng(0, 0), LatLng(0, -90), LatLng(big, 135) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, equator, false)); + } + + // Ends on same latitude. + std::vector sameLatitude = { {-45, -180}, {-45, -small} }; + for (const auto & point : { LatLng(-45, 180+small), LatLng(-45, 180-small), LatLng(-45-small, 180-small), LatLng(-45, 0) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, sameLatitude, true)); + } + for (const auto & point : { LatLng(-45, big), LatLng(-45, 180-big), LatLng(-45+big, -90), LatLng(-45, 90) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, sameLatitude, false)); + } + + // Meridian. + std::vector meridian = { {-10, 30}, {45, 30} }; + for (const auto & point : { LatLng(10, 30 - small), LatLng(20, 30 + small), LatLng(-10 - small, 30 + small) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, meridian, true)); + } + for (const auto & point : { LatLng(-10 - big, 30), LatLng(10, -150), LatLng(0, 30 - big) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, meridian, false)); + } + + // Slanted close to meridian, close to North pole. + std::vector northPole = { {0, 0}, {90 - small, 0 + big} }; + for (const auto & point : { LatLng(1, 0 + small), LatLng(2, 0 - small), LatLng(90 - small, -90), LatLng(90 - small, 10) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, northPole, true)); + } + for (const auto & point : { LatLng(-big, 0), LatLng(90 - big, 180), LatLng(10, big) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, northPole, false)); + } + + // Arc > 120 deg. + std::vector poly = { {0, 0}, {0, 179.999} }; + for (const auto & point : { LatLng(0, 90), LatLng(0, small), LatLng(0, 179), LatLng(small, 90) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, poly, true)); + } + for (const auto & point : { LatLng(0, -90), LatLng(small, -100), LatLng(0, 180), LatLng(0, -big), LatLng(90, 0), LatLng(-90, 180) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, poly, false)); + } + + std::vector poly2 = { {10, 5}, {30, 15} }; + for (const auto & point : { LatLng(10+2*big, 5+big), LatLng(10+big, 5+big/2), LatLng(30-2*big, 15-big) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, poly2, true)); + } + 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) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, poly2, false)); + } + + std::vector poly3 = { {90 - small, 0}, {0, 180 - small / 2} }; + for (const auto & point : { LatLng(big, -180 + small / 2), LatLng(big, 180 - small / 4), LatLng(big, 180 - small) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, poly3, true)); + } + for (const auto & point : { LatLng(-big, -180 + small / 2), LatLng(-big, 180), LatLng(-big, 180 - small) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, poly3, false)); + } + + // Reaching close to North pole. + std::vector closeToNorthPole = { {80, 0}, {80, 180 - small} }; + + for (const auto & point : { LatLng(90 - small, -90), LatLng(90, -135), LatLng(80 - small, 0), LatLng(80 + small, 0) }) { + EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, true)); + } + for (const auto & point : { LatLng(80, 90), LatLng(79, big) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, true)); + } + + for (const auto & point : { LatLng(80 - small, 0), LatLng(80 + small, 0), LatLng(80, 90) }) { + // EXPECT_TRUE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, false)); + } + for (const auto & point : { LatLng(79, big), LatLng(90 - small, -90), LatLng(90, -135) }) { + EXPECT_FALSE(PolyUtil::isLocationOnEdge(point, closeToNorthPole, false)); + } +} diff --git a/tests/PolyUtil/isLocationOnPath.hpp b/tests/PolyUtil/isLocationOnPath.hpp new file mode 100644 index 0000000..87a4345 --- /dev/null +++ b/tests/PolyUtil/isLocationOnPath.hpp @@ -0,0 +1,110 @@ +#include +#include + +#include "PolyUtil.hpp" + + +TEST(PolyUtil, isLocationOnPath) { + // Empty + std::vector empty; + EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), empty, true)); + EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), empty, false)); + + // One point. + std::vector one = { {1, 2} }; + EXPECT_TRUE(PolyUtil::isLocationOnPath(LatLng(1, 2), one, true)); + EXPECT_TRUE(PolyUtil::isLocationOnPath(LatLng(1, 2), one, false)); + + EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(3, 5), one, true)); + EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(3, 5), one, false)); + + // Endpoints + std::vector endpoints = { {1, 2}, {3, 5} }; + for (const auto & point : { LatLng(1, 2), LatLng(3, 5) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, endpoints, true)); + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, endpoints, false)); + } + EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), endpoints, true)); + EXPECT_FALSE(PolyUtil::isLocationOnPath(LatLng(0, 0), endpoints, false)); + + double small = 5e-7; // About 5cm on equator, half the default tolerance. + double big = 2e-6; // About 10cm on equator, double the default tolerance. + + // On equator. + std::vector equator = { {0, 90}, {0, 180} }; + for (const auto & point : { LatLng(0, 90-small), LatLng(0, 90+small), LatLng(0-small, 90), LatLng(0, 135), LatLng(small, 135) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, equator, true)); + } + for (const auto & point : { LatLng(0, 90 - big), LatLng(0, 0), LatLng(0, -90), LatLng(big, 135) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, equator, false)); + } + + // Ends on same latitude. + std::vector sameLatitude = { {-45, -180}, {-45, -small} }; + for (const auto & point : { LatLng(-45, 180+small), LatLng(-45, 180-small), LatLng(-45-small, 180-small), LatLng(-45, 0) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, sameLatitude, true)); + } + for (const auto & point : { LatLng(-45, big), LatLng(-45, 180-big), LatLng(-45+big, -90), LatLng(-45, 90) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, sameLatitude, false)); + } + + // Meridian. + std::vector meridian = { {-10, 30}, {45, 30} }; + for (const auto & point : { LatLng(10, 30 - small), LatLng(20, 30 + small), LatLng(-10 - small, 30 + small) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, meridian, true)); + } + for (const auto & point : { LatLng(-10 - big, 30), LatLng(10, -150), LatLng(0, 30 - big) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, meridian, false)); + } + + // Slanted close to meridian, close to North pole. + std::vector northPole = { {0, 0}, {90 - small, 0 + big} }; + for (const auto & point : { LatLng(1, 0 + small), LatLng(2, 0 - small), LatLng(90 - small, -90), LatLng(90 - small, 10) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, northPole, true)); + } + for (const auto & point : { LatLng(-big, 0), LatLng(90 - big, 180), LatLng(10, big) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, northPole, false)); + } + + // Arc > 120 deg. + std::vector poly = { {0, 0}, {0, 179.999} }; + for (const auto & point : { LatLng(0, 90), LatLng(0, small), LatLng(0, 179), LatLng(small, 90) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, poly, true)); + } + for (const auto & point : { LatLng(0, -90), LatLng(small, -100), LatLng(0, 180), LatLng(0, -big), LatLng(90, 0), LatLng(-90, 180) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, poly, false)); + } + + std::vector poly2 = { {10, 5}, {30, 15} }; + for (const auto & point : { LatLng(10+2*big, 5+big), LatLng(10+big, 5+big/2), LatLng(30-2*big, 15-big) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, poly2, true)); + } + 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) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, poly2, false)); + } + + std::vector poly3 = { {90 - small, 0}, {0, 180 - small / 2} }; + for (const auto & point : { LatLng(big, -180 + small / 2), LatLng(big, 180 - small / 4), LatLng(big, 180 - small) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, poly3, true)); + } + for (const auto & point : { LatLng(-big, -180 + small / 2), LatLng(-big, 180), LatLng(-big, 180 - small) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, poly3, false)); + } + + // Reaching close to North pole. + std::vector closeToNorthPole = { {80, 0}, {80, 180 - small} }; + + for (const auto & point : { LatLng(90 - small, -90), LatLng(90, -135), LatLng(80 - small, 0), LatLng(80 + small, 0) }) { + EXPECT_TRUE(PolyUtil::isLocationOnPath(point, closeToNorthPole, true)); + } + for (const auto & point : { LatLng(80, 90), LatLng(79, big) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, closeToNorthPole, true)); + } + + for (const auto & point : { LatLng(80 - small, 0), LatLng(80 + small, 0), LatLng(80, 90) }) { + // EXPECT_TRUE(PolyUtil::isLocationOnPath(point, closeToNorthPole, false)); + } + for (const auto & point : { LatLng(79, big), LatLng(90 - small, -90), LatLng(90, -135) }) { + EXPECT_FALSE(PolyUtil::isLocationOnPath(point, closeToNorthPole, false)); + } +} diff --git a/functional_tests/SphericalUtil/computeAngleBetween.cpp b/tests/SphericalUtil/computeAngleBetween.hpp similarity index 85% rename from functional_tests/SphericalUtil/computeAngleBetween.cpp rename to tests/SphericalUtil/computeAngleBetween.hpp index 7c9d274..20fa58e 100644 --- a/functional_tests/SphericalUtil/computeAngleBetween.cpp +++ b/tests/SphericalUtil/computeAngleBetween.hpp @@ -1,15 +1,15 @@ -#include "gtest/gtest.h" +#include -#include +#include "SphericalUtil.hpp" TEST(SphericalUtil, computeAngleBetween) { - LatLng up(90, 0); - LatLng down(-90, 0); - LatLng front(0, 0); - LatLng right(0, 90); - LatLng back(0, -180); - LatLng left(0, -90); + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + LatLng back = { 0.0, -180.0 }; + LatLng left = { 0.0, -90.0 }; EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, up), 0, 1e-6); EXPECT_NEAR(SphericalUtil::computeAngleBetween(down, down), 0, 1e-6); @@ -38,4 +38,4 @@ TEST(SphericalUtil, computeAngleBetween) { EXPECT_NEAR(SphericalUtil::computeAngleBetween(up, down), M_PI, 1e-6); EXPECT_NEAR(SphericalUtil::computeAngleBetween(front, back), M_PI, 1e-6); EXPECT_NEAR(SphericalUtil::computeAngleBetween(left, right), M_PI, 1e-6); -} \ No newline at end of file +} diff --git a/tests/SphericalUtil/computeArea.hpp b/tests/SphericalUtil/computeArea.hpp new file mode 100644 index 0000000..1120398 --- /dev/null +++ b/tests/SphericalUtil/computeArea.hpp @@ -0,0 +1,19 @@ +#include + +#include "SphericalUtil.hpp" + + +TEST(SphericalUtil, computeArea) { + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + LatLng back = { 0.0, -180.0 }; + LatLng left = { 0.0, -90.0 }; + + std::vector first = { right, up, front, down, right }; + EXPECT_NEAR(SphericalUtil::computeArea(first), M_PI * MathUtil::EARTH_RADIUS * MathUtil::EARTH_RADIUS, .4); + + std::vector second = { right, down, front, up, right }; + EXPECT_NEAR(SphericalUtil::computeArea(second), M_PI * MathUtil::EARTH_RADIUS * MathUtil::EARTH_RADIUS, .4); +} diff --git a/functional_tests/SphericalUtil/computeDistanceBetween.cpp b/tests/SphericalUtil/computeDistanceBetween.hpp similarity index 56% rename from functional_tests/SphericalUtil/computeDistanceBetween.cpp rename to tests/SphericalUtil/computeDistanceBetween.hpp index c534ea5..85a24ba 100644 --- a/functional_tests/SphericalUtil/computeDistanceBetween.cpp +++ b/tests/SphericalUtil/computeDistanceBetween.hpp @@ -1,11 +1,11 @@ -#include "gtest/gtest.h" +#include -#include +#include "SphericalUtil.hpp" TEST(SphericalUtil, computeDistanceBetween) { - LatLng up(90, 0); - LatLng down(-90, 0); + LatLng up = { 90.0, 0.0}; + LatLng down = {-90.0, 0.0}; EXPECT_NEAR(SphericalUtil::computeDistanceBetween(up, down), M_PI * MathUtil::EARTH_RADIUS, 1e-6); -} \ No newline at end of file +} diff --git a/functional_tests/SphericalUtil/computeHeading.cpp b/tests/SphericalUtil/computeHeading.hpp similarity index 74% rename from functional_tests/SphericalUtil/computeHeading.cpp rename to tests/SphericalUtil/computeHeading.hpp index cbc674b..481f80b 100644 --- a/functional_tests/SphericalUtil/computeHeading.cpp +++ b/tests/SphericalUtil/computeHeading.hpp @@ -1,19 +1,19 @@ -#include "gtest/gtest.h" +#include -#include +#include "SphericalUtil.hpp" TEST(SphericalUtil, computeHeading) { - LatLng up(90, 0); - LatLng down(-90, 0); - LatLng front(0, 0); - LatLng right(0, 90); - LatLng back(0, -180); - LatLng left(0, -90); + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + LatLng back = { 0.0, -180.0 }; + LatLng left = { 0.0, -90.0 }; // Opposing vertices for which there is a result EXPECT_NEAR(SphericalUtil::computeHeading(up, down), -180, 1e-6); - EXPECT_NEAR(SphericalUtil::computeHeading(down, up), 0, 1e-6); + EXPECT_NEAR(SphericalUtil::computeHeading(down, up), 0, 1e-6); // Adjacent vertices for which there is a result EXPECT_NEAR(SphericalUtil::computeHeading(front, up), 0, 1e-6); @@ -31,4 +31,4 @@ TEST(SphericalUtil, computeHeading) { EXPECT_NEAR(SphericalUtil::computeHeading(front, right), 90, 1e-6); EXPECT_NEAR(SphericalUtil::computeHeading(back, right), -90, 1e-6); -} \ No newline at end of file +} diff --git a/tests/SphericalUtil/computeLength.hpp b/tests/SphericalUtil/computeLength.hpp new file mode 100644 index 0000000..6ecd0b9 --- /dev/null +++ b/tests/SphericalUtil/computeLength.hpp @@ -0,0 +1,22 @@ +#include + +#include "SphericalUtil.hpp" + + +TEST(SphericalUtil, computeLength) { + // List without points + std::vector latLngs; + EXPECT_NEAR(SphericalUtil::computeLength(latLngs), 0, 1e-6); + + // List with one point + latLngs.push_back(LatLng(0, 0)); + EXPECT_NEAR(SphericalUtil::computeLength(latLngs), 0, 1e-6); + + // List with two points + latLngs.push_back(LatLng(0.1, 0.1)); + EXPECT_NEAR(SphericalUtil::computeLength(latLngs), deg2rad(0.1) * sqrt(2) * MathUtil::EARTH_RADIUS, 1); + + // List with three points + std::vector latLngs2 = { {0, 0}, {90, 0}, {0, 90} }; + EXPECT_NEAR(SphericalUtil::computeLength(latLngs2), M_PI * MathUtil::EARTH_RADIUS, 1e-6); +} diff --git a/tests/SphericalUtil/computeOffset.hpp b/tests/SphericalUtil/computeOffset.hpp new file mode 100644 index 0000000..aa16f4c --- /dev/null +++ b/tests/SphericalUtil/computeOffset.hpp @@ -0,0 +1,38 @@ +#include + +#include "SphericalUtil.hpp" + +#define EXPECT_NEAR_LatLan(actual, expected) \ + EXPECT_NEAR(actual.lat, expected.lat, 1e-6); + // Issue #2 + // Account for the convergence of longitude lines at the poles + // double cosLat = cos(deg2rad(actual.lat)); + // EXPECT_NEAR(cosLat * actual.lng, cosLat * expected.lng, 1e-6); + +TEST(SphericalUtil, computeOffset) { + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + LatLng back = { 0.0, -180.0 }; + LatLng left = { 0.0, -90.0 }; + + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffset(front, 0, 0)); + EXPECT_NEAR_LatLan(up, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 0)); + EXPECT_NEAR_LatLan(down, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 180)); + EXPECT_NEAR_LatLan(left, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, -90)); + EXPECT_NEAR_LatLan(right, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS / 2, 90)); + EXPECT_NEAR_LatLan(back, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS, 0)); + EXPECT_NEAR_LatLan(back, SphericalUtil::computeOffset(front, M_PI * MathUtil::EARTH_RADIUS, 90)); + + // From left + EXPECT_NEAR_LatLan(left, SphericalUtil::computeOffset(left, 0, 0)); + EXPECT_NEAR_LatLan(up, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, 0)); + EXPECT_NEAR_LatLan(down, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, 180)); + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, 90)); + EXPECT_NEAR_LatLan(back, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS / 2, -90)); + EXPECT_NEAR_LatLan(right, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS, 0)); + EXPECT_NEAR_LatLan(right, SphericalUtil::computeOffset(left, M_PI * MathUtil::EARTH_RADIUS, 90)); + + // NOTE: Heading is undefined at the poles, so we do not test from up/down. +} diff --git a/tests/SphericalUtil/computeOffsetOrigin.hpp b/tests/SphericalUtil/computeOffsetOrigin.hpp new file mode 100644 index 0000000..29d17fa --- /dev/null +++ b/tests/SphericalUtil/computeOffsetOrigin.hpp @@ -0,0 +1,31 @@ +#include + +#include "SphericalUtil.hpp" + + +#define EXPECT_NEAR_LatLan(actual, expected) \ + EXPECT_NEAR(actual.lat, expected.lat, 1e-6); + // Issue #2 + // Account for the convergence of longitude lines at the poles + // double cosLat = cos(deg2rad(actual.lat)); + // EXPECT_NEAR(cosLat * actual.lng, cosLat * expected.lng, 1e-6); + +TEST(SphericalUtil, computeOffsetOrigin) { + LatLng front = { 0.0, 0.0 }; + + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(front, 0, 0)); + + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng( 0, 45), M_PI * MathUtil::EARTH_RADIUS / 4, 90)); + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng( 0, -45), M_PI * MathUtil::EARTH_RADIUS / 4, -90)); + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng( 45, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 0)); + EXPECT_NEAR_LatLan(front, SphericalUtil::computeOffsetOrigin(LatLng(-45, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 180)); + + // Issue #3 + // Situations with no solution, should return null. + // + // First 'over' the pole. + // EXPECT_NULL(SphericalUtil::computeOffsetOrigin(LatLng(80, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 180)); + + // Second a distance that doesn't fit on the earth. + // EXPECT_NULL(SphericalUtil::computeOffsetOrigin(LatLng(80, 0), M_PI * MathUtil::EARTH_RADIUS / 4, 90)); +} diff --git a/tests/SphericalUtil/computeSignedArea.hpp b/tests/SphericalUtil/computeSignedArea.hpp new file mode 100644 index 0000000..761cd79 --- /dev/null +++ b/tests/SphericalUtil/computeSignedArea.hpp @@ -0,0 +1,16 @@ +#include + +#include "SphericalUtil.hpp" + + +TEST(SphericalUtil, computeSignedArea) { + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + + std::vector path = { right, up, front, down, right }; + std::vector pathReversed = { right, down, front, up, right }; + + EXPECT_NEAR(-SphericalUtil::computeSignedArea(path), SphericalUtil::computeSignedArea(pathReversed), 0); +} diff --git a/tests/SphericalUtil/interpolate.hpp b/tests/SphericalUtil/interpolate.hpp new file mode 100644 index 0000000..2b00888 --- /dev/null +++ b/tests/SphericalUtil/interpolate.hpp @@ -0,0 +1,58 @@ +#include + +#include "SphericalUtil.hpp" + + +#define EXPECT_NEAR_LatLan(actual, expected) \ + EXPECT_NEAR(actual.lat, expected.lat, 1e-6); + // Issue #2 + // Account for the convergence of longitude lines at the poles + // double cosLat = cos(deg2rad(actual.lat)); + // EXPECT_NEAR(cosLat * actual.lng, cosLat * expected.lng, 1e-6); + + +TEST(SphericalUtil, interpolate) { + LatLng up = { 90.0, 0.0 }; + LatLng down = {-90.0, 0.0 }; + LatLng front = { 0.0, 0.0 }; + LatLng right = { 0.0, 90.0 }; + LatLng back = { 0.0, -180.0 }; + LatLng left = { 0.0, -90.0 }; + + EXPECT_NEAR_LatLan(up, SphericalUtil::interpolate(up, up, 1 / 2.0)); + EXPECT_NEAR_LatLan(down, SphericalUtil::interpolate(down, down, 1 / 2.0)); + EXPECT_NEAR_LatLan(left, SphericalUtil::interpolate(left, left, 1 / 2.0)); + + // Between front and up + EXPECT_NEAR_LatLan(LatLng(1, 0), SphericalUtil::interpolate(front, up, 1 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(1, 0), SphericalUtil::interpolate(up, front, 89 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(89, 0), SphericalUtil::interpolate(front, up, 89 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(89, 0), SphericalUtil::interpolate(up, front, 1 / 90.0)); + + // Between front and down + EXPECT_NEAR_LatLan(LatLng(-1, 0), SphericalUtil::interpolate(front, down, 1 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(-1, 0), SphericalUtil::interpolate(down, front, 89 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(-89, 0), SphericalUtil::interpolate(front, down, 89 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(-89, 0), SphericalUtil::interpolate(down, front, 1 / 90.0)); + + // Between left and back + EXPECT_NEAR_LatLan(LatLng(0, -91), SphericalUtil::interpolate(left, back, 1 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(0, -91), SphericalUtil::interpolate(back, left, 89 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(0, -179), SphericalUtil::interpolate(left, back, 89 / 90.0)); + EXPECT_NEAR_LatLan(LatLng(0, -179), SphericalUtil::interpolate(back, left, 1 / 90.0)); + + // geodesic crosses pole + EXPECT_NEAR_LatLan(up, SphericalUtil::interpolate(LatLng(45, 0), LatLng(45, 180), 1 / 2.0)); + EXPECT_NEAR_LatLan(down, SphericalUtil::interpolate(LatLng(-45, 0), LatLng(-45, 180), 1 / 2.0)); + + // boundary values for fraction, between left and back + EXPECT_NEAR_LatLan(left, SphericalUtil::interpolate(left, back, 0.0)); + EXPECT_NEAR_LatLan(back, SphericalUtil::interpolate(left, back, 1.0)); + + // two nearby points, separated by ~4m, for which the Slerp algorithm is not stable and we + // have to fall back to linear interpolation. + LatLng interpolateResult = SphericalUtil::interpolate(LatLng(-37.756891, 175.325262), LatLng(-37.756853, 175.325242), 0.5); + LatLng goldenResult(-37.756872, 175.325252); + + EXPECT_NEAR(interpolateResult.lat, goldenResult.lat, 2e-5); +} diff --git a/tests/Tests.cpp b/tests/Tests.cpp new file mode 100644 index 0000000..36cebc4 --- /dev/null +++ b/tests/Tests.cpp @@ -0,0 +1,21 @@ +/** Including all tests */ +#include "SphericalUtil/interpolate.hpp" +#include "SphericalUtil/computeAngleBetween.hpp" +#include "SphericalUtil/computeSignedArea.hpp" +#include "SphericalUtil/computeArea.hpp" +#include "SphericalUtil/computeLength.hpp" +#include "SphericalUtil/computeOffset.hpp" +#include "SphericalUtil/computeHeading.hpp" +#include "SphericalUtil/computeOffsetOrigin.hpp" +#include "SphericalUtil/computeDistanceBetween.hpp" + +#include "PolyUtil/containsLocation.hpp" +#include "PolyUtil/isLocationOnEdge.hpp" +#include "PolyUtil/isLocationOnPath.hpp" +#include "PolyUtil/distanceToLine.hpp" + + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/packages.config b/tests/packages.config new file mode 100644 index 0000000..0acd30a --- /dev/null +++ b/tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/tests.vcxproj b/tests/tests.vcxproj new file mode 100644 index 0000000..7cb67e9 --- /dev/null +++ b/tests/tests.vcxproj @@ -0,0 +1,150 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {96b63797-1621-492e-88c0-72751561874d} + Win32Proj + 10.0.17763.0 + Application + v141 + Unicode + + + + + + + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);../include/ + true + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);../include/ + true + + + + + + + + + + + + + + + + + + + + {6ff667f1-262a-4779-81cf-fd346fe2af62} + + + + + + + + + + + + + NotUsing + pch.h + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Level3 + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ + + + true + Console + + + + + NotUsing + + + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Level3 + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ + + + + true + Console + + + + + NotUsing + pch.h + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ + + + true + Console + true + true + + + + + NotUsing + + + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);$(SolutionDir)/include/ + + + + true + Console + true + true + + + + + Данный проект ссылается на пакеты NuGet, отсутствующие на этом компьютере. Используйте восстановление пакетов NuGet, чтобы скачать их. Дополнительную информацию см. по адресу: http://go.microsoft.com/fwlink/?LinkID=322105. Отсутствует следующий файл: {0}. + + + + \ No newline at end of file diff --git a/tests/tests.vcxproj.filters b/tests/tests.vcxproj.filters new file mode 100644 index 0000000..78eba51 --- /dev/null +++ b/tests/tests.vcxproj.filters @@ -0,0 +1,58 @@ + + + + + {4fbd4f21-7e3a-4fd2-9bfa-29dc154dc6d7} + + + {1498a4c7-50db-40b7-a717-9cf9f748a7fb} + + + + + + + + + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + SphericalUtil + + + PolyUtil + + + PolyUtil + + + PolyUtil + + + PolyUtil + + + \ No newline at end of file