diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 41f6fdbe..179c1020 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -430,8 +430,12 @@ void MainWindow::on_actionOpenNexrad_triggered() { logger_->info("Selected: {}", file.toStdString()); + auto radarSite = p->activeMap_->GetRadarSite(); + std::string currentRadarSite = + (radarSite != nullptr) ? radarSite->id() : std::string {}; + std::shared_ptr request = - std::make_shared(); + std::make_shared(currentRadarSite); connect( // request.get(), @@ -882,9 +886,9 @@ void MainWindowImpl::ConnectAnimationSignals() &manager::TimelineManager::VolumeTimeUpdated, [this](std::chrono::system_clock::time_point dateTime) { + volumeTime_ = dateTime; for (auto map : maps_) { - volumeTime_ = dateTime; map->SelectTime(dateTime); } }); diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp index bc5e8b98..732529ff 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -207,16 +207,18 @@ public: void UpdateRecentRecords(RadarProductRecordList& recentList, std::shared_ptr record); - void LoadNexradFileAsync(CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time); - void LoadProviderData(std::chrono::system_clock::time_point time, + void LoadNexradFileAsync( + CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time); + void + LoadProviderData(std::chrono::system_clock::time_point time, std::shared_ptr providerManager, RadarProductRecordMap& recordMap, std::shared_mutex& recordMutex, std::mutex& loadDataMutex, - std::shared_ptr request); + const std::shared_ptr& request); void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time); void PopulateLevel3ProductTimes(const std::string& product, std::chrono::system_clock::time_point time); @@ -228,10 +230,10 @@ public: std::chrono::system_clock::time_point time); static void - LoadNexradFile(CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time = {}); + LoadNexradFile(CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time = {}); const std::string radarId_; bool initialized_; @@ -394,6 +396,11 @@ float RadarProductManager::gate_size() const return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f; } +std::string RadarProductManager::radar_id() const +{ + return p->radarId_; +} + std::shared_ptr RadarProductManager::radar_site() const { return p->radarSite_; @@ -777,12 +784,12 @@ RadarProductManager::GetActiveVolumeTimes( } void RadarProductManagerImpl::LoadProviderData( - std::chrono::system_clock::time_point time, - std::shared_ptr providerManager, - RadarProductRecordMap& recordMap, - std::shared_mutex& recordMutex, - std::mutex& loadDataMutex, - std::shared_ptr request) + std::chrono::system_clock::time_point time, + std::shared_ptr providerManager, + RadarProductRecordMap& recordMap, + std::shared_mutex& recordMutex, + std::mutex& loadDataMutex, + const std::shared_ptr& request) { logger_->debug("LoadProviderData: {}, {}", providerManager->name(), @@ -837,8 +844,8 @@ void RadarProductManagerImpl::LoadProviderData( } void RadarProductManager::LoadLevel2Data( - std::chrono::system_clock::time_point time, - std::shared_ptr request) + std::chrono::system_clock::time_point time, + const std::shared_ptr& request) { logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time)); @@ -851,9 +858,9 @@ void RadarProductManager::LoadLevel2Data( } void RadarProductManager::LoadLevel3Data( - const std::string& product, - std::chrono::system_clock::time_point time, - std::shared_ptr request) + const std::string& product, + std::chrono::system_clock::time_point time, + const std::shared_ptr& request) { logger_->debug("LoadLevel3Data: {}", scwx::util::TimeString(time)); @@ -883,7 +890,7 @@ void RadarProductManager::LoadLevel3Data( } void RadarProductManager::LoadData( - std::istream& is, std::shared_ptr request) + std::istream& is, const std::shared_ptr& request) { logger_->debug("LoadData()"); @@ -899,8 +906,8 @@ void RadarProductManager::LoadData( } void RadarProductManager::LoadFile( - const std::string& filename, - std::shared_ptr request) + const std::string& filename, + const std::shared_ptr& request) { logger_->debug("LoadFile: {}", filename); @@ -950,10 +957,10 @@ void RadarProductManager::LoadFile( } void RadarProductManagerImpl::LoadNexradFileAsync( - CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time) + CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time) { boost::asio::post(threadPool_, [=, &mutex]() @@ -961,10 +968,10 @@ void RadarProductManagerImpl::LoadNexradFileAsync( } void RadarProductManagerImpl::LoadNexradFile( - CreateNexradFileFunction load, - std::shared_ptr request, - std::mutex& mutex, - std::chrono::system_clock::time_point time) + CreateNexradFileFunction load, + const std::shared_ptr& request, + std::mutex& mutex, + std::chrono::system_clock::time_point time) { std::unique_lock lock {mutex}; @@ -987,8 +994,14 @@ void RadarProductManagerImpl::LoadNexradFile( record->set_time(time); } + std::string recordRadarId = (record->radar_id()); + if (recordRadarId.empty()) + { + recordRadarId = request->current_radar_site(); + } + std::shared_ptr manager = - RadarProductManager::Instance(record->radar_id()); + RadarProductManager::Instance(recordRadarId); manager->Initialize(); record = manager->p->StoreRadarProductRecord(record); @@ -1035,7 +1048,14 @@ void RadarProductManagerImpl::PopulateProductTimes( std::shared_mutex& productRecordMutex, std::chrono::system_clock::time_point time) { - const auto today = std::chrono::floor(time); + const auto today = std::chrono::floor(time); + + // Don't query for the epoch + if (today == std::chrono::system_clock::time_point {}) + { + return; + } + const auto yesterday = today - std::chrono::days {1}; const auto tomorrow = today + std::chrono::days {1}; const auto dates = {yesterday, today, tomorrow}; @@ -1119,7 +1139,7 @@ RadarProductManagerImpl::GetLevel2ProductRecord( { // Product is expired, reload it std::shared_ptr request = - std::make_shared(); + std::make_shared(radarId_); QObject::connect( request.get(), @@ -1184,7 +1204,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord( { // Product is expired, reload it std::shared_ptr request = - std::make_shared(); + std::make_shared(radarId_); QObject::connect( request.get(), diff --git a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp index ed8ba97b..aaf28996 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp @@ -42,6 +42,7 @@ public: const std::vector& coordinates(common::RadialSize radialSize) const; float gate_size() const; + std::string radar_id() const; std::shared_ptr radar_site() const; void Initialize(); @@ -110,19 +111,19 @@ public: Instance(const std::string& radarSite); void LoadLevel2Data( - std::chrono::system_clock::time_point time, - std::shared_ptr request = nullptr); + std::chrono::system_clock::time_point time, + const std::shared_ptr& request = nullptr); void LoadLevel3Data( - const std::string& product, - std::chrono::system_clock::time_point time, - std::shared_ptr request = nullptr); + const std::string& product, + std::chrono::system_clock::time_point time, + const std::shared_ptr& request = nullptr); - static void - LoadData(std::istream& is, - std::shared_ptr request = nullptr); - static void - LoadFile(const std::string& filename, - std::shared_ptr request = nullptr); + static void LoadData( + std::istream& is, + const std::shared_ptr& request = nullptr); + static void LoadFile( + const std::string& filename, + const std::shared_ptr& request = nullptr); common::Level3ProductCategoryMap GetAvailableLevel3Categories(); std::vector GetLevel3Products(); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 5f16ebf3..a5100e4a 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1332,7 +1332,8 @@ void MapWidgetImpl::RadarProductManagerConnect() { // Create file request std::shared_ptr request = - std::make_shared(); + std::make_shared( + radarProductManager_->radar_id()); // File request callback if (autoUpdateEnabled_) diff --git a/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp b/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp index 5324027f..42f0ab33 100644 --- a/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp +++ b/scwx-qt/source/scwx/qt/request/nexrad_file_request.cpp @@ -9,22 +9,31 @@ namespace request static const std::string logPrefix_ = "scwx::qt::request::nexrad_file_request"; -class NexradFileRequestImpl +class NexradFileRequest::Impl { public: - explicit NexradFileRequestImpl() : radarProductRecord_ {nullptr} {} + explicit Impl(const std::string& currentRadarSite) : + currentRadarSite_ {currentRadarSite} + { + } + ~Impl() = default; - ~NexradFileRequestImpl() {} + const std::string currentRadarSite_; - std::shared_ptr radarProductRecord_; + std::shared_ptr radarProductRecord_ {nullptr}; }; -NexradFileRequest::NexradFileRequest() : - p(std::make_unique()) +NexradFileRequest::NexradFileRequest(const std::string& currentRadarSite) : + p(std::make_unique(currentRadarSite)) { } NexradFileRequest::~NexradFileRequest() = default; +std::string NexradFileRequest::current_radar_site() const +{ + return p->currentRadarSite_; +} + std::shared_ptr NexradFileRequest::radar_product_record() const { @@ -32,7 +41,7 @@ NexradFileRequest::radar_product_record() const } void NexradFileRequest::set_radar_product_record( - std::shared_ptr record) + const std::shared_ptr& record) { p->radarProductRecord_ = record; } diff --git a/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp b/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp index 787f513b..090d7169 100644 --- a/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp +++ b/scwx-qt/source/scwx/qt/request/nexrad_file_request.hpp @@ -13,23 +13,23 @@ namespace qt namespace request { -class NexradFileRequestImpl; - class NexradFileRequest : public QObject { Q_OBJECT public: - explicit NexradFileRequest(); + explicit NexradFileRequest(const std::string& currentRadarSite = {}); ~NexradFileRequest(); + std::string current_radar_site() const; std::shared_ptr radar_product_record() const; - void - set_radar_product_record(std::shared_ptr record); + void set_radar_product_record( + const std::shared_ptr& record); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; signals: void RequestComplete(std::shared_ptr request); 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 6c89ea6d..f0199c4b 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -89,8 +89,9 @@ public: float selectedElevation_; - std::shared_ptr elevationScan_; - std::shared_ptr momentDataBlock0_; + std::shared_ptr elevationScan_; + std::shared_ptr + momentDataBlock0_; std::vector coordinates_ {}; std::vector vertices_ {}; @@ -98,12 +99,12 @@ public: std::vector dataMoments16_ {}; std::vector cfpMoments_ {}; - float latitude_; - float longitude_; - float elevationCut_; - std::vector elevationCuts_; - float range_; - uint16_t vcp_; + float latitude_; + float longitude_; + float elevationCut_; + std::vector elevationCuts_; + units::kilometers range_; + uint16_t vcp_; std::chrono::system_clock::time_point sweepTime_; @@ -212,7 +213,7 @@ float Level2ProductView::elevation() const float Level2ProductView::range() const { - return p->range_; + return p->range_.value(); } std::chrono::system_clock::time_point Level2ProductView::sweep_time() const @@ -468,15 +469,15 @@ void Level2ProductView::ComputeSweep() const uint32_t gates = momentData0->number_of_data_moment_gates(); - auto volumeData0 = radarData0->volume_data_block(); - p->latitude_ = volumeData0->latitude(); - p->longitude_ = volumeData0->longitude(); + auto radarSite = radarProductManager->radar_site(); + p->latitude_ = radarSite->latitude(); + p->longitude_ = radarSite->longitude(); p->range_ = momentData0->data_moment_range() + momentData0->data_moment_range_sample_interval() * (gates - 0.5f); p->sweepTime_ = scwx::util::TimePoint(radarData0->modified_julian_date(), radarData0->collection_time()); - p->vcp_ = volumeData0->volume_coverage_pattern_number(); + p->vcp_ = radarData0->volume_coverage_pattern_number(); // Calculate vertices timer.start(); @@ -521,17 +522,17 @@ void Level2ProductView::ComputeSweep() } // Compute threshold at which to display an individual bin (minimum of 2) - const uint16_t snrThreshold = - std::max(2, momentData0->snr_threshold_raw()); + const std::uint16_t snrThreshold = + std::max(2, momentData0->snr_threshold_raw()); // Start radial is always 0, as coordinates are calculated for each sweep constexpr std::uint16_t startRadial = 0u; for (auto& radialPair : *radarData) { - uint16_t radial = radialPair.first; - auto radialData = radialPair.second; - auto momentData = radialData->moment_data_block(p->dataBlockType_); + std::uint16_t radial = radialPair.first; + auto& radialData = radialPair.second; + auto momentData = radialData->moment_data_block(p->dataBlockType_); if (momentData0->data_word_size() != momentData->data_word_size()) { @@ -540,64 +541,70 @@ void Level2ProductView::ComputeSweep() } // Compute gate interval - const uint16_t dataMomentRange = momentData->data_moment_range_raw(); - const uint16_t dataMomentInterval = + const std::int32_t dataMomentInterval = momentData->data_moment_range_sample_interval_raw(); - const uint16_t dataMomentIntervalH = dataMomentInterval / 2; + const std::int32_t dataMomentIntervalH = dataMomentInterval / 2; + const std::int32_t dataMomentRange = std::max( + momentData->data_moment_range_raw(), dataMomentIntervalH); // Compute gate size (number of base 250m gates per bin) - const uint16_t gateSizeMeters = - static_cast(radarProductManager->gate_size()); - const uint16_t gateSize = - std::max(1, dataMomentInterval / gateSizeMeters); + const std::int32_t gateSizeMeters = + static_cast(radarProductManager->gate_size()); + const std::int32_t gateSize = + std::max(1, dataMomentInterval / gateSizeMeters); // Compute gate range [startGate, endGate) - const uint16_t startGate = + const std::int32_t startGate = (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; - const uint16_t numberOfDataMomentGates = - std::min(momentData->number_of_data_moment_gates(), - static_cast(gates)); - const uint16_t endGate = - std::min(startGate + numberOfDataMomentGates * gateSize, - common::MAX_DATA_MOMENT_GATES); + const std::int32_t numberOfDataMomentGates = + std::min(momentData->number_of_data_moment_gates(), + static_cast(gates)); + const std::int32_t endGate = std::min( + startGate + numberOfDataMomentGates * gateSize, + static_cast(common::MAX_DATA_MOMENT_GATES)); - const uint8_t* dataMomentsArray8 = nullptr; - const uint16_t* dataMomentsArray16 = nullptr; - const uint8_t* cfpMomentsArray = nullptr; + const std::uint8_t* dataMomentsArray8 = nullptr; + const std::uint16_t* dataMomentsArray16 = nullptr; + const std::uint8_t* cfpMomentsArray = nullptr; if (momentData->data_word_size() == 8) { dataMomentsArray8 = - reinterpret_cast(momentData->data_moments()); + reinterpret_cast(momentData->data_moments()); } else { dataMomentsArray16 = - reinterpret_cast(momentData->data_moments()); + reinterpret_cast(momentData->data_moments()); } if (cfpMoments.size() > 0) { - cfpMomentsArray = reinterpret_cast( + cfpMomentsArray = reinterpret_cast( radialData->moment_data_block(wsr88d::rda::DataBlockType::MomentCfp) ->data_moments()); } - for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; + for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate; gate += gateSize, ++i) { - size_t vertexCount = (gate > 0) ? 6 : 3; + if (gate < 0) + { + continue; + } + + std::size_t vertexCount = (gate > 0) ? 6 : 3; // Store data moment value if (dataMomentsArray8 != nullptr) { - uint8_t dataValue = dataMomentsArray8[i]; + std::uint8_t dataValue = dataMomentsArray8[i]; if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) { continue; } - for (size_t m = 0; m < vertexCount; m++) + for (std::size_t m = 0; m < vertexCount; m++) { dataMoments8[mIndex++] = dataMomentsArray8[i]; @@ -609,13 +616,13 @@ void Level2ProductView::ComputeSweep() } else { - uint16_t dataValue = dataMomentsArray16[i]; + std::uint16_t dataValue = dataMomentsArray16[i]; if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) { continue; } - for (size_t m = 0; m < vertexCount; m++) + for (std::size_t m = 0; m < vertexCount; m++) { dataMoments16[mIndex++] = dataMomentsArray16[i]; } @@ -624,18 +631,18 @@ void Level2ProductView::ComputeSweep() // Store vertices if (gate > 0) { - const uint16_t baseCoord = gate - 1; + const std::uint16_t baseCoord = gate - 1; - size_t offset1 = ((startRadial + radial) % radials * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - size_t offset2 = offset1 + gateSize * 2; - size_t offset3 = (((startRadial + radial + 1) % radials) * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - size_t offset4 = offset3 + gateSize * 2; + std::size_t offset1 = ((startRadial + radial) % radials * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + std::size_t offset2 = offset1 + gateSize * 2; + std::size_t offset3 = (((startRadial + radial + 1) % radials) * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + std::size_t offset4 = offset3 + gateSize * 2; vertices[vIndex++] = coordinates[offset1]; vertices[vIndex++] = coordinates[offset1 + 1]; @@ -659,16 +666,16 @@ void Level2ProductView::ComputeSweep() } else { - const uint16_t baseCoord = gate; + const std::uint16_t baseCoord = gate; - size_t offset1 = ((startRadial + radial) % radials * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; - size_t offset2 = (((startRadial + radial + 1) % radials) * - common::MAX_DATA_MOMENT_GATES + - baseCoord) * - 2; + std::size_t offset1 = ((startRadial + radial) % radials * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; + std::size_t offset2 = (((startRadial + radial + 1) % radials) * + common::MAX_DATA_MOMENT_GATES + + baseCoord) * + 2; vertices[vIndex++] = p->latitude_; vertices[vIndex++] = p->longitude_; @@ -747,7 +754,8 @@ void Level2ProductViewImpl::ComputeCoordinates( radials.end(), [&](std::uint32_t radial) { - const float angle = (*radarData)[radial]->azimuth_angle(); + const units::degrees angle = + (*radarData)[radial]->azimuth_angle(); std::for_each(std::execution::par_unseq, gates.begin(), @@ -765,7 +773,7 @@ void Level2ProductViewImpl::ComputeCoordinates( geodesic.Direct(radarLatitude, radarLongitude, - angle, + angle.value(), range, latitude, longitude); @@ -830,14 +838,15 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const radials.end(), [&](std::uint32_t i) { - bool found = false; - const float startAngle = (*radarData)[i]->azimuth_angle(); - const float nextAngle = + bool found = false; + const units::degrees startAngle = + (*radarData)[i]->azimuth_angle(); + const units::degrees nextAngle = (*radarData)[(i + 1) % numRadials]->azimuth_angle(); if (startAngle < nextAngle) { - if (startAngle <= azi1 && azi1 < nextAngle) + if (startAngle.value() <= azi1 && azi1 < nextAngle.value()) { found = true; } @@ -845,7 +854,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const else { // If the bin crosses 0/360 degrees, special handling is needed - if (startAngle <= azi1 || azi1 < nextAngle) + if (startAngle.value() <= azi1 || azi1 < nextAngle.value()) { found = true; } @@ -862,24 +871,26 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const // Compute gate interval auto momentData = (*radarData)[*radial]->moment_data_block(dataBlockType); - const std::uint16_t dataMomentRange = momentData->data_moment_range_raw(); - const std::uint16_t dataMomentInterval = + const std::int32_t dataMomentInterval = momentData->data_moment_range_sample_interval_raw(); - const std::uint16_t dataMomentIntervalH = dataMomentInterval / 2; + const std::int32_t dataMomentIntervalH = dataMomentInterval / 2; + const std::int32_t dataMomentRange = std::max( + momentData->data_moment_range_raw(), dataMomentIntervalH); // Compute gate size (number of base 250m gates per bin) - const std::uint16_t gateSizeMeters = - static_cast(radarProductManager->gate_size()); + const std::int32_t gateSizeMeters = + static_cast(radarProductManager->gate_size()); // Compute gate range [startGate, endGate) - const std::uint16_t startGate = + const std::int32_t startGate = (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; - const std::uint16_t numberOfDataMomentGates = + const std::int32_t numberOfDataMomentGates = momentData->number_of_data_moment_gates(); - const std::uint16_t gate = s12 / dataMomentInterval - startGate; + const std::int32_t gate = s12 / dataMomentInterval - startGate; - if (gate > numberOfDataMomentGates || gate > common::MAX_DATA_MOMENT_GATES) + if (gate < 0 || gate > numberOfDataMomentGates || + gate > static_cast(common::MAX_DATA_MOMENT_GATES)) { // Coordinate is beyond radar range return std::nullopt; diff --git a/test/data b/test/data index 6632ffd6..e3e743a5 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 6632ffd6ba35b799dd803e9711281d54a3858a29 +Subproject commit e3e743a5cc9c065d05f00151380fea892fb2156c diff --git a/test/source/scwx/wsr88d/ar2v_file.test.cpp b/test/source/scwx/wsr88d/ar2v_file.test.cpp index 03b0a25b..097fb69c 100644 --- a/test/source/scwx/wsr88d/ar2v_file.test.cpp +++ b/test/source/scwx/wsr88d/ar2v_file.test.cpp @@ -1,7 +1,5 @@ #include -#include - #include namespace scwx @@ -9,25 +7,32 @@ namespace scwx namespace wsr88d { -TEST(ar2v_file, klsx) +class Ar2vValidFileTest : + public testing::TestWithParam> { +}; + +TEST_P(Ar2vValidFileTest, ValidFile) +{ + auto& param = GetParam(); + Ar2vFile file; bool fileValid = - file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + - "/nexrad/level2/Level2_KLSX_20210527_1757.ar2v"); + file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + param.first); EXPECT_EQ(fileValid, true); + EXPECT_EQ(file.message_count(), param.second); } -TEST(ar2v_file, tstl) -{ - Ar2vFile file; - bool fileValid = - file.LoadFile(std::string(SCWX_TEST_DATA_DIR) + - "/nexrad/level2/Level2_TSTL_20220213_2357.ar2v"); - - EXPECT_EQ(fileValid, true); -} +INSTANTIATE_TEST_SUITE_P( + Ar2vFile, + Ar2vValidFileTest, + testing::Values(std::pair // + {"/nexrad/level2/KCLE20021110_221234", 4031}, + std::pair // + {"/nexrad/level2/Level2_KLSX_20210527_1757.ar2v", 11167}, + std::pair // + {"/nexrad/level2/Level2_TSTL_20220213_2357.ar2v", 5763})); } // namespace wsr88d } // namespace scwx diff --git a/wxdata/archive2_format.txt b/wxdata/archive2_format.txt new file mode 100644 index 00000000..77825083 --- /dev/null +++ b/wxdata/archive2_format.txt @@ -0,0 +1,509 @@ +[Image] NEXRAD DOCUMENTATION +---------------------------------------------------------------------------- + + LEVEL II TAPE DOCUMENTATION + + WSR-88D BASE DATA + +INTRODUCTION: + +Weather Surveillance Radar - 1988 Doppler (WSR-88D), or NEXt Generation +RADar (NEXRAD), Level II data are the base digital data produced by the +signal processor (mean radial velocity, reflectivity, and spectrum width) at +the full spatial and temporal resolution of the radar. Level II data also +contain status messages, performance/maintenance data, volume scan strategy, +clutter filter bypass map, and wideband communication console messages. +These are the same data transmitted over high-speed, wideband communications +to the WSR-88D Radar Product Generator (RPG) for processing by the +meteorological analysis algorithms. + +Initially it was thought that Level II recorders would be used at selected +sites, and only when significant weather events were taking place. As system +development progressed, it became evident that the Level II data would be of +vital importance to ensure proper calibration of the radars and for use by +researchers to investigate events in more detail than would be possible by +using the Level III products. The Level II data can also be used to test +revised algorithms that may later be applied to operational use. + +The NEXRAD agencies (Departments of Commerce, Defense, and Transportation) +recognized the value of Level II data. In June 1994, the agencies agreed to +record Level II data throughout the WSR-88D network. The Level II recording +is not essential to the operational use of the WSR-88D system. The NEXRAD +agencies agreed to certain procedures to minimize the impact of Level II +data collection on the operations of base weather stations, forecast offices +and FAA control locations. The priority of Level II recorder maintenance, +reloading of tapes and continuous recording of data will be assigned by the +local site management. + +RECORDING: + +The vast amounts of data collected at the Radar Data Acquisition (RDA) site +made it mandatory that the most economical recording devices and media +available at that time be used. It was determined that EXABYTE tape drives +and 8mm tapes provided the most viable system. Depending on operation of the +radar, the recorder model used, and station requirements, one tape may be +filled every 1.8 days for each site. Data grade tapes are used for recording +and archiving. Initially, sites were equipped with EXABYTE 8200 recorders. +These tapes can contain up to 2.3 gigabytes per tape. Later, EXABYTE 8500 +recorders were installed which record at higher density with up to 4.7 +gigabytes per tape. Also available are 8500c (capable of recording in a +standard compressed mode), and 8505 which is a half height drive fully +downward compatible. The 8505 records up 4.7 gigabytes in an uncompressed +mode. + +PROCESSING AND ARCHIVING: + +The Level II recorder system consists of an 8mm reorder, 10-tape jukebox +(automated sequential loading of new tapes), an uninterruptable power supply +ans a controller board seated in the RDA computer. Under jukebox operation +the 10-tape supply will last 11 to 27 days depending on the radar scanning +strategies used. Tapes are received at the National Climatic Data Center +(NCDC) from the individual sites in 10-tape cases. Incoming tapes are +processed on a series of 8505 EXABYTE drives, reblocked, cataloged, +inventoried, and archived. The original tapes are sent to an off-site +storage facility for security back-up to the NCDC NEXRAD files. + +SPECIAL NOTE: + +The WSR-88D is a very complex system. Program modifications and engineering +changes are rather constant features during the phase-in process. Some early +pre-production models experienced considerable difficulties in the recording +of Level II data. Even today, tapes are received that contain spurious, +erroneous, or illegal configurations. We have attempted to recover as much +data as possible from these problem tapes. The user is cautioned that these +anomalies may be encountered while reading the archive tapes. Special care +must be taken to ensure that illegal configurations do not contaminate any +summaries or statistical studies. + +NCDC will be glad to assist in solving problems encountered in reading the +tapes, but technical questions about the data themselves must be addressed +to the: + + NWS/Operational Support Facility + Operations Branch + 1200 Westheimer Dr. + Norman, OK 73069 + + Telephone: (405) 366-6530 + FAX: (405) 366-6550 + +Definitive information about all aspects of the Doppler radar is contained +in Federal Meteorological Handbook No. 11 (FMH-11), Volumes A through D. +These may be ordered from the National Climatic Data Center. + +DATA AVAILABILITY: + +As stated previously, all NCDC archives are being generated on EXABYTE 8505 +drives. Users must specify whether they require 8200 or 8500 mode tapes. If +copies are requested in the 8200 mode, two or more output tapes may be +required. A header record will appear on each output tape. + +Each 8mm tape records approximately 10 hours of Volume Coverage Pattern 11 +(VCP 11), 18 hours of VCP 21, or 40 hours of VCP 31 or 32 using the EXABYTE +8200 mode. Using the EXABYTE 8500 mode doubles both the storage capacity and +number of hours of data possible per 8mm tape. + +FORMAT: + +HEADER FILE: The first file on tape contains only one 31616 byte record. +This record is called the header record. + +HEADER RECORD: This 31616 byte "physical record" is divided into 494 +"logical records" of 64 bytes each with position 1 as the first byte. + +POSITIONS FORMAT DESCRIPTION + +1 - 8 C*8 Always ARCHIVE2 + +9 - 12 C*4 4-letter site ID. e.g. KLMB + +13 - 18 C*6 NCDC tape number. e.g. N00001 + +19 Blank + +20 - 28 C*9 Date tape written. dd-MMM-yy e.g. 19-FEB-93 + +29 Blank + +30 - 37 C*8 Time tape written. hh:mm:ss. e.g. 10:22:59 + (local time) + +38 Blank + +39 - 43 C*5 Data Center writing tape: RDASC or NCDC + (Left justified, blank filled) + +44 - 48 C*5 WBAN Number of this NEXRAD site. (This is a unique + 5-digit number assigned at NCDC. Numbers are + contained in the NCDC NEXRAD Station History file + (WSR-88D RDA LOCATIONS). The file also contains the + four letter site ID, Latitude, Longitude, Elevation, + and Standard location name.) + +49 - 53 C*5 Tape output mode. Current values are 8200, 8500, + 8500C + +54 - 58 C*5 A volume number to be used for copies and extractions + of data from tapes. The form would be VOL01, VOL02, + VOL03 ....VOLnn. + +59 - 64 Blank (Available for future use.) + +65 - 31616 May be used for internal controls or other + information at each archive center. Information of + value to users will be documented at the time of tape + shipment. + +During the process of copying archive tapes, positions 1-18 and 44-48 will +be duplicated. New values will be written in positions 19-43 and 49-58. + +DATA FILES: + +A new data file is created upon completion of a volume scan. A data file +contains a title, a complete radar volume scan (360 degree revolutions at +each specified elevation cut) of base data, digital radar data message, and +any control/response messages from the RDA to the RPG. The title is the +first record located in each data file and contains a file name, creation +date, and creation time. After the title record through the remainder of the +data file, variable length records containing base data intermixed with +control/response messages are recorded. Messages and base data are +distinguishable by a message header coded for either digital radar base data +or one of the thirteen types of messages. The message header uses a format +common to both data and messages and is included in each 2432 byte packet. +Depending on the predefined volume scan strategy (selected elevations, sweep +rate, pulse rate etc.) used during the collection period, each data file +could contain either five, six, or ten minutes of base data. +Control/response messages are used during actual operations and are of +limited use for post analyses. + +DATA TYPES SUPPORTED WITHIN DATA FILES: + +A Concurrent minicomputer serves as the host computer for generation of all +Archive Level II data. Depending on the computer used for reading the tapes, +the data types may be different from those used in the Concurrent system. +The Concurrent computer byte (8 bits) structure places bit 0 as the left +most bit and designates bit 0 as the Most Significant Bit (MSB). Bit 7 for a +byte, bit 15 for a halfword (2 bytes), bit 31 for a fullword (4 bytes) and +bit 63 for a double word (8 bytes) are all the Least Significant Bit (LSB) +for their respective data formats. + +Level II is recorded using the following data types: + + Unsigned byte (byte) - number ranging from 0-255 + + Character (C) - Standard ASCII characters + + Signed Short Integer (I*2) - Most Significant Bit (MSB) is the sign bit + (bit 0). (1-Negative, 0-Positive). + + Signed Long Integer (I*4) - MSB (bit 0) is the sign bit. + + Single Precision Real (R*4) - MSB (bit 0) is the sign bit (positive), + bit 1-7 is the exponent in excess-64 notation format, and bit 8-31 is + the fraction field. An example may be helpful: + + Starting with 4180 69E8 (hex), the sign bit = 0 (positive), the + exponent = +1 [e.g. 41 (hex) converted to 65 (dec) - 64 (excess 64 + notation) = +1], and the fraction 8069E8 (hex) shifted by exponent of + +1 gives 8.069E8 (hex). To convert 8.069E8 (hex) to decimal, start with + the whole number 8 (hex) which in this case equals 8 (dec). Next, the + precision of the fraction .069E8 must be noted. This fraction has 5 + digits of precision. Next, the fraction portion in hex (069E8) is + converted to decimal (27112) and divided by 16 raised to the power of + the precision of the fraction (5). In other words 27112/(16**5) = + .02585 plus the whole number 8, gives 8.02585 in decimal. + +DATA RECORDS: + +Within the data file, base data and control/response messages are stored +using a variable record-length structure. The convention here is to begin +with byte 0 as the first byte. Included as the first record of each data +file is a volume scan title containing the following information: + +Bytes Format Description + +0-8 C*9 Filename (root) - "ARCHIVE2." + +9-11 C*3 Filename (extension) - "1", "2", etc. + +12-15 I*4 Modified Julian Date referenced from 1/1/70 + +16-19 I*4 Time - Milliseconds from midnight (UTC) of the day + when the file was created. + +20-23 Unused + +All remaining records in the data file are composed of data and +command/response messages which are initially stored in separate 2432 byte +packets within an RDA memory buffer. During the archive process the packets +are copied from memory and grouped together to form a record. Record lengths +are variable and are always sized in multiples of the 2432 byte packets. +During the reblocking process, physical records are set to 31616 bytes (2432 +x 13). + +The following example shows a portion of one packet which includes +Concurrent computer Channel Terminal Manager (CTM) information, a message +header, and a digital radar data message containing reflectivity only. + +0000 0000 0980 0000 0002 0000 04B8 0001 +0060 1E9E 04B0 1841 0001 0001 0480 14A2 +1E9E 1234 6530 0059 0001 0058 0001 0000 +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 +0064 0000 0000 0000 0015 0000 0000 0000 +0000 0064 0000 0000 0000 FFF4 0064 0000 +0000 0000 0000 0000 0000 0000 0000 0000 +0000 0000 0000 0000 0000 0000 0000 0000 +005A 5A00 0070 6D51 6455 6060 4F54 0040 +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + +Using the above example, each portion of the packet is described in detail. +Remember, this packet may be one of several contained in one record within +the data file. + + Bytes 0-11 (halfwords 1-6) Channel Terminal Manager (CTM) + information: +0000 0000 0980 0000 0002 0000 04B8 0001 +0060 1E9E 04B0 1841 0001 0001 0480 14A2 Archive II (the data tape) is a +1E9E 1234 6530 0059 0001 0058 0001 0000 copy of messages or data packets +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 prepared for transmission from the +0064 0000 0000 0000 0015 0000 0000 0000 RDA to the RPG. CTM information is +0000 0064 0000 0000 0000 FFF4 0064 0000 attached to a message or data +0000 0000 0000 0000 0000 0000 0000 0000 packet for checking data integrity +0000 0000 0000 0000 0000 0000 0000 0000 during the transmission process +005A 5A00 0070 6D51 6455 6060 4F54 0040 and is of no importance to the base +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 data (omit or read past these +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D bytes). +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + + Bytes 12-27 (halfwords 7-14) Message Header: + +0000 0000 0980 0000 0002 0000 04B8 0001 This information is +0060 1E9E 04B0 1841 0001 0001 0480 14A2 used to identify +1E9E 1234 6530 0059 0001 0058 0001 0000 either base data or one of thirteen +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 types of messages that may follow +0064 0000 0000 0000 0015 0000 0000 0000 in bytes 28 - 2431. This header +0000 0064 0000 0000 0000 FFF4 0064 0000 includes the information indicated +0000 0000 0000 0000 0000 0000 0000 0000 below: +0000 0000 0000 0000 0000 0000 0000 0000 +005A 5A00 0070 6D51 6455 6060 4F54 0040 +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + +Halfword Format Description + +7 I*2 Message size in halfwords measured from this + halfword to the end of the record. + +8 I*1 (Left Byte) Channel ID: + 0 = Non-Redundant Site + 1 = Redundant Site Channel 1 + 2 = Redundant Site Channel 2 + +8 I*1 (Right Byte) Message type, where: + 1 = DIGITAL RADAR DATA (This message + may contain a combination of either + reflectivity, aliased velocity, or + spectrum width) + 2 = RDA STATUS DATA. + 3 = PERFORMANCE/MAINTENANCE DATA. + 4 = CONSOLE MESSAGE - RDA TO RPG. + 5 = MAINTENANCE LOG DATA. + 6 = RDA CONTROL COMMANDS. + 7 = VOLUME COVERAGE PATTERN. + 8 = CLUTTER CENSOR ZONES. + 9 = REQUEST FOR DATA. + 10 = CONSOLE MESSAGE - RPG TO RDA. + 11 = LOOP BACK TEST - RDA TO RPG. + 12 = LOOP BACK TEST - RPG TO RDA. + 13 = CLUTTER FILTER BYPASS MAP - RDA to RPG. + 14 = EDITED CLUTTER FILTER BYPASS MAP - RPG to RDA. + +9 I*2 I.D. Sequence = 0 to 7FFF, then roll over back to 0. + +10 I*2 Modified Julian date starting from 1/1/70. + +11-12 I*4 Generation time of messages in milliseconds of day past + midnight (UTC). This time may be different than time + listed in halfwords 15-16 defined below. + +13 I*2 Number of message segments. Messages larger than message + size (halfword 7 defined above) are segmented and + recorded in separate data packets. + +14 I*2 Message segment number. + + Bytes 28-127 (halfwords 15-64) Digital Radar Data Header: + +0000 0000 0980 0000 0002 0000 04B8 0001 This information describes the +0060 1E9E 04B0 1841 0001 0001 0480 14A2 date, time, azimuth, +1E9E 1234 6530 0059 0001 0058 0001 0000 elevation, and type +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 of base data included +0064 0000 0000 0000 0015 0000 0000 0000 in the radial. This +0000 0064 0000 0000 0000 FFF4 0064 0000 header includes the +0000 0000 0000 0000 0000 0000 0000 0000 following +0000 0000 0000 0000 0000 0000 0000 0000 information: +005A 5A00 0070 6D51 6455 6060 4F54 0040 +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D +3C45 3A43 433C 3E43 413C 393F 3F40 4038 + (etc.) + +Halfword Format Description + +15-16 I*4 Collection time for this radial in + milliseconds of the day from midnight (UTC). + +17 I*2 Modified Julian date referenced from 1/1/70. + +18 I*2 Unambiguous range (scaled: Value/10. = KM). + +19 I*2 Azimuth angle (coded: [Value/8.]*[180./4096.] = DEG). + An azimuth of "0 degrees" points to true north while "90 + degrees" points east. Rotation is always clockwise as + viewed from above the radar. + +20 I*2 Radial number within the elevation scan. + +21 I*2 Radial status where: + 0 = START OF NEW ELEVATION. + 1 = INTERMEDIATE RADIAL. + 2 = END OF ELEVATION. + 3 = BEGINNING OF VOLUME SCAN. + 4 = END OF VOLUME SCAN. + +22 I*2 Elevation angle (coded:[Value/8.]*[180./4096.] = DEG). + An elevation of "0 degree" is parallel to the pedestal + base while "90 degrees" is perpendicular to the pedestal + base. + +23 I*2 RDA elevation number within the volume scan. + +24 I*2 Range to first gate of reflectivity data (METERS). + Range may be negative to account for system delays + in transmitter and/or receiver components. + +25 I*2 Range to first gate of Doppler data. + Doppler data - velocity and spectrum width (METERS). + Range may be negative to account for system delays in + transmitter and/or receiver components. + +26 I*2 Reflectivity data gate size (METERS). + +27 I*2 Doppler data gate size (METERS). + +28 I*2 Number of reflectivity gates. + +29 I*2 Number of velocity and/or spectrum width data gates. + +30 I*2 Sector number within cut. + +31-32 R*4 System gain calibration constant (dB biased). + +33 I*2 Reflectivity data pointer (byte # from the start of + digital radar data message header). This pointer + locates the beginning of reflectivity data. + +34 I*2 Velocity data pointer (byte # from the start of digital + radar data message header). This pointer locates + beginning of velocity data. + +35 I*2 Spectrum-width pointer (byte # from the start of + digital radar data message header). This pointer + locates beginning of spectrum-width data. + +36 I*2 Doppler velocity resolution. + Value of: 2 = 0.5 m/s + 4 = 1.0 + +37 I*2 Volume coverage pattern. + Value of: 11 = 16 elev. scans/ 5 mins. + 21 = 11 elev. scans/ 6 mins. + 31 = 8 elev. scans/ 10 mins. + 32 = 7 elev. scans/ 10 mins. + +38-41 Unused. Reserved for V&V Simulator. + +42 I*2 Reflectivity data pointer for Archive II playback. + Archive II playback pointer used exclusively by RDA. + +43 I*2 Velocity data pointer for Archive II playback. + Archive II playback pointer used exclusively by RDA. + +44 I*2 Spectrum-width data pointer for Archive II playback. + Archive II playback pointer used exclusively by RDA. + +45 I*2 Nyquist velocity (scaled: Value/100. = M/S). + +46 I*2 Atmospheric attenuation factor (scaled: + [Value/1000. = dB/KM]). + +47 I*2 Threshold parameter for minimum difference in echo + power between two resolution volumes for them not + to be labeled range ambiguous (i.e.,overlaid) + [Value/10. = Watts]. + +48-64 Unused. + + Bytes 128-2431 (halfwords 65-1216) Base Data: + +0000 0000 0980 0000 0002 0000 04B8 0001 This information includes the three +0060 1E9E 04B0 1841 0001 0001 0480 14A2 base data moments; reflectivity, +1E9E 1234 6530 0059 0001 0058 0001 0000 velocity and spectrum width. +FE89 03E8 00FA 01CC 0000 0001 4180 69E8 Depending on the collection method, +0064 0000 0000 0000 0015 0000 0000 0000 up to three base data moments may +0000 0064 0000 0000 0000 FFF4 0064 0000 exist in this section of the +0000 0000 0000 0000 0000 0000 0000 0000 packet. (For this example, only +0000 0000 0000 0000 0000 0000 0000 0000 reflectivity is present.) Base data +005A 5A00 0070 6D51 6455 6060 4F54 0040 is coded and placed +5C3F 4049 4900 4D42 4349 434E 4B3D 4430 in a single byte and +4340 3F3D 4644 4443 3A3D 473F 3A3A 3D3D is archived in the +3C45 3A43 433C 3E43 413C 393F 3F40 4038 following format: + (etc.) + +Halfword Format Description + +65-294 BYTE Reflectivity data (0 - 460 gates) (coded: + [((Value-2)/2.)-32. = dBZ], for Value of 0 or + 1 see note below). + +65-754 BYTE Doppler velocity data (coded: for doppler velocity + resolution of 0.5 M/S, [((Value-2)/2.)-63.5 = M/S]; + for doppler resolution of 1.0 M/S, [(Value-2)-127.] + = M/S], for Value of 0 or 1 see note below), (0 - 92 + gates). Starting data location depends on length of + the reflectivity field, stop location depends on length + of the velocity field. Velocity data is range unambiguous + out to 230 KM. + +65-1214 BYTE Doppler spectrum width (coded: [((Value - 2)/2.)-63.5 + = M/S], for Value of 0 or 1 see note below), (0 - 920 + gates). Starting data location depends on length of + the reflectivity and velocity fields, stop location + depends on length of the spectrum width field. Spectrum + width is range unambiguous out to 230 KM. + + Four bytes of trailer characters referred to the Frame + Check Sequence (FCS) follow the data. In cases where + the three moments are not all present or the number of + gates for each moment have been reduced, the record is + padded out to a constant size of 1216 halfwords (2432 + bytes) following the trailer characters. + +Note: + +Any base data value of 0 is data below Signal to Noise Ratio(SNR) thresholds +set for that specific base data. Any base data value of 1 is data considered +range ambiguous (i.e., overlaid). +---------------------------------------------------------------------------- +[Image] NEXRAD DOCUMENTATION + +---------------------------------------------------------------------------- +http://www.ncdc.noaa.gov/pub/data/nexrad/tapeii.html +Created by Dick Cram (dcram@ncdc.noaa.gov) +Last updated 18 April 96 diff --git a/wxdata/include/scwx/wsr88d/ar2v_file.hpp b/wxdata/include/scwx/wsr88d/ar2v_file.hpp index 90702e8e..1f3ab0cc 100644 --- a/wxdata/include/scwx/wsr88d/ar2v_file.hpp +++ b/wxdata/include/scwx/wsr88d/ar2v_file.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -26,21 +26,24 @@ public: explicit Ar2vFile(); ~Ar2vFile(); - Ar2vFile(const Ar2vFile&) = delete; + Ar2vFile(const Ar2vFile&) = delete; Ar2vFile& operator=(const Ar2vFile&) = delete; Ar2vFile(Ar2vFile&&) noexcept; Ar2vFile& operator=(Ar2vFile&&) noexcept; - uint32_t julian_date() const; - uint32_t milliseconds() const; - std::string icao() const; + std::uint32_t julian_date() const; + std::uint32_t milliseconds() const; + std::string icao() const; + + std::size_t message_count() const; std::chrono::system_clock::time_point start_time() const; std::chrono::system_clock::time_point end_time() const; - std::map> radar_data() const; - std::shared_ptr vcp_data() const; + std::map> + radar_data() const; + std::shared_ptr vcp_data() const; std::tuple, float, std::vector> GetElevationScan(rda::DataBlockType dataBlockType, diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp index 17a56b57..01c2cd41 100644 --- a/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data.hpp @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include namespace scwx { @@ -10,198 +9,52 @@ namespace wsr88d namespace rda { -enum class DataBlockType -{ - Volume, - Elevation, - Radial, - MomentRef, - MomentVel, - MomentSw, - MomentZdr, - MomentPhi, - MomentRho, - MomentCfp, - Unknown -}; -typedef util:: - Iterator - MomentDataBlockTypeIterator; - -class DataBlockImpl; -class ElevationDataBlockImpl; -class MomentDataBlockImpl; -class RadialDataBlockImpl; -class VolumeDataBlockImpl; - -class DigitalRadarData; -class DigitalRadarDataImpl; - -typedef std::map> ElevationScan; - -class DataBlock -{ -protected: - explicit DataBlock(const std::string& dataBlockType, - const std::string& dataName); - ~DataBlock(); - - DataBlock(const DataBlock&) = delete; - DataBlock& operator=(const DataBlock&) = delete; - - DataBlock(DataBlock&&) noexcept; - DataBlock& operator=(DataBlock&&) noexcept; - -private: - std::unique_ptr p; -}; - -class ElevationDataBlock : public DataBlock -{ -public: - explicit ElevationDataBlock(const std::string& dataBlockType, - const std::string& dataName); - ~ElevationDataBlock(); - - ElevationDataBlock(const ElevationDataBlock&) = delete; - ElevationDataBlock& operator=(const ElevationDataBlock&) = delete; - - ElevationDataBlock(ElevationDataBlock&&) noexcept; - ElevationDataBlock& operator=(ElevationDataBlock&&) noexcept; - - static std::shared_ptr - Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is); - -private: - std::unique_ptr p; - - bool Parse(std::istream& is); -}; - -class MomentDataBlock : public DataBlock -{ -public: - explicit MomentDataBlock(const std::string& dataBlockType, - const std::string& dataName); - ~MomentDataBlock(); - - MomentDataBlock(const MomentDataBlock&) = delete; - MomentDataBlock& operator=(const MomentDataBlock&) = delete; - - MomentDataBlock(MomentDataBlock&&) noexcept; - MomentDataBlock& operator=(MomentDataBlock&&) noexcept; - - uint16_t number_of_data_moment_gates() const; - float data_moment_range() const; - uint16_t data_moment_range_raw() const; - float data_moment_range_sample_interval() const; - uint16_t data_moment_range_sample_interval_raw() const; - float snr_threshold() const; - int16_t snr_threshold_raw() const; - uint8_t data_word_size() const; - float scale() const; - float offset() const; - const void* data_moments() const; - - static std::shared_ptr - Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is); - -private: - std::unique_ptr p; - - bool Parse(std::istream& is); -}; - -class RadialDataBlock : public DataBlock -{ -public: - explicit RadialDataBlock(const std::string& dataBlockType, - const std::string& dataName); - ~RadialDataBlock(); - - RadialDataBlock(const RadialDataBlock&) = delete; - RadialDataBlock& operator=(const RadialDataBlock&) = delete; - - RadialDataBlock(RadialDataBlock&&) noexcept; - RadialDataBlock& operator=(RadialDataBlock&&) noexcept; - - float unambiguous_range() const; - - static std::shared_ptr - Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is); - -private: - std::unique_ptr p; - - bool Parse(std::istream& is); -}; - -class VolumeDataBlock : public DataBlock -{ -public: - explicit VolumeDataBlock(const std::string& dataBlockType, - const std::string& dataName); - ~VolumeDataBlock(); - - VolumeDataBlock(const VolumeDataBlock&) = delete; - VolumeDataBlock& operator=(const VolumeDataBlock&) = delete; - - VolumeDataBlock(VolumeDataBlock&&) noexcept; - VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept; - - float latitude() const; - float longitude() const; - uint16_t volume_coverage_pattern_number() const; - - static std::shared_ptr - Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is); - -private: - std::unique_ptr p; - - bool Parse(std::istream& is); -}; - -class DigitalRadarData : public Level2Message +class DigitalRadarData : public GenericRadarData { public: explicit DigitalRadarData(); ~DigitalRadarData(); - DigitalRadarData(const DigitalRadarData&) = delete; + DigitalRadarData(const DigitalRadarData&) = delete; DigitalRadarData& operator=(const DigitalRadarData&) = delete; DigitalRadarData(DigitalRadarData&&) noexcept; DigitalRadarData& operator=(DigitalRadarData&&) noexcept; - std::string radar_identifier() const; - uint32_t collection_time() const; - uint16_t modified_julian_date() const; - uint16_t azimuth_number() const; - float azimuth_angle() const; - uint8_t compression_indicator() const; - uint16_t radial_length() const; - uint8_t azimuth_resolution_spacing() const; - uint8_t radial_status() const; - uint8_t elevation_number() const; - uint8_t cut_sector_number() const; - float elevation_angle() const; - uint8_t radial_spot_blanking_status() const; - uint8_t azimuth_indexing_mode() const; - uint16_t data_block_count() const; + std::uint32_t collection_time() const; + std::uint16_t modified_julian_date() const; + std::uint16_t unambiguous_range() const; + std::uint16_t azimuth_angle_raw() const; + units::degrees azimuth_angle() const; + std::uint16_t azimuth_number() const; + std::uint16_t radial_status() const; + std::uint16_t elevation_angle_raw() const; + units::degrees elevation_angle() const; + std::uint16_t elevation_number() const; + std::int16_t surveillance_range_raw() const; + units::kilometers surveillance_range() const; + std::int16_t doppler_range_raw() const; + units::kilometers doppler_range() const; + std::uint16_t surveillance_range_sample_interval_raw() const; + units::kilometers surveillance_range_sample_interval() const; + std::uint16_t doppler_range_sample_interval_raw() const; + units::kilometers doppler_range_sample_interval() const; + std::uint16_t number_of_surveillance_bins() const; + std::uint16_t number_of_doppler_bins() const; + std::uint16_t cut_sector_number() const; + float calibration_constant() const; + std::uint16_t surveillance_pointer() const; + std::uint16_t velocity_pointer() const; + std::uint16_t spectral_width_pointer() const; + std::uint16_t doppler_velocity_resolution() const; + std::uint16_t volume_coverage_pattern_number() const; + std::uint16_t nyquist_velocity() const; + std::uint16_t atmos() const; + std::uint16_t tover() const; + std::uint16_t radial_spot_blanking_status() const; - std::shared_ptr elevation_data_block() const; - std::shared_ptr radial_data_block() const; - std::shared_ptr volume_data_block() const; - std::shared_ptr moment_data_block(DataBlockType type) const; + std::shared_ptr + moment_data_block(DataBlockType type) const; bool Parse(std::istream& is); @@ -209,7 +62,8 @@ public: std::istream& is); private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; } // namespace rda diff --git a/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp new file mode 100644 index 00000000..ba911898 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/digital_radar_data_generic.hpp @@ -0,0 +1,203 @@ +#pragma once + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +class DigitalRadarDataGeneric : public GenericRadarData +{ +public: + class DataBlock; + class ElevationDataBlock; + class MomentDataBlock; + class RadialDataBlock; + class VolumeDataBlock; + + explicit DigitalRadarDataGeneric(); + ~DigitalRadarDataGeneric(); + + DigitalRadarDataGeneric(const DigitalRadarDataGeneric&) = delete; + DigitalRadarDataGeneric& operator=(const DigitalRadarDataGeneric&) = delete; + + DigitalRadarDataGeneric(DigitalRadarDataGeneric&&) noexcept; + DigitalRadarDataGeneric& operator=(DigitalRadarDataGeneric&&) noexcept; + + std::string radar_identifier() const; + std::uint32_t collection_time() const; + std::uint16_t modified_julian_date() const; + std::uint16_t azimuth_number() const; + units::degrees azimuth_angle() const; + std::uint8_t compression_indicator() const; + std::uint16_t radial_length() const; + std::uint8_t azimuth_resolution_spacing() const; + std::uint8_t radial_status() const; + std::uint16_t elevation_number() const; + std::uint8_t cut_sector_number() const; + units::degrees elevation_angle() const; + std::uint8_t radial_spot_blanking_status() const; + std::uint8_t azimuth_indexing_mode() const; + std::uint16_t data_block_count() const; + std::uint16_t volume_coverage_pattern_number() const; + + std::shared_ptr elevation_data_block() const; + std::shared_ptr radial_data_block() const; + std::shared_ptr volume_data_block() const; + std::shared_ptr + moment_data_block(DataBlockType type) const; + + bool Parse(std::istream& is); + + static std::shared_ptr + Create(Level2MessageHeader&& header, std::istream& is); + +private: + class Impl; + std::unique_ptr p; +}; + +class DigitalRadarDataGeneric::DataBlock +{ +protected: + explicit DataBlock(const std::string& dataBlockType, + const std::string& dataName); + virtual ~DataBlock(); + + DataBlock(const DataBlock&) = delete; + DataBlock& operator=(const DataBlock&) = delete; + + DataBlock(DataBlock&&) noexcept; + DataBlock& operator=(DataBlock&&) noexcept; + +private: + class Impl; + std::unique_ptr p; +}; + +class DigitalRadarDataGeneric::ElevationDataBlock : public DataBlock +{ +public: + explicit ElevationDataBlock(const std::string& dataBlockType, + const std::string& dataName); + ~ElevationDataBlock(); + + ElevationDataBlock(const ElevationDataBlock&) = delete; + ElevationDataBlock& operator=(const ElevationDataBlock&) = delete; + + ElevationDataBlock(ElevationDataBlock&&) noexcept; + ElevationDataBlock& operator=(ElevationDataBlock&&) noexcept; + + static std::shared_ptr + Create(const std::string& dataBlockType, + const std::string& dataName, + std::istream& is); + +private: + class Impl; + std::unique_ptr p; + + bool Parse(std::istream& is); +}; + +class DigitalRadarDataGeneric::MomentDataBlock : + public DataBlock, + public GenericRadarData::MomentDataBlock +{ +public: + explicit MomentDataBlock(const std::string& dataBlockType, + const std::string& dataName); + ~MomentDataBlock(); + + MomentDataBlock(const MomentDataBlock&) = delete; + MomentDataBlock& operator=(const MomentDataBlock&) = delete; + + MomentDataBlock(MomentDataBlock&&) noexcept; + MomentDataBlock& operator=(MomentDataBlock&&) noexcept; + + std::uint16_t number_of_data_moment_gates() const; + units::kilometers data_moment_range() const; + std::int16_t data_moment_range_raw() const; + units::kilometers data_moment_range_sample_interval() const; + std::uint16_t data_moment_range_sample_interval_raw() const; + float snr_threshold() const; + std::int16_t snr_threshold_raw() const; + std::uint8_t data_word_size() const; + float scale() const; + float offset() const; + const void* data_moments() const; + + static std::shared_ptr + Create(const std::string& dataBlockType, + const std::string& dataName, + std::istream& is); + +private: + class Impl; + std::unique_ptr p; + + bool Parse(std::istream& is); +}; + +class DigitalRadarDataGeneric::RadialDataBlock : public DataBlock +{ +public: + explicit RadialDataBlock(const std::string& dataBlockType, + const std::string& dataName); + ~RadialDataBlock(); + + RadialDataBlock(const RadialDataBlock&) = delete; + RadialDataBlock& operator=(const RadialDataBlock&) = delete; + + RadialDataBlock(RadialDataBlock&&) noexcept; + RadialDataBlock& operator=(RadialDataBlock&&) noexcept; + + float unambiguous_range() const; + + static std::shared_ptr + Create(const std::string& dataBlockType, + const std::string& dataName, + std::istream& is); + +private: + class Impl; + std::unique_ptr p; + + bool Parse(std::istream& is); +}; + +class DigitalRadarDataGeneric::VolumeDataBlock : public DataBlock +{ +public: + explicit VolumeDataBlock(const std::string& dataBlockType, + const std::string& dataName); + ~VolumeDataBlock(); + + VolumeDataBlock(const VolumeDataBlock&) = delete; + VolumeDataBlock& operator=(const VolumeDataBlock&) = delete; + + VolumeDataBlock(VolumeDataBlock&&) noexcept; + VolumeDataBlock& operator=(VolumeDataBlock&&) noexcept; + + float latitude() const; + float longitude() const; + std::uint16_t volume_coverage_pattern_number() const; + + static std::shared_ptr + Create(const std::string& dataBlockType, + const std::string& dataName, + std::istream& is); + +private: + class Impl; + std::unique_ptr p; + + bool Parse(std::istream& is); +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp new file mode 100644 index 00000000..2b4b49e3 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/generic_radar_data.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +enum class DataBlockType +{ + Volume, + Elevation, + Radial, + MomentRef, + MomentVel, + MomentSw, + MomentZdr, + MomentPhi, + MomentRho, + MomentCfp, + Unknown +}; +typedef util:: + Iterator + MomentDataBlockTypeIterator; + +class GenericRadarData; + +typedef std::map> + ElevationScan; + +class GenericRadarData : public Level2Message +{ +public: + class MomentDataBlock; + + explicit GenericRadarData(); + virtual ~GenericRadarData(); + + GenericRadarData(const GenericRadarData&) = delete; + GenericRadarData& operator=(const GenericRadarData&) = delete; + + GenericRadarData(GenericRadarData&&) noexcept; + GenericRadarData& operator=(GenericRadarData&&) noexcept; + + virtual std::uint32_t collection_time() const = 0; + virtual std::uint16_t modified_julian_date() const = 0; + virtual units::degrees azimuth_angle() const = 0; + virtual std::uint16_t azimuth_number() const = 0; + virtual std::uint16_t elevation_number() const = 0; + virtual std::uint16_t volume_coverage_pattern_number() const = 0; + + virtual std::shared_ptr + moment_data_block(DataBlockType type) const = 0; + +private: + class Impl; + std::unique_ptr p; +}; + +class GenericRadarData::MomentDataBlock +{ +public: + explicit MomentDataBlock(); + virtual ~MomentDataBlock(); + + MomentDataBlock(const MomentDataBlock&) = delete; + MomentDataBlock& operator=(const MomentDataBlock&) = delete; + + MomentDataBlock(MomentDataBlock&&) noexcept; + MomentDataBlock& operator=(MomentDataBlock&&) noexcept; + + virtual std::uint16_t number_of_data_moment_gates() const = 0; + virtual units::kilometers data_moment_range() const = 0; + virtual std::int16_t data_moment_range_raw() const = 0; + virtual units::kilometers + data_moment_range_sample_interval() const = 0; + virtual std::uint16_t data_moment_range_sample_interval_raw() const = 0; + virtual std::int16_t snr_threshold_raw() const = 0; + virtual std::uint8_t data_word_size() const = 0; + virtual float scale() const = 0; + virtual float offset() const = 0; + virtual const void* data_moments() const = 0; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp b/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp index a6d87c54..7359e72b 100644 --- a/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp +++ b/wxdata/include/scwx/wsr88d/rda/level2_message_factory.hpp @@ -27,18 +27,18 @@ private: explicit Level2MessageFactory() = delete; ~Level2MessageFactory() = delete; - Level2MessageFactory(const Level2MessageFactory&) = delete; + Level2MessageFactory(const Level2MessageFactory&) = delete; Level2MessageFactory& operator=(const Level2MessageFactory&) = delete; - Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; + Level2MessageFactory(Level2MessageFactory&&) noexcept = delete; Level2MessageFactory& operator=(Level2MessageFactory&&) noexcept = delete; public: struct Context; static std::shared_ptr CreateContext(); - static Level2MessageInfo Create(std::istream& is, - std::shared_ptr ctx); + static Level2MessageInfo Create(std::istream& is, + std::shared_ptr& ctx); }; } // namespace rda diff --git a/wxdata/include/scwx/wsr88d/rda/types.hpp b/wxdata/include/scwx/wsr88d/rda/rda_types.hpp similarity index 74% rename from wxdata/include/scwx/wsr88d/rda/types.hpp rename to wxdata/include/scwx/wsr88d/rda/rda_types.hpp index 617054a2..032cab77 100644 --- a/wxdata/include/scwx/wsr88d/rda/types.hpp +++ b/wxdata/include/scwx/wsr88d/rda/rda_types.hpp @@ -7,14 +7,15 @@ namespace wsr88d namespace rda { -enum class MessageId : uint8_t +enum class MessageId : std::uint8_t { + DigitalRadarData = 1, RdaStatusData = 2, PerformanceMaintenanceData = 3, VolumeCoveragePatternData = 5, ClutterFilterMap = 15, RdaAdaptationData = 18, - DigitalRadarData = 31 + DigitalRadarDataGeneric = 31 }; } // namespace rda diff --git a/wxdata/source/scwx/common/vcp.cpp b/wxdata/source/scwx/common/vcp.cpp index 9708cfd0..347d09a7 100644 --- a/wxdata/source/scwx/common/vcp.cpp +++ b/wxdata/source/scwx/common/vcp.cpp @@ -17,16 +17,23 @@ std::string GetVcpDescription(uint16_t vcp) case 31: case 32: case 35: - case 90: return CLEAR_AIR_MODE; + case 90: + return CLEAR_AIR_MODE; + case 11: case 12: + case 21: case 80: case 112: case 121: + case 211: case 212: - case 215: return PRECIPITATION_MODE; + case 215: + case 221: + return PRECIPITATION_MODE; - default: return "?"; + default: + return "?"; } } diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index c2d0128e..04b16731 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -246,7 +246,8 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) { std::string key = object.GetKey(); - if (!key.ends_with("_MDM")) + if (key.find("NWS_NEXRAD_") == std::string::npos && + !key.ends_with("_MDM")) { auto time = GetTimePointByKey(key); diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index b1790d6f..6d951d6c 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -1,6 +1,7 @@ #include +#include #include -#include +#include #include #include #include @@ -18,6 +19,7 @@ # pragma GCC diagnostic ignored "-Wdeprecated-copy" #endif +#include #include #include #include @@ -41,16 +43,7 @@ static const auto logger_ = util::Logger::Create(logPrefix_); class Ar2vFileImpl { public: - explicit Ar2vFileImpl() : - tapeFilename_ {}, - extensionNumber_ {}, - julianDate_ {0}, - milliseconds_ {0}, - icao_ {}, - vcpData_ {nullptr}, - radarData_ {}, - index_ {}, - rawRecords_ {} {}; + explicit Ar2vFileImpl() {}; ~Ar2vFileImpl() = default; std::size_t DecompressLDMRecords(std::istream& is); @@ -58,22 +51,24 @@ public: void IndexFile(); void ParseLDMRecords(); void ParseLDMRecord(std::istream& is); - void ProcessRadarData(std::shared_ptr message); + void ProcessRadarData(const std::shared_ptr& message); - std::string tapeFilename_; - std::string extensionNumber_; - std::uint32_t julianDate_; - std::uint32_t milliseconds_; - std::string icao_; + std::string tapeFilename_ {}; + std::string extensionNumber_ {}; + std::uint32_t julianDate_ {0}; + std::uint32_t milliseconds_ {0}; + std::string icao_ {}; - std::shared_ptr vcpData_; - std::map> radarData_; + std::size_t messageCount_ {0}; + + std::shared_ptr vcpData_ {}; + std::map> radarData_ {}; std::map>> - index_; + index_ {}; - std::list rawRecords_; + std::list rawRecords_ {}; }; Ar2vFile::Ar2vFile() : p(std::make_unique()) {} @@ -82,12 +77,12 @@ Ar2vFile::~Ar2vFile() = default; Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default; Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) noexcept = default; -uint32_t Ar2vFile::julian_date() const +std::uint32_t Ar2vFile::julian_date() const { return p->julianDate_; } -uint32_t Ar2vFile::milliseconds() const +std::uint32_t Ar2vFile::milliseconds() const { return p->milliseconds_; } @@ -97,6 +92,11 @@ std::string Ar2vFile::icao() const return p->icao_; } +std::size_t Ar2vFile::message_count() const +{ + return p->messageCount_; +} + std::chrono::system_clock::time_point Ar2vFile::start_time() const { return util::TimePoint(p->julianDate_, p->milliseconds_); @@ -108,7 +108,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const if (p->radarData_.size() > 0) { - std::shared_ptr lastRadial = + std::shared_ptr lastRadial = p->radarData_.crbegin()->second->crbegin()->second; endTime = util::TimePoint(lastRadial->modified_julian_date(), @@ -118,7 +118,7 @@ std::chrono::system_clock::time_point Ar2vFile::end_time() const return endTime; } -std::map> +std::map> Ar2vFile::radar_data() const { return p->radarData_; @@ -142,17 +142,17 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, float elevationCut = 0.0f; std::vector elevationCuts; - uint16_t codedElevation = - static_cast(std::lroundf(elevation * scaleFactor)); + std::uint16_t codedElevation = + static_cast(std::lroundf(elevation * scaleFactor)); if (p->index_.contains(dataBlockType)) { - auto scans = p->index_.at(dataBlockType); + auto& scans = p->index_.at(dataBlockType); - uint16_t lowerBound = scans.cbegin()->first; - uint16_t upperBound = scans.crbegin()->first; + std::uint16_t lowerBound = scans.cbegin()->first; + std::uint16_t upperBound = scans.crbegin()->first; - for (auto scan : scans) + for (auto& scan : scans) { if (scan.first > lowerBound && scan.first <= codedElevation) { @@ -166,10 +166,12 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType, elevationCuts.push_back(scan.first / scaleFactor); } - int32_t lowerDelta = std::abs(static_cast(codedElevation) - - static_cast(lowerBound)); - int32_t upperDelta = std::abs(static_cast(codedElevation) - - static_cast(upperBound)); + std::int32_t lowerDelta = + std::abs(static_cast(codedElevation) - + static_cast(lowerBound)); + std::int32_t upperDelta = + std::abs(static_cast(codedElevation) - + static_cast(upperBound)); if (lowerDelta < upperDelta) { @@ -232,6 +234,10 @@ bool Ar2vFile::LoadData(std::istream& is) dataValid = false; } + // Trim spaces and null characters from the end of the ICAO + boost::trim_right_if(p->icao_, + [](char x) { return std::isspace(x) || x == '\0'; }); + if (dataValid) { logger_->debug("Filename: {}", p->tapeFilename_); @@ -256,17 +262,17 @@ bool Ar2vFile::LoadData(std::istream& is) return dataValid; } -size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) +std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is) { logger_->debug("Decompressing LDM Records"); - size_t numRecords = 0; + std::size_t numRecords = 0; while (is.peek() != EOF) { std::streampos startPosition = is.tellg(); - int32_t controlWord = 0; - size_t recordSize; + std::int32_t controlWord = 0; + std::size_t recordSize; is.read(reinterpret_cast(&controlWord), 4); @@ -315,7 +321,7 @@ void Ar2vFileImpl::ParseLDMRecords() { logger_->debug("Parsing LDM Records"); - size_t count = 0; + std::size_t count = 0; for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++) { @@ -331,65 +337,82 @@ void Ar2vFileImpl::ParseLDMRecords() void Ar2vFileImpl::ParseLDMRecord(std::istream& is) { + static constexpr std::size_t kDefaultSegmentSize = 2432; + static constexpr std::size_t kCtmHeaderSize = 12; + auto ctx = rda::Level2MessageFactory::CreateContext(); - // The communications manager inserts an extra 12 bytes at the beginning - // of each record - is.seekg(12, std::ios_base::cur); - - while (!is.eof()) + while (!is.eof() && !is.fail()) { - off_t offset = 0; - uint16_t nextSize = 0u; - do + // The communications manager inserts an extra 12 bytes at the beginning + // of each record + is.seekg(kCtmHeaderSize, std::ios_base::cur); + + // Each message requires 2432 bytes of storage, with the exception of + // Message Types 29 and 31. + std::size_t messageSize = kDefaultSegmentSize - kCtmHeaderSize; + + // Mark current position + std::streampos messageStart = is.tellg(); + + // Parse the header + rda::Level2MessageHeader messageHeader; + bool headerValid = messageHeader.Parse(is); + is.seekg(messageStart, std::ios_base::beg); + + if (headerValid) { - is.read(reinterpret_cast(&nextSize), 2); - if (nextSize == 0) + std::uint8_t messageType = messageHeader.message_type(); + + // Each message requires 2432 bytes of storage, with the exception of + // Message Types 29 and 31. + if (messageType == 29 || messageType == 31) { - offset += 2; + if (messageHeader.message_size() == 65535) + { + messageSize = (static_cast( + messageHeader.number_of_message_segments()) + << 16) + + messageHeader.message_segment_number(); + } + else + { + messageSize = + static_cast(messageHeader.message_size()) * 2; + } } - else + + // Parse the current message + rda::Level2MessageInfo msgInfo = + rda::Level2MessageFactory::Create(is, ctx); + + if (msgInfo.messageValid) { - is.seekg(-2, std::ios_base::cur); + HandleMessage(msgInfo.message); } - } while (!is.eof() && nextSize == 0u); - - if (!is.eof() && offset != 0) - { - logger_->trace("Next record offset by {} bytes", offset); - } - else if (is.eof()) - { - break; } - rda::Level2MessageInfo msgInfo = - rda::Level2MessageFactory::Create(is, ctx); - if (!msgInfo.headerValid) - { - // Invalid message - break; - } - - if (msgInfo.messageValid) - { - HandleMessage(msgInfo.message); - } + // Skip to next message + is.seekg(messageStart + static_cast(messageSize), + std::ios_base::beg); } } void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) { + ++messageCount_; + switch (message->header().message_type()) { - case static_cast(rda::MessageId::VolumeCoveragePatternData): + case static_cast(rda::MessageId::VolumeCoveragePatternData): vcpData_ = std::static_pointer_cast(message); break; - case static_cast(rda::MessageId::DigitalRadarData): + case static_cast(rda::MessageId::DigitalRadarData): + case static_cast(rda::MessageId::DigitalRadarDataGeneric): ProcessRadarData( - std::static_pointer_cast(message)); + std::static_pointer_cast(message)); break; default: @@ -398,10 +421,10 @@ void Ar2vFileImpl::HandleMessage(std::shared_ptr& message) } void Ar2vFileImpl::ProcessRadarData( - std::shared_ptr message) + const std::shared_ptr& message) { - uint16_t azimuthIndex = message->azimuth_number() - 1; - uint16_t elevationIndex = message->elevation_number() - 1; + std::uint16_t azimuthIndex = message->azimuth_number() - 1; + std::uint16_t elevationIndex = message->elevation_number() - 1; if (radarData_[elevationIndex] == nullptr) { @@ -415,20 +438,12 @@ void Ar2vFileImpl::IndexFile() { logger_->debug("Indexing file"); - if (vcpData_ == nullptr) + for (auto& elevationCut : radarData_) { - logger_->warn("Cannot index file without VCP data"); - return; - } + std::uint16_t elevationAngle {}; + rda::WaveformType waveformType = rda::WaveformType::Unknown; - for (auto elevationCut : radarData_) - { - uint16_t elevationAngle = - vcpData_->elevation_angle_raw(elevationCut.first); - rda::WaveformType waveformType = - vcpData_->waveform_type(elevationCut.first); - - std::shared_ptr radial0 = + std::shared_ptr& radial0 = (*elevationCut.second)[0]; if (radial0 == nullptr) @@ -437,6 +452,26 @@ void Ar2vFileImpl::IndexFile() continue; } + std::shared_ptr digitalRadarData0 = nullptr; + + if (vcpData_ != nullptr) + { + elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first); + waveformType = vcpData_->waveform_type(elevationCut.first); + } + else if ((digitalRadarData0 = + std::dynamic_pointer_cast( + (*elevationCut.second)[0])) != nullptr) + { + elevationAngle = digitalRadarData0->elevation_angle_raw(); + } + else + { + // Return here, because we should only have a single message type + logger_->warn("Cannot index file without VCP data"); + return; + } + for (rda::DataBlockType dataBlockType : rda::MomentDataBlockTypeIterator()) { diff --git a/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp b/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp index 8ef0c49d..98d83ff2 100644 --- a/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp +++ b/wxdata/source/scwx/wsr88d/nexrad_file_factory.cpp @@ -70,9 +70,9 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) std::string buffer; bool dataValid; - buffer.resize(4); + buffer.resize(8); - is.read(buffer.data(), 4); + is.read(buffer.data(), 8); dataValid = is.good(); is.seekg(pisBegin, std::ios_base::beg); @@ -89,7 +89,7 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) pis = &ss; pisBegin = ss.tellg(); - ss.read(buffer.data(), 4); + ss.read(buffer.data(), 8); dataValid = ss.good(); ss.seekg(pisBegin, std::ios_base::beg); @@ -114,7 +114,7 @@ std::shared_ptr NexradFileFactory::Create(std::istream& is) if (dataValid) { - if (buffer.starts_with("AR2V")) + if (buffer.starts_with("AR2V") || buffer.starts_with("ARCHIVE2")) { message = std::make_shared(); } diff --git a/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp b/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp index c0e8a7f9..5f95d3a5 100644 --- a/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp +++ b/wxdata/source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp @@ -78,19 +78,22 @@ bool ClutterFilterBypassMap::Parse(std::istream& is) if (p->mapGenerationDate_ < 1) { - logger_->warn("Invalid date: {}", p->mapGenerationDate_); + logger_->trace("Ignoring empty message"); messageValid = false; } - if (p->mapGenerationTime_ > 1440) + else { - logger_->warn("Invalid time: {}", p->mapGenerationTime_); - messageValid = false; - } - if (numElevationSegments < 1 || numElevationSegments > 5) - { - logger_->warn("Invalid number of elevation segments: {}", - numElevationSegments); - messageValid = false; + if (p->mapGenerationTime_ > 1440) + { + logger_->warn("Invalid time: {}", p->mapGenerationTime_); + messageValid = false; + } + if (numElevationSegments < 1 || numElevationSegments > 5) + { + logger_->warn("Invalid number of elevation segments: {}", + numElevationSegments); + messageValid = false; + } } if (!messageValid) diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp index b31955a7..f98dafcd 100644 --- a/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data.cpp @@ -11,540 +11,99 @@ namespace rda static const std::string logPrefix_ = "scwx::wsr88d::rda::digital_radar_data"; static const auto logger_ = util::Logger::Create(logPrefix_); -static const std::unordered_map strToDataBlock_ { - {"VOL", DataBlockType::Volume}, - {"ELV", DataBlockType::Elevation}, - {"RAD", DataBlockType::Radial}, - {"REF", DataBlockType::MomentRef}, - {"VEL", DataBlockType::MomentVel}, - {"SW ", DataBlockType::MomentSw}, - {"ZDR", DataBlockType::MomentZdr}, - {"PHI", DataBlockType::MomentPhi}, - {"RHO", DataBlockType::MomentRho}, - {"CFP", DataBlockType::MomentCfp}}; +// Table III-A Angle Data Format +constexpr float kAngleDataScale = 0.0054931640625f; -class DataBlockImpl +// Table III-B Range Format +constexpr float kRangeScale = 0.001f; + +class DigitalRadarData::Impl { public: - explicit DataBlockImpl(const std::string& dataBlockType, - const std::string& dataName) : - dataBlockType_ {dataBlockType}, dataName_ {dataName} - { - } + class MomentDataBlock; - std::string dataBlockType_; - std::string dataName_; + explicit Impl() {}; + ~Impl() = default; + + std::uint32_t collectionTime_ {}; + std::uint16_t modifiedJulianDate_ {}; + std::uint16_t unambiguousRange_ {}; + std::uint16_t azimuthAngle_ {}; + std::uint16_t azimuthNumber_ {}; + std::uint16_t radialStatus_ {}; + std::uint16_t elevationAngle_ {}; + std::uint16_t elevationNumber_ {}; + std::int16_t surveillanceRange_ {}; + std::int16_t dopplerRange_ {}; + std::uint16_t surveillanceRangeSampleInterval_ {}; + std::uint16_t dopplerRangeSampleInterval_ {}; + std::uint16_t numberOfSurveillanceBins_ {}; + std::uint16_t numberOfDopplerBins_ {}; + std::uint16_t cutSectorNumber_ {}; + float calibrationConstant_ {}; + std::uint16_t surveillancePointer_ {}; + std::uint16_t velocityPointer_ {}; + std::uint16_t spectralWidthPointer_ {}; + std::uint16_t dopplerVelocityResolution_ {}; + std::uint16_t vcpNumber_ {}; + std::uint16_t nyquistVelocity_ {}; + std::uint16_t atmos_ {}; + std::uint16_t tover_ {}; + std::uint16_t radialSpotBlankingStatus_ {}; + + std::shared_ptr reflectivityDataBlock_ {nullptr}; + std::shared_ptr dopplerVelocityDataBlock_ {nullptr}; + std::shared_ptr dopplerSpectrumWidthDataBlock_ {nullptr}; }; -DataBlock::DataBlock(const std::string& dataBlockType, - const std::string& dataName) : - p(std::make_unique(dataBlockType, dataName)) -{ -} -DataBlock::~DataBlock() = default; - -DataBlock::DataBlock(DataBlock&&) noexcept = default; -DataBlock& DataBlock::operator=(DataBlock&&) noexcept = default; - -class MomentDataBlockImpl +class DigitalRadarData::Impl::MomentDataBlock : + public GenericRadarData::MomentDataBlock { public: - explicit MomentDataBlockImpl() : - numberOfDataMomentGates_ {0}, - dataMomentRange_ {0}, - dataMomentRangeSampleInterval_ {0}, - tover_ {0}, - snrThreshold_ {0}, - controlFlags_ {0}, - dataWordSize_ {0}, - scale_ {0.0f}, - offset_ {0.0f} - { - } + explicit MomentDataBlock(const DigitalRadarData* self, DataBlockType type); + ~MomentDataBlock() = default; - uint16_t numberOfDataMomentGates_; - uint16_t dataMomentRange_; - uint16_t dataMomentRangeSampleInterval_; - uint16_t tover_; - int16_t snrThreshold_; - uint8_t controlFlags_; - uint8_t dataWordSize_; - float scale_; - float offset_; + MomentDataBlock(const MomentDataBlock&) = delete; + MomentDataBlock& operator=(const MomentDataBlock&) = delete; - std::vector momentGates8_; - std::vector momentGates16_; + MomentDataBlock(MomentDataBlock&&) noexcept = default; + MomentDataBlock& operator=(MomentDataBlock&&) noexcept = default; + + std::uint16_t number_of_data_moment_gates() const; + units::kilometers data_moment_range() const; + std::int16_t data_moment_range_raw() const; + units::kilometers data_moment_range_sample_interval() const; + std::uint16_t data_moment_range_sample_interval_raw() const; + std::int16_t snr_threshold_raw() const; + std::uint8_t data_word_size() const; + float scale() const; + float offset() const; + const void* data_moments() const; + + std::vector& data_moment_vector() const; + +private: + class Impl; + std::unique_ptr p; }; -MomentDataBlock::MomentDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) -{ -} -MomentDataBlock::~MomentDataBlock() = default; - -MomentDataBlock::MomentDataBlock(MomentDataBlock&&) noexcept = default; -MomentDataBlock& -MomentDataBlock::operator=(MomentDataBlock&&) noexcept = default; - -uint16_t MomentDataBlock::number_of_data_moment_gates() const -{ - return p->numberOfDataMomentGates_; -} - -float MomentDataBlock::data_moment_range() const -{ - return p->dataMomentRange_ * 0.001f; -} - -uint16_t MomentDataBlock::data_moment_range_raw() const -{ - return p->dataMomentRange_; -} - -float MomentDataBlock::data_moment_range_sample_interval() const -{ - return p->dataMomentRangeSampleInterval_ * 0.001f; -} - -uint16_t MomentDataBlock::data_moment_range_sample_interval_raw() const -{ - return p->dataMomentRangeSampleInterval_; -} - -float MomentDataBlock::snr_threshold() const -{ - return p->snrThreshold_ * 0.1f; -} - -int16_t MomentDataBlock::snr_threshold_raw() const -{ - return p->snrThreshold_; -} - -uint8_t MomentDataBlock::data_word_size() const -{ - return p->dataWordSize_; -} - -float MomentDataBlock::scale() const -{ - return p->scale_; -} - -float MomentDataBlock::offset() const -{ - return p->offset_; -} - -const void* MomentDataBlock::data_moments() const -{ - const void* dataMoments; - - switch (p->dataWordSize_) - { - case 8: - dataMoments = p->momentGates8_.data(); - break; - case 16: - dataMoments = p->momentGates16_.data(); - break; - default: - dataMoments = nullptr; - break; - } - - return dataMoments; -} - -std::shared_ptr -MomentDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) -{ - std::shared_ptr p = - std::make_shared(dataBlockType, dataName); - - if (!p->Parse(is)) - { - p.reset(); - } - - return p; -} - -bool MomentDataBlock::Parse(std::istream& is) -{ - bool dataBlockValid = true; - - is.seekg(4, std::ios_base::cur); // 4-7 - is.read(reinterpret_cast(&p->numberOfDataMomentGates_), 2); // 8-9 - is.read(reinterpret_cast(&p->dataMomentRange_), 2); // 10-11 - is.read(reinterpret_cast(&p->dataMomentRangeSampleInterval_), - 2); // 12-13 - is.read(reinterpret_cast(&p->tover_), 2); // 14-15 - is.read(reinterpret_cast(&p->snrThreshold_), 2); // 16-17 - is.read(reinterpret_cast(&p->controlFlags_), 1); // 18 - is.read(reinterpret_cast(&p->dataWordSize_), 1); // 19 - is.read(reinterpret_cast(&p->scale_), 4); // 20-23 - is.read(reinterpret_cast(&p->offset_), 4); // 24-27 - - p->numberOfDataMomentGates_ = ntohs(p->numberOfDataMomentGates_); - p->dataMomentRange_ = ntohs(p->dataMomentRange_); - p->dataMomentRangeSampleInterval_ = ntohs(p->dataMomentRangeSampleInterval_); - p->tover_ = ntohs(p->tover_); - p->snrThreshold_ = ntohs(p->snrThreshold_); - p->scale_ = awips::Message::SwapFloat(p->scale_); - p->offset_ = awips::Message::SwapFloat(p->offset_); - - if (p->numberOfDataMomentGates_ <= 1840) - { - if (p->dataWordSize_ == 8) - { - p->momentGates8_.resize(p->numberOfDataMomentGates_); - is.read(reinterpret_cast(p->momentGates8_.data()), - p->numberOfDataMomentGates_); - } - else if (p->dataWordSize_ == 16) - { - p->momentGates16_.resize(p->numberOfDataMomentGates_); - is.read(reinterpret_cast(p->momentGates16_.data()), - p->numberOfDataMomentGates_ * 2); - awips::Message::SwapVector(p->momentGates16_); - } - else - { - logger_->warn("Invalid data word size: {}", p->dataWordSize_); - dataBlockValid = false; - } - } - else - { - logger_->warn("Invalid number of data moment gates: {}", - p->numberOfDataMomentGates_); - dataBlockValid = false; - } - - return dataBlockValid; -} - -class VolumeDataBlockImpl +class DigitalRadarData::Impl::MomentDataBlock::Impl { public: - explicit VolumeDataBlockImpl() : - lrtup_ {0}, - versionNumberMajor_ {0}, - versionNumberMinor_ {0}, - latitude_ {0.0f}, - longitude_ {0.0f}, - siteHeight_ {0}, - feedhornHeight_ {0}, - calibrationConstant_ {0.0f}, - horizontaShvTxPower_ {0.0f}, - verticalShvTxPower_ {0.0f}, - systemDifferentialReflectivity_ {0.0f}, - initialSystemDifferentialPhase_ {0.0f}, - volumeCoveragePatternNumber_ {0}, - processingStatus_ {0} - { - } + explicit Impl() {}; + ~Impl() = default; - uint16_t lrtup_; - uint8_t versionNumberMajor_; - uint8_t versionNumberMinor_; - float latitude_; - float longitude_; - int16_t siteHeight_; - uint16_t feedhornHeight_; - float calibrationConstant_; - float horizontaShvTxPower_; - float verticalShvTxPower_; - float systemDifferentialReflectivity_; - float initialSystemDifferentialPhase_; - uint16_t volumeCoveragePatternNumber_; - uint16_t processingStatus_; -}; + std::uint16_t numberOfDataMomentGates_ {}; + std::int16_t dataMomentRange_ {}; + std::uint16_t dataMomentRangeSampleInterval_ {}; + float scale_ {}; + float offset_ {}; -VolumeDataBlock::VolumeDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) -{ -} -VolumeDataBlock::~VolumeDataBlock() = default; - -VolumeDataBlock::VolumeDataBlock(VolumeDataBlock&&) noexcept = default; -VolumeDataBlock& -VolumeDataBlock::operator=(VolumeDataBlock&&) noexcept = default; - -float VolumeDataBlock::latitude() const -{ - return p->latitude_; -} - -float VolumeDataBlock::longitude() const -{ - return p->longitude_; -} - -uint16_t VolumeDataBlock::volume_coverage_pattern_number() const -{ - return p->volumeCoveragePatternNumber_; -} - -std::shared_ptr -VolumeDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) -{ - std::shared_ptr p = - std::make_shared(dataBlockType, dataName); - - if (!p->Parse(is)) - { - p.reset(); - } - - return p; -} - -bool VolumeDataBlock::Parse(std::istream& is) -{ - bool dataBlockValid = true; - - is.read(reinterpret_cast(&p->lrtup_), 2); // 4-5 - is.read(reinterpret_cast(&p->versionNumberMajor_), 1); // 6 - is.read(reinterpret_cast(&p->versionNumberMinor_), 1); // 7 - is.read(reinterpret_cast(&p->latitude_), 4); // 8-11 - is.read(reinterpret_cast(&p->longitude_), 4); // 12-15 - is.read(reinterpret_cast(&p->siteHeight_), 2); // 16-17 - is.read(reinterpret_cast(&p->feedhornHeight_), 2); // 18-19 - is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 20-23 - is.read(reinterpret_cast(&p->horizontaShvTxPower_), 4); // 24-27 - is.read(reinterpret_cast(&p->verticalShvTxPower_), 4); // 28-31 - is.read(reinterpret_cast(&p->systemDifferentialReflectivity_), - 4); // 32-35 - is.read(reinterpret_cast(&p->initialSystemDifferentialPhase_), - 4); // 36-39 - is.read(reinterpret_cast(&p->volumeCoveragePatternNumber_), - 2); // 40-41 - is.read(reinterpret_cast(&p->processingStatus_), 2); // 42-43 - - p->lrtup_ = ntohs(p->lrtup_); - p->latitude_ = awips::Message::SwapFloat(p->latitude_); - p->longitude_ = awips::Message::SwapFloat(p->longitude_); - p->siteHeight_ = ntohs(p->siteHeight_); - p->feedhornHeight_ = ntohs(p->feedhornHeight_); - p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); - p->horizontaShvTxPower_ = awips::Message::SwapFloat(p->horizontaShvTxPower_); - p->verticalShvTxPower_ = awips::Message::SwapFloat(p->verticalShvTxPower_); - p->systemDifferentialReflectivity_ = - awips::Message::SwapFloat(p->systemDifferentialReflectivity_); - p->initialSystemDifferentialPhase_ = - awips::Message::SwapFloat(p->initialSystemDifferentialPhase_); - p->volumeCoveragePatternNumber_ = ntohs(p->volumeCoveragePatternNumber_); - p->processingStatus_ = ntohs(p->processingStatus_); - - return dataBlockValid; -} - -class ElevationDataBlockImpl -{ -public: - explicit ElevationDataBlockImpl() : - lrtup_ {0}, atmos_ {0}, calibrationConstant_ {0.0f} - { - } - - uint16_t lrtup_; - int16_t atmos_; - float calibrationConstant_; -}; - -ElevationDataBlock::ElevationDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) -{ -} -ElevationDataBlock::~ElevationDataBlock() = default; - -ElevationDataBlock::ElevationDataBlock(ElevationDataBlock&&) noexcept = default; -ElevationDataBlock& -ElevationDataBlock::operator=(ElevationDataBlock&&) noexcept = default; - -std::shared_ptr -ElevationDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) -{ - std::shared_ptr p = - std::make_shared(dataBlockType, dataName); - - if (!p->Parse(is)) - { - p.reset(); - } - - return p; -} - -bool ElevationDataBlock::Parse(std::istream& is) -{ - bool dataBlockValid = true; - - is.read(reinterpret_cast(&p->lrtup_), 2); // 4-5 - is.read(reinterpret_cast(&p->atmos_), 2); // 6-7 - is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 8-11 - - p->lrtup_ = ntohs(p->lrtup_); - p->atmos_ = ntohs(p->atmos_); - p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); - - return dataBlockValid; -} - -class RadialDataBlockImpl -{ -public: - explicit RadialDataBlockImpl() : - lrtup_ {0}, - unambigiousRange_ {0}, - noiseLevelHorizontal_ {0.0f}, - noiseLevelVertical_ {0.0f}, - nyquistVelocity_ {0}, - radialFlags_ {0}, - calibrationConstantHorizontal_ {0.0f}, - calibrationConstantVertical_ {0.0f} - { - } - - uint16_t lrtup_; - uint16_t unambigiousRange_; - float noiseLevelHorizontal_; - float noiseLevelVertical_; - uint16_t nyquistVelocity_; - uint16_t radialFlags_; - float calibrationConstantHorizontal_; - float calibrationConstantVertical_; -}; - -RadialDataBlock::RadialDataBlock(const std::string& dataBlockType, - const std::string& dataName) : - DataBlock(dataBlockType, dataName), - p(std::make_unique()) -{ -} -RadialDataBlock::~RadialDataBlock() = default; - -RadialDataBlock::RadialDataBlock(RadialDataBlock&&) noexcept = default; -RadialDataBlock& -RadialDataBlock::operator=(RadialDataBlock&&) noexcept = default; - -float RadialDataBlock::unambiguous_range() const -{ - return p->unambigiousRange_ / 10.0f; -} - -std::shared_ptr -RadialDataBlock::Create(const std::string& dataBlockType, - const std::string& dataName, - std::istream& is) -{ - std::shared_ptr p = - std::make_shared(dataBlockType, dataName); - - if (!p->Parse(is)) - { - p.reset(); - } - - return p; -} - -bool RadialDataBlock::Parse(std::istream& is) -{ - bool dataBlockValid = true; - - is.read(reinterpret_cast(&p->lrtup_), 2); // 4-5 - is.read(reinterpret_cast(&p->unambigiousRange_), 2); // 6-7 - is.read(reinterpret_cast(&p->noiseLevelHorizontal_), 4); // 8-11 - is.read(reinterpret_cast(&p->noiseLevelVertical_), 4); // 12-15 - is.read(reinterpret_cast(&p->nyquistVelocity_), 2); // 16-17 - is.read(reinterpret_cast(&p->radialFlags_), 2); // 18-19 - is.read(reinterpret_cast(&p->calibrationConstantHorizontal_), - 4); // 20-23 - is.read(reinterpret_cast(&p->calibrationConstantVertical_), - 4); // 24-27 - - p->lrtup_ = ntohs(p->lrtup_); - p->unambigiousRange_ = ntohs(p->unambigiousRange_); - p->noiseLevelHorizontal_ = - awips::Message::SwapFloat(p->noiseLevelHorizontal_); - p->noiseLevelVertical_ = awips::Message::SwapFloat(p->noiseLevelVertical_); - p->nyquistVelocity_ = ntohs(p->nyquistVelocity_); - p->radialFlags_ = ntohs(p->radialFlags_); - p->calibrationConstantHorizontal_ = - awips::Message::SwapFloat(p->calibrationConstantHorizontal_); - p->calibrationConstantVertical_ = - awips::Message::SwapFloat(p->calibrationConstantVertical_); - - return dataBlockValid; -} - -class DigitalRadarDataImpl -{ -public: - explicit DigitalRadarDataImpl() : - radarIdentifier_ {}, - collectionTime_ {0}, - modifiedJulianDate_ {0}, - azimuthNumber_ {0}, - azimuthAngle_ {0.0f}, - compressionIndicator_ {0}, - radialLength_ {0}, - azimuthResolutionSpacing_ {0}, - radialStatus_ {0}, - elevationNumber_ {0}, - cutSectorNumber_ {0}, - elevationAngle_ {0.0f}, - radialSpotBlankingStatus_ {0}, - azimuthIndexingMode_ {0}, - dataBlockCount_ {0}, - dataBlockPointer_ {0}, - volumeDataBlock_ {nullptr}, - elevationDataBlock_ {nullptr}, - radialDataBlock_ {nullptr}, - momentDataBlock_ {} {}; - ~DigitalRadarDataImpl() = default; - - std::string radarIdentifier_; - uint32_t collectionTime_; - uint16_t modifiedJulianDate_; - uint16_t azimuthNumber_; - float azimuthAngle_; - uint8_t compressionIndicator_; - uint16_t radialLength_; - uint8_t azimuthResolutionSpacing_; - uint8_t radialStatus_; - uint8_t elevationNumber_; - uint8_t cutSectorNumber_; - float elevationAngle_; - uint8_t radialSpotBlankingStatus_; - uint8_t azimuthIndexingMode_; - uint16_t dataBlockCount_; - std::array dataBlockPointer_; - - std::shared_ptr volumeDataBlock_; - std::shared_ptr elevationDataBlock_; - std::shared_ptr radialDataBlock_; - std::unordered_map> - momentDataBlock_; + std::vector dataMoments_ {}; }; DigitalRadarData::DigitalRadarData() : - Level2Message(), p(std::make_unique()) + GenericRadarData(), p(std::make_unique()) { } DigitalRadarData::~DigitalRadarData() = default; @@ -553,226 +112,469 @@ DigitalRadarData::DigitalRadarData(DigitalRadarData&&) noexcept = default; DigitalRadarData& DigitalRadarData::operator=(DigitalRadarData&&) noexcept = default; -std::string DigitalRadarData::radar_identifier() const -{ - return p->radarIdentifier_; -} - -uint32_t DigitalRadarData::collection_time() const +std::uint32_t DigitalRadarData::collection_time() const { return p->collectionTime_; } -uint16_t DigitalRadarData::modified_julian_date() const +std::uint16_t DigitalRadarData::modified_julian_date() const { return p->modifiedJulianDate_; } -uint16_t DigitalRadarData::azimuth_number() const +std::uint16_t DigitalRadarData::unambiguous_range() const { - return p->azimuthNumber_; + return p->unambiguousRange_; } -float DigitalRadarData::azimuth_angle() const +std::uint16_t DigitalRadarData::azimuth_angle_raw() const { return p->azimuthAngle_; } -uint8_t DigitalRadarData::compression_indicator() const +units::degrees DigitalRadarData::azimuth_angle() const { - return p->compressionIndicator_; + return units::degrees {p->azimuthAngle_ * kAngleDataScale}; } -uint16_t DigitalRadarData::radial_length() const +std::uint16_t DigitalRadarData::azimuth_number() const { - return p->radialLength_; + return p->azimuthNumber_; } -uint8_t DigitalRadarData::azimuth_resolution_spacing() const -{ - return p->azimuthResolutionSpacing_; -} - -uint8_t DigitalRadarData::radial_status() const +std::uint16_t DigitalRadarData::radial_status() const { return p->radialStatus_; } -uint8_t DigitalRadarData::elevation_number() const -{ - return p->elevationNumber_; -} - -uint8_t DigitalRadarData::cut_sector_number() const -{ - return p->cutSectorNumber_; -} - -float DigitalRadarData::elevation_angle() const +std::uint16_t DigitalRadarData::elevation_angle_raw() const { return p->elevationAngle_; } -uint8_t DigitalRadarData::radial_spot_blanking_status() const +units::degrees DigitalRadarData::elevation_angle() const +{ + return units::degrees {p->elevationAngle_ * kAngleDataScale}; +} + +std::uint16_t DigitalRadarData::elevation_number() const +{ + return p->elevationNumber_; +} + +std::int16_t DigitalRadarData::surveillance_range_raw() const +{ + return p->surveillanceRange_; +} + +units::kilometers DigitalRadarData::surveillance_range() const +{ + return units::kilometers {p->surveillanceRange_ * kRangeScale}; +} + +std::int16_t DigitalRadarData::doppler_range_raw() const +{ + return p->dopplerRange_; +} + +units::kilometers DigitalRadarData::doppler_range() const +{ + return units::kilometers {p->dopplerRange_ * kRangeScale}; +} + +std::uint16_t DigitalRadarData::surveillance_range_sample_interval_raw() const +{ + return p->surveillanceRangeSampleInterval_; +} + +units::kilometers +DigitalRadarData::surveillance_range_sample_interval() const +{ + return units::kilometers {p->surveillanceRangeSampleInterval_ * + kRangeScale}; +} + +std::uint16_t DigitalRadarData::doppler_range_sample_interval_raw() const +{ + return p->dopplerRangeSampleInterval_; +} + +units::kilometers DigitalRadarData::doppler_range_sample_interval() const +{ + return units::kilometers {p->dopplerRangeSampleInterval_ * + kRangeScale}; +} + +std::uint16_t DigitalRadarData::number_of_surveillance_bins() const +{ + return p->numberOfSurveillanceBins_; +} + +std::uint16_t DigitalRadarData::number_of_doppler_bins() const +{ + return p->numberOfDopplerBins_; +} + +std::uint16_t DigitalRadarData::cut_sector_number() const +{ + return p->cutSectorNumber_; +} + +float DigitalRadarData::calibration_constant() const +{ + return p->calibrationConstant_; +} + +std::uint16_t DigitalRadarData::surveillance_pointer() const +{ + return p->surveillancePointer_; +} + +std::uint16_t DigitalRadarData::velocity_pointer() const +{ + return p->velocityPointer_; +} + +std::uint16_t DigitalRadarData::spectral_width_pointer() const +{ + return p->spectralWidthPointer_; +} + +std::uint16_t DigitalRadarData::doppler_velocity_resolution() const +{ + return p->dopplerVelocityResolution_; +} + +std::uint16_t DigitalRadarData::volume_coverage_pattern_number() const +{ + return p->vcpNumber_; +} + +std::uint16_t DigitalRadarData::nyquist_velocity() const +{ + return p->nyquistVelocity_; +} + +std::uint16_t DigitalRadarData::atmos() const +{ + return p->atmos_; +} + +std::uint16_t DigitalRadarData::tover() const +{ + return p->tover_; +} + +std::uint16_t DigitalRadarData::radial_spot_blanking_status() const { return p->radialSpotBlankingStatus_; } -uint8_t DigitalRadarData::azimuth_indexing_mode() const -{ - return p->azimuthIndexingMode_; -} - -uint16_t DigitalRadarData::data_block_count() const -{ - return p->dataBlockCount_; -} - -std::shared_ptr -DigitalRadarData::elevation_data_block() const -{ - return p->elevationDataBlock_; -} - -std::shared_ptr DigitalRadarData::radial_data_block() const -{ - return p->radialDataBlock_; -} - -std::shared_ptr DigitalRadarData::volume_data_block() const -{ - return p->volumeDataBlock_; -} - -std::shared_ptr +std::shared_ptr DigitalRadarData::moment_data_block(DataBlockType type) const { - std::shared_ptr momentDataBlock = nullptr; + std::shared_ptr block = nullptr; - auto it = p->momentDataBlock_.find(type); - if (it != p->momentDataBlock_.end()) + switch (type) { - momentDataBlock = it->second; + case DataBlockType::MomentRef: + block = p->reflectivityDataBlock_; + break; + + case DataBlockType::MomentVel: + block = p->dopplerVelocityDataBlock_; + break; + + case DataBlockType::MomentSw: + block = p->dopplerSpectrumWidthDataBlock_; + break; + + default: + break; } - return momentDataBlock; + return block; +} + +DigitalRadarData::Impl::MomentDataBlock::MomentDataBlock( + const DigitalRadarData* self, DataBlockType type) : + p(std::make_unique()) +{ + switch (type) + { + case DataBlockType::MomentRef: + p->numberOfDataMomentGates_ = self->number_of_surveillance_bins(); + p->dataMomentRange_ = self->surveillance_range_raw(); + p->dataMomentRangeSampleInterval_ = + self->surveillance_range_sample_interval_raw(); + + // Table III-E Base Data Scaling + // Rnum = (R / 2) - 33.0 + p->scale_ = 2.0f; + p->offset_ = 66.0f; // (33.0 * 2) + break; + + case DataBlockType::MomentVel: + p->numberOfDataMomentGates_ = self->number_of_doppler_bins(); + p->dataMomentRange_ = self->doppler_range_raw(); + p->dataMomentRangeSampleInterval_ = + self->doppler_range_sample_interval_raw(); + + // Table III-E Base Data Scaling + if (self->doppler_velocity_resolution() == 2) // 2 = 0.5 m/s + { + // Vnum = (V / 2) - 64.5 + p->scale_ = 2.0f; + p->offset_ = 129.0f; // (64.5 * 2) + } + else // 4 = 1.0 m/s + { + // Vnum = V - 129.0 + p->scale_ = 1.0f; + p->offset_ = 129.0f; + } + break; + + case DataBlockType::MomentSw: + p->numberOfDataMomentGates_ = self->number_of_doppler_bins(); + p->dataMomentRange_ = self->doppler_range_raw(); + p->dataMomentRangeSampleInterval_ = + self->doppler_range_sample_interval_raw(); + + // Table III-E Base Data Scaling + // SWnum = (SW / 2) - 64.5 + p->scale_ = 2.0f; + p->offset_ = 129.0f; // (64.5 * 2) + break; + + default: + break; + } +} +std::uint16_t +DigitalRadarData::Impl::MomentDataBlock::number_of_data_moment_gates() const +{ + return p->numberOfDataMomentGates_; +} + +units::kilometers +DigitalRadarData::Impl::MomentDataBlock::data_moment_range() const +{ + return units::kilometers {p->dataMomentRange_ * kRangeScale}; +} + +std::int16_t +DigitalRadarData::Impl::MomentDataBlock::data_moment_range_raw() const +{ + return p->dataMomentRange_; +} + +units::kilometers +DigitalRadarData::Impl::MomentDataBlock::data_moment_range_sample_interval() + const +{ + return units::kilometers {p->dataMomentRangeSampleInterval_ * + kRangeScale}; +} + +std::uint16_t +DigitalRadarData::Impl::MomentDataBlock::data_moment_range_sample_interval_raw() + const +{ + return p->dataMomentRangeSampleInterval_; +} + +std::int16_t DigitalRadarData::Impl::MomentDataBlock::snr_threshold_raw() const +{ + // Table III Digital Radar Data (Message Type 1) Note 10: + // Value of 00 (prior to scaling) is Signal Below Threshold, value of 01 + // (prior to scaling) is Signal Overlaid + return 2; +} + +std::uint8_t DigitalRadarData::Impl::MomentDataBlock::data_word_size() const +{ + // Data moments are 8-bit for Digital Radar Data + return 8; +} + +float DigitalRadarData::Impl::MomentDataBlock::scale() const +{ + return p->scale_; +} + +float DigitalRadarData::Impl::MomentDataBlock::offset() const +{ + return p->offset_; +} + +const void* DigitalRadarData::Impl::MomentDataBlock::data_moments() const +{ + return p->dataMoments_.data(); +} + +std::vector& +DigitalRadarData::Impl::MomentDataBlock::data_moment_vector() const +{ + return p->dataMoments_; } bool DigitalRadarData::Parse(std::istream& is) { - logger_->trace("Parsing Digital Radar Data (Message Type 31)"); + logger_->trace("Parsing Digital Radar Data (Message Type 1)"); - bool messageValid = true; - size_t bytesRead = 0; + bool messageValid = true; + std::size_t bytesRead = 0; std::streampos isBegin = is.tellg(); - p->radarIdentifier_.resize(4); + is.read(reinterpret_cast(&p->collectionTime_), 4); // 0-3 + is.read(reinterpret_cast(&p->modifiedJulianDate_), 2); // 4-5 + is.read(reinterpret_cast(&p->unambiguousRange_), 2); // 6-7 + is.read(reinterpret_cast(&p->azimuthAngle_), 2); // 8-9 + is.read(reinterpret_cast(&p->azimuthNumber_), 2); // 10-11 + is.read(reinterpret_cast(&p->radialStatus_), 2); // 12-13 + is.read(reinterpret_cast(&p->elevationAngle_), 2); // 14-15 + is.read(reinterpret_cast(&p->elevationNumber_), 2); // 16-17 + is.read(reinterpret_cast(&p->surveillanceRange_), 2); // 18-19 + is.read(reinterpret_cast(&p->dopplerRange_), 2); // 20-21 - is.read(&p->radarIdentifier_[0], 4); // 0-3 - is.read(reinterpret_cast(&p->collectionTime_), 4); // 4-7 - is.read(reinterpret_cast(&p->modifiedJulianDate_), 2); // 8-9 - is.read(reinterpret_cast(&p->azimuthNumber_), 2); // 10-11 - is.read(reinterpret_cast(&p->azimuthAngle_), 4); // 12-15 - is.read(reinterpret_cast(&p->compressionIndicator_), 1); // 16 - is.seekg(1, std::ios_base::cur); // 17 - is.read(reinterpret_cast(&p->radialLength_), 2); // 18-19 - is.read(reinterpret_cast(&p->azimuthResolutionSpacing_), 1); // 20 - is.read(reinterpret_cast(&p->radialStatus_), 1); // 21 - is.read(reinterpret_cast(&p->elevationNumber_), 1); // 22 - is.read(reinterpret_cast(&p->cutSectorNumber_), 1); // 23 - is.read(reinterpret_cast(&p->elevationAngle_), 4); // 24-27 - is.read(reinterpret_cast(&p->radialSpotBlankingStatus_), 1); // 28 - is.read(reinterpret_cast(&p->azimuthIndexingMode_), 1); // 29 - is.read(reinterpret_cast(&p->dataBlockCount_), 2); // 30-31 + is.read(reinterpret_cast(&p->surveillanceRangeSampleInterval_), + 2); // 22-23 + is.read(reinterpret_cast(&p->dopplerRangeSampleInterval_), + 2); // 24-25 + + is.read(reinterpret_cast(&p->numberOfSurveillanceBins_), 2); // 26-27 + is.read(reinterpret_cast(&p->numberOfDopplerBins_), 2); // 28-29 + is.read(reinterpret_cast(&p->cutSectorNumber_), 2); // 30-31 + is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 32-35 + is.read(reinterpret_cast(&p->surveillancePointer_), 2); // 36-37 + is.read(reinterpret_cast(&p->velocityPointer_), 2); // 38-39 + is.read(reinterpret_cast(&p->spectralWidthPointer_), 2); // 40-41 + is.read(reinterpret_cast(&p->dopplerVelocityResolution_), 2); // 42-43 + is.read(reinterpret_cast(&p->vcpNumber_), 2); // 44-45 + is.seekg(14, std::ios_base::cur); // 46-59 + is.read(reinterpret_cast(&p->nyquistVelocity_), 2); // 60-61 + is.read(reinterpret_cast(&p->atmos_), 2); // 62-63 + is.read(reinterpret_cast(&p->tover_), 2); // 64-65 + is.read(reinterpret_cast(&p->radialSpotBlankingStatus_), 2); // 66-67 + is.seekg(32, std::ios_base::cur); // 68-99 p->collectionTime_ = ntohl(p->collectionTime_); p->modifiedJulianDate_ = ntohs(p->modifiedJulianDate_); + p->unambiguousRange_ = ntohs(p->unambiguousRange_); + p->azimuthAngle_ = ntohs(p->azimuthAngle_); p->azimuthNumber_ = ntohs(p->azimuthNumber_); - p->azimuthAngle_ = SwapFloat(p->azimuthAngle_); - p->radialLength_ = ntohs(p->radialLength_); - p->elevationAngle_ = SwapFloat(p->elevationAngle_); - p->dataBlockCount_ = ntohs(p->dataBlockCount_); + p->radialStatus_ = ntohs(p->radialStatus_); + p->elevationAngle_ = ntohs(p->elevationAngle_); + p->elevationNumber_ = ntohs(p->elevationNumber_); + p->surveillanceRange_ = ntohs(p->surveillanceRange_); + p->dopplerRange_ = ntohs(p->dopplerRange_); - if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 720) + p->surveillanceRangeSampleInterval_ = + ntohs(p->surveillanceRangeSampleInterval_); + + p->dopplerRangeSampleInterval_ = ntohs(p->dopplerRangeSampleInterval_); + p->numberOfSurveillanceBins_ = ntohs(p->numberOfSurveillanceBins_); + p->numberOfDopplerBins_ = ntohs(p->numberOfDopplerBins_); + p->cutSectorNumber_ = ntohs(p->cutSectorNumber_); + p->calibrationConstant_ = SwapFloat(p->calibrationConstant_); + p->surveillancePointer_ = ntohs(p->surveillancePointer_); + p->velocityPointer_ = ntohs(p->velocityPointer_); + p->spectralWidthPointer_ = ntohs(p->spectralWidthPointer_); + p->dopplerVelocityResolution_ = ntohs(p->dopplerVelocityResolution_); + p->vcpNumber_ = ntohs(p->vcpNumber_); + p->nyquistVelocity_ = ntohs(p->nyquistVelocity_); + p->atmos_ = ntohs(p->atmos_); + p->tover_ = ntohs(p->tover_); + p->radialSpotBlankingStatus_ = ntohs(p->radialSpotBlankingStatus_); + + if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 400) { logger_->warn("Invalid azimuth number: {}", p->azimuthNumber_); messageValid = false; } - if (p->elevationNumber_ < 1 || p->elevationNumber_ > 32) + if (p->elevationNumber_ < 1 || p->elevationNumber_ > 25) { - logger_->warn("Invalid elevation number: ", p->elevationNumber_); + logger_->warn("Invalid elevation number: {}", p->elevationNumber_); messageValid = false; } - if (p->dataBlockCount_ < 4 || p->dataBlockCount_ > 10) + if (p->numberOfSurveillanceBins_ > 460) { - logger_->warn("Invalid number of data blocks: {}", p->dataBlockCount_); + logger_->warn("Invalid number of surveillance bins: {}", + p->numberOfSurveillanceBins_); messageValid = false; } - if (p->compressionIndicator_ != 0) + if (p->numberOfDopplerBins_ > 920) { - logger_->warn("Compression not supported"); + logger_->warn("Invalid number of doppler bins: {}", + p->numberOfDopplerBins_); + messageValid = false; + } + if (p->surveillancePointer_ != 0 && p->surveillancePointer_ != 100) + { + logger_->warn("Invalid surveillance pointer: {}", + p->surveillancePointer_); + messageValid = false; + } + if (p->velocityPointer_ != 0 && + (p->velocityPointer_ < 100 || p->velocityPointer_ > 560)) + { + logger_->warn("Invalid velocity pointer: {}", p->velocityPointer_); + messageValid = false; + } + if (p->spectralWidthPointer_ != 0 && + (p->spectralWidthPointer_ < 100 || p->spectralWidthPointer_ > 1480 || + p->spectralWidthPointer_ > data_size())) + { + logger_->warn("Invalid spectral width pointer: {}", + p->spectralWidthPointer_); messageValid = false; } - if (!messageValid) + if (messageValid && p->surveillancePointer_ != 0) { - p->dataBlockCount_ = 0; - } + p->reflectivityDataBlock_ = std::make_shared( + this, DataBlockType::MomentRef); + auto& reflectivity = p->reflectivityDataBlock_->data_moment_vector(); - is.read(reinterpret_cast(&p->dataBlockPointer_), - p->dataBlockCount_ * 4); - - SwapArray(p->dataBlockPointer_, p->dataBlockCount_); - - for (uint16_t b = 0; b < p->dataBlockCount_; ++b) - { - is.seekg(isBegin + std::streamoff(p->dataBlockPointer_[b]), + is.seekg(isBegin + std::streamoff(p->surveillancePointer_), std::ios_base::beg); - std::string dataBlockType(1, 0); - std::string dataName(3, 0); + reflectivity.resize(p->numberOfSurveillanceBins_); + is.read(reinterpret_cast(reflectivity.data()), + p->numberOfSurveillanceBins_); + } - is.read(&dataBlockType[0], 1); - is.read(&dataName[0], 3); + if (messageValid && p->velocityPointer_ != 0) + { + p->dopplerVelocityDataBlock_ = std::make_shared( + this, DataBlockType::MomentVel); + auto& dopplerVelocity = + p->dopplerVelocityDataBlock_->data_moment_vector(); - DataBlockType dataBlock = DataBlockType::Unknown; - try - { - dataBlock = strToDataBlock_.at(dataName); - } - catch (const std::exception&) - { - } + is.seekg(isBegin + std::streamoff(p->velocityPointer_), + std::ios_base::beg); - switch (dataBlock) - { - case DataBlockType::Volume: - p->volumeDataBlock_ = - std::move(VolumeDataBlock::Create(dataBlockType, dataName, is)); - break; - case DataBlockType::Elevation: - p->elevationDataBlock_ = - std::move(ElevationDataBlock::Create(dataBlockType, dataName, is)); - break; - case DataBlockType::Radial: - p->radialDataBlock_ = - std::move(RadialDataBlock::Create(dataBlockType, dataName, is)); - break; - case DataBlockType::MomentRef: - case DataBlockType::MomentVel: - case DataBlockType::MomentSw: - case DataBlockType::MomentZdr: - case DataBlockType::MomentPhi: - case DataBlockType::MomentRho: - case DataBlockType::MomentCfp: - p->momentDataBlock_[dataBlock] = - std::move(MomentDataBlock::Create(dataBlockType, dataName, is)); - break; - default: - logger_->warn("Unknown data name: {}", dataName); - break; - } + dopplerVelocity.resize(p->numberOfDopplerBins_); + is.read(reinterpret_cast(dopplerVelocity.data()), + p->numberOfDopplerBins_); + } + + if (messageValid && p->spectralWidthPointer_ != 0) + { + p->dopplerSpectrumWidthDataBlock_ = + std::make_shared(this, + DataBlockType::MomentVel); + auto& dopplerSpectrumWidth = + p->dopplerSpectrumWidthDataBlock_->data_moment_vector(); + + is.seekg(isBegin + std::streamoff(p->spectralWidthPointer_), + std::ios_base::beg); + + dopplerSpectrumWidth.resize(p->numberOfDopplerBins_); + is.read(reinterpret_cast(dopplerSpectrumWidth.data()), + p->numberOfDopplerBins_); } is.seekg(isBegin, std::ios_base::beg); diff --git a/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp new file mode 100644 index 00000000..4870c1c3 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/digital_radar_data_generic.cpp @@ -0,0 +1,776 @@ +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = + "scwx::wsr88d::rda::digital_radar_data_generic"; +static const auto logger_ = util::Logger::Create(logPrefix_); + +static const std::unordered_map strToDataBlock_ { + {"VOL", DataBlockType::Volume}, + {"ELV", DataBlockType::Elevation}, + {"RAD", DataBlockType::Radial}, + {"REF", DataBlockType::MomentRef}, + {"VEL", DataBlockType::MomentVel}, + {"SW ", DataBlockType::MomentSw}, + {"ZDR", DataBlockType::MomentZdr}, + {"PHI", DataBlockType::MomentPhi}, + {"RHO", DataBlockType::MomentRho}, + {"CFP", DataBlockType::MomentCfp}}; + +class DigitalRadarDataGeneric::DataBlock::Impl +{ +public: + explicit Impl(const std::string& dataBlockType, + const std::string& dataName) : + dataBlockType_ {dataBlockType}, dataName_ {dataName} + { + } + + std::string dataBlockType_; + std::string dataName_; +}; + +DigitalRadarDataGeneric::DataBlock::DataBlock(const std::string& dataBlockType, + const std::string& dataName) : + p(std::make_unique(dataBlockType, dataName)) +{ +} +DigitalRadarDataGeneric::DataBlock::~DataBlock() = default; + +DigitalRadarDataGeneric::DataBlock::DataBlock(DataBlock&&) noexcept = default; +DigitalRadarDataGeneric::DataBlock& +DigitalRadarDataGeneric::DataBlock::operator=(DataBlock&&) noexcept = default; + +class DigitalRadarDataGeneric::MomentDataBlock::Impl +{ +public: + explicit Impl() {} + + std::uint16_t numberOfDataMomentGates_ {0}; + std::int16_t dataMomentRange_ {0}; + std::uint16_t dataMomentRangeSampleInterval_ {0}; + std::uint16_t tover_ {0}; + std::int16_t snrThreshold_ {0}; + std::uint8_t controlFlags_ {0}; + std::uint8_t dataWordSize_ {0}; + float scale_ {0.0f}; + float offset_ {0.0f}; + + std::vector momentGates8_ {}; + std::vector momentGates16_ {}; +}; + +DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) +{ +} +DigitalRadarDataGeneric::MomentDataBlock::~MomentDataBlock() = default; + +DigitalRadarDataGeneric::MomentDataBlock::MomentDataBlock( + MomentDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::MomentDataBlock& +DigitalRadarDataGeneric::MomentDataBlock::operator=( + MomentDataBlock&&) noexcept = default; + +std::uint16_t +DigitalRadarDataGeneric::MomentDataBlock::number_of_data_moment_gates() const +{ + return p->numberOfDataMomentGates_; +} + +units::kilometers +DigitalRadarDataGeneric::MomentDataBlock::data_moment_range() const +{ + return units::kilometers {p->dataMomentRange_ * 0.001f}; +} + +std::int16_t +DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_raw() const +{ + return p->dataMomentRange_; +} + +units::kilometers +DigitalRadarDataGeneric::MomentDataBlock::data_moment_range_sample_interval() + const +{ + return units::kilometers {p->dataMomentRangeSampleInterval_ * 0.001f}; +} + +std::uint16_t DigitalRadarDataGeneric::MomentDataBlock:: + data_moment_range_sample_interval_raw() const +{ + return p->dataMomentRangeSampleInterval_; +} + +float DigitalRadarDataGeneric::MomentDataBlock::snr_threshold() const +{ + return p->snrThreshold_ * 0.1f; +} + +std::int16_t DigitalRadarDataGeneric::MomentDataBlock::snr_threshold_raw() const +{ + return p->snrThreshold_; +} + +std::uint8_t DigitalRadarDataGeneric::MomentDataBlock::data_word_size() const +{ + return p->dataWordSize_; +} + +float DigitalRadarDataGeneric::MomentDataBlock::scale() const +{ + return p->scale_; +} + +float DigitalRadarDataGeneric::MomentDataBlock::offset() const +{ + return p->offset_; +} + +const void* DigitalRadarDataGeneric::MomentDataBlock::data_moments() const +{ + const void* dataMoments; + + switch (p->dataWordSize_) + { + case 8: + dataMoments = p->momentGates8_.data(); + break; + case 16: + dataMoments = p->momentGates16_.data(); + break; + default: + dataMoments = nullptr; + break; + } + + return dataMoments; +} + +std::shared_ptr +DigitalRadarDataGeneric::MomentDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) +{ + std::shared_ptr p = + std::make_shared(dataBlockType, dataName); + + if (!p->Parse(is)) + { + p.reset(); + } + + return p; +} + +bool DigitalRadarDataGeneric::MomentDataBlock::Parse(std::istream& is) +{ + bool dataBlockValid = true; + + is.seekg(4, std::ios_base::cur); // 4-7 + is.read(reinterpret_cast(&p->numberOfDataMomentGates_), 2); // 8-9 + is.read(reinterpret_cast(&p->dataMomentRange_), 2); // 10-11 + is.read(reinterpret_cast(&p->dataMomentRangeSampleInterval_), + 2); // 12-13 + is.read(reinterpret_cast(&p->tover_), 2); // 14-15 + is.read(reinterpret_cast(&p->snrThreshold_), 2); // 16-17 + is.read(reinterpret_cast(&p->controlFlags_), 1); // 18 + is.read(reinterpret_cast(&p->dataWordSize_), 1); // 19 + is.read(reinterpret_cast(&p->scale_), 4); // 20-23 + is.read(reinterpret_cast(&p->offset_), 4); // 24-27 + + p->numberOfDataMomentGates_ = ntohs(p->numberOfDataMomentGates_); + p->dataMomentRange_ = ntohs(p->dataMomentRange_); + p->dataMomentRangeSampleInterval_ = ntohs(p->dataMomentRangeSampleInterval_); + p->tover_ = ntohs(p->tover_); + p->snrThreshold_ = ntohs(p->snrThreshold_); + p->scale_ = awips::Message::SwapFloat(p->scale_); + p->offset_ = awips::Message::SwapFloat(p->offset_); + + if (p->numberOfDataMomentGates_ <= 1840) + { + if (p->dataWordSize_ == 8) + { + p->momentGates8_.resize(p->numberOfDataMomentGates_); + is.read(reinterpret_cast(p->momentGates8_.data()), + p->numberOfDataMomentGates_); + } + else if (p->dataWordSize_ == 16) + { + p->momentGates16_.resize(p->numberOfDataMomentGates_); + is.read(reinterpret_cast(p->momentGates16_.data()), + p->numberOfDataMomentGates_ * 2); + awips::Message::SwapVector(p->momentGates16_); + } + else + { + logger_->warn("Invalid data word size: {}", p->dataWordSize_); + dataBlockValid = false; + } + } + else + { + logger_->warn("Invalid number of data moment gates: {}", + p->numberOfDataMomentGates_); + dataBlockValid = false; + } + + return dataBlockValid; +} + +class DigitalRadarDataGeneric::VolumeDataBlock::Impl +{ +public: + explicit Impl() {} + + std::uint16_t lrtup_ {0}; + std::uint8_t versionNumberMajor_ {0}; + std::uint8_t versionNumberMinor_ {0}; + float latitude_ {0.0f}; + float longitude_ {0.0f}; + std::int16_t siteHeight_ {0}; + std::uint16_t feedhornHeight_ {0}; + float calibrationConstant_ {0.0f}; + float horizontaShvTxPower_ {0.0f}; + float verticalShvTxPower_ {0.0f}; + float systemDifferentialReflectivity_ {0.0f}; + float initialSystemDifferentialPhase_ {0.0f}; + std::uint16_t volumeCoveragePatternNumber_ {0}; + std::uint16_t processingStatus_ {0}; +}; + +DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) +{ +} +DigitalRadarDataGeneric::VolumeDataBlock::~VolumeDataBlock() = default; + +DigitalRadarDataGeneric::VolumeDataBlock::VolumeDataBlock( + VolumeDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::VolumeDataBlock& +DigitalRadarDataGeneric::VolumeDataBlock::operator=( + VolumeDataBlock&&) noexcept = default; + +float DigitalRadarDataGeneric::VolumeDataBlock::latitude() const +{ + return p->latitude_; +} + +float DigitalRadarDataGeneric::VolumeDataBlock::longitude() const +{ + return p->longitude_; +} + +std::uint16_t +DigitalRadarDataGeneric::VolumeDataBlock::volume_coverage_pattern_number() const +{ + return p->volumeCoveragePatternNumber_; +} + +std::shared_ptr +DigitalRadarDataGeneric::VolumeDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) +{ + std::shared_ptr p = + std::make_shared(dataBlockType, dataName); + + if (!p->Parse(is)) + { + p.reset(); + } + + return p; +} + +bool DigitalRadarDataGeneric::VolumeDataBlock::Parse(std::istream& is) +{ + bool dataBlockValid = true; + + is.read(reinterpret_cast(&p->lrtup_), 2); // 4-5 + is.read(reinterpret_cast(&p->versionNumberMajor_), 1); // 6 + is.read(reinterpret_cast(&p->versionNumberMinor_), 1); // 7 + is.read(reinterpret_cast(&p->latitude_), 4); // 8-11 + is.read(reinterpret_cast(&p->longitude_), 4); // 12-15 + is.read(reinterpret_cast(&p->siteHeight_), 2); // 16-17 + is.read(reinterpret_cast(&p->feedhornHeight_), 2); // 18-19 + is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 20-23 + is.read(reinterpret_cast(&p->horizontaShvTxPower_), 4); // 24-27 + is.read(reinterpret_cast(&p->verticalShvTxPower_), 4); // 28-31 + is.read(reinterpret_cast(&p->systemDifferentialReflectivity_), + 4); // 32-35 + is.read(reinterpret_cast(&p->initialSystemDifferentialPhase_), + 4); // 36-39 + is.read(reinterpret_cast(&p->volumeCoveragePatternNumber_), + 2); // 40-41 + is.read(reinterpret_cast(&p->processingStatus_), 2); // 42-43 + + p->lrtup_ = ntohs(p->lrtup_); + p->latitude_ = awips::Message::SwapFloat(p->latitude_); + p->longitude_ = awips::Message::SwapFloat(p->longitude_); + p->siteHeight_ = ntohs(p->siteHeight_); + p->feedhornHeight_ = ntohs(p->feedhornHeight_); + p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); + p->horizontaShvTxPower_ = awips::Message::SwapFloat(p->horizontaShvTxPower_); + p->verticalShvTxPower_ = awips::Message::SwapFloat(p->verticalShvTxPower_); + p->systemDifferentialReflectivity_ = + awips::Message::SwapFloat(p->systemDifferentialReflectivity_); + p->initialSystemDifferentialPhase_ = + awips::Message::SwapFloat(p->initialSystemDifferentialPhase_); + p->volumeCoveragePatternNumber_ = ntohs(p->volumeCoveragePatternNumber_); + p->processingStatus_ = ntohs(p->processingStatus_); + + return dataBlockValid; +} + +class DigitalRadarDataGeneric::ElevationDataBlock::Impl +{ +public: + explicit Impl() {} + + std::uint16_t lrtup_ {0}; + std::int16_t atmos_ {0}; + float calibrationConstant_ {0.0f}; +}; + +DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) +{ +} +DigitalRadarDataGeneric::ElevationDataBlock::~ElevationDataBlock() = default; + +DigitalRadarDataGeneric::ElevationDataBlock::ElevationDataBlock( + ElevationDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::ElevationDataBlock& +DigitalRadarDataGeneric::ElevationDataBlock::operator=( + ElevationDataBlock&&) noexcept = default; + +std::shared_ptr +DigitalRadarDataGeneric::ElevationDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) +{ + std::shared_ptr p = + std::make_shared(dataBlockType, dataName); + + if (!p->Parse(is)) + { + p.reset(); + } + + return p; +} + +bool DigitalRadarDataGeneric::ElevationDataBlock::Parse(std::istream& is) +{ + bool dataBlockValid = true; + + is.read(reinterpret_cast(&p->lrtup_), 2); // 4-5 + is.read(reinterpret_cast(&p->atmos_), 2); // 6-7 + is.read(reinterpret_cast(&p->calibrationConstant_), 4); // 8-11 + + p->lrtup_ = ntohs(p->lrtup_); + p->atmos_ = ntohs(p->atmos_); + p->calibrationConstant_ = awips::Message::SwapFloat(p->calibrationConstant_); + + return dataBlockValid; +} + +class DigitalRadarDataGeneric::RadialDataBlock::Impl +{ +public: + explicit Impl() {} + + std::uint16_t lrtup_ {0}; + std::uint16_t unambigiousRange_ {0}; + float noiseLevelHorizontal_ {0.0f}; + float noiseLevelVertical_ {0.0f}; + std::uint16_t nyquistVelocity_ {0}; + std::uint16_t radialFlags_ {0}; + float calibrationConstantHorizontal_ {0.0f}; + float calibrationConstantVertical_ {0.0f}; +}; + +DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock( + const std::string& dataBlockType, const std::string& dataName) : + DataBlock(dataBlockType, dataName), p(std::make_unique()) +{ +} +DigitalRadarDataGeneric::RadialDataBlock::~RadialDataBlock() = default; + +DigitalRadarDataGeneric::RadialDataBlock::RadialDataBlock( + RadialDataBlock&&) noexcept = default; +DigitalRadarDataGeneric::RadialDataBlock& +DigitalRadarDataGeneric::RadialDataBlock::operator=( + RadialDataBlock&&) noexcept = default; + +float DigitalRadarDataGeneric::RadialDataBlock::unambiguous_range() const +{ + return p->unambigiousRange_ / 10.0f; +} + +std::shared_ptr +DigitalRadarDataGeneric::RadialDataBlock::Create( + const std::string& dataBlockType, + const std::string& dataName, + std::istream& is) +{ + std::shared_ptr p = + std::make_shared(dataBlockType, dataName); + + if (!p->Parse(is)) + { + p.reset(); + } + + return p; +} + +bool DigitalRadarDataGeneric::RadialDataBlock::Parse(std::istream& is) +{ + bool dataBlockValid = true; + + is.read(reinterpret_cast(&p->lrtup_), 2); // 4-5 + is.read(reinterpret_cast(&p->unambigiousRange_), 2); // 6-7 + is.read(reinterpret_cast(&p->noiseLevelHorizontal_), 4); // 8-11 + is.read(reinterpret_cast(&p->noiseLevelVertical_), 4); // 12-15 + is.read(reinterpret_cast(&p->nyquistVelocity_), 2); // 16-17 + is.read(reinterpret_cast(&p->radialFlags_), 2); // 18-19 + is.read(reinterpret_cast(&p->calibrationConstantHorizontal_), + 4); // 20-23 + is.read(reinterpret_cast(&p->calibrationConstantVertical_), + 4); // 24-27 + + p->lrtup_ = ntohs(p->lrtup_); + p->unambigiousRange_ = ntohs(p->unambigiousRange_); + p->noiseLevelHorizontal_ = + awips::Message::SwapFloat(p->noiseLevelHorizontal_); + p->noiseLevelVertical_ = awips::Message::SwapFloat(p->noiseLevelVertical_); + p->nyquistVelocity_ = ntohs(p->nyquistVelocity_); + p->radialFlags_ = ntohs(p->radialFlags_); + p->calibrationConstantHorizontal_ = + awips::Message::SwapFloat(p->calibrationConstantHorizontal_); + p->calibrationConstantVertical_ = + awips::Message::SwapFloat(p->calibrationConstantVertical_); + + return dataBlockValid; +} + +class DigitalRadarDataGeneric::Impl +{ +public: + explicit Impl() {}; + ~Impl() = default; + + std::string radarIdentifier_ {}; + std::uint32_t collectionTime_ {0}; + std::uint16_t modifiedJulianDate_ {0}; + std::uint16_t azimuthNumber_ {0}; + float azimuthAngle_ {0.0f}; + std::uint8_t compressionIndicator_ {0}; + std::uint16_t radialLength_ {0}; + std::uint8_t azimuthResolutionSpacing_ {0}; + std::uint8_t radialStatus_ {0}; + std::uint8_t elevationNumber_ {0}; + std::uint8_t cutSectorNumber_ {0}; + float elevationAngle_ {0.0f}; + std::uint8_t radialSpotBlankingStatus_ {0}; + std::uint8_t azimuthIndexingMode_ {0}; + std::uint16_t dataBlockCount_ {0}; + std::array dataBlockPointer_ {0}; + + std::shared_ptr volumeDataBlock_ {nullptr}; + std::shared_ptr elevationDataBlock_ {nullptr}; + std::shared_ptr radialDataBlock_ {nullptr}; + std::unordered_map> + momentDataBlock_ {}; +}; + +DigitalRadarDataGeneric::DigitalRadarDataGeneric() : + GenericRadarData(), p(std::make_unique()) +{ +} +DigitalRadarDataGeneric::~DigitalRadarDataGeneric() = default; + +DigitalRadarDataGeneric::DigitalRadarDataGeneric( + DigitalRadarDataGeneric&&) noexcept = default; +DigitalRadarDataGeneric& DigitalRadarDataGeneric::operator=( + DigitalRadarDataGeneric&&) noexcept = default; + +std::string DigitalRadarDataGeneric::radar_identifier() const +{ + return p->radarIdentifier_; +} + +std::uint32_t DigitalRadarDataGeneric::collection_time() const +{ + return p->collectionTime_; +} + +std::uint16_t DigitalRadarDataGeneric::modified_julian_date() const +{ + return p->modifiedJulianDate_; +} + +std::uint16_t DigitalRadarDataGeneric::azimuth_number() const +{ + return p->azimuthNumber_; +} + +units::degrees DigitalRadarDataGeneric::azimuth_angle() const +{ + return units::degrees {p->azimuthAngle_}; +} + +std::uint8_t DigitalRadarDataGeneric::compression_indicator() const +{ + return p->compressionIndicator_; +} + +std::uint16_t DigitalRadarDataGeneric::radial_length() const +{ + return p->radialLength_; +} + +std::uint8_t DigitalRadarDataGeneric::azimuth_resolution_spacing() const +{ + return p->azimuthResolutionSpacing_; +} + +std::uint8_t DigitalRadarDataGeneric::radial_status() const +{ + return p->radialStatus_; +} + +std::uint16_t DigitalRadarDataGeneric::elevation_number() const +{ + return p->elevationNumber_; +} + +std::uint8_t DigitalRadarDataGeneric::cut_sector_number() const +{ + return p->cutSectorNumber_; +} + +units::degrees DigitalRadarDataGeneric::elevation_angle() const +{ + return units::degrees {p->elevationAngle_}; +} + +std::uint8_t DigitalRadarDataGeneric::radial_spot_blanking_status() const +{ + return p->radialSpotBlankingStatus_; +} + +std::uint8_t DigitalRadarDataGeneric::azimuth_indexing_mode() const +{ + return p->azimuthIndexingMode_; +} + +std::uint16_t DigitalRadarDataGeneric::data_block_count() const +{ + return p->dataBlockCount_; +} + +std::uint16_t DigitalRadarDataGeneric::volume_coverage_pattern_number() const +{ + std::uint16_t vcpNumber = 0; + + if (p->volumeDataBlock_ != nullptr) + { + vcpNumber = p->volumeDataBlock_->volume_coverage_pattern_number(); + } + + return vcpNumber; +} + +std::shared_ptr +DigitalRadarDataGeneric::elevation_data_block() const +{ + return p->elevationDataBlock_; +} + +std::shared_ptr +DigitalRadarDataGeneric::radial_data_block() const +{ + return p->radialDataBlock_; +} + +std::shared_ptr +DigitalRadarDataGeneric::volume_data_block() const +{ + return p->volumeDataBlock_; +} + +std::shared_ptr +DigitalRadarDataGeneric::moment_data_block(DataBlockType type) const +{ + std::shared_ptr momentDataBlock = nullptr; + + auto it = p->momentDataBlock_.find(type); + if (it != p->momentDataBlock_.end()) + { + momentDataBlock = it->second; + } + + return momentDataBlock; +} + +bool DigitalRadarDataGeneric::Parse(std::istream& is) +{ + logger_->trace("Parsing Digital Radar Data (Message Type 31)"); + + bool messageValid = true; + std::size_t bytesRead = 0; + + std::streampos isBegin = is.tellg(); + + p->radarIdentifier_.resize(4); + + is.read(&p->radarIdentifier_[0], 4); // 0-3 + is.read(reinterpret_cast(&p->collectionTime_), 4); // 4-7 + is.read(reinterpret_cast(&p->modifiedJulianDate_), 2); // 8-9 + is.read(reinterpret_cast(&p->azimuthNumber_), 2); // 10-11 + is.read(reinterpret_cast(&p->azimuthAngle_), 4); // 12-15 + is.read(reinterpret_cast(&p->compressionIndicator_), 1); // 16 + is.seekg(1, std::ios_base::cur); // 17 + is.read(reinterpret_cast(&p->radialLength_), 2); // 18-19 + is.read(reinterpret_cast(&p->azimuthResolutionSpacing_), 1); // 20 + is.read(reinterpret_cast(&p->radialStatus_), 1); // 21 + is.read(reinterpret_cast(&p->elevationNumber_), 1); // 22 + is.read(reinterpret_cast(&p->cutSectorNumber_), 1); // 23 + is.read(reinterpret_cast(&p->elevationAngle_), 4); // 24-27 + is.read(reinterpret_cast(&p->radialSpotBlankingStatus_), 1); // 28 + is.read(reinterpret_cast(&p->azimuthIndexingMode_), 1); // 29 + is.read(reinterpret_cast(&p->dataBlockCount_), 2); // 30-31 + + p->collectionTime_ = ntohl(p->collectionTime_); + p->modifiedJulianDate_ = ntohs(p->modifiedJulianDate_); + p->azimuthNumber_ = ntohs(p->azimuthNumber_); + p->azimuthAngle_ = SwapFloat(p->azimuthAngle_); + p->radialLength_ = ntohs(p->radialLength_); + p->elevationAngle_ = SwapFloat(p->elevationAngle_); + p->dataBlockCount_ = ntohs(p->dataBlockCount_); + + if (p->azimuthNumber_ < 1 || p->azimuthNumber_ > 720) + { + logger_->warn("Invalid azimuth number: {}", p->azimuthNumber_); + messageValid = false; + } + if (p->elevationNumber_ < 1 || p->elevationNumber_ > 32) + { + logger_->warn("Invalid elevation number: {}", p->elevationNumber_); + messageValid = false; + } + if (p->dataBlockCount_ < 4 || p->dataBlockCount_ > 10) + { + logger_->warn("Invalid number of data blocks: {}", p->dataBlockCount_); + messageValid = false; + } + if (p->compressionIndicator_ != 0) + { + logger_->warn("Compression not supported"); + messageValid = false; + } + + if (!messageValid) + { + p->dataBlockCount_ = 0; + } + + is.read(reinterpret_cast(&p->dataBlockPointer_), + p->dataBlockCount_ * 4); + + SwapArray(p->dataBlockPointer_, p->dataBlockCount_); + + for (uint16_t b = 0; b < p->dataBlockCount_; ++b) + { + is.seekg(isBegin + std::streamoff(p->dataBlockPointer_[b]), + std::ios_base::beg); + + std::string dataBlockType(1, 0); + std::string dataName(3, 0); + + is.read(&dataBlockType[0], 1); + is.read(&dataName[0], 3); + + DataBlockType dataBlock = DataBlockType::Unknown; + try + { + dataBlock = strToDataBlock_.at(dataName); + } + catch (const std::exception&) + { + } + + switch (dataBlock) + { + case DataBlockType::Volume: + p->volumeDataBlock_ = + std::move(VolumeDataBlock::Create(dataBlockType, dataName, is)); + break; + case DataBlockType::Elevation: + p->elevationDataBlock_ = + std::move(ElevationDataBlock::Create(dataBlockType, dataName, is)); + break; + case DataBlockType::Radial: + p->radialDataBlock_ = + std::move(RadialDataBlock::Create(dataBlockType, dataName, is)); + break; + case DataBlockType::MomentRef: + case DataBlockType::MomentVel: + case DataBlockType::MomentSw: + case DataBlockType::MomentZdr: + case DataBlockType::MomentPhi: + case DataBlockType::MomentRho: + case DataBlockType::MomentCfp: + p->momentDataBlock_[dataBlock] = + std::move(MomentDataBlock::Create(dataBlockType, dataName, is)); + break; + default: + logger_->warn("Unknown data name: {}", dataName); + break; + } + } + + is.seekg(isBegin, std::ios_base::beg); + if (!ValidateMessage(is, bytesRead)) + { + messageValid = false; + } + + return messageValid; +} + +std::shared_ptr +DigitalRadarDataGeneric::Create(Level2MessageHeader&& header, std::istream& is) +{ + std::shared_ptr message = + std::make_shared(); + message->set_header(std::move(header)); + + if (!message->Parse(is)) + { + message.reset(); + } + + return message; +} + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp b/wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp new file mode 100644 index 00000000..1ca7e237 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/generic_radar_data.cpp @@ -0,0 +1,61 @@ +#include +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = "scwx::wsr88d::rda::generic_radar_data"; + +static const std::unordered_map strToDataBlock_ { + {"VOL", DataBlockType::Volume}, + {"ELV", DataBlockType::Elevation}, + {"RAD", DataBlockType::Radial}, + {"REF", DataBlockType::MomentRef}, + {"VEL", DataBlockType::MomentVel}, + {"SW ", DataBlockType::MomentSw}, + {"ZDR", DataBlockType::MomentZdr}, + {"PHI", DataBlockType::MomentPhi}, + {"RHO", DataBlockType::MomentRho}, + {"CFP", DataBlockType::MomentCfp}}; + +class GenericRadarData::MomentDataBlock::Impl +{ +public: + explicit Impl() {} +}; + +GenericRadarData::MomentDataBlock::MomentDataBlock() : + p(std::make_unique()) +{ +} +GenericRadarData::MomentDataBlock::~MomentDataBlock() = default; + +GenericRadarData::MomentDataBlock::MomentDataBlock(MomentDataBlock&&) noexcept = + default; +GenericRadarData::MomentDataBlock& GenericRadarData::MomentDataBlock::operator=( + MomentDataBlock&&) noexcept = default; + +class GenericRadarData::Impl +{ +public: + explicit Impl() {}; + ~Impl() = default; +}; + +GenericRadarData::GenericRadarData() : + Level2Message(), p(std::make_unique()) +{ +} +GenericRadarData::~GenericRadarData() = default; + +GenericRadarData::GenericRadarData(GenericRadarData&&) noexcept = default; +GenericRadarData& +GenericRadarData::operator=(GenericRadarData&&) noexcept = default; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp index 48d2d8d7..2a478a2f 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_factory.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -28,14 +29,15 @@ typedef std::function(Level2MessageHeader&&, std::istream&)> CreateLevel2MessageFunction; -static const std::unordered_map create_ { - {2, RdaStatusData::Create}, - {3, PerformanceMaintenanceData::Create}, - {5, VolumeCoveragePatternData::Create}, - {13, ClutterFilterBypassMap::Create}, - {15, ClutterFilterMap::Create}, - {18, RdaAdaptationData::Create}, - {31, DigitalRadarData::Create}}; +static const std::unordered_map + create_ {{1, DigitalRadarData::Create}, + {2, RdaStatusData::Create}, + {3, PerformanceMaintenanceData::Create}, + {5, VolumeCoveragePatternData::Create}, + {13, ClutterFilterBypassMap::Create}, + {15, ClutterFilterMap::Create}, + {18, RdaAdaptationData::Create}, + {31, DigitalRadarDataGeneric::Create}}; struct Level2MessageFactory::Context { @@ -51,6 +53,7 @@ struct Level2MessageFactory::Context size_t bufferedSize_; util::vectorbuf messageBuffer_; std::istream messageBufferStream_; + bool bufferingData_ {false}; }; std::shared_ptr @@ -59,14 +62,38 @@ Level2MessageFactory::CreateContext() return std::make_shared(); } -Level2MessageInfo Level2MessageFactory::Create(std::istream& is, - std::shared_ptr ctx) +Level2MessageInfo Level2MessageFactory::Create(std::istream& is, + std::shared_ptr& ctx) { Level2MessageInfo info; Level2MessageHeader header; info.headerValid = header.Parse(is); info.messageValid = info.headerValid; + std::uint16_t segment = 0; + std::uint16_t totalSegments = 0; + std::size_t dataSize = 0; + + if (info.headerValid) + { + if (header.message_size() == 65535) + { + segment = 1; + totalSegments = 1; + dataSize = + (static_cast(header.number_of_message_segments()) + << 16) + + header.message_segment_number(); + } + else + { + segment = header.message_segment_number(); + totalSegments = header.number_of_message_segments(); + dataSize = static_cast(header.message_size()) * 2 - + Level2MessageHeader::SIZE; + } + } + if (info.headerValid && create_.find(header.message_type()) == create_.end()) { logger_->warn("Unknown message type: {}", @@ -76,10 +103,7 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, if (info.messageValid) { - uint16_t segment = header.message_segment_number(); - uint16_t totalSegments = header.number_of_message_segments(); - uint8_t messageType = header.message_type(); - size_t dataSize = header.message_size() * 2 - Level2MessageHeader::SIZE; + std::uint8_t messageType = header.message_type(); std::istream* messageStream = nullptr; @@ -100,38 +124,51 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, // Estimate total message size ctx->messageData_.resize(dataSize * totalSegments); ctx->messageBufferStream_.clear(); - ctx->bufferedSize_ = 0; + ctx->bufferedSize_ = 0; + ctx->bufferingData_ = true; } - - if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize) + else if (!ctx->bufferingData_) { - logger_->debug("Bad size estimate, increasing size"); - - // Estimate remaining size - uint16_t remainingSegments = - std::max(totalSegments - segment + 1, 100u); - size_t remainingSize = remainingSegments * dataSize; - - ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); - } - - is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); - ctx->bufferedSize_ += dataSize; - - if (is.eof()) - { - logger_->warn("End of file reached trying to buffer message"); + // Segment number did not start at 1 + logger_->trace("Ignoring Segment {}/{}, did not start at 1", + segment, + totalSegments); info.messageValid = false; - ctx->messageData_.shrink_to_fit(); - ctx->bufferedSize_ = 0; } - else if (segment == totalSegments) - { - ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); - header.set_message_size(static_cast( - ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); - messageStream = &ctx->messageBufferStream_; + if (ctx->bufferingData_) + { + if (ctx->messageData_.capacity() < ctx->bufferedSize_ + dataSize) + { + logger_->debug("Bad size estimate, increasing size"); + + // Estimate remaining size + uint16_t remainingSegments = + std::max(totalSegments - segment + 1, 100u); + size_t remainingSize = remainingSegments * dataSize; + + ctx->messageData_.resize(ctx->bufferedSize_ + remainingSize); + } + + is.read(ctx->messageData_.data() + ctx->bufferedSize_, dataSize); + ctx->bufferedSize_ += dataSize; + + if (is.eof()) + { + logger_->warn("End of file reached trying to buffer message"); + info.messageValid = false; + ctx->messageData_.shrink_to_fit(); + ctx->bufferedSize_ = 0; + ctx->bufferingData_ = false; + } + else if (segment == totalSegments) + { + ctx->messageBuffer_.update_read_pointers(ctx->bufferedSize_); + header.set_message_size(static_cast( + ctx->bufferedSize_ / 2 + Level2MessageHeader::SIZE)); + + messageStream = &ctx->messageBufferStream_; + } } } @@ -142,14 +179,14 @@ Level2MessageInfo Level2MessageFactory::Create(std::istream& is, ctx->messageData_.resize(0); ctx->messageData_.shrink_to_fit(); ctx->messageBufferStream_.clear(); - ctx->bufferedSize_ = 0; + ctx->bufferedSize_ = 0; + ctx->bufferingData_ = false; } } else if (info.headerValid) { // Seek to the end of the current message - is.seekg(header.message_size() * 2 - rda::Level2MessageHeader::SIZE, - std::ios_base::cur); + is.seekg(dataSize, std::ios_base::cur); } if (info.message == nullptr) diff --git a/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp b/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp index 2d450bf4..a8ed4125 100644 --- a/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp +++ b/wxdata/source/scwx/wsr88d/rda/level2_message_header.cpp @@ -130,12 +130,10 @@ bool Level2MessageHeader::Parse(std::istream& is) { if (p->messageSize_ < 9) { - logger_->warn("Invalid message size: {}", p->messageSize_); - headerValid = false; - } - if (p->julianDate_ < 1) - { - logger_->warn("Invalid date: {}", p->julianDate_); + if (p->messageSize_ != 0) + { + logger_->warn("Invalid message size: {}", p->messageSize_); + } headerValid = false; } if (p->millisecondsOfDay_ > 86'399'999u) diff --git a/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp b/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp index 72943869..d2f3dec2 100644 --- a/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp +++ b/wxdata/source/scwx/wsr88d/rda/volume_coverage_pattern_data.cpp @@ -102,7 +102,7 @@ VolumeCoveragePatternData::VolumeCoveragePatternData() : VolumeCoveragePatternData::~VolumeCoveragePatternData() = default; VolumeCoveragePatternData::VolumeCoveragePatternData( - VolumeCoveragePatternData&&) noexcept = default; + VolumeCoveragePatternData&&) noexcept = default; VolumeCoveragePatternData& VolumeCoveragePatternData::operator=( VolumeCoveragePatternData&&) noexcept = default; @@ -419,16 +419,24 @@ bool VolumeCoveragePatternData::Parse(std::istream& is) p->vcpSequencing_ = ntohs(p->vcpSequencing_); p->vcpSupplementalData_ = ntohs(p->vcpSupplementalData_); - if (messageSize < 34 || messageSize > 747) + if (messageSize == 0) { - logger_->warn("Invalid message size: {}", messageSize); + logger_->trace("Ignoring empty message"); messageValid = false; } - if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) + else { - logger_->warn("Invalid number of elevation cuts: {}", - numberOfElevationCuts); - messageValid = false; + if (messageSize < 34 || messageSize > 747) + { + logger_->warn("Invalid message size: {}", messageSize); + messageValid = false; + } + if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) + { + logger_->warn("Invalid number of elevation cuts: {}", + numberOfElevationCuts); + messageValid = false; + } } if (!messageValid) diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index d0170819..0652d898 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -103,17 +103,21 @@ set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_bypass_map.hpp include/scwx/wsr88d/rda/clutter_filter_map.hpp include/scwx/wsr88d/rda/digital_radar_data.hpp + include/scwx/wsr88d/rda/digital_radar_data_generic.hpp + include/scwx/wsr88d/rda/generic_radar_data.hpp include/scwx/wsr88d/rda/level2_message.hpp include/scwx/wsr88d/rda/level2_message_factory.hpp include/scwx/wsr88d/rda/level2_message_header.hpp include/scwx/wsr88d/rda/performance_maintenance_data.hpp include/scwx/wsr88d/rda/rda_adaptation_data.hpp include/scwx/wsr88d/rda/rda_status_data.hpp - include/scwx/wsr88d/rda/types.hpp + include/scwx/wsr88d/rda/rda_types.hpp include/scwx/wsr88d/rda/volume_coverage_pattern_data.hpp) set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_bypass_map.cpp source/scwx/wsr88d/rda/clutter_filter_map.cpp source/scwx/wsr88d/rda/digital_radar_data.cpp + source/scwx/wsr88d/rda/digital_radar_data_generic.cpp + source/scwx/wsr88d/rda/generic_radar_data.cpp source/scwx/wsr88d/rda/level2_message.cpp source/scwx/wsr88d/rda/level2_message_factory.cpp source/scwx/wsr88d/rda/level2_message_header.cpp