diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index e3b78660..a8f7911e 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -107,11 +107,13 @@ set(SRC_UTIL source/scwx/qt/util/font.cpp set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp source/scwx/qt/view/level3_product_view.hpp source/scwx/qt/view/level3_radial_view.hpp + source/scwx/qt/view/level3_raster_view.hpp source/scwx/qt/view/radar_product_view.hpp source/scwx/qt/view/radar_product_view_factory.hpp) set(SRC_VIEW source/scwx/qt/view/level2_product_view.cpp source/scwx/qt/view/level3_product_view.cpp source/scwx/qt/view/level3_radial_view.cpp + source/scwx/qt/view/level3_raster_view.cpp source/scwx/qt/view/radar_product_view.cpp source/scwx/qt/view/radar_product_view_factory.cpp) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 1e4d680e..d554a327 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -314,24 +314,35 @@ void MapWidget::SelectRadarProduct( << common::GetRadarProductGroupName(group) << ", " << product << ", " << util::TimeString(time) << ")"; - p->radarProductManager_ = manager::RadarProductManager::Instance(radarId); - p->selectedTime_ = time; - if (group == common::RadarProductGroup::Level2) { common::Level2Product level2Product = p->GetLevel2ProductOrDefault(product); + p->radarProductManager_ = manager::RadarProductManager::Instance(radarId); + p->selectedTime_ = time; + SelectRadarProduct(level2Product); } else { // TODO: Combine this with the SelectRadarProduct(Level2Product) function - std::shared_ptr& radarProductView = - p->context_->radarProductView_; + std::shared_ptr radarProductManager = + manager::RadarProductManager::Instance(radarId); + std::shared_ptr radarProductView = + view::RadarProductViewFactory::Create( + group, product, productCode, 0.0f, radarProductManager); - radarProductView = view::RadarProductViewFactory::Create( - group, product, 0.0f, p->radarProductManager_); + if (radarProductView == nullptr) + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "No view created for product"; + return; + } + + p->context_->radarProductView_ = radarProductView; + p->radarProductManager_ = radarProductManager; + p->selectedTime_ = time; radarProductView->SelectTime(p->selectedTime_); connect( @@ -344,7 +355,7 @@ void MapWidget::SelectRadarProduct( radarProductView.get(), &view::RadarProductView::SweepComputed, this, - [&]() + [=]() { std::shared_ptr radarSite = p->radarProductManager_->radar_site(); @@ -472,7 +483,9 @@ void MapWidget::keyPressEvent(QKeyEvent* ev) { switch (ev->key()) { - case Qt::Key_S: changeStyle(); break; + case Qt::Key_S: + changeStyle(); + break; case Qt::Key_L: { for (const QString& layer : p->map_->layerIds()) @@ -481,7 +494,8 @@ void MapWidget::keyPressEvent(QKeyEvent* ev) } } break; - default: break; + default: + break; } ev->accept(); @@ -602,7 +616,9 @@ void MapWidget::mapChanged(QMapboxGL::MapChange mapChange) { switch (mapChange) { - case QMapboxGL::MapChangeDidFinishLoadingStyle: AddLayers(); break; + case QMapboxGL::MapChangeDidFinishLoadingStyle: + AddLayers(); + break; } } diff --git a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp new file mode 100644 index 00000000..3bf9605d --- /dev/null +++ b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace view +{ + +static const std::string logPrefix_ = "[scwx::qt::view::level3_raster_view] "; + +static constexpr uint16_t RANGE_FOLDED = 1u; +static constexpr uint32_t VERTICES_PER_BIN = 6u; +static constexpr uint32_t VALUES_PER_VERTEX = 2u; + +class Level3RasterViewImpl +{ +public: + explicit Level3RasterViewImpl( + const std::string& product, + std::shared_ptr radarProductManager) : + product_ {product}, + radarProductManager_ {radarProductManager}, + selectedTime_ {}, + latitude_ {}, + longitude_ {}, + range_ {}, + vcp_ {}, + sweepTime_ {} + { + } + ~Level3RasterViewImpl() = default; + + std::string product_; + std::shared_ptr radarProductManager_; + + std::chrono::system_clock::time_point selectedTime_; + + std::vector vertices_; + std::vector dataMoments8_; + + float latitude_; + float longitude_; + float range_; + uint16_t vcp_; + + std::chrono::system_clock::time_point sweepTime_; +}; + +Level3RasterView::Level3RasterView( + const std::string& product, + std::shared_ptr radarProductManager) : + Level3ProductView(product), + p(std::make_unique(product, radarProductManager)) +{ +} +Level3RasterView::~Level3RasterView() = default; + +float Level3RasterView::range() const +{ + return p->range_; +} + +std::chrono::system_clock::time_point Level3RasterView::sweep_time() const +{ + return p->sweepTime_; +} + +uint16_t Level3RasterView::vcp() const +{ + return p->vcp_; +} + +const std::vector& Level3RasterView::vertices() const +{ + return p->vertices_; +} + +std::tuple Level3RasterView::GetMomentData() const +{ + const void* data; + size_t dataSize; + size_t componentSize; + + data = p->dataMoments8_.data(); + dataSize = p->dataMoments8_.size() * sizeof(uint8_t); + componentSize = 1; + + return std::tie(data, dataSize, componentSize); +} + +void Level3RasterView::SelectTime(std::chrono::system_clock::time_point time) +{ + p->selectedTime_ = time; +} + +void Level3RasterView::ComputeSweep() +{ + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "ComputeSweep()"; + + boost::timer::cpu_timer timer; + + std::scoped_lock sweepLock(sweep_mutex()); + + // Retrieve message from Radar Product Manager + std::shared_ptr message = + p->radarProductManager_->GetLevel3Data(p->product_, p->selectedTime_); + + // A message with radial data should be a Graphic Product Message + std::shared_ptr gpm = + std::dynamic_pointer_cast(message); + if (gpm == nullptr) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Graphic Product Message not found"; + return; + } + else if (gpm == graphic_product_message()) + { + // Skip if this is the message we previously processed + return; + } + set_graphic_product_message(gpm); + + // A message with radial data should have a Product Description Block and + // Product Symbology Block + std::shared_ptr descriptionBlock = + message->description_block(); + std::shared_ptr symbologyBlock = + gpm->symbology_block(); + if (descriptionBlock == nullptr || symbologyBlock == nullptr) + { + BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Missing blocks"; + return; + } + + // A valid message should have a positive number of layers + uint16_t numberOfLayers = symbologyBlock->number_of_layers(); + if (numberOfLayers < 1) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "No layers present in symbology block"; + return; + } + + // A message with raster data should have a Raster Data Packet + std::shared_ptr rasterData = nullptr; + + for (uint16_t layer = 0; layer < numberOfLayers; layer++) + { + std::vector> packetList = + symbologyBlock->packet_list(layer); + + for (auto it = packetList.begin(); it != packetList.end(); it++) + { + rasterData = + std::dynamic_pointer_cast(*it); + + if (rasterData != nullptr) + { + break; + } + } + + if (rasterData != nullptr) + { + break; + } + } + + if (rasterData == nullptr) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "No raster data found"; + return; + } + + // Calculate raster grid size + const uint16_t rows = rasterData->number_of_rows(); + size_t maxColumns = 0; + for (uint16_t r = 0; r < rows; r++) + { + maxColumns = std::max(maxColumns, rasterData->level(r).size()); + } + + if (maxColumns == 0) + { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "No raster bins found"; + return; + } + + p->latitude_ = descriptionBlock->latitude_of_radar(); + p->longitude_ = descriptionBlock->longitude_of_radar(); + p->range_ = descriptionBlock->range(); + p->sweepTime_ = + util::TimePoint(descriptionBlock->volume_scan_date(), + descriptionBlock->volume_scan_start_time() * 1000); + p->vcp_ = descriptionBlock->volume_coverage_pattern(); + + GeographicLib::Geodesic geodesic(GeographicLib::Constants::WGS84_a(), + GeographicLib::Constants::WGS84_f()); + + const uint16_t xResolution = descriptionBlock->x_resolution_raw(); + const uint16_t yResolution = descriptionBlock->y_resolution_raw(); + double iCoordinate = + (-rasterData->i_coordinate_start() - 1.0 - p->range_) * 1000.0; + double jCoordinate = + (rasterData->j_coordinate_start() + 1.0 + p->range_) * 1000.0; + + size_t numCoordinates = + static_cast(rows + 1) * static_cast(maxColumns + 1); + auto coordinateRange = + boost::irange(0, static_cast(numCoordinates)); + + std::vector coordinates; + coordinates.resize(numCoordinates * 2); + + // Calculate coordinates + timer.start(); + + std::for_each( + std::execution::par_unseq, + coordinateRange.begin(), + coordinateRange.end(), + [&](uint32_t index) + { + // For each row or column, there is one additional coordinate. Each bin + // is bounded by 4 coordinates. + const uint32_t col = index % (rows + 1); + const uint32_t row = index / (rows + 1); + + const double i = iCoordinate + xResolution * col; + const double j = jCoordinate - yResolution * row; + + // Calculate polar coordinates based on i and j + const double angle = std::atan2(i, j) * 180.0 / M_PI; + const double range = std::sqrt(i * i + j * j); + const size_t offset = static_cast(index) * 2; + + double latitude; + double longitude; + + geodesic.Direct( + p->latitude_, p->longitude_, angle, range, latitude, longitude); + + coordinates[offset] = latitude; + coordinates[offset + 1] = longitude; + }); + + timer.stop(); + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Coordinates calculated in " << timer.format(6, "%ws"); + + // Calculate vertices + timer.start(); + + // Setup vertex vector + std::vector& vertices = p->vertices_; + size_t vIndex = 0; + vertices.clear(); + vertices.resize(rows * maxColumns * VERTICES_PER_BIN * VALUES_PER_VERTEX); + + // Setup data moment vector + std::vector& dataMoments8 = p->dataMoments8_; + size_t mIndex = 0; + + dataMoments8.resize(rows * maxColumns * VERTICES_PER_BIN); + + // Compute threshold at which to display an individual bin + const float scale = descriptionBlock->scale(); + const float offset = descriptionBlock->offset(); + const uint16_t snrThreshold = descriptionBlock->threshold(); + + for (size_t row = 0; row < rasterData->number_of_rows(); ++row) + { + const auto dataMomentsArray8 = + rasterData->level(static_cast(row)); + + for (size_t bin = 0; bin < dataMomentsArray8.size(); ++bin) + { + constexpr size_t vertexCount = 6; + + // Store data moment value + uint8_t dataValue = dataMomentsArray8[bin]; + if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) + { + continue; + } + + for (size_t m = 0; m < vertexCount; m++) + { + dataMoments8[mIndex++] = dataValue; + } + + // Store vertices + size_t offset1 = (row * (maxColumns + 1) + bin) * 2; + size_t offset2 = offset1 + 2; + size_t offset3 = ((row + 1) * (maxColumns + 1) + bin) * 2; + size_t offset4 = offset3 + 2; + + vertices[vIndex++] = coordinates[offset1]; + vertices[vIndex++] = coordinates[offset1 + 1]; + + vertices[vIndex++] = coordinates[offset2]; + vertices[vIndex++] = coordinates[offset2 + 1]; + + vertices[vIndex++] = coordinates[offset3]; + vertices[vIndex++] = coordinates[offset3 + 1]; + + vertices[vIndex++] = coordinates[offset3]; + vertices[vIndex++] = coordinates[offset3 + 1]; + + vertices[vIndex++] = coordinates[offset4]; + vertices[vIndex++] = coordinates[offset4 + 1]; + + vertices[vIndex++] = coordinates[offset2]; + vertices[vIndex++] = coordinates[offset2 + 1]; + } + } + vertices.resize(vIndex); + vertices.shrink_to_fit(); + + dataMoments8.resize(mIndex); + dataMoments8.shrink_to_fit(); + + timer.stop(); + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Vertices calculated in " << timer.format(6, "%ws"); + + UpdateColorTable(); + + emit SweepComputed(); +} + +std::shared_ptr Level3RasterView::Create( + const std::string& product, + std::shared_ptr radarProductManager) +{ + return std::make_shared(product, radarProductManager); +} + +} // namespace view +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/view/level3_raster_view.hpp b/scwx-qt/source/scwx/qt/view/level3_raster_view.hpp new file mode 100644 index 00000000..e7f384a7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/view/level3_raster_view.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace view +{ + +class Level3RasterViewImpl; + +class Level3RasterView : public Level3ProductView +{ + Q_OBJECT + +public: + explicit Level3RasterView( + const std::string& product, + std::shared_ptr radarProductManager); + ~Level3RasterView(); + + float range() const override; + std::chrono::system_clock::time_point sweep_time() const override; + uint16_t vcp() const override; + const std::vector& vertices() const override; + + void SelectTime(std::chrono::system_clock::time_point time) override; + + std::tuple GetMomentData() const override; + + static std::shared_ptr + Create(const std::string& product, + std::shared_ptr radarProductManager); + +protected slots: + void ComputeSweep() override; + +private: + std::unique_ptr p; +}; + +} // namespace view +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view_factory.cpp b/scwx-qt/source/scwx/qt/view/radar_product_view_factory.cpp index 4188c140..a188ddbb 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view_factory.cpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view_factory.cpp @@ -1,6 +1,9 @@ #include #include #include +#include + +#include #include @@ -19,9 +22,19 @@ typedef std::function( std::shared_ptr radarProductManager)> CreateRadarProductFunction; +std::unordered_set level3GenericRadialProducts_ {176, 178, 179}; +std::unordered_set level3RadialProducts_ { + 19, 20, 27, 30, 31, 32, 33, 34, 56, 78, 79, 80, 93, + 94, 99, 113, 132, 133, 134, 135, 137, 138, 144, 145, 146, 147, + 150, 151, 153, 154, 155, 159, 161, 163, 165, 167, 168, 169, 170, + 171, 172, 173, 174, 175, 177, 180, 181, 182, 186, 193, 195}; +std::unordered_set level3RasterProducts_ { + 37, 38, 41, 49, 50, 51, 57, 65, 66, 67, 81, 86, 90, 97, 98}; + std::shared_ptr RadarProductViewFactory::Create( common::RadarProductGroup productGroup, const std::string& productName, + int16_t productCode, float elevation, std::shared_ptr radarProductManager) { @@ -43,7 +56,14 @@ std::shared_ptr RadarProductViewFactory::Create( } else if (productGroup == common::RadarProductGroup::Level3) { - view = Level3RadialView::Create(productName, radarProductManager); + if (level3RadialProducts_.contains(productCode)) + { + view = Level3RadialView::Create(productName, radarProductManager); + } + else if (level3RasterProducts_.contains(productCode)) + { + view = Level3RasterView::Create(productName, radarProductManager); + } } else { diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view_factory.hpp b/scwx-qt/source/scwx/qt/view/radar_product_view_factory.hpp index 752b988a..87fdf74f 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view_factory.hpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view_factory.hpp @@ -30,6 +30,7 @@ public: static std::shared_ptr Create(common::RadarProductGroup productGroup, const std::string& productName, + int16_t productCode, float elevation, std::shared_ptr radarProductManager); static std::shared_ptr