From c7a4706f43315c2b5ae58f8ac492e635df6a2f4a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 9 Jun 2023 00:10:47 -0500 Subject: [PATCH 1/7] Loop delay configuration --- scwx-qt/source/scwx/qt/main/main_window.cpp | 4 ++ .../scwx/qt/manager/timeline_manager.cpp | 12 +++++- .../scwx/qt/manager/timeline_manager.hpp | 1 + .../scwx/qt/ui/animation_dock_widget.cpp | 10 +++++ .../scwx/qt/ui/animation_dock_widget.hpp | 1 + .../scwx/qt/ui/animation_dock_widget.ui | 42 +++++++++++++++---- 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 07945d18..4d258327 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -672,6 +672,10 @@ void MainWindowImpl::ConnectAnimationSignals() &ui::AnimationDockWidget::LoopSpeedChanged, timelineManager_.get(), &manager::TimelineManager::SetLoopSpeed); + connect(animationDockWidget_, + &ui::AnimationDockWidget::LoopDelayChanged, + timelineManager_.get(), + &manager::TimelineManager::SetLoopDelay); connect(animationDockWidget_, &ui::AnimationDockWidget::AnimationStepBeginSelected, timelineManager_.get(), diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 606a58ca..ce9283f5 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -71,6 +71,7 @@ public: types::MapTime viewType_ {types::MapTime::Live}; std::chrono::minutes loopTime_ {30}; double loopSpeed_ {5.0}; + std::chrono::milliseconds loopDelay_ {2500}; bool radarSweepMonitorActive_ {false}; std::mutex radarSweepMonitorMutex_ {}; @@ -170,6 +171,13 @@ void TimelineManager::SetLoopSpeed(double loopSpeed) p->loopSpeed_ = loopSpeed; } +void TimelineManager::SetLoopDelay(std::chrono::milliseconds loopDelay) +{ + logger_->debug("SetLoopDelay: {}", loopDelay); + + p->loopDelay_ = loopDelay; +} + void TimelineManager::AnimationStepBegin() { logger_->debug("AnimationStepBegin"); @@ -417,8 +425,8 @@ void TimelineManager::Impl::Play() } else { - // Pause for 2.5 seconds at the end of the loop - interval = std::chrono::milliseconds(2500); + // Pause at the end of the loop + interval = loopDelay_; } animationTimer_.expires_after(interval); diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index 0751fed1..1a4154ac 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -34,6 +34,7 @@ public slots: void SetLoopTime(std::chrono::minutes loopTime); void SetLoopSpeed(double loopSpeed); + void SetLoopDelay(std::chrono::milliseconds loopDelay); void AnimationStepBegin(); void AnimationStepBack(); diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 4714b2e3..5bfc5a89 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -88,6 +88,7 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : // Set loop defaults ui->loopTimeSpinBox->setValue(30); ui->loopSpeedSpinBox->setValue(5.0); + ui->loopDelaySpinBox->setValue(2.5); // Connect widget signals p->ConnectSignals(); @@ -161,6 +162,15 @@ void AnimationDockWidgetImpl::ConnectSignals() &QDoubleSpinBox::valueChanged, self_, [this](double d) { Q_EMIT self_->LoopSpeedChanged(d); }); + QObject::connect( + self_->ui->loopDelaySpinBox, + &QDoubleSpinBox::valueChanged, + self_, + [this](double d) + { + Q_EMIT self_->LoopDelayChanged(std::chrono::milliseconds( + static_cast(d * 1000.0))); + }); // Animation controls QObject::connect(self_->ui->beginButton, diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp index 705ca02f..ea42a541 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -38,6 +38,7 @@ signals: void LoopTimeChanged(std::chrono::minutes loopTime); void LoopSpeedChanged(double loopSpeed); + void LoopDelayChanged(std::chrono::milliseconds loopDelay); void AnimationStepBeginSelected(); void AnimationStepBackSelected(); diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui index 4e3502ca..8a3ab3b8 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -7,7 +7,7 @@ 0 0 200 - 337 + 348 @@ -130,13 +130,6 @@ 0 - - - - Loop Time - - - @@ -156,6 +149,13 @@ + + + + Loop Time + + + @@ -179,6 +179,32 @@ + + + + Loop Delay + + + + + + + sec + + + 1 + + + 15.000000000000000 + + + 0.100000000000000 + + + 2.500000000000000 + + + From ca61ed257db25e10b9604526fee0523b3fd10007 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 9 Jun 2023 21:51:30 -0500 Subject: [PATCH 2/7] Calculate coordinates for one past the number of level 2 data moment gates Fixes the "lightsaber" effect toward (0, 0), tweak to #53 --- scwx-qt/source/scwx/qt/view/level2_product_view.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 06c912bc..f55c2fb2 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -725,7 +725,8 @@ void Level2ProductViewImpl::ComputeCoordinates( const std::uint16_t numRadials = static_cast(radarData->size()); const std::uint16_t numRangeBins = - momentData0->number_of_data_moment_gates(); + std::max(momentData0->number_of_data_moment_gates() + 1u, + common::MAX_DATA_MOMENT_GATES); auto radials = boost::irange(0u, numRadials); auto gates = boost::irange(0u, numRangeBins); From 580534d39641508ffcd48a9bddb1f887791d68a3 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 10 Jun 2023 23:00:42 -0500 Subject: [PATCH 3/7] Protect against invalidated iterator in radar product manager --- scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 1d3d1126..dd7c8d35 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -1192,15 +1192,17 @@ void RadarProductManagerImpl::UpdateRecentRecords( std::shared_ptr record) { const std::size_t recentListMaxSize {cacheLimit_}; + bool iteratorErased = false; auto it = std::find(recentList.cbegin(), recentList.cend(), record); if (it != recentList.cbegin() && it != recentList.cend()) { // If the record exists beyond the front of the list, remove it recentList.erase(it); + iteratorErased = true; } - if (recentList.size() == 0 || it != recentList.cbegin()) + if (iteratorErased || recentList.size() == 0 || it != recentList.cbegin()) { // Add the record to the front of the list, unless it's already there recentList.push_front(record); From 9bb4ba4d93d0934bf051d8f85d67de4accb7aa1c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 10 Jun 2023 23:27:58 -0500 Subject: [PATCH 4/7] Only use condition variable synchronization for animation, not other timeline functionality --- .../scwx/qt/manager/timeline_manager.cpp | 221 +++++++++--------- 1 file changed, 110 insertions(+), 111 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index ce9283f5..7bcb9b64 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -59,8 +59,11 @@ public: void Pause(); void Play(); - void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); - void Step(Direction direction); + void + SelectTimeAsync(std::chrono::system_clock::time_point selectedTime = {}); + std::pair + SelectTime(std::chrono::system_clock::time_point selectedTime = {}); + void StepAsync(Direction direction); std::size_t mapCount_ {0}; std::string radarSite_ {"?"}; @@ -114,7 +117,7 @@ void TimelineManager::SetRadarSite(const std::string& radarSite) else { // If the selected view type is archive, select using the selected time - p->SelectTime(p->selectedTime_); + p->SelectTimeAsync(p->selectedTime_); } } @@ -128,7 +131,7 @@ void TimelineManager::SetDateTime( if (p->viewType_ == types::MapTime::Archive) { // Only select if the view type is archive - p->SelectTime(dateTime); + p->SelectTimeAsync(dateTime); } // Ignore a date/time selection if the view type is live @@ -148,7 +151,7 @@ void TimelineManager::SetViewType(types::MapTime viewType) else { // If the selected view type is archive, select using the pinned time - p->SelectTime(p->pinnedTime_); + p->SelectTimeAsync(p->pinnedTime_); } } @@ -188,12 +191,12 @@ void TimelineManager::AnimationStepBegin() p->pinnedTime_ == std::chrono::system_clock::time_point {}) { // If the selected view type is live, select the current products - p->SelectTime(std::chrono::system_clock::now() - p->loopTime_); + p->SelectTimeAsync(std::chrono::system_clock::now() - p->loopTime_); } else { // If the selected view type is archive, select using the pinned time - p->SelectTime(p->pinnedTime_ - p->loopTime_); + p->SelectTimeAsync(p->pinnedTime_ - p->loopTime_); } } @@ -202,7 +205,7 @@ void TimelineManager::AnimationStepBack() logger_->debug("AnimationStepBack"); p->Pause(); - p->Step(Direction::Back); + p->StepAsync(Direction::Back); } void TimelineManager::AnimationPlayPause() @@ -224,7 +227,7 @@ void TimelineManager::AnimationStepNext() logger_->debug("AnimationStepNext"); p->Pause(); - p->Step(Direction::Next); + p->StepAsync(Direction::Next); } void TimelineManager::AnimationStepEnd() @@ -241,7 +244,7 @@ void TimelineManager::AnimationStepEnd() else { // If the selected view type is archive, select using the pinned time - p->SelectTime(p->pinnedTime_); + p->SelectTimeAsync(p->pinnedTime_); } } @@ -415,30 +418,50 @@ void TimelineManager::Impl::Play() newTime = currentTime + 1min; } + // Unlock prior to selecting time + lock.unlock(); + + // Lock radar sweep monitor + std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_}; + + // Reset radar sweep monitor in preparation for update + RadarSweepMonitorReset(); + + // Select the time + auto selectTimeStart = std::chrono::steady_clock::now(); + auto [volumeTimeUpdated, selectedTimeUpdated] = SelectTime(newTime); + auto selectTimeEnd = std::chrono::steady_clock::now(); + auto elapsedTime = selectTimeEnd - selectTimeStart; + + if (volumeTimeUpdated) + { + // Wait for radar sweeps to update + RadarSweepMonitorWait(radarSweepMonitorLock); + } + else + { + // Disable radar sweep monitor + RadarSweepMonitorDisable(); + } + // Calculate the interval until the next update, prior to selecting std::chrono::milliseconds interval; if (newTime != endTime) { // Determine repeat interval (speed of 1.0 is 1 minute per second) - interval = - std::chrono::milliseconds(std::lroundl(1000.0 / loopSpeed_)); + interval = std::chrono::duration_cast( + std::chrono::milliseconds(std::lroundl(1000.0 / loopSpeed_)) - + elapsedTime); } else { // Pause at the end of the loop - interval = loopDelay_; + interval = std::chrono::duration_cast( + loopDelay_ - elapsedTime); } - animationTimer_.expires_after(interval); - - // Unlock prior to selecting time - lock.unlock(); - - // Select the time - SelectTime(newTime); - std::unique_lock animationTimerLock {animationTimerMutex_}; - + animationTimer_.expires_after(interval); animationTimer_.async_wait( [this](const boost::system::error_code& e) { @@ -461,118 +484,94 @@ void TimelineManager::Impl::Play() }); } -void TimelineManager::Impl::SelectTime( +void TimelineManager::Impl::SelectTimeAsync( std::chrono::system_clock::time_point selectedTime) { + scwx::util::async([=, this]() { SelectTime(selectedTime); }); +} + +std::pair TimelineManager::Impl::SelectTime( + std::chrono::system_clock::time_point selectedTime) +{ + bool volumeTimeUpdated = false; + bool selectedTimeUpdated = false; + if (selectedTime_ == selectedTime && radarSite_ == previousRadarSite_) { // Nothing to do - return; + return {volumeTimeUpdated, selectedTimeUpdated}; } else if (selectedTime == std::chrono::system_clock::time_point {}) { - scwx::util::async( - [=, this]() - { - // Take a lock for time selection - std::unique_lock lock {selectTimeMutex_}; + // If a default time point is given, reset to a live view + selectedTime_ = selectedTime; + adjustedTime_ = selectedTime; + previousRadarSite_ = radarSite_; - // If a default time point is given, reset to a live view - selectedTime_ = selectedTime; - adjustedTime_ = selectedTime; + logger_->debug("Time updated: Live"); - logger_->debug("Time updated: Live"); + Q_EMIT self_->LiveStateUpdated(true); + Q_EMIT self_->VolumeTimeUpdated(selectedTime); + Q_EMIT self_->SelectedTimeUpdated(selectedTime); - std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_}; + volumeTimeUpdated = true; + selectedTimeUpdated = true; - // Reset radar sweep monitor in preparation for update - RadarSweepMonitorReset(); - - Q_EMIT self_->LiveStateUpdated(true); - Q_EMIT self_->VolumeTimeUpdated(selectedTime); - Q_EMIT self_->SelectedTimeUpdated(selectedTime); - - // Wait for radar sweeps to update - RadarSweepMonitorWait(radarSweepMonitorLock); - }); - - return; + return {volumeTimeUpdated, selectedTimeUpdated}; } - scwx::util::async( - [=, this]() + // Take a lock for time selection + std::unique_lock lock {selectTimeMutex_}; + + // Request active volume times + auto radarProductManager = + manager::RadarProductManager::Instance(radarSite_); + auto volumeTimes = radarProductManager->GetActiveVolumeTimes(selectedTime); + + // Dynamically update maximum cached volume scans + UpdateCacheLimit(radarProductManager, volumeTimes); + + // Find the best match bounded time + auto elementPtr = util::GetBoundedElementPointer(volumeTimes, selectedTime); + + // The timeline is no longer live + Q_EMIT self_->LiveStateUpdated(false); + + if (elementPtr != nullptr) + { + // If the adjusted time changed, or if a new radar site has been selected + if (adjustedTime_ != *elementPtr || radarSite_ != previousRadarSite_) { - // Take a lock for time selection - std::unique_lock lock {selectTimeMutex_}; + // If the time was found, select it + adjustedTime_ = *elementPtr; - // Request active volume times - auto radarProductManager = - manager::RadarProductManager::Instance(radarSite_); - auto volumeTimes = - radarProductManager->GetActiveVolumeTimes(selectedTime); + logger_->debug("Volume time updated: {}", + scwx::util::TimeString(adjustedTime_)); - // Dynamically update maximum cached volume scans - UpdateCacheLimit(radarProductManager, volumeTimes); + volumeTimeUpdated = true; + Q_EMIT self_->VolumeTimeUpdated(adjustedTime_); + } + } + else + { + // No volume time was found + logger_->info("No volume scan found for {}", + scwx::util::TimeString(selectedTime)); + } - // Find the best match bounded time - auto elementPtr = - util::GetBoundedElementPointer(volumeTimes, selectedTime); + logger_->trace("Selected time updated: {}", + scwx::util::TimeString(selectedTime)); - // The timeline is no longer live - Q_EMIT self_->LiveStateUpdated(false); + selectedTime_ = selectedTime; + selectedTimeUpdated = true; + Q_EMIT self_->SelectedTimeUpdated(selectedTime); - bool volumeTimeUpdated = false; + previousRadarSite_ = radarSite_; - std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_}; - - // Reset radar sweep monitor in preparation for update - RadarSweepMonitorReset(); - - if (elementPtr != nullptr) - { - // If the adjusted time changed, or if a new radar site has been - // selected - if (adjustedTime_ != *elementPtr || - radarSite_ != previousRadarSite_) - { - // If the time was found, select it - adjustedTime_ = *elementPtr; - - logger_->debug("Volume time updated: {}", - scwx::util::TimeString(adjustedTime_)); - - volumeTimeUpdated = true; - Q_EMIT self_->VolumeTimeUpdated(adjustedTime_); - } - } - else - { - // No volume time was found - logger_->info("No volume scan found for {}", - scwx::util::TimeString(selectedTime)); - } - - logger_->trace("Selected time updated: {}", - scwx::util::TimeString(selectedTime)); - - selectedTime_ = selectedTime; - Q_EMIT self_->SelectedTimeUpdated(selectedTime); - - previousRadarSite_ = radarSite_; - - if (volumeTimeUpdated) - { - // Wait for radar sweeps to update - RadarSweepMonitorWait(radarSweepMonitorLock); - } - else - { - RadarSweepMonitorDisable(); - } - }); + return {volumeTimeUpdated, selectedTimeUpdated}; } -void TimelineManager::Impl::Step(Direction direction) +void TimelineManager::Impl::StepAsync(Direction direction) { scwx::util::async( [=, this]() From 4e5a28fcab5969b56a13fb2c0b5af0e443046a29 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 10 Jun 2023 23:47:42 -0500 Subject: [PATCH 5/7] Auto update should be re-enabled when switching radar sites - Otherwise, the new site may not display any data --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 6d0146c0..05ce34f4 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1053,6 +1053,9 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite) // Set new RadarProductManager radarProductManager_ = manager::RadarProductManager::Instance(radarSite); + // Re-enable auto-update + autoUpdateEnabled_ = true; + // Connect signals to new RadarProductManager RadarProductManagerConnect(); From 96db63d5f35deb0e1879eb832f418e9ec0e8d2b2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 12 Jun 2023 00:10:56 -0500 Subject: [PATCH 6/7] If the time in the filename and file data differ, the filename should take precedence This is required for properly indexing the file. If the file data is used, the data is stored under the file data index. Before the file is loaded, the data retrieval is attempted using the filename as the time. --- .../scwx/qt/manager/radar_product_manager.cpp | 18 +++++++++++++++--- .../scwx/qt/types/radar_product_record.cpp | 5 +++++ .../scwx/qt/types/radar_product_record.hpp | 4 +++- 3 files changed, 23 insertions(+), 4 deletions(-) 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 dd7c8d35..e9bb8463 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -210,7 +210,8 @@ public: static void LoadNexradFile(CreateNexradFileFunction load, std::shared_ptr request, - std::mutex& mutex); + std::mutex& mutex, + std::chrono::system_clock::time_point time = {}); const std::string radarId_; bool initialized_; @@ -801,7 +802,8 @@ void RadarProductManagerImpl::LoadProviderData( return nexradFile; }, request, - loadDataMutex); + loadDataMutex, + time); } void RadarProductManager::LoadLevel2Data( @@ -912,7 +914,8 @@ void RadarProductManager::LoadFile( void RadarProductManagerImpl::LoadNexradFile( CreateNexradFileFunction load, std::shared_ptr request, - std::mutex& mutex) + std::mutex& mutex, + std::chrono::system_clock::time_point time) { scwx::util::async( [=, &mutex]() @@ -929,6 +932,15 @@ void RadarProductManagerImpl::LoadNexradFile( { record = types::RadarProductRecord::Create(nexradFile); + // If the time is already determined, override the time in the file. + // Sometimes, level 2 data has been seen to be a few seconds off + // between filename and file data. Overriding this can help prevent + // issues with locating and storing the correct records. + if (time != std::chrono::system_clock::time_point {}) + { + record->set_time(time); + } + std::shared_ptr manager = RadarProductManager::Instance(record->radar_id()); diff --git a/scwx-qt/source/scwx/qt/types/radar_product_record.cpp b/scwx-qt/source/scwx/qt/types/radar_product_record.cpp index ce402342..6a1953e3 100644 --- a/scwx-qt/source/scwx/qt/types/radar_product_record.cpp +++ b/scwx-qt/source/scwx/qt/types/radar_product_record.cpp @@ -133,6 +133,11 @@ std::chrono::system_clock::time_point RadarProductRecord::time() const return p->time_; } +void RadarProductRecord::set_time(std::chrono::system_clock::time_point time) +{ + p->time_ = time; +} + std::shared_ptr RadarProductRecord::Create(std::shared_ptr nexradFile) { diff --git a/scwx-qt/source/scwx/qt/types/radar_product_record.hpp b/scwx-qt/source/scwx/qt/types/radar_product_record.hpp index ab1775d0..dd27ccbc 100644 --- a/scwx-qt/source/scwx/qt/types/radar_product_record.hpp +++ b/scwx-qt/source/scwx/qt/types/radar_product_record.hpp @@ -22,7 +22,7 @@ public: explicit RadarProductRecord(std::shared_ptr nexradFile); ~RadarProductRecord(); - RadarProductRecord(const RadarProductRecord&) = delete; + RadarProductRecord(const RadarProductRecord&) = delete; RadarProductRecord& operator=(const RadarProductRecord&) = delete; RadarProductRecord(RadarProductRecord&&) noexcept; @@ -38,6 +38,8 @@ public: std::string site_id() const; std::chrono::system_clock::time_point time() const; + void set_time(std::chrono::system_clock::time_point time); + static std::shared_ptr Create(std::shared_ptr nexradFile); From 774d08a63e9ec01374631ee1e17a3797a52c2282 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 13 Jun 2023 19:02:18 -0500 Subject: [PATCH 7/7] Fix level 2 empty radial data crash --- wxdata/source/scwx/wsr88d/ar2v_file.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 87d4a37f..72368076 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -422,6 +422,12 @@ void Ar2vFileImpl::IndexFile() std::shared_ptr radial0 = (*elevationCut.second)[0]; + if (radial0 == nullptr) + { + logger_->warn("Empty radial data"); + continue; + } + for (rda::DataBlockType dataBlockType : rda::MomentDataBlockTypeIterator()) {