From 8780da4148857bfe855f8a6c014db0c3427bc37b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 2 Dec 2023 07:42:29 -0600 Subject: [PATCH] Determine if a geographic area contains a point --- ACKNOWLEDGEMENTS.md | 1 + conanfile.py | 4 +- scwx-qt/scwx-qt.cmake | 3 + .../source/scwx/qt/util/geographic_lib.cpp | 62 +++++++++++++++++++ .../source/scwx/qt/util/geographic_lib.hpp | 16 +++++ 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 8b08f4ee..0435d04c 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -22,6 +22,7 @@ Supercell Wx uses code from the following dependencies: | [FreeType](https://freetype.org/) | [Freetype Project License](https://spdx.org/licenses/FTL.html) | | [FreeType GL](https://github.com/rougier/freetype-gl) | [BSD 2-Clause with views sentence](https://spdx.org/licenses/BSD-2-Clause-Views.html) | | [GeographicLib](https://geographiclib.sourceforge.io/) | [MIT License](https://spdx.org/licenses/MIT.html) | +| [geos](https://libgeos.org/) | [GNU Lesser General Public License v2.1 or later](https://spdx.org/licenses/LGPL-2.1-or-later.html) | | [GLEW](https://www.opengl.org/sdk/libs/GLEW/) | [MIT License](https://spdx.org/licenses/MIT.html) | | [GLM](https://github.com/g-truc/glm) | [MIT License](https://spdx.org/licenses/MIT.html) | | [GoogleTest](https://google.github.io/googletest/) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) | diff --git a/conanfile.py b/conanfile.py index fdfae57a..8ffb23f5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -6,6 +6,7 @@ class SupercellWxConan(ConanFile): "cpr/1.10.5", "fontconfig/2.14.2", "geographiclib/2.3", + "geos/3.12.0", "glew/2.2.0", "glm/cci.20230113", "gtest/1.14.0", @@ -19,7 +20,8 @@ class SupercellWxConan(ConanFile): generators = ("cmake", "cmake_find_package", "cmake_paths") - default_options = {"libiconv:shared" : True, + default_options = {"geos:shared" : True, + "libiconv:shared" : True, "openssl:no_module": True, "openssl:shared" : True} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index af2339b9..44e0c0ae 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -14,6 +14,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Boost) find_package(Fontconfig) find_package(geographiclib) +find_package(geos) find_package(GLEW) find_package(glm) find_package(Python COMPONENTS Interpreter) @@ -533,6 +534,8 @@ target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets $<$:opengl32> Fontconfig::Fontconfig GeographicLib::GeographicLib + GEOS::geos + GEOS::geos_cxx_flags GLEW::GLEW glm::glm imgui diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index 6718715a..1736f6a2 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -1,4 +1,9 @@ #include +#include + +#include +#include +#include namespace scwx { @@ -9,6 +14,9 @@ namespace util namespace GeographicLib { +static const std::string logPrefix_ = "scwx::qt::util::geographic_lib"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + const ::GeographicLib::Geodesic& DefaultGeodesic() { static const ::GeographicLib::Geodesic geodesic_ { @@ -18,6 +26,60 @@ const ::GeographicLib::Geodesic& DefaultGeodesic() return geodesic_; } +bool AreaContainsPoint(const std::vector& area, + const common::Coordinate& point) +{ + // Cannot have an area with just two points + if (area.size() <= 2 || area.size() == 3 && area.front() == area.back()) + { + return false; + } + + ::GeographicLib::Gnomonic gnomonic {}; + geos::geom::CoordinateSequence sequence {}; + double x; + double y; + bool areaContainsPoint = false; + + // Using a gnomonic projection with the test point as the center + // latitude/longitude, the projected test point will be at (0, 0) + geos::geom::CoordinateXY zero {}; + + // Create the area coordinate sequence using a gnomonic projection + for (auto& areaCoordinate : area) + { + gnomonic.Forward(point.latitude_, + point.longitude_, + areaCoordinate.latitude_, + areaCoordinate.longitude_, + x, + y); + sequence.add(x, y); + } + + // If the sequence is not a ring, add the first point again for closure + if (!sequence.isRing()) + { + sequence.add(sequence.front(), false); + } + + // The sequence should be a ring at this point, but make sure + if (sequence.isRing()) + { + try + { + areaContainsPoint = + geos::algorithm::PointLocation::isInRing(zero, &sequence); + } + catch (const std::exception&) + { + logger_->trace("Invalid area sequence"); + } + } + + return areaContainsPoint; +} + units::angle::degrees GetAngle(double lat1, double lon1, double lat2, double lon2) { diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index d03aac04..66b4f42b 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -1,5 +1,9 @@ #pragma once +#include + +#include + #include #include #include @@ -20,6 +24,18 @@ namespace GeographicLib */ const ::GeographicLib::Geodesic& DefaultGeodesic(); +/** + * Determine if an area/ring, oriented in either direction, contains a point. A + * point lying on the area boundary is considered to be inside the area. + * + * @param [in] area A vector of Coordinates representing the area + * @param [in] point The point to check against the area + * + * @return true if point is inside the area + */ +bool AreaContainsPoint(const std::vector& area, + const common::Coordinate& point); + /** * Get the angle between two points. *