diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp index a952b863..15112410 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -106,14 +106,17 @@ public: threadPool_.join(); }; - void - ComputeCoordinates(std::shared_ptr radarData); + void ComputeCoordinates( + const std::shared_ptr& radarData); void SetProduct(const std::string& productName); void SetProduct(common::Level2Product product); void UpdateOtherUnits(const std::string& name); void UpdateSpeedUnits(const std::string& name); + static bool IsRadarDataIncomplete( + const std::shared_ptr& radarData); + Level2ProductView* self_; boost::asio::thread_pool threadPool_ {1u}; @@ -536,7 +539,17 @@ void Level2ProductView::ComputeSweep() return; } - const std::size_t radials = radarData->crbegin()->first; + const std::size_t radials = radarData->crbegin()->first + 1; + std::size_t vertexRadials = radials; + + // When there is missing data, insert another empty vertex radial at the end + // to avoid stretching + const bool isRadarDataIncomplete = + Level2ProductViewImpl::IsRadarDataIncomplete(radarData); + if (isRadarDataIncomplete) + { + ++vertexRadials; + } p->ComputeCoordinates(radarData); @@ -574,7 +587,8 @@ void Level2ProductView::ComputeSweep() std::vector& vertices = p->vertices_; size_t vIndex = 0; vertices.clear(); - vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); + vertices.resize(vertexRadials * gates * VERTICES_PER_BIN * + VALUES_PER_VERTEX); // Setup data moment vector std::vector& dataMoments8 = p->dataMoments8_; @@ -807,7 +821,7 @@ void Level2ProductView::ComputeSweep() } void Level2ProductViewImpl::ComputeCoordinates( - std::shared_ptr radarData) + const std::shared_ptr& radarData) { logger_->debug("ComputeCoordinates()"); @@ -828,12 +842,22 @@ void Level2ProductViewImpl::ComputeCoordinates( auto& radarData0 = (*radarData)[0]; auto momentData0 = radarData0->moment_data_block(dataBlockType_); - const std::uint16_t numRadials = + std::uint16_t numRadials = static_cast(radarData->crbegin()->first + 1); const std::uint16_t numRangeBins = std::max(momentData0->number_of_data_moment_gates() + 1u, common::MAX_DATA_MOMENT_GATES); + // Add an extra radial when incomplete data exists + if (IsRadarDataIncomplete(radarData)) + { + ++numRadials; + } + + // Limit radials + numRadials = + std::min(numRadials, common::MAX_0_5_DEGREE_RADIALS); + auto radials = boost::irange(0u, numRadials); auto gates = boost::irange(0u, numRangeBins); @@ -878,8 +902,7 @@ void Level2ProductViewImpl::ComputeCoordinates( // Assume a half degree delta if there aren't enough angles // to determine a delta angle - constexpr units::degrees deltaAngle = - units::degrees {0.5}; + constexpr units::degrees deltaAngle {0.5f}; angle = prevAngle1 + deltaAngle; } @@ -918,6 +941,23 @@ void Level2ProductViewImpl::ComputeCoordinates( logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws")); } +bool Level2ProductViewImpl::IsRadarDataIncomplete( + const std::shared_ptr& radarData) +{ + // Assume the data is incomplete when the delta between the first and last + // angles is greater than 2.5 degrees. + constexpr units::degrees kIncompleteDataAngleThreshold_ {2.5}; + + const units::degrees firstAngle = + radarData->cbegin()->second->azimuth_angle(); + const units::degrees lastAngle = + radarData->crbegin()->second->azimuth_angle(); + const units::degrees angleDelta = + common::GetAngleDelta(firstAngle, lastAngle); + + return angleDelta > kIncompleteDataAngleThreshold_; +} + std::optional Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const { diff --git a/wxdata/include/scwx/common/geographic.hpp b/wxdata/include/scwx/common/geographic.hpp index 8945db17..8b234fd2 100644 --- a/wxdata/include/scwx/common/geographic.hpp +++ b/wxdata/include/scwx/common/geographic.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace scwx { namespace common @@ -46,6 +48,17 @@ enum class DistanceType Miles }; +/** + * Calculate the absolute angle delta between two angles. + * + * @param [in] angle1 First angle + * @param [in] angle2 Second angle + * + * @return Absolute angle delta normalized to [0, 360) + */ +units::degrees GetAngleDelta(units::degrees angle1, + units::degrees angle2); + /** * Calculate the geographic midpoint of a set of coordinates. Uses Method A * described at http://www.geomidpoint.com/calculation.html. diff --git a/wxdata/source/scwx/common/geographic.cpp b/wxdata/source/scwx/common/geographic.cpp index bf7d1a2c..e9a494d3 100644 --- a/wxdata/source/scwx/common/geographic.cpp +++ b/wxdata/source/scwx/common/geographic.cpp @@ -14,6 +14,34 @@ static std::string GetDegreeString(double degrees, DegreeStringType type, const std::string& suffix); +units::degrees GetAngleDelta(units::degrees angle1, + units::degrees angle2) +{ + // Normalize angles to [0, 360) + while (angle1.value() < 0.0f) + { + angle1 += units::degrees {360.0f}; + } + while (angle2.value() < 0.0f) + { + angle2 += units::degrees {360.0f}; + } + angle1 = units::degrees {std::fmod(angle1.value(), 360.f)}; + angle2 = units::degrees {std::fmod(angle2.value(), 360.f)}; + + // Calculate the absolute difference + auto delta = angle1 - angle2; + if (delta < units::degrees {0.0f}) + { + delta *= -1.0f; + } + + // Account for wrapping + delta = std::min(delta, units::degrees {360.0f} - delta); + + return delta; +} + Coordinate GetCentroid(const std::vector& coordinates) { double x = 0.0;