diff --git a/scwx-qt/source/scwx/qt/manager/alert_manager.cpp b/scwx-qt/source/scwx/qt/manager/alert_manager.cpp index d68f9226..e50babdd 100644 --- a/scwx-qt/source/scwx/qt/manager/alert_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/alert_manager.cpp @@ -85,7 +85,8 @@ common::Coordinate AlertManager::Impl::CurrentCoordinate( settings::AudioSettings& audioSettings = settings::AudioSettings::Instance(); common::Coordinate coordinate {}; - if (locationMethod == types::LocationMethod::Fixed) + if (locationMethod == types::LocationMethod::Fixed || + locationMethod == types::LocationMethod::Radius) { coordinate.latitude_ = audioSettings.alert_latitude().GetValue(); coordinate.longitude_ = audioSettings.alert_longitude().GetValue(); @@ -153,6 +154,15 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key, activeAtLocation = util::GeographicLib::AreaContainsPoint( alertCoordinates, currentCoordinate); } + else if (locationMethod == types::LocationMethod::Radius) + { + auto alertCoordinates = segment->codedLocation_->coordinates(); + + activeAtLocation = util::GeographicLib::AreaInRangeOfPoint( + alertCoordinates, + currentCoordinate, + units::length::meters(1e6)); + } else if (locationMethod == types::LocationMethod::County) { // Determine if the alert contains the current county diff --git a/scwx-qt/source/scwx/qt/types/location_types.cpp b/scwx-qt/source/scwx/qt/types/location_types.cpp index b1c759de..f5ad6ac8 100644 --- a/scwx-qt/source/scwx/qt/types/location_types.cpp +++ b/scwx-qt/source/scwx/qt/types/location_types.cpp @@ -14,6 +14,7 @@ namespace types static const std::unordered_map locationMethodName_ {{LocationMethod::Fixed, "Fixed"}, + {LocationMethod::Radius, "Radius"}, {LocationMethod::Track, "Track"}, {LocationMethod::County, "County"}, {LocationMethod::All, "All"}, diff --git a/scwx-qt/source/scwx/qt/types/location_types.hpp b/scwx-qt/source/scwx/qt/types/location_types.hpp index 65d32f1e..30f793c9 100644 --- a/scwx-qt/source/scwx/qt/types/location_types.hpp +++ b/scwx-qt/source/scwx/qt/types/location_types.hpp @@ -14,6 +14,7 @@ namespace types enum class LocationMethod { Fixed, + Radius, Track, County, All, diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index 89466975..bc4066ce 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -5,14 +5,16 @@ #include #include +#include +#include #include +#include namespace scwx { namespace qt { -namespace util -{ +namespace util { namespace GeographicLib { @@ -82,6 +84,12 @@ bool AreaContainsPoint(const std::vector& area, return areaContainsPoint; } + + + + + + units::angle::degrees GetAngle(double lat1, double lon1, double lat2, double lon2) { @@ -137,6 +145,93 @@ GetDistance(double lat1, double lon1, double lat2, double lon2) return units::length::meters {distance}; } +bool AreaInRangeOfPoint(const std::vector& area, + const common::Coordinate& point, + const units::length::meters distance) +{ + // Cannot have an area with just two points + if (area.size() <= 2 || (area.size() == 3 && area.front() == area.back())) + { + return false; + } + + + + ::GeographicLib::Gnomonic gnomonic = + ::GeographicLib::Gnomonic(DefaultGeodesic()); + geos::geom::CoordinateSequence sequence {}; + double x; + double y; + + // 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); + } + + // get a point on the circle with the radius of the range in lat lon. + units::angle::degrees angle = units::angle::degrees(0); + common::Coordinate radiusPoint = GetCoordinate(point, angle, distance); + // get the radius in gnomonic projection + gnomonic.Forward(point.latitude_, + point.longitude_, + radiusPoint.latitude_, + radiusPoint.longitude_, + x, + y); + double gnomonicRadius = sqrt(x * x + y * 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 + { + if (geos::algorithm::PointLocation::isInRing(zero, &sequence)) + { + return true; + } + + // Calculate the distance the point is from the output + geos::algorithm::distance::PointPairDistance distancePair; + auto geometryFactory = + geos::geom::GeometryFactory::getDefaultInstance(); + auto linearRing = geometryFactory->createLinearRing(sequence); + auto polygon = + geometryFactory->createPolygon(std::move(linearRing)); + geos::algorithm::distance::DistanceToPoint::computeDistance(*polygon, + zero, + distancePair); + if (gnomonicRadius > distancePair.getDistance()) + { + return true; + } + + } + catch (const std::exception&) + { + logger_->trace("Invalid area sequence"); + } + } + + return false; +} + } // namespace GeographicLib } // namespace util } // namespace qt diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index 30f7a63c..93469802 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -90,6 +90,23 @@ common::Coordinate GetCoordinate(const common::Coordinate& center, units::length::meters GetDistance(double lat1, double lon1, double lat2, double lon2); + +/** + * Determine if an area/ring, oriented in either direction, is within a + * distance of a point. A point lying on the area boundary is considered to be + * inside the area, and thus always in range. Any part of the area being inside + * the radius counts as inside. + * + * @param [in] area A vector of Coordinates representing the area + * @param [in] point The point to check against the area + * @param [in] distance The max distance in meters + * + * @return true if area is inside the radius of the point + */ +bool AreaInRangeOfPoint(const std::vector& area, + const common::Coordinate& point, + const units::length::meters distance); + } // namespace GeographicLib } // namespace util } // namespace qt diff --git a/test/source/scwx/qt/util/geographic_lib.test.cpp b/test/source/scwx/qt/util/geographic_lib.test.cpp new file mode 100644 index 00000000..0052b028 --- /dev/null +++ b/test/source/scwx/qt/util/geographic_lib.test.cpp @@ -0,0 +1,69 @@ +#include + +#include +#include +#include + +namespace scwx +{ +namespace util +{ + +std::vector area = { + common::Coordinate(37.0193692, -91.8778413), + common::Coordinate(36.9719180, -91.3006973), + common::Coordinate(36.7270831, -91.6815753), +}; + +TEST(geographic_lib, area_in_range_inside) +{ + auto inside = common::Coordinate(36.9241584, -91.6425933); + bool value; + + // inside is always true + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, inside, units::length::meters(0)); + EXPECT_EQ(value, true); + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, inside, units::length::meters(1e6)); + EXPECT_EQ(value, true); +} + +TEST(geographic_lib, area_in_range_near) +{ + auto near = common::Coordinate(36.8009181, -91.3922700); + bool value; + + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, near, units::length::meters(9000)); + EXPECT_EQ(value, false); + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, near, units::length::meters(10100)); + EXPECT_EQ(value, true); + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, near, units::length::meters(1e6)); + EXPECT_EQ(value, true); +} + +TEST(geographic_lib, area_in_range_far) +{ + auto far = common::Coordinate(37.6481966, -94.2163834); + bool value; + + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, far, units::length::meters(9000)); + EXPECT_EQ(value, false); + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, far, units::length::meters(10100)); + EXPECT_EQ(value, false); + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, far, units::length::meters(100e3)); + EXPECT_EQ(value, false); + value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint( + area, far, units::length::meters(300e3)); + EXPECT_EQ(value, true); +} + + +} // namespace util +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index 0643c057..b3cfedd2 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -28,7 +28,8 @@ set(SRC_QT_MAP_TESTS source/scwx/qt/map/map_provider.test.cpp) set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp) set(SRC_QT_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp source/scwx/qt/settings/settings_variable.test.cpp) -set(SRC_QT_UTIL_TESTS source/scwx/qt/util/q_file_input_stream.test.cpp) +set(SRC_QT_UTIL_TESTS source/scwx/qt/util/q_file_input_stream.test.cpp + source/scwx/qt/util/geographic_lib.test.cpp) set(SRC_UTIL_TESTS source/scwx/util/float.test.cpp source/scwx/util/rangebuf.test.cpp source/scwx/util/streams.test.cpp