From 0d44513d0a00f9bb5a10e4339e35834570e5abd0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 6 May 2023 23:22:02 -0500 Subject: [PATCH 01/50] Add animation dock widget --- .../res/icons/font-awesome-6/pause-solid.svg | 1 + .../res/icons/font-awesome-6/play-solid.svg | 1 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 2 + scwx-qt/source/scwx/qt/main/main_window.cpp | 28 ++- scwx-qt/source/scwx/qt/main/main_window.ui | 8 +- .../scwx/qt/ui/animation_dock_widget.cpp | 38 ++++ .../scwx/qt/ui/animation_dock_widget.hpp | 35 +++ .../scwx/qt/ui/animation_dock_widget.ui | 201 ++++++++++++++++++ 9 files changed, 309 insertions(+), 8 deletions(-) create mode 100644 scwx-qt/res/icons/font-awesome-6/pause-solid.svg create mode 100644 scwx-qt/res/icons/font-awesome-6/play-solid.svg create mode 100644 scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp create mode 100644 scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp create mode 100644 scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui diff --git a/scwx-qt/res/icons/font-awesome-6/pause-solid.svg b/scwx-qt/res/icons/font-awesome-6/pause-solid.svg new file mode 100644 index 00000000..57ee1e2e --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/pause-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/res/icons/font-awesome-6/play-solid.svg b/scwx-qt/res/icons/font-awesome-6/play-solid.svg new file mode 100644 index 00000000..1298e049 --- /dev/null +++ b/scwx-qt/res/icons/font-awesome-6/play-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index becb5901..1618d1a5 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -142,6 +142,7 @@ set(SRC_TYPES source/scwx/qt/types/alert_types.cpp set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/alert_dialog.hpp source/scwx/qt/ui/alert_dock_widget.hpp + source/scwx/qt/ui/animation_dock_widget.hpp source/scwx/qt/ui/flow_layout.hpp source/scwx/qt/ui/imgui_debug_dialog.hpp source/scwx/qt/ui/imgui_debug_widget.hpp @@ -154,6 +155,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp set(SRC_UI source/scwx/qt/ui/about_dialog.cpp source/scwx/qt/ui/alert_dialog.cpp source/scwx/qt/ui/alert_dock_widget.cpp + source/scwx/qt/ui/animation_dock_widget.cpp source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/imgui_debug_dialog.cpp source/scwx/qt/ui/imgui_debug_widget.cpp @@ -166,6 +168,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp set(UI_UI source/scwx/qt/ui/about_dialog.ui source/scwx/qt/ui/alert_dialog.ui source/scwx/qt/ui/alert_dock_widget.ui + source/scwx/qt/ui/animation_dock_widget.ui source/scwx/qt/ui/imgui_debug_dialog.ui source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/settings_dialog.ui diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index c708db9e..36618bee 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -24,6 +24,8 @@ res/icons/font-awesome-6/gears-solid.svg res/icons/font-awesome-6/github.svg res/icons/font-awesome-6/palette-solid.svg + res/icons/font-awesome-6/pause-solid.svg + res/icons/font-awesome-6/play-solid.svg res/icons/font-awesome-6/rotate-left-solid.svg res/icons/font-awesome-6/sliders-solid.svg res/icons/font-awesome-6/square-minus-regular.svg diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 452aeeb8..95a9af27 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ public: level2SettingsWidget_ {nullptr}, level3ProductsWidget_ {nullptr}, alertDockWidget_ {nullptr}, + animationDockWidget_ {nullptr}, aboutDialog_ {nullptr}, imGuiDebugDialog_ {nullptr}, radarSiteDialog_ {nullptr}, @@ -138,12 +140,13 @@ public: ui::Level3ProductsWidget* level3ProductsWidget_; - ui::AlertDockWidget* alertDockWidget_; - ui::AboutDialog* aboutDialog_; - ui::ImGuiDebugDialog* imGuiDebugDialog_; - ui::RadarSiteDialog* radarSiteDialog_; - ui::SettingsDialog* settingsDialog_; - ui::UpdateDialog* updateDialog_; + ui::AlertDockWidget* alertDockWidget_; + ui::AnimationDockWidget* animationDockWidget_; + ui::AboutDialog* aboutDialog_; + ui::ImGuiDebugDialog* imGuiDebugDialog_; + ui::RadarSiteDialog* radarSiteDialog_; + ui::SettingsDialog* settingsDialog_; + ui::UpdateDialog* updateDialog_; std::unique_ptr radarProductModel_; std::shared_ptr textEventManager_; @@ -182,12 +185,23 @@ MainWindow::MainWindow(QWidget* parent) : p->alertDockWidget_->setVisible(false); addDockWidget(Qt::BottomDockWidgetArea, p->alertDockWidget_); + // Animation Dock Widget + p->animationDockWidget_ = new ui::AnimationDockWidget(this); + p->animationDockWidget_->setVisible(true); + addDockWidget(Qt::LeftDockWidgetArea, p->animationDockWidget_); + // Configure Menu ui->menuView->insertAction(ui->actionRadarToolbox, ui->radarToolboxDock->toggleViewAction()); ui->radarToolboxDock->toggleViewAction()->setText(tr("Radar &Toolbox")); ui->actionRadarToolbox->setVisible(false); + ui->menuView->insertAction(ui->actionAnimationToolbox, + p->animationDockWidget_->toggleViewAction()); + p->animationDockWidget_->toggleViewAction()->setText( + tr("A&nimation Toolbox")); + ui->actionAnimationToolbox->setVisible(false); + ui->menuView->insertAction(ui->actionResourceExplorer, ui->resourceExplorerDock->toggleViewAction()); ui->resourceExplorerDock->toggleViewAction()->setText( @@ -273,7 +287,7 @@ void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); - resizeDocks({ui->radarToolboxDock}, {150}, Qt::Horizontal); + resizeDocks({ui->radarToolboxDock}, {188}, Qt::Horizontal); } void MainWindow::on_actionOpenNexrad_triggered() diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 0d5c5489..33cf502f 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -76,6 +76,7 @@ &View + @@ -359,7 +360,7 @@ - Radar Toolbox + Radar &Toolbox @@ -429,6 +430,11 @@ &Check for Updates + + + A&nimation Toolbox + + diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp new file mode 100644 index 00000000..395fca29 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -0,0 +1,38 @@ +#include "animation_dock_widget.hpp" +#include "ui_animation_dock_widget.h" + +#include + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class AnimationDockWidgetImpl +{ +public: + explicit AnimationDockWidgetImpl() = default; + ~AnimationDockWidgetImpl() = default; +}; + +AnimationDockWidget::AnimationDockWidget(QWidget* parent) : + QDockWidget(parent), + p {std::make_unique()}, + ui(new Ui::AnimationDockWidget) +{ + ui->setupUi(this); +} + +AnimationDockWidget::~AnimationDockWidget() +{ + delete ui; +} + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp new file mode 100644 index 00000000..c8b33d5a --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Ui +{ +class AnimationDockWidget; +} + +namespace scwx +{ +namespace qt +{ +namespace ui +{ + +class AnimationDockWidgetImpl; + +class AnimationDockWidget : public QDockWidget +{ + Q_OBJECT + +public: + explicit AnimationDockWidget(QWidget* parent = nullptr); + ~AnimationDockWidget(); + +private: + friend class AnimationDockWidgetImpl; + std::unique_ptr p; + Ui::AnimationDockWidget* ui; +}; + +} // namespace ui +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui new file mode 100644 index 00000000..b81177b1 --- /dev/null +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -0,0 +1,201 @@ + + + AnimationDockWidget + + + + 0 + 0 + 200 + 335 + + + + Animation Toolbox + + + + + + + Timeline + + + + + + Live View + + + true + + + + + + + Archive View + + + + + + + + 0 + 0 + 0 + 1991 + 9 + 14 + + + + yyyy-MM-dd + + + true + + + + + + + HH:mm t + + + + + + + Loop Time (Minutes) + + + + + + + 30 + + + + + + + Loop Speed + + + + + + + 1.000000000000000 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ... + + + + :/res/icons/font-awesome-6/backward-step-solid.svg:/res/icons/font-awesome-6/backward-step-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/angle-left-solid.svg:/res/icons/font-awesome-6/angle-left-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/play-solid.svg:/res/icons/font-awesome-6/play-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/angle-right-solid.svg:/res/icons/font-awesome-6/angle-right-solid.svg + + + + + + + ... + + + + :/res/icons/font-awesome-6/forward-step-solid.svg:/res/icons/font-awesome-6/forward-step-solid.svg + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + From aee4fecacc13117efbfeb7304c2d776cf04178b6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 7 May 2023 13:06:16 -0500 Subject: [PATCH 02/50] Configure animation dock widget display --- .../scwx/qt/ui/animation_dock_widget.cpp | 40 +++++++++++++++++++ .../scwx/qt/ui/animation_dock_widget.ui | 4 +- 2 files changed, 42 insertions(+), 2 deletions(-) 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 395fca29..a427f310 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -3,6 +3,8 @@ #include +#include + namespace scwx { namespace qt @@ -26,6 +28,44 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : ui(new Ui::AnimationDockWidget) { ui->setupUi(this); + + // Set date/time edit enabled/disabled + ui->dateEdit->setEnabled(ui->archiveViewRadioButton->isChecked()); + ui->timeEdit->setEnabled(ui->archiveViewRadioButton->isChecked()); + + // Update date/time edit enabled/disabled based on Archive View radio button + connect(ui->archiveViewRadioButton, + &QRadioButton::toggled, + this, + [this](bool checked) + { + ui->dateEdit->setEnabled(checked); + ui->timeEdit->setEnabled(checked); + }); + + // Set current date/time + QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); + ui->dateEdit->setDate(currentDateTime.date()); + ui->timeEdit->setTime(currentDateTime.time()); + ui->dateEdit->setMaximumDate(currentDateTime.date()); + + // Update maximum date on a timer + QTimer* maxDateTimer = new QTimer(this); + connect(maxDateTimer, + &QTimer::timeout, + this, + [this]() + { + // Update maximum date to today + QDate currentDate = QDateTime::currentDateTimeUtc().date(); + if (ui->dateEdit->maximumDate() != currentDate) + { + ui->dateEdit->setMaximumDate(currentDate); + } + }); + + // Evaluate every 15 seconds, every second is unnecessary + maxDateTimer->start(15000); } AnimationDockWidget::~AnimationDockWidget() 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 b81177b1..8202dcc3 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -46,8 +46,8 @@ 0 0 1991 - 9 - 14 + 6 + 1 From 6e4b9cd3310fd8a08748ea953df3ce9526b70caf Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 7 May 2023 13:07:02 -0500 Subject: [PATCH 03/50] Don't use RelWithDebInfo Conan packages - Some packages fail to build (e.g., libtool) --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 678da305..62bdd8a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,15 @@ set_property(DIRECTORY PROPERTY CMAKE_CONFIGURE_DEPENDS conanfile.txt) -conan_cmake_autodetect(settings) +# Don't use RelWithDebInfo Conan packages +if (${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") + set(conan_build_type "Release") +else() + set(conan_build_type ${CMAKE_BUILD_TYPE}) +endif() + +conan_cmake_autodetect(settings + BUILD_TYPE ${conan_build_type}) conan_cmake_install(PATH_OR_REFERENCE ${PROJECT_SOURCE_DIR} BUILD missing From b50bfc564fb7d538f436e8f65e7f16311578954a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 8 May 2023 00:19:20 -0500 Subject: [PATCH 04/50] Add MapTime type --- scwx-qt/scwx-qt.cmake | 1 + scwx-qt/source/scwx/qt/types/map_types.hpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/types/map_types.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 1618d1a5..b56ec7cb 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -132,6 +132,7 @@ set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/font_types.hpp source/scwx/qt/types/github_types.hpp + source/scwx/qt/types/map_types.hpp source/scwx/qt/types/qt_types.hpp source/scwx/qt/types/radar_product_record.hpp source/scwx/qt/types/text_event_key.hpp) diff --git a/scwx-qt/source/scwx/qt/types/map_types.hpp b/scwx-qt/source/scwx/qt/types/map_types.hpp new file mode 100644 index 00000000..b9d9cb9d --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/map_types.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +enum class MapTime +{ + Live, + Archive +}; + +} // namespace types +} // namespace qt +} // namespace scwx From 5453997208f13ce00e74373290beea48198f469d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 13 May 2023 00:41:32 -0500 Subject: [PATCH 05/50] Adding animation control signals --- .../scwx/qt/ui/animation_dock_widget.cpp | 119 +++++++++++++++++- .../scwx/qt/ui/animation_dock_widget.hpp | 18 +++ 2 files changed, 134 insertions(+), 3 deletions(-) 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 a427f310..f5edab98 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -15,16 +15,31 @@ namespace ui static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +enum class AnimationState +{ + Play, + Pause +}; + class AnimationDockWidgetImpl { public: - explicit AnimationDockWidgetImpl() = default; - ~AnimationDockWidgetImpl() = default; + explicit AnimationDockWidgetImpl(AnimationDockWidget* self) : self_ {self} {} + ~AnimationDockWidgetImpl() = default; + + AnimationDockWidget* self_; + + AnimationState animationState_ {AnimationState::Pause}; + + std::chrono::sys_days selectedDate_ {}; + std::chrono::seconds selectedTime_ {}; + + void ConnectSignals(); }; AnimationDockWidget::AnimationDockWidget(QWidget* parent) : QDockWidget(parent), - p {std::make_unique()}, + p {std::make_unique(this)}, ui(new Ui::AnimationDockWidget) { ui->setupUi(this); @@ -66,6 +81,9 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : // Evaluate every 15 seconds, every second is unnecessary maxDateTimer->start(15000); + + // Connect widget signals + p->ConnectSignals(); } AnimationDockWidget::~AnimationDockWidget() @@ -73,6 +91,101 @@ AnimationDockWidget::~AnimationDockWidget() delete ui; } +void AnimationDockWidgetImpl::ConnectSignals() +{ + // View type + QObject::connect(self_->ui->liveViewRadioButton, + &QRadioButton::toggled, + self_, + [this](bool checked) + { + if (checked) + { + emit self_->ViewTypeChanged(types::MapTime::Live); + } + }); + QObject::connect(self_->ui->archiveViewRadioButton, + &QRadioButton::toggled, + self_, + [this](bool checked) + { + if (checked) + { + emit self_->ViewTypeChanged(types::MapTime::Archive); + } + }); + + // Date/time controls + QObject::connect( // + self_->ui->dateEdit, + &QDateTimeEdit::dateChanged, + self_, + [this](QDate date) + { + if (date.isValid()) + { + selectedDate_ = date.toStdSysDays(); + emit self_->DateTimeChanged(selectedDate_ + selectedTime_); + } + }); + QObject::connect( + self_->ui->timeEdit, + &QDateTimeEdit::timeChanged, + self_, + [this](QTime time) + { + if (time.isValid()) + { + selectedTime_ = + std::chrono::seconds(time.msecsSinceStartOfDay() / 1000); + emit self_->DateTimeChanged(selectedDate_ + selectedTime_); + } + }); + + // Loop controls + QObject::connect(self_->ui->loopTimeSpinBox, + &QSpinBox::valueChanged, + self_, + [this](int i) + { emit self_->LoopTimeChanged(std::chrono::minutes(i)); }); + QObject::connect(self_->ui->loopSpeedSpinBox, + &QDoubleSpinBox::valueChanged, + self_, + [this](double d) { emit self_->LoopSpeedChanged(d); }); + + // Animation controls + QObject::connect(self_->ui->beginButton, + &QAbstractButton::clicked, + self_, + [this]() { emit self_->AnimationStepBeginSelected(); }); + QObject::connect(self_->ui->stepBackButton, + &QAbstractButton::clicked, + self_, + [this]() { emit self_->AnimationStepBackSelected(); }); + QObject::connect(self_->ui->playButton, + &QAbstractButton::clicked, + self_, + [this]() + { + if (animationState_ == AnimationState::Pause) + { + emit self_->AnimationPlaySelected(); + } + else + { + emit self_->AnimationPauseSelected(); + } + }); + QObject::connect(self_->ui->stepNextButton, + &QAbstractButton::clicked, + self_, + [this]() { emit self_->AnimationStepNextSelected(); }); + QObject::connect(self_->ui->endButton, + &QAbstractButton::clicked, + self_, + [this]() { emit self_->AnimationStepEndSelected(); }); +} + } // namespace ui } // namespace qt } // namespace scwx 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 c8b33d5a..a054e592 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -1,5 +1,9 @@ #pragma once +#include + +#include + #include namespace Ui @@ -24,6 +28,20 @@ public: explicit AnimationDockWidget(QWidget* parent = nullptr); ~AnimationDockWidget(); +signals: + void ViewTypeChanged(types::MapTime viewType); + void DateTimeChanged(std::chrono::system_clock::time_point dateTime); + + void LoopTimeChanged(std::chrono::minutes loopTime); + void LoopSpeedChanged(double loopSpeed); + + void AnimationStepBeginSelected(); + void AnimationStepBackSelected(); + void AnimationPauseSelected(); + void AnimationPlaySelected(); + void AnimationStepNextSelected(); + void AnimationStepEndSelected(); + private: friend class AnimationDockWidgetImpl; std::unique_ptr p; From 81eb3b1af3ca83034474e559f06bd2f3ce125a3e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 14 May 2023 08:36:21 -0500 Subject: [PATCH 06/50] Add timeline manager --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/manager/timeline_manager.cpp | 48 +++++++++++++++++++ .../scwx/qt/manager/timeline_manager.hpp | 31 ++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/manager/timeline_manager.cpp create mode 100644 scwx-qt/source/scwx/qt/manager/timeline_manager.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index b56ec7cb..cf084fa1 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -65,12 +65,14 @@ set(HDR_MANAGER source/scwx/qt/manager/radar_product_manager.hpp source/scwx/qt/manager/resource_manager.hpp source/scwx/qt/manager/settings_manager.hpp source/scwx/qt/manager/text_event_manager.hpp + source/scwx/qt/manager/timeline_manager.hpp source/scwx/qt/manager/update_manager.hpp) set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp source/scwx/qt/manager/radar_product_manager_notifier.cpp source/scwx/qt/manager/resource_manager.cpp source/scwx/qt/manager/settings_manager.cpp source/scwx/qt/manager/text_event_manager.cpp + source/scwx/qt/manager/timeline_manager.cpp source/scwx/qt/manager/update_manager.cpp) set(HDR_MAP source/scwx/qt/map/alert_layer.hpp source/scwx/qt/map/color_table_layer.hpp diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp new file mode 100644 index 00000000..ebf7d43a --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -0,0 +1,48 @@ +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::timeline_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class TimelineManager::Impl +{ +public: + explicit Impl(TimelineManager* self) : self_ {self} {} + + ~Impl() {} + + TimelineManager* self_; +}; + +TimelineManager::TimelineManager() : p(std::make_unique(this)) {} +TimelineManager::~TimelineManager() = default; + +std::shared_ptr TimelineManager::Instance() +{ + static std::weak_ptr timelineManagerReference_ {}; + static std::mutex instanceMutex_ {}; + + std::unique_lock lock(instanceMutex_); + + std::shared_ptr timelineManager = + timelineManagerReference_.lock(); + + if (timelineManager == nullptr) + { + timelineManager = std::make_shared(); + timelineManagerReference_ = timelineManager; + } + + return timelineManager; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp new file mode 100644 index 00000000..f37eadd3 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class TimelineManager : public QObject +{ + Q_OBJECT + +public: + explicit TimelineManager(); + ~TimelineManager(); + + static std::shared_ptr Instance(); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx From e1ec81e23086aa696edbdd14b83922c8b74fece2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 15 May 2023 23:27:54 -0500 Subject: [PATCH 07/50] Adding MapTime name function --- scwx-qt/scwx-qt.cmake | 1 + scwx-qt/source/scwx/qt/types/map_types.cpp | 22 ++++++++++++++++++++++ scwx-qt/source/scwx/qt/types/map_types.hpp | 4 ++++ 3 files changed, 27 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/types/map_types.cpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index cf084fa1..5aaa1f2a 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -140,6 +140,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/text_event_key.hpp) set(SRC_TYPES source/scwx/qt/types/alert_types.cpp source/scwx/qt/types/github_types.cpp + source/scwx/qt/types/map_types.cpp source/scwx/qt/types/radar_product_record.cpp source/scwx/qt/types/text_event_key.cpp) set(HDR_UI source/scwx/qt/ui/about_dialog.hpp diff --git a/scwx-qt/source/scwx/qt/types/map_types.cpp b/scwx-qt/source/scwx/qt/types/map_types.cpp new file mode 100644 index 00000000..d3ea6bc0 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/map_types.cpp @@ -0,0 +1,22 @@ +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +static const std::unordered_map mapTimeName_ { + {MapTime::Live, "Live"}, {MapTime::Archive, "Archive"}}; + +std::string GetMapTimeName(MapTime mapTime) +{ + return mapTimeName_.at(mapTime); +} + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/map_types.hpp b/scwx-qt/source/scwx/qt/types/map_types.hpp index b9d9cb9d..a23932ee 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.hpp +++ b/scwx-qt/source/scwx/qt/types/map_types.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace scwx { namespace qt @@ -13,6 +15,8 @@ enum class MapTime Archive }; +std::string GetMapTimeName(MapTime mapTime); + } // namespace types } // namespace qt } // namespace scwx From 3bee6f65e5712692fb17c5ec86968d217c0bc9b0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 15 May 2023 23:38:39 -0500 Subject: [PATCH 08/50] Starting some timeline manager stubs --- .../scwx/qt/manager/timeline_manager.cpp | 102 ++++++++++++++++++ .../scwx/qt/manager/timeline_manager.hpp | 21 ++++ 2 files changed, 123 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index ebf7d43a..cd0958ca 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -1,5 +1,9 @@ #include #include +#include +#include + +#include namespace scwx { @@ -19,11 +23,109 @@ public: ~Impl() {} TimelineManager* self_; + + void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); + + std::chrono::system_clock::time_point pinnedTime_ {}; + std::chrono::system_clock::time_point currentTime_ {}; + types::MapTime viewType_ {types::MapTime::Live}; + std::chrono::minutes loopTime_ {30}; + double loopSpeed_ {1.0}; }; TimelineManager::TimelineManager() : p(std::make_unique(this)) {} TimelineManager::~TimelineManager() = default; +void TimelineManager::SetDateTime( + std::chrono::system_clock::time_point dateTime) +{ + logger_->debug("SetDateTime: {}", scwx::util::TimeString(dateTime)); + + p->pinnedTime_ = dateTime; + + if (p->viewType_ == types::MapTime::Archive) + { + // Only select if the view type is archive + p->SelectTime(dateTime); + } + + // Ignore a date/time selection if the view type is live +} + +void TimelineManager::SetViewType(types::MapTime viewType) +{ + logger_->debug("SetViewType: {}", types::GetMapTimeName(viewType)); + + p->viewType_ = viewType; + + if (p->viewType_ == types::MapTime::Live) + { + // If the selected view type is live, select the current products + p->SelectTime(); + } + else + { + // If the selected view type is archive, select using the pinned time + p->SelectTime(p->pinnedTime_); + } +} + +void TimelineManager::SetLoopTime(std::chrono::minutes loopTime) +{ + logger_->debug("SetLoopTime: {}", loopTime); + + p->loopTime_ = loopTime; +} + +void TimelineManager::SetLoopSpeed(double loopSpeed) +{ + logger_->debug("SetLoopSpeed: {}", loopSpeed); + + p->loopSpeed_ = loopSpeed; +} + +void TimelineManager::AnimationStepBegin() +{ + logger_->debug("AnimationStepBegin"); +} + +void TimelineManager::AnimationStepBack() +{ + logger_->debug("AnimationStepBack"); +} + +void TimelineManager::AnimationPlay() +{ + logger_->debug("AnimationPlay"); +} + +void TimelineManager::AnimationPause() +{ + logger_->debug("AnimationPause"); +} + +void TimelineManager::AnimationStepNext() +{ + logger_->debug("AnimationStepNext"); +} + +void TimelineManager::AnimationStepEnd() +{ + logger_->debug("AnimationStepEnd"); +} + +void TimelineManager::Impl::SelectTime( + std::chrono::system_clock::time_point selectedTime) +{ + if (currentTime_ == selectedTime) + { + // Nothing to do + return; + } + + currentTime_ = selectedTime; +} + std::shared_ptr TimelineManager::Instance() { static std::weak_ptr timelineManagerReference_ {}; diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index f37eadd3..9d98857e 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -1,5 +1,8 @@ #pragma once +#include + +#include #include #include @@ -21,6 +24,24 @@ public: static std::shared_ptr Instance(); +public slots: + void SetDateTime(std::chrono::system_clock::time_point dateTime); + void SetViewType(types::MapTime viewType); + + void SetLoopTime(std::chrono::minutes loopTime); + void SetLoopSpeed(double loopSpeed); + + void AnimationStepBegin(); + void AnimationStepBack(); + void AnimationPlay(); + void AnimationPause(); + void AnimationStepNext(); + void AnimationStepEnd(); + +signals: + void TimeUpdated(std::chrono::system_clock::time_point dateTime); + void ViewTypeUpdated(types::MapTime viewType); + private: class Impl; std::unique_ptr p; From a9f5a766cc5d0007e1305f53aca51118c46f016d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 16 May 2023 22:42:09 -0500 Subject: [PATCH 09/50] Add NEXRAD data provider function to get time points by date --- .../aws_level3_data_provider.test.cpp | 20 ++++++++++ .../provider/aws_nexrad_data_provider.hpp | 21 +++++----- .../scwx/provider/nexrad_data_provider.hpp | 11 +++++ .../provider/aws_nexrad_data_provider.cpp | 40 +++++++++++++++++++ 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/test/source/scwx/provider/aws_level3_data_provider.test.cpp b/test/source/scwx/provider/aws_level3_data_provider.test.cpp index 4e88e4e6..b076d6bb 100644 --- a/test/source/scwx/provider/aws_level3_data_provider.test.cpp +++ b/test/source/scwx/provider/aws_level3_data_provider.test.cpp @@ -66,6 +66,26 @@ TEST(AwsLevel3DataProvider, GetAvailableProducts) EXPECT_GT(products.size(), 0); } +TEST(AwsLevel3DataProvider, GetTimePointsByDate) +{ + using namespace std::chrono; + using sys_days = time_point; + + const auto date = sys_days {2021y / May / 27d}; + const auto tomorrow = date + days {1}; + + AwsLevel3DataProvider provider("KLSX", "N0Q"); + + auto timePoints = provider.GetTimePointsByDate(date); + + EXPECT_GT(timePoints.size(), 0); + for (auto timePoint : timePoints) + { + EXPECT_GE(timePoint, date); + EXPECT_LT(timePoint, tomorrow); + } +} + TEST(AwsLevel3DataProvider, TimePointValid) { using namespace std::chrono; diff --git a/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp b/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp index 999a4952..b258e0d8 100644 --- a/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp +++ b/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp @@ -20,23 +20,26 @@ public: const std::string& region); virtual ~AwsNexradDataProvider(); - AwsNexradDataProvider(const AwsNexradDataProvider&) = delete; + AwsNexradDataProvider(const AwsNexradDataProvider&) = delete; AwsNexradDataProvider& operator=(const AwsNexradDataProvider&) = delete; AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept; AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept; - size_t cache_size() const; + size_t cache_size() const override; - std::chrono::system_clock::time_point last_modified() const; - std::chrono::seconds update_period() const; + std::chrono::system_clock::time_point last_modified() const override; + std::chrono::seconds update_period() const override; - std::string FindKey(std::chrono::system_clock::time_point time); - std::string FindLatestKey(); + std::string FindKey(std::chrono::system_clock::time_point time) override; + std::string FindLatestKey() override; + std::vector + GetTimePointsByDate(std::chrono::system_clock::time_point date) override; std::pair - ListObjects(std::chrono::system_clock::time_point date); - std::shared_ptr LoadObjectByKey(const std::string& key); - std::pair Refresh(); + ListObjects(std::chrono::system_clock::time_point date) override; + std::shared_ptr + LoadObjectByKey(const std::string& key) override; + std::pair Refresh() override; protected: std::shared_ptr client(); diff --git a/wxdata/include/scwx/provider/nexrad_data_provider.hpp b/wxdata/include/scwx/provider/nexrad_data_provider.hpp index 5041f9aa..2b1f683b 100644 --- a/wxdata/include/scwx/provider/nexrad_data_provider.hpp +++ b/wxdata/include/scwx/provider/nexrad_data_provider.hpp @@ -101,6 +101,17 @@ public: virtual std::chrono::system_clock::time_point GetTimePointByKey(const std::string& key) const = 0; + /** + * Gets NEXRAD data time points for the date supplied. Lists and adds them + * to the cache if required. + * + * @param date Date for which to get NEXRAD data time points + * + * @return NEXRAD data time points + */ + virtual std::vector + GetTimePointsByDate(std::chrono::system_clock::time_point date) = 0; + /** * Requests available NEXRAD products for the current radar site, and adds * the list to the cache. diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index 4d763135..e8f99b47 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace scwx { @@ -156,6 +157,45 @@ std::string AwsNexradDataProvider::FindLatestKey() return key; } +std::vector +AwsNexradDataProvider::GetTimePointsByDate( + std::chrono::system_clock::time_point date) +{ + const auto day = std::chrono::floor(date); + + std::vector timePoints {}; + + logger_->debug("GetTimePointsByDate: {}", day); + + std::shared_lock lock(p->objectsMutex_); + + // Is the date present in the date list? + if (std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day) == + p->objectDates_.cend()) + { + // Temporarily unlock mutex + lock.unlock(); + + // List objects, since the date is not present in the date list + ListObjects(date); + + // Re-lock mutex + lock.lock(); + } + + // Determine objects to retrieve + auto objectsBegin = p->objects_.lower_bound(day); + auto objectsEnd = p->objects_.lower_bound(day + std::chrono::days {1}); + + // Copy time points to destination vector + std::transform(objectsBegin, + objectsEnd, + std::back_inserter(timePoints), + [](const auto& object) { return object.first; }); + + return timePoints; +} + std::pair AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) { From a7a34e063cf3b68a624e4be5cec15f12be980467 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 21 May 2023 23:42:00 -0500 Subject: [PATCH 10/50] Active Volume Times for Radar Product Manager --- .../scwx/qt/manager/radar_product_manager.cpp | 65 ++++++++++++++++++- .../scwx/qt/manager/radar_product_manager.hpp | 12 ++++ 2 files changed, 75 insertions(+), 2 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 d062e67a..b036fb8d 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #if defined(_MSC_VER) # pragma warning(push, 0) @@ -244,8 +245,8 @@ public: std::unordered_map, boost::hash> - refreshMap_ {}; - std::mutex refreshMapMutex_ {}; + refreshMap_ {}; + std::shared_mutex refreshMapMutex_ {}; }; RadarProductManager::RadarProductManager(const std::string& radarId) : @@ -676,6 +677,66 @@ void RadarProductManagerImpl::RefreshData( }); } +std::set +RadarProductManager::GetActiveVolumeTimes( + std::chrono::system_clock::time_point time) +{ + std::unordered_set> + providers {}; + std::set volumeTimes; + std::mutex volumeTimesMutex {}; + + // Lock the refresh map + std::shared_lock refreshLock {p->refreshMapMutex_}; + + // For each entry in the refresh map (refresh is enabled) + for (auto& refreshEntry : p->refreshMap_) + { + // Add the provider for the current entry + providers.insert(refreshEntry.second->provider_); + } + + // Unlock the refresh map + refreshLock.unlock(); + + // For each provider (in parallel) + std::for_each( + std::execution::par_unseq, + providers.begin(), + providers.end(), + [&](const std::shared_ptr& provider) + { + const auto today = std::chrono::floor(time); + const auto yesterday = today - std::chrono::days {1}; + const auto dates = {yesterday, today}; + + // For today and yesterday (in parallel) + std::for_each(std::execution::par_unseq, + dates.begin(), + dates.end(), + [&](const auto& date) + { + // Query the provider for volume time points + auto timePoints = provider->GetTimePointsByDate(date); + + // TODO: Note, this will miss volume times present in + // Level 2 products with a second scan + + // Lock the merged volume time list + std::unique_lock volumeTimesLock {volumeTimesMutex}; + + // Copy time points to the merged list + std::copy( + timePoints.begin(), + timePoints.end(), + std::inserter(volumeTimes, volumeTimes.end())); + }); + }); + + // Return merged volume times list + return volumeTimes; +} + void RadarProductManagerImpl::LoadProviderData( std::chrono::system_clock::time_point time, std::shared_ptr providerManager, 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 54a57499..9e74f95b 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -63,6 +64,17 @@ public: bool enabled, boost::uuids::uuid uuid = boost::uuids::nil_uuid()); + /** + * @brief Gets a merged list of the volume times for products with refresh + * enabled. The volume times will be for the current and previous day. + * + * @param [in] time Current date to provide to volume time query + * + * @return Merged list of active volume times + */ + std::set + GetActiveVolumeTimes(std::chrono::system_clock::time_point time); + /** * @brief Get level 2 radar data for a data block type, elevation, and time. * From 4bba7f4c64f5a0ae6d86b626feb9d62d86fb0923 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 21 May 2023 23:42:35 -0500 Subject: [PATCH 11/50] Timeline Manager should have knowledge of radar site --- scwx-qt/source/scwx/qt/manager/timeline_manager.cpp | 6 ++++++ scwx-qt/source/scwx/qt/manager/timeline_manager.hpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index cd0958ca..b3d2e94e 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -26,6 +26,7 @@ public: void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); + std::string radarSite_ {"?"}; std::chrono::system_clock::time_point pinnedTime_ {}; std::chrono::system_clock::time_point currentTime_ {}; types::MapTime viewType_ {types::MapTime::Live}; @@ -36,6 +37,11 @@ public: TimelineManager::TimelineManager() : p(std::make_unique(this)) {} TimelineManager::~TimelineManager() = default; +void TimelineManager::SetRadarSite(const std::string& radarSite) +{ + p->radarSite_ = radarSite; +} + void TimelineManager::SetDateTime( std::chrono::system_clock::time_point dateTime) { diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index 9d98857e..84ca7ad5 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -25,6 +25,8 @@ public: static std::shared_ptr Instance(); public slots: + void SetRadarSite(const std::string& radarSite); + void SetDateTime(std::chrono::system_clock::time_point dateTime); void SetViewType(types::MapTime viewType); From 69730515aaa9da3575b543453f8a350a33ab1639 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 21 May 2023 23:52:03 -0500 Subject: [PATCH 12/50] Get next day in addition to previous when querying volume times --- scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp | 5 +++-- scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp | 2 +- 2 files changed, 4 insertions(+), 3 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 b036fb8d..9c5fd712 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -708,9 +708,10 @@ RadarProductManager::GetActiveVolumeTimes( { const auto today = std::chrono::floor(time); const auto yesterday = today - std::chrono::days {1}; - const auto dates = {yesterday, today}; + const auto tomorrow = today + std::chrono::days {1}; + const auto dates = {yesterday, today, tomorrow}; - // For today and yesterday (in parallel) + // For yesterday, today and tomorrow (in parallel) std::for_each(std::execution::par_unseq, dates.begin(), dates.end(), 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 9e74f95b..34c2c6fb 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp @@ -66,7 +66,7 @@ public: /** * @brief Gets a merged list of the volume times for products with refresh - * enabled. The volume times will be for the current and previous day. + * enabled. The volume times will be for the previous, current and next day. * * @param [in] time Current date to provide to volume time query * From fc3b1f2d2ef19166689782c030797250771eb913 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 May 2023 22:44:52 -0500 Subject: [PATCH 13/50] Updating map utility to accept a generic container --- wxdata/include/scwx/util/map.hpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/wxdata/include/scwx/util/map.hpp b/wxdata/include/scwx/util/map.hpp index 3392b5d7..b6282807 100644 --- a/wxdata/include/scwx/util/map.hpp +++ b/wxdata/include/scwx/util/map.hpp @@ -8,19 +8,20 @@ namespace scwx namespace util { -template::const_pointer> -ReturnType GetBoundedElementPointer(std::map& map, const Key& key) +template +ReturnType GetBoundedElementPointer(Container& container, + const typename Container::key_type& key) { ReturnType elementPtr {nullptr}; // Find the first element greater than the key requested - auto it = map.upper_bound(key); + auto it = container.upper_bound(key); // An element with a key greater was found - if (it != map.cend()) + if (it != container.cend()) { // Are there elements prior to this element? - if (it != map.cbegin()) + if (it != container.cbegin()) { // Get the element immediately preceding, this the element we are // looking for @@ -32,11 +33,11 @@ ReturnType GetBoundedElementPointer(std::map& map, const Key& key) elementPtr = &(*it); } } - else if (map.size() > 0) + else if (container.size() > 0) { // An element with a key greater was not found. If it exists, it must be // the last element. - elementPtr = &(*map.rbegin()); + elementPtr = &(*container.rbegin()); } return elementPtr; @@ -48,8 +49,8 @@ ReturnType GetBoundedElement(std::map& map, const Key& key) ReturnType element; typename std::map::pointer elementPtr = - GetBoundedElementPointer::pointer>(map, - key); + GetBoundedElementPointer, + typename std::map::pointer>(map, key); if (elementPtr != nullptr) { element = elementPtr->second; From 266be01d8f5645a25de43e3fc1593bd8728dd2c2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 22 May 2023 22:46:48 -0500 Subject: [PATCH 14/50] Timeline manager time selection --- .../scwx/qt/manager/timeline_manager.cpp | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index b3d2e94e..cb4df207 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -1,8 +1,12 @@ #include +#include #include +#include #include #include +#include + #include namespace scwx @@ -28,10 +32,13 @@ public: std::string radarSite_ {"?"}; std::chrono::system_clock::time_point pinnedTime_ {}; - std::chrono::system_clock::time_point currentTime_ {}; + std::chrono::system_clock::time_point currentAdjustedTime_ {}; + std::chrono::system_clock::time_point currentSelectedTime_ {}; types::MapTime viewType_ {types::MapTime::Live}; std::chrono::minutes loopTime_ {30}; double loopSpeed_ {1.0}; + + std::mutex selectTimeMutex_ {}; }; TimelineManager::TimelineManager() : p(std::make_unique(this)) {} @@ -123,13 +130,42 @@ void TimelineManager::AnimationStepEnd() void TimelineManager::Impl::SelectTime( std::chrono::system_clock::time_point selectedTime) { - if (currentTime_ == selectedTime) + if (currentSelectedTime_ == selectedTime) { // Nothing to do return; } - currentTime_ = selectedTime; + 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); + + // Find the best match bounded time + auto elementPtr = + util::GetBoundedElementPointer(volumeTimes, selectedTime); + + if (elementPtr != nullptr) + { + // If the time was found, select it + currentAdjustedTime_ = *elementPtr; + currentSelectedTime_ = selectedTime; + + emit self_->TimeUpdated(currentAdjustedTime_); + } + else + { + // No volume time was found + logger_->info("No volume scan found for {}", selectedTime); + } + }); } std::shared_ptr TimelineManager::Instance() From f6de4d0742dc0ddafb2d440af60c2ac9c8cbf5e0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 23 May 2023 22:41:27 -0500 Subject: [PATCH 15/50] Include radar site in time update determination --- .../scwx/qt/manager/timeline_manager.cpp | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index cb4df207..be661cbd 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -31,9 +31,10 @@ public: void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); std::string radarSite_ {"?"}; + std::string previousRadarSite_ {"?"}; std::chrono::system_clock::time_point pinnedTime_ {}; - std::chrono::system_clock::time_point currentAdjustedTime_ {}; - std::chrono::system_clock::time_point currentSelectedTime_ {}; + std::chrono::system_clock::time_point adjustedTime_ {}; + std::chrono::system_clock::time_point selectedTime_ {}; types::MapTime viewType_ {types::MapTime::Live}; std::chrono::minutes loopTime_ {30}; double loopSpeed_ {1.0}; @@ -47,6 +48,17 @@ TimelineManager::~TimelineManager() = default; void TimelineManager::SetRadarSite(const std::string& radarSite) { p->radarSite_ = radarSite; + + if (p->viewType_ == types::MapTime::Live) + { + // If the selected view type is live, select the current products + p->SelectTime(); + } + else + { + // If the selected view type is archive, select using the selected time + p->SelectTime(p->selectedTime_); + } } void TimelineManager::SetDateTime( @@ -130,7 +142,7 @@ void TimelineManager::AnimationStepEnd() void TimelineManager::Impl::SelectTime( std::chrono::system_clock::time_point selectedTime) { - if (currentSelectedTime_ == selectedTime) + if (selectedTime_ == selectedTime && radarSite_ == previousRadarSite_) { // Nothing to do return; @@ -154,17 +166,26 @@ void TimelineManager::Impl::SelectTime( if (elementPtr != nullptr) { - // If the time was found, select it - currentAdjustedTime_ = *elementPtr; - currentSelectedTime_ = selectedTime; + selectedTime_ = selectedTime; - emit self_->TimeUpdated(currentAdjustedTime_); + // 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; + + emit self_->TimeUpdated(adjustedTime_); + } } else { // No volume time was found logger_->info("No volume scan found for {}", selectedTime); } + + previousRadarSite_ = radarSite_; }); } From 63a746d25f13ecda7ca30fa44bbc9dd2538d3580 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:02:00 -0500 Subject: [PATCH 16/50] Map widget time selection --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 24 ++++++++++++++++++----- scwx-qt/source/scwx/qt/map/map_widget.hpp | 8 ++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index b63fa565..b7fe9ec1 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -63,7 +63,6 @@ public: colorTableLayer_ {nullptr}, autoRefreshEnabled_ {true}, selectedLevel2Product_ {common::Level2Product::Unknown}, - selectedTime_ {}, lastPos_(), currentStyleIndex_ {0}, currentStyle_ {nullptr}, @@ -148,8 +147,7 @@ public: bool autoRefreshEnabled_; - common::Level2Product selectedLevel2Product_; - std::chrono::system_clock::time_point selectedTime_; + common::Level2Product selectedLevel2Product_; QPointF lastPos_; std::size_t currentStyleIndex_; @@ -434,9 +432,8 @@ void MapWidget::SelectRadarProduct( scwx::util::TimeString(time)); p->SetRadarSite(radarId); - p->selectedTime_ = time; - SelectRadarProduct(group, product, productCode); + SelectRadarProduct(group, product, productCode, time); } void MapWidget::SelectRadarSite(const std::string& id, bool updateCoordinates) @@ -478,6 +475,23 @@ void MapWidget::SelectRadarSite(std::shared_ptr radarSite, AddLayers(); // TODO: Disable refresh from old site + + emit RadarSiteUpdated(radarSite); + } +} + +void MapWidget::SelectTime(std::chrono::system_clock::time_point time) +{ + auto radarProductView = p->context_->radar_product_view(); + + // If there is an active radar product view + if (radarProductView != nullptr) + { + // Select the time associated with the active radar product + radarProductView->SelectTime(time); + + // Trigger an update of the radar product view + radarProductView->Update(); } } diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index fbb5746e..22b6da40 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -80,6 +80,13 @@ public: void SelectRadarSite(std::shared_ptr radarSite, bool updateCoordinates = true); + /** + * @brief Selects the time associated with the active radar product. + * + * @param [in] time Product time + */ + void SelectTime(std::chrono::system_clock::time_point time); + void SetActive(bool isActive); void SetAutoRefresh(bool enabled); @@ -132,6 +139,7 @@ signals: double bearing, double pitch); void MapStyleChanged(const std::string& styleName); + void RadarSiteUpdated(std::shared_ptr radarSite); void RadarSweepUpdated(); }; From afe63df72a642ceffce6148b1ac8de3f048b2258 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:02:42 -0500 Subject: [PATCH 17/50] Initialize animation dock widget date/time --- scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 f5edab98..a147cef3 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -60,9 +60,14 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : // Set current date/time QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - ui->dateEdit->setDate(currentDateTime.date()); - ui->timeEdit->setTime(currentDateTime.time()); + QDate currentDate = currentDateTime.date(); + QTime currentTime = currentDateTime.time(); + ui->dateEdit->setDate(currentDate); + ui->timeEdit->setTime(currentTime); ui->dateEdit->setMaximumDate(currentDateTime.date()); + p->selectedDate_ = currentDate.toStdSysDays(); + p->selectedTime_ = + std::chrono::seconds(currentTime.msecsSinceStartOfDay() / 1000); // Update maximum date on a timer QTimer* maxDateTimer = new QTimer(this); From b162fda895e825eb6ea768793d7381a76e3dadf1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:11:47 -0500 Subject: [PATCH 18/50] Don't crash if a bad date is provided to data provider formatter --- wxdata/source/scwx/provider/aws_level2_data_provider.cpp | 5 +++++ wxdata/source/scwx/provider/aws_level3_data_provider.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp index 53b0f85f..6ac939c0 100644 --- a/wxdata/source/scwx/provider/aws_level2_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_level2_data_provider.cpp @@ -52,6 +52,11 @@ AwsLevel2DataProvider::operator=(AwsLevel2DataProvider&&) noexcept = default; std::string AwsLevel2DataProvider::GetPrefix(std::chrono::system_clock::time_point date) { + if (date < std::chrono::system_clock::time_point {}) + { + date = std::chrono::system_clock::time_point {}; + } + return fmt::format("{0:%Y/%m/%d}/{1}/", fmt::gmtime(date), p->radarSite_); } diff --git a/wxdata/source/scwx/provider/aws_level3_data_provider.cpp b/wxdata/source/scwx/provider/aws_level3_data_provider.cpp index 327f4c9f..029dd5db 100644 --- a/wxdata/source/scwx/provider/aws_level3_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_level3_data_provider.cpp @@ -78,6 +78,11 @@ AwsLevel3DataProvider::operator=(AwsLevel3DataProvider&&) noexcept = default; std::string AwsLevel3DataProvider::GetPrefix(std::chrono::system_clock::time_point date) { + if (date < std::chrono::system_clock::time_point {}) + { + date = std::chrono::system_clock::time_point {}; + } + return fmt::format( "{0}_{1}_{2:%Y_%m_%d}_", p->siteId_, p->product_, fmt::gmtime(date)); } From 344d32081ea3ef83114f7121149fd54a97622b5e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:12:13 -0500 Subject: [PATCH 19/50] Define constant dates outside loop --- .../source/scwx/qt/manager/radar_product_manager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 9c5fd712..73cdf7df 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -699,6 +699,11 @@ RadarProductManager::GetActiveVolumeTimes( // Unlock the refresh map refreshLock.unlock(); + const auto today = std::chrono::floor(time); + const auto yesterday = today - std::chrono::days {1}; + const auto tomorrow = today + std::chrono::days {1}; + const auto dates = {yesterday, today, tomorrow}; + // For each provider (in parallel) std::for_each( std::execution::par_unseq, @@ -706,11 +711,6 @@ RadarProductManager::GetActiveVolumeTimes( providers.end(), [&](const std::shared_ptr& provider) { - const auto today = std::chrono::floor(time); - const auto yesterday = today - std::chrono::days {1}; - const auto tomorrow = today + std::chrono::days {1}; - const auto dates = {yesterday, today, tomorrow}; - // For yesterday, today and tomorrow (in parallel) std::for_each(std::execution::par_unseq, dates.begin(), From ab42772f37cb9d72bf9df10da29f2cfbdeaea888 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:12:37 -0500 Subject: [PATCH 20/50] Lock timeline mutexes before destroying --- scwx-qt/source/scwx/qt/manager/timeline_manager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index be661cbd..cb744f92 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -24,7 +24,11 @@ class TimelineManager::Impl public: explicit Impl(TimelineManager* self) : self_ {self} {} - ~Impl() {} + ~Impl() + { + // Lock mutexes before destroying + std::unique_lock selectTimeLock {selectTimeMutex_}; + } TimelineManager* self_; From 5500b2f4c21843c80dc95e7cc794de07df2e7d33 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:13:07 -0500 Subject: [PATCH 21/50] Add debug statement for timeline time updated --- scwx-qt/source/scwx/qt/manager/timeline_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index cb744f92..d14e10a6 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -180,6 +180,8 @@ void TimelineManager::Impl::SelectTime( // If the time was found, select it adjustedTime_ = *elementPtr; + logger_->debug("Time updated: {}", adjustedTime_); + emit self_->TimeUpdated(adjustedTime_); } } From f452d3f15d1adc6a223517598e6cd9e52759921e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 00:14:06 -0500 Subject: [PATCH 22/50] Connect animation dock and timeline manager slots and signals --- scwx-qt/source/scwx/qt/main/main_window.cpp | 72 +++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 95a9af27..2a637600 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,7 @@ public: updateDialog_ {nullptr}, radarProductModel_ {nullptr}, textEventManager_ {manager::TextEventManager::Instance()}, + timelineManager_ {manager::TimelineManager::Instance()}, updateManager_ {manager::UpdateManager::Instance()}, maps_ {}, elevationCuts_ {}, @@ -111,6 +113,7 @@ public: void AsyncSetup(); void ConfigureMapLayout(); + void ConnectAnimationSignals(); void ConnectMapSignals(); void ConnectOtherSignals(); void HandleFocusChange(QWidget* focused); @@ -150,6 +153,7 @@ public: std::unique_ptr radarProductModel_; std::shared_ptr textEventManager_; + std::shared_ptr timelineManager_; std::shared_ptr updateManager_; std::vector maps_; @@ -271,6 +275,7 @@ MainWindow::MainWindow(QWidget* parent) : p->PopulateMapStyles(); p->ConnectMapSignals(); + p->ConnectAnimationSignals(); p->ConnectOtherSignals(); p->HandleFocusChange(p->activeMap_); p->AsyncSetup(); @@ -648,6 +653,73 @@ void MainWindowImpl::ConnectMapSignals() } } +void MainWindowImpl::ConnectAnimationSignals() +{ + connect(animationDockWidget_, + &ui::AnimationDockWidget::DateTimeChanged, + timelineManager_.get(), + &manager::TimelineManager::SetDateTime); + connect(animationDockWidget_, + &ui::AnimationDockWidget::ViewTypeChanged, + timelineManager_.get(), + &manager::TimelineManager::SetViewType); + connect(animationDockWidget_, + &ui::AnimationDockWidget::LoopTimeChanged, + timelineManager_.get(), + &manager::TimelineManager::SetLoopTime); + connect(animationDockWidget_, + &ui::AnimationDockWidget::LoopSpeedChanged, + timelineManager_.get(), + &manager::TimelineManager::SetLoopSpeed); + connect(animationDockWidget_, + &ui::AnimationDockWidget::AnimationStepBeginSelected, + timelineManager_.get(), + &manager::TimelineManager::AnimationStepBegin); + connect(animationDockWidget_, + &ui::AnimationDockWidget::AnimationStepBackSelected, + timelineManager_.get(), + &manager::TimelineManager::AnimationStepBack); + connect(animationDockWidget_, + &ui::AnimationDockWidget::AnimationPlaySelected, + timelineManager_.get(), + &manager::TimelineManager::AnimationPlay); + connect(animationDockWidget_, + &ui::AnimationDockWidget::AnimationPauseSelected, + timelineManager_.get(), + &manager::TimelineManager::AnimationPause); + connect(animationDockWidget_, + &ui::AnimationDockWidget::AnimationStepNextSelected, + timelineManager_.get(), + &manager::TimelineManager::AnimationStepNext); + connect(animationDockWidget_, + &ui::AnimationDockWidget::AnimationStepEndSelected, + timelineManager_.get(), + &manager::TimelineManager::AnimationStepEnd); + + connect(timelineManager_.get(), + &manager::TimelineManager::TimeUpdated, + [this](std::chrono::system_clock::time_point dateTime) + { + for (auto map : maps_) + { + map->SelectTime(dateTime); + } + }); + + for (auto map : maps_) + { + connect(map, + &map::MapWidget::RadarSiteUpdated, + [this, map](std::shared_ptr radarSite) + { + if (map == activeMap_) + { + timelineManager_->SetRadarSite(radarSite->id()); + } + }); + } +} + void MainWindowImpl::ConnectOtherSignals() { connect(qApp, From da835d7226605aa99feb960883b8960349f6cdf0 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 22:56:17 -0500 Subject: [PATCH 23/50] Optimization of object listing by date --- .../scwx/qt/manager/radar_product_manager.cpp | 14 ++++++++++++- .../provider/aws_nexrad_data_provider.hpp | 2 +- .../scwx/provider/nexrad_data_provider.hpp | 5 +++-- .../provider/aws_nexrad_data_provider.cpp | 20 +++++++++++-------- 4 files changed, 29 insertions(+), 12 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 73cdf7df..5ca206d1 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -683,9 +683,15 @@ RadarProductManager::GetActiveVolumeTimes( { std::unordered_set> providers {}; - std::set volumeTimes; + std::set volumeTimes {}; std::mutex volumeTimesMutex {}; + // Return a default set of volume times if the default time point is given + if (time == std::chrono::system_clock::time_point {}) + { + return volumeTimes; + } + // Lock the refresh map std::shared_lock refreshLock {p->refreshMapMutex_}; @@ -717,6 +723,12 @@ RadarProductManager::GetActiveVolumeTimes( dates.end(), [&](const auto& date) { + // Don't query for a time point in the future + if (date > std::chrono::system_clock::now()) + { + return; + } + // Query the provider for volume time points auto timePoints = provider->GetTimePointsByDate(date); diff --git a/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp b/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp index b258e0d8..436ad0ba 100644 --- a/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp +++ b/wxdata/include/scwx/provider/aws_nexrad_data_provider.hpp @@ -35,7 +35,7 @@ public: std::string FindLatestKey() override; std::vector GetTimePointsByDate(std::chrono::system_clock::time_point date) override; - std::pair + std::tuple ListObjects(std::chrono::system_clock::time_point date) override; std::shared_ptr LoadObjectByKey(const std::string& key) override; diff --git a/wxdata/include/scwx/provider/nexrad_data_provider.hpp b/wxdata/include/scwx/provider/nexrad_data_provider.hpp index 2b1f683b..14a75815 100644 --- a/wxdata/include/scwx/provider/nexrad_data_provider.hpp +++ b/wxdata/include/scwx/provider/nexrad_data_provider.hpp @@ -64,10 +64,11 @@ public: * * @param date Date for which to list objects * - * @return - New objects found for the given date + * @return - Whether query was successful + * - New objects found for the given date * - Total objects found for the given date */ - virtual std::pair + virtual std::tuple ListObjects(std::chrono::system_clock::time_point date) = 0; /** diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index e8f99b47..2960a3df 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -165,7 +165,7 @@ AwsNexradDataProvider::GetTimePointsByDate( std::vector timePoints {}; - logger_->debug("GetTimePointsByDate: {}", day); + logger_->debug("GetTimePointsByDate: {}", util::TimeString(date)); std::shared_lock lock(p->objectsMutex_); @@ -177,7 +177,11 @@ AwsNexradDataProvider::GetTimePointsByDate( lock.unlock(); // List objects, since the date is not present in the date list - ListObjects(date); + auto [success, newObjects, totalObjects] = ListObjects(date); + if (success) + { + p->UpdateObjectDates(date); + } // Re-lock mutex lock.lock(); @@ -196,7 +200,7 @@ AwsNexradDataProvider::GetTimePointsByDate( return timePoints; } -std::pair +std::tuple AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) { const std::string prefix {GetPrefix(date)}; @@ -262,7 +266,7 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) outcome.GetError().GetMessage()); } - return std::make_pair(newObjects, totalObjects); + return {outcome.IsSuccess(), newObjects, totalObjects}; } std::shared_ptr @@ -309,16 +313,16 @@ std::pair AwsNexradDataProvider::Refresh() // yesterday, to ensure we haven't missed any objects near midnight if (p->refreshDate_ < today) { - auto [newObjects, totalObjects] = ListObjects(yesterday); - allNewObjects = newObjects; - allTotalObjects = totalObjects; + auto [success, newObjects, totalObjects] = ListObjects(yesterday); + allNewObjects = newObjects; + allTotalObjects = totalObjects; if (totalObjects > 0) { p->refreshDate_ = yesterday; } } - auto [newObjects, totalObjects] = ListObjects(today); + auto [success, newObjects, totalObjects] = ListObjects(today); allNewObjects += newObjects; allTotalObjects += totalObjects; if (totalObjects > 0) From 27ce694df82a0dc0d1251d01a5612e61cbfd0a99 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 24 May 2023 23:02:08 -0500 Subject: [PATCH 24/50] Timeline manager live selection time update --- scwx-qt/source/scwx/qt/manager/timeline_manager.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index d14e10a6..47856aac 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -151,6 +151,18 @@ void TimelineManager::Impl::SelectTime( // Nothing to do return; } + else if (selectedTime == std::chrono::system_clock::time_point {}) + { + // If a default time point is given, reset to a live view + selectedTime_ = selectedTime; + adjustedTime_ = selectedTime; + + logger_->debug("Time updated: Live"); + + emit self_->TimeUpdated(selectedTime); + + return; + } scwx::util::async( [=, this]() From ca88d60a5dfce1a0e904311f0639ba44d7b70615 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 01:14:42 -0500 Subject: [PATCH 25/50] Update recent object dates when date is already cached --- .../scwx/provider/aws_nexrad_data_provider.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index 2960a3df..6c749e82 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -165,13 +165,14 @@ AwsNexradDataProvider::GetTimePointsByDate( std::vector timePoints {}; - logger_->debug("GetTimePointsByDate: {}", util::TimeString(date)); + logger_->trace("GetTimePointsByDate: {}", util::TimeString(date)); std::shared_lock lock(p->objectsMutex_); // Is the date present in the date list? - if (std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day) == - p->objectDates_.cend()) + auto currentDateIterator = + std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day); + if (currentDateIterator == p->objectDates_.cend()) { // Temporarily unlock mutex lock.unlock(); @@ -197,6 +198,16 @@ AwsNexradDataProvider::GetTimePointsByDate( std::back_inserter(timePoints), [](const auto& object) { return object.first; }); + // Unlock mutex, finished + lock.unlock(); + + // If we haven't updated the most recently queried dates yet, because the + // date was already cached, update + if (currentDateIterator != p->objectDates_.cend()) + { + p->UpdateObjectDates(date); + } + return timePoints; } From a41f4b802eee6867c6e8e53eadf44e6bd14468dd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 01:25:16 -0500 Subject: [PATCH 26/50] Split volume time and selected time - Volume time to be used for radar product view - Selected time to be used for animated alerts --- scwx-qt/source/scwx/qt/main/main_window.cpp | 2 +- .../scwx/qt/manager/timeline_manager.cpp | 18 +++++++++++++----- .../scwx/qt/manager/timeline_manager.hpp | 4 +++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 2a637600..31d0d537 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -697,7 +697,7 @@ void MainWindowImpl::ConnectAnimationSignals() &manager::TimelineManager::AnimationStepEnd); connect(timelineManager_.get(), - &manager::TimelineManager::TimeUpdated, + &manager::TimelineManager::VolumeTimeUpdated, [this](std::chrono::system_clock::time_point dateTime) { for (auto map : maps_) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 47856aac..b4fee799 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -159,7 +159,8 @@ void TimelineManager::Impl::SelectTime( logger_->debug("Time updated: Live"); - emit self_->TimeUpdated(selectedTime); + emit self_->VolumeTimeUpdated(selectedTime); + emit self_->SelectedTimeUpdated(selectedTime); return; } @@ -182,7 +183,6 @@ void TimelineManager::Impl::SelectTime( if (elementPtr != nullptr) { - selectedTime_ = selectedTime; // If the adjusted time changed, or if a new radar site has been // selected @@ -192,17 +192,25 @@ void TimelineManager::Impl::SelectTime( // If the time was found, select it adjustedTime_ = *elementPtr; - logger_->debug("Time updated: {}", adjustedTime_); + logger_->debug("Volume time updated: {}", + scwx::util::TimeString(adjustedTime_)); - emit self_->TimeUpdated(adjustedTime_); + emit self_->VolumeTimeUpdated(adjustedTime_); } } else { // No volume time was found - logger_->info("No volume scan found for {}", selectedTime); + logger_->info("No volume scan found for {}", + scwx::util::TimeString(selectedTime)); } + logger_->trace("Selected time updated: {}", + scwx::util::TimeString(selectedTime)); + + selectedTime_ = selectedTime; + emit self_->SelectedTimeUpdated(selectedTime); + previousRadarSite_ = radarSite_; }); } diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index 84ca7ad5..3d6e0cc9 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -41,7 +41,9 @@ public slots: void AnimationStepEnd(); signals: - void TimeUpdated(std::chrono::system_clock::time_point dateTime); + void SelectedTimeUpdated(std::chrono::system_clock::time_point dateTime); + void VolumeTimeUpdated(std::chrono::system_clock::time_point dateTime); + void ViewTypeUpdated(types::MapTime viewType); private: From 6f1fb843973704347c93f7fae0358489aa765225 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 10:45:55 -0500 Subject: [PATCH 27/50] Update timeline radar site selection logic --- scwx-qt/source/scwx/qt/main/main_window.cpp | 17 ++++------------- .../source/scwx/qt/manager/timeline_manager.cpp | 2 ++ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 31d0d537..1791e15e 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -705,19 +705,6 @@ void MainWindowImpl::ConnectAnimationSignals() map->SelectTime(dateTime); } }); - - for (auto map : maps_) - { - connect(map, - &map::MapWidget::RadarSiteUpdated, - [this, map](std::shared_ptr radarSite) - { - if (map == activeMap_) - { - timelineManager_->SetRadarSite(radarSite->id()); - } - }); - } } void MainWindowImpl::ConnectOtherSignals() @@ -927,11 +914,15 @@ void MainWindowImpl::UpdateRadarSite() mainWindow_->ui->radarSiteValueLabel->setText(radarSite->id().c_str()); mainWindow_->ui->radarLocationLabel->setText( radarSite->location_name().c_str()); + + timelineManager_->SetRadarSite(radarSite->id()); } else { mainWindow_->ui->radarSiteValueLabel->setVisible(false); mainWindow_->ui->radarLocationLabel->setVisible(false); + + timelineManager_->SetRadarSite("?"); } } diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index b4fee799..7269aee1 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -51,6 +51,8 @@ TimelineManager::~TimelineManager() = default; void TimelineManager::SetRadarSite(const std::string& radarSite) { + logger_->debug("SetRadarSite: {}", radarSite); + p->radarSite_ = radarSite; if (p->viewType_ == types::MapTime::Live) From 0ddd9d91eaa59ddd76f0cb62644dc9894a0a8e60 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 16:14:33 -0500 Subject: [PATCH 28/50] Timeline step back --- .../scwx/qt/manager/timeline_manager.cpp | 63 +++++++++++++++++++ wxdata/include/scwx/util/map.hpp | 39 ++++++++---- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 7269aee1..4d25f09b 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -33,6 +33,7 @@ public: TimelineManager* self_; void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); + void StepBack(); std::string radarSite_ {"?"}; std::string previousRadarSite_ {"?"}; @@ -51,6 +52,12 @@ TimelineManager::~TimelineManager() = default; void TimelineManager::SetRadarSite(const std::string& radarSite) { + if (p->radarSite_ == radarSite) + { + // No action needed + return; + } + logger_->debug("SetRadarSite: {}", radarSite); p->radarSite_ = radarSite; @@ -123,6 +130,8 @@ void TimelineManager::AnimationStepBegin() void TimelineManager::AnimationStepBack() { logger_->debug("AnimationStepBack"); + + p->StepBack(); } void TimelineManager::AnimationPlay() @@ -217,6 +226,60 @@ void TimelineManager::Impl::SelectTime( }); } +void TimelineManager::Impl::StepBack() +{ + scwx::util::async( + [this]() + { + // Take a lock for time selection + std::unique_lock lock {selectTimeMutex_}; + + // Determine time to get active volume times + std::chrono::system_clock::time_point queryTime = adjustedTime_; + if (queryTime == std::chrono::system_clock::time_point {}) + { + queryTime = std::chrono::system_clock::now(); + } + + // Request active volume times + auto radarProductManager = + manager::RadarProductManager::Instance(radarSite_); + auto volumeTimes = + radarProductManager->GetActiveVolumeTimes(queryTime); + + std::set::const_iterator it; + + if (adjustedTime_ == std::chrono::system_clock::time_point {}) + { + // If the adjusted time is live, get the last element in the set + it = volumeTimes.cend(); + if (!volumeTimes.empty()) + { + --it; + } + } + else + { + // Get the current element in the set + it = scwx::util::GetBoundedElementIterator(volumeTimes, + adjustedTime_); + } + + // Only if we aren't at the beginning of the volume times set + if (it != volumeTimes.cbegin()) + { + // Select the previous time + adjustedTime_ = *(--it); + selectedTime_ = adjustedTime_; + + logger_->debug("Volume time updated: {}", + scwx::util::TimeString(adjustedTime_)); + + emit self_->VolumeTimeUpdated(adjustedTime_); + } + }); +} + std::shared_ptr TimelineManager::Instance() { static std::weak_ptr timelineManagerReference_ {}; diff --git a/wxdata/include/scwx/util/map.hpp b/wxdata/include/scwx/util/map.hpp index b6282807..f35e2abf 100644 --- a/wxdata/include/scwx/util/map.hpp +++ b/wxdata/include/scwx/util/map.hpp @@ -8,14 +8,13 @@ namespace scwx namespace util { -template -ReturnType GetBoundedElementPointer(Container& container, - const typename Container::key_type& key) +template +Container::const_iterator +GetBoundedElementIterator(Container& container, + const typename Container::key_type& key) { - ReturnType elementPtr {nullptr}; - // Find the first element greater than the key requested - auto it = container.upper_bound(key); + typename Container::const_iterator it = container.upper_bound(key); // An element with a key greater was found if (it != container.cend()) @@ -25,19 +24,34 @@ ReturnType GetBoundedElementPointer(Container& container, { // Get the element immediately preceding, this the element we are // looking for - elementPtr = &(*(--it)); + --it; } else { // The current element is a good substitute - elementPtr = &(*it); } } else if (container.size() > 0) { // An element with a key greater was not found. If it exists, it must be - // the last element. - elementPtr = &(*container.rbegin()); + // the last element. Decrement the end iterator. + --it; + } + + return it; +} + +template +ReturnType GetBoundedElementPointer(Container& container, + const typename Container::key_type& key) +{ + ReturnType elementPtr {nullptr}; + + auto it = GetBoundedElementIterator(container, key); + + if (it != container.cend()) + { + elementPtr = &(*(it)); } return elementPtr; @@ -48,9 +62,10 @@ ReturnType GetBoundedElement(std::map& map, const Key& key) { ReturnType element; - typename std::map::pointer elementPtr = + typename std::map::const_pointer elementPtr = GetBoundedElementPointer, - typename std::map::pointer>(map, key); + typename std::map::const_pointer>(map, + key); if (elementPtr != nullptr) { element = elementPtr->second; From 5f9771846982218cec4221db175b25a2bd55e3ee Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 16:32:21 -0500 Subject: [PATCH 29/50] Timeline step next --- .../scwx/qt/manager/timeline_manager.cpp | 64 ++++++++++++++----- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 4d25f09b..dc215ac7 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -19,6 +19,12 @@ namespace manager static const std::string logPrefix_ = "scwx::qt::manager::timeline_manager"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +enum class Direction +{ + Back, + Next +}; + class TimelineManager::Impl { public: @@ -33,7 +39,7 @@ public: TimelineManager* self_; void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); - void StepBack(); + void Step(Direction direction); std::string radarSite_ {"?"}; std::string previousRadarSite_ {"?"}; @@ -131,7 +137,7 @@ void TimelineManager::AnimationStepBack() { logger_->debug("AnimationStepBack"); - p->StepBack(); + p->Step(Direction::Back); } void TimelineManager::AnimationPlay() @@ -147,6 +153,8 @@ void TimelineManager::AnimationPause() void TimelineManager::AnimationStepNext() { logger_->debug("AnimationStepNext"); + + p->Step(Direction::Next); } void TimelineManager::AnimationStepEnd() @@ -226,10 +234,10 @@ void TimelineManager::Impl::SelectTime( }); } -void TimelineManager::Impl::StepBack() +void TimelineManager::Impl::Step(Direction direction) { scwx::util::async( - [this]() + [=, this]() { // Take a lock for time selection std::unique_lock lock {selectTimeMutex_}; @@ -247,16 +255,18 @@ void TimelineManager::Impl::StepBack() auto volumeTimes = radarProductManager->GetActiveVolumeTimes(queryTime); + if (volumeTimes.empty()) + { + logger_->debug("No products to step through"); + return; + } + std::set::const_iterator it; if (adjustedTime_ == std::chrono::system_clock::time_point {}) { // If the adjusted time is live, get the last element in the set - it = volumeTimes.cend(); - if (!volumeTimes.empty()) - { - --it; - } + it = std::prev(volumeTimes.cend()); } else { @@ -265,17 +275,37 @@ void TimelineManager::Impl::StepBack() adjustedTime_); } - // Only if we aren't at the beginning of the volume times set - if (it != volumeTimes.cbegin()) + if (direction == Direction::Back) { - // Select the previous time - adjustedTime_ = *(--it); - selectedTime_ = adjustedTime_; + // Only if we aren't at the beginning of the volume times set + if (it != volumeTimes.cbegin()) + { + // Select the previous time + adjustedTime_ = *(--it); + selectedTime_ = adjustedTime_; - logger_->debug("Volume time updated: {}", - scwx::util::TimeString(adjustedTime_)); + logger_->debug("Volume time updated: {}", + scwx::util::TimeString(adjustedTime_)); - emit self_->VolumeTimeUpdated(adjustedTime_); + emit self_->VolumeTimeUpdated(adjustedTime_); + emit self_->SelectedTimeUpdated(adjustedTime_); + } + } + else + { + // Only if we aren't at the end of the volume times set + if (it != std::prev(volumeTimes.cend())) + { + // Select the next time + adjustedTime_ = *(++it); + selectedTime_ = adjustedTime_; + + logger_->debug("Volume time updated: {}", + scwx::util::TimeString(adjustedTime_)); + + emit self_->VolumeTimeUpdated(adjustedTime_); + emit self_->SelectedTimeUpdated(adjustedTime_); + } } }); } From 1479525c4a72864084117af546e8ecc20a5e820b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 17:12:12 -0500 Subject: [PATCH 30/50] Timeline step begin and end --- .../scwx/qt/manager/timeline_manager.cpp | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index dc215ac7..2ebd038e 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -131,6 +131,18 @@ void TimelineManager::SetLoopSpeed(double loopSpeed) void TimelineManager::AnimationStepBegin() { logger_->debug("AnimationStepBegin"); + + if (p->viewType_ == types::MapTime::Live || + 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_); + } + else + { + // If the selected view type is archive, select using the pinned time + p->SelectTime(p->pinnedTime_ - p->loopTime_); + } } void TimelineManager::AnimationStepBack() @@ -160,6 +172,17 @@ void TimelineManager::AnimationStepNext() void TimelineManager::AnimationStepEnd() { logger_->debug("AnimationStepEnd"); + + if (p->viewType_ == types::MapTime::Live) + { + // If the selected view type is live, select the current products + p->SelectTime(); + } + else + { + // If the selected view type is archive, select using the pinned time + p->SelectTime(p->pinnedTime_); + } } void TimelineManager::Impl::SelectTime( From 5a078800e4abad08532b397a5fd4d8f70fb19002 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 26 May 2023 22:42:06 -0500 Subject: [PATCH 31/50] QDate::toStdSysDays is needlessly behind __cpp_lib_chrono >= 201907L --- scwx-qt/scwx-qt.cmake | 6 +++-- .../scwx/qt/ui/animation_dock_widget.cpp | 5 ++-- scwx-qt/source/scwx/qt/util/time.cpp | 24 ++++++++++++++++++ scwx-qt/source/scwx/qt/util/time.hpp | 25 +++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/util/time.cpp create mode 100644 scwx-qt/source/scwx/qt/util/time.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 5aaa1f2a..1cee1716 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -186,7 +186,8 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/streams.hpp source/scwx/qt/util/texture_atlas.hpp source/scwx/qt/util/q_file_buffer.hpp - source/scwx/qt/util/q_file_input_stream.hpp) + source/scwx/qt/util/q_file_input_stream.hpp + source/scwx/qt/util/time.hpp) set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/file.cpp source/scwx/qt/util/font.cpp @@ -195,7 +196,8 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/json.cpp source/scwx/qt/util/texture_atlas.cpp source/scwx/qt/util/q_file_buffer.cpp - source/scwx/qt/util/q_file_input_stream.cpp) + source/scwx/qt/util/q_file_input_stream.cpp + source/scwx/qt/util/time.cpp) set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp source/scwx/qt/view/level3_product_view.hpp source/scwx/qt/view/level3_radial_view.hpp 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 a147cef3..f9bcbd58 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -1,6 +1,7 @@ #include "animation_dock_widget.hpp" #include "ui_animation_dock_widget.h" +#include #include #include @@ -65,7 +66,7 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : ui->dateEdit->setDate(currentDate); ui->timeEdit->setTime(currentTime); ui->dateEdit->setMaximumDate(currentDateTime.date()); - p->selectedDate_ = currentDate.toStdSysDays(); + p->selectedDate_ = util::SysDays(currentDate); p->selectedTime_ = std::chrono::seconds(currentTime.msecsSinceStartOfDay() / 1000); @@ -129,7 +130,7 @@ void AnimationDockWidgetImpl::ConnectSignals() { if (date.isValid()) { - selectedDate_ = date.toStdSysDays(); + selectedDate_ = util::SysDays(date); emit self_->DateTimeChanged(selectedDate_ + selectedTime_); } }); diff --git a/scwx-qt/source/scwx/qt/util/time.cpp b/scwx-qt/source/scwx/qt/util/time.cpp new file mode 100644 index 00000000..f34c6ea5 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/time.cpp @@ -0,0 +1,24 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +std::chrono::sys_days SysDays(const QDate& date) +{ + using namespace std::chrono; + using sys_days = time_point; + constexpr auto julianEpoch = sys_days {-4713y / November / 24d}; + constexpr auto unixEpoch = sys_days {1970y / January / 1d}; + constexpr auto offset = std::chrono::days(julianEpoch - unixEpoch); + + return std::chrono::sys_days(std::chrono::days(date.toJulianDay()) + + julianEpoch); +} + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/time.hpp b/scwx-qt/source/scwx/qt/util/time.hpp new file mode 100644 index 00000000..8f3e7a53 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/time.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +/** + * @brief Convert QDate to std::chrono::sys_days. + * + * @param [in] date Date to convert + * + * @return Days + */ +std::chrono::sys_days SysDays(const QDate& date); + +} // namespace util +} // namespace qt +} // namespace scwx From 41b9e25ea899ba8e904c7f67392b6e0817eb43ce Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 May 2023 00:22:34 -0500 Subject: [PATCH 32/50] Timeline play --- .../scwx/qt/manager/timeline_manager.cpp | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 2ebd038e..534daed7 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -7,6 +7,7 @@ #include +#include #include namespace scwx @@ -33,11 +34,15 @@ public: ~Impl() { // Lock mutexes before destroying + std::unique_lock animationTimerLock {animationTimerMutex_}; + animationTimer_.cancel(); + std::unique_lock selectTimeLock {selectTimeMutex_}; } TimelineManager* self_; + void Play(); void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); void Step(Direction direction); @@ -50,6 +55,9 @@ public: std::chrono::minutes loopTime_ {30}; double loopSpeed_ {1.0}; + boost::asio::steady_timer animationTimer_ {scwx::util::io_context()}; + std::mutex animationTimerMutex_ {}; + std::mutex selectTimeMutex_ {}; }; @@ -125,6 +133,11 @@ void TimelineManager::SetLoopSpeed(double loopSpeed) { logger_->debug("SetLoopSpeed: {}", loopSpeed); + if (loopSpeed < 1.0) + { + loopSpeed = 1.0; + } + p->loopSpeed_ = loopSpeed; } @@ -155,6 +168,8 @@ void TimelineManager::AnimationStepBack() void TimelineManager::AnimationPlay() { logger_->debug("AnimationPlay"); + + p->Play(); } void TimelineManager::AnimationPause() @@ -185,6 +200,80 @@ void TimelineManager::AnimationStepEnd() } } +void TimelineManager::Impl::Play() +{ + using namespace std::chrono_literals; + + { + std::unique_lock animationTimerLock {animationTimerMutex_}; + animationTimer_.cancel(); + } + + scwx::util::async( + [this]() + { + // Take a lock for time selection + std::unique_lock lock {selectTimeMutex_}; + + // Determine loop end time + std::chrono::system_clock::time_point endTime; + + if (viewType_ == types::MapTime::Live || + pinnedTime_ == std::chrono::system_clock::time_point {}) + { + endTime = std::chrono::floor( + std::chrono::system_clock::now()); + } + else + { + endTime = pinnedTime_; + } + + // Determine loop start time and current position in the loop + std::chrono::system_clock::time_point startTime = endTime - loopTime_; + std::chrono::system_clock::time_point currentTime = selectedTime_; + + // Unlock prior to selecting time + lock.unlock(); + + if (currentTime < startTime || currentTime >= endTime) + { + // If the currently selected time is out of the loop, select the + // start time + SelectTime(startTime); + } + else + { + // If the currently selected time is in the loop, increment + SelectTime(currentTime + 1min); + } + + // Determine repeat interval (loop speed of 1.0 is 1 minute per second) + std::chrono::milliseconds interval = + std::chrono::milliseconds(std::lroundl(1000.0 / loopSpeed_)); + + std::unique_lock animationTimerLock {animationTimerMutex_}; + + animationTimer_.expires_after(interval); + animationTimer_.async_wait( + [this](const boost::system::error_code& e) + { + if (e == boost::system::errc::success) + { + Play(); + } + else if (e == boost::asio::error::operation_aborted) + { + logger_->debug("Play timer cancelled"); + } + else + { + logger_->warn("Play timer error: {}", e.message()); + } + }); + }); +} + void TimelineManager::Impl::SelectTime( std::chrono::system_clock::time_point selectedTime) { From ba1de683fae11b8f65e6c63f63b8d5363c7ef6a6 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 May 2023 01:17:19 -0500 Subject: [PATCH 33/50] Timeline pause --- scwx-qt/source/scwx/qt/main/main_window.cpp | 11 ++-- .../scwx/qt/manager/timeline_manager.cpp | 52 +++++++++++++++---- .../scwx/qt/manager/timeline_manager.hpp | 4 +- scwx-qt/source/scwx/qt/types/map_types.hpp | 6 +++ .../scwx/qt/ui/animation_dock_widget.cpp | 38 +++++++------- .../scwx/qt/ui/animation_dock_widget.hpp | 4 +- 6 files changed, 79 insertions(+), 36 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 1791e15e..43ef66af 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -682,11 +682,7 @@ void MainWindowImpl::ConnectAnimationSignals() connect(animationDockWidget_, &ui::AnimationDockWidget::AnimationPlaySelected, timelineManager_.get(), - &manager::TimelineManager::AnimationPlay); - connect(animationDockWidget_, - &ui::AnimationDockWidget::AnimationPauseSelected, - timelineManager_.get(), - &manager::TimelineManager::AnimationPause); + &manager::TimelineManager::AnimationPlayPause); connect(animationDockWidget_, &ui::AnimationDockWidget::AnimationStepNextSelected, timelineManager_.get(), @@ -705,6 +701,11 @@ void MainWindowImpl::ConnectAnimationSignals() map->SelectTime(dateTime); } }); + + connect(timelineManager_.get(), + &manager::TimelineManager::AnimationStateUpdated, + animationDockWidget_, + &ui::AnimationDockWidget::UpdateAnimationState); } void MainWindowImpl::ConnectOtherSignals() diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 534daed7..d2955f33 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -42,6 +42,7 @@ public: TimelineManager* self_; + void Pause(); void Play(); void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); void Step(Direction direction); @@ -55,6 +56,7 @@ public: std::chrono::minutes loopTime_ {30}; double loopSpeed_ {1.0}; + types::AnimationState animationState_ {types::AnimationState::Pause}; boost::asio::steady_timer animationTimer_ {scwx::util::io_context()}; std::mutex animationTimerMutex_ {}; @@ -145,6 +147,8 @@ void TimelineManager::AnimationStepBegin() { logger_->debug("AnimationStepBegin"); + p->Pause(); + if (p->viewType_ == types::MapTime::Live || p->pinnedTime_ == std::chrono::system_clock::time_point {}) { @@ -162,25 +166,29 @@ void TimelineManager::AnimationStepBack() { logger_->debug("AnimationStepBack"); + p->Pause(); p->Step(Direction::Back); } -void TimelineManager::AnimationPlay() +void TimelineManager::AnimationPlayPause() { - logger_->debug("AnimationPlay"); - - p->Play(); -} - -void TimelineManager::AnimationPause() -{ - logger_->debug("AnimationPause"); + if (p->animationState_ == types::AnimationState::Pause) + { + logger_->debug("AnimationPlay"); + p->Play(); + } + else + { + logger_->debug("AnimationPause"); + p->Pause(); + } } void TimelineManager::AnimationStepNext() { logger_->debug("AnimationStepNext"); + p->Pause(); p->Step(Direction::Next); } @@ -188,6 +196,8 @@ void TimelineManager::AnimationStepEnd() { logger_->debug("AnimationStepEnd"); + p->Pause(); + if (p->viewType_ == types::MapTime::Live) { // If the selected view type is live, select the current products @@ -200,10 +210,29 @@ void TimelineManager::AnimationStepEnd() } } +void TimelineManager::Impl::Pause() +{ + // Cancel animation + std::unique_lock animationTimerLock {animationTimerMutex_}; + animationTimer_.cancel(); + + if (animationState_ != types::AnimationState::Pause) + { + animationState_ = types::AnimationState::Pause; + emit self_->AnimationStateUpdated(animationState_); + } +} + void TimelineManager::Impl::Play() { using namespace std::chrono_literals; + if (animationState_ != types::AnimationState::Play) + { + animationState_ = types::AnimationState::Play; + emit self_->AnimationStateUpdated(animationState_); + } + { std::unique_lock animationTimerLock {animationTimerMutex_}; animationTimer_.cancel(); @@ -260,7 +289,10 @@ void TimelineManager::Impl::Play() { if (e == boost::system::errc::success) { - Play(); + if (animationState_ == types::AnimationState::Play) + { + Play(); + } } else if (e == boost::asio::error::operation_aborted) { diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index 3d6e0cc9..2f3a3fd9 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -35,8 +35,7 @@ public slots: void AnimationStepBegin(); void AnimationStepBack(); - void AnimationPlay(); - void AnimationPause(); + void AnimationPlayPause(); void AnimationStepNext(); void AnimationStepEnd(); @@ -44,6 +43,7 @@ signals: void SelectedTimeUpdated(std::chrono::system_clock::time_point dateTime); void VolumeTimeUpdated(std::chrono::system_clock::time_point dateTime); + void AnimationStateUpdated(types::AnimationState state); void ViewTypeUpdated(types::MapTime viewType); private: diff --git a/scwx-qt/source/scwx/qt/types/map_types.hpp b/scwx-qt/source/scwx/qt/types/map_types.hpp index a23932ee..d2c804a2 100644 --- a/scwx-qt/source/scwx/qt/types/map_types.hpp +++ b/scwx-qt/source/scwx/qt/types/map_types.hpp @@ -9,6 +9,12 @@ namespace qt namespace types { +enum class AnimationState +{ + Play, + Pause +}; + enum class MapTime { Live, 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 f9bcbd58..49035d12 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -16,21 +16,18 @@ namespace ui static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -enum class AnimationState -{ - Play, - Pause -}; - class AnimationDockWidgetImpl { public: explicit AnimationDockWidgetImpl(AnimationDockWidget* self) : self_ {self} {} ~AnimationDockWidgetImpl() = default; + const QIcon kPauseIcon_ {":/res/icons/font-awesome-6/pause-solid.svg"}; + const QIcon kPlayIcon_ {":/res/icons/font-awesome-6/play-solid.svg"}; + AnimationDockWidget* self_; - AnimationState animationState_ {AnimationState::Pause}; + types::AnimationState animationState_ {types::AnimationState::Pause}; std::chrono::sys_days selectedDate_ {}; std::chrono::seconds selectedTime_ {}; @@ -171,17 +168,7 @@ void AnimationDockWidgetImpl::ConnectSignals() QObject::connect(self_->ui->playButton, &QAbstractButton::clicked, self_, - [this]() - { - if (animationState_ == AnimationState::Pause) - { - emit self_->AnimationPlaySelected(); - } - else - { - emit self_->AnimationPauseSelected(); - } - }); + [this]() { emit self_->AnimationPlaySelected(); }); QObject::connect(self_->ui->stepNextButton, &QAbstractButton::clicked, self_, @@ -192,6 +179,21 @@ void AnimationDockWidgetImpl::ConnectSignals() [this]() { emit self_->AnimationStepEndSelected(); }); } +void AnimationDockWidget::UpdateAnimationState(types::AnimationState state) +{ + // Update icon to opposite of state + switch (state) + { + case types::AnimationState::Pause: + ui->playButton->setIcon(p->kPlayIcon_); + break; + + case types::AnimationState::Play: + ui->playButton->setIcon(p->kPauseIcon_); + break; + } +} + } // namespace ui } // namespace qt } // namespace scwx 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 a054e592..a80d8912 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -28,6 +28,9 @@ public: explicit AnimationDockWidget(QWidget* parent = nullptr); ~AnimationDockWidget(); +public slots: + void UpdateAnimationState(types::AnimationState state); + signals: void ViewTypeChanged(types::MapTime viewType); void DateTimeChanged(std::chrono::system_clock::time_point dateTime); @@ -37,7 +40,6 @@ signals: void AnimationStepBeginSelected(); void AnimationStepBackSelected(); - void AnimationPauseSelected(); void AnimationPlaySelected(); void AnimationStepNextSelected(); void AnimationStepEndSelected(); From 5970eaf678b5bb9c35f38e6b5a9f8b018b4430b1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 May 2023 01:25:51 -0500 Subject: [PATCH 34/50] Add short delay at the end of a loop --- .../scwx/qt/manager/timeline_manager.cpp | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index d2955f33..531dfe15 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -261,25 +261,38 @@ void TimelineManager::Impl::Play() // Determine loop start time and current position in the loop std::chrono::system_clock::time_point startTime = endTime - loopTime_; std::chrono::system_clock::time_point currentTime = selectedTime_; - - // Unlock prior to selecting time - lock.unlock(); + std::chrono::system_clock::time_point newTime; if (currentTime < startTime || currentTime >= endTime) { // If the currently selected time is out of the loop, select the // start time - SelectTime(startTime); + newTime = startTime; } else { // If the currently selected time is in the loop, increment - SelectTime(currentTime + 1min); + newTime = currentTime + 1min; } - // Determine repeat interval (loop speed of 1.0 is 1 minute per second) - std::chrono::milliseconds interval = - std::chrono::milliseconds(std::lroundl(1000.0 / loopSpeed_)); + // Unlock prior to selecting time + lock.unlock(); + + // Select the time + SelectTime(newTime); + + 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_)); + } + else + { + // Pause for 2.5 seconds at the end of the loop + interval = std::chrono::milliseconds(2500); + } std::unique_lock animationTimerLock {animationTimerMutex_}; From 3d42ca9e1258ec4f0ab2bf9a0097740609e6ddc1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 May 2023 10:12:40 -0500 Subject: [PATCH 35/50] Allow manual entry of timeline time --- .../scwx/qt/ui/animation_dock_widget.ui | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) 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 8202dcc3..a072c2c5 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 - 335 + 337 @@ -59,10 +59,41 @@ - - - HH:mm t + + + QFrame::StyledPanel + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + HH:mm + + + + + + + UTC + + + + From 11e74b46ecad5b0cad206ab940f01ceb9e23467d Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 May 2023 23:14:38 -0500 Subject: [PATCH 36/50] Adjust number of stored/cached products based on loop time --- .../scwx/qt/manager/radar_product_manager.cpp | 10 ++- .../scwx/qt/manager/radar_product_manager.hpp | 10 ++- .../scwx/qt/manager/timeline_manager.cpp | 69 ++++++++++++++----- 3 files changed, 70 insertions(+), 19 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 5ca206d1..272a23cb 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -216,6 +216,7 @@ public: bool level3ProductsInitialized_; std::shared_ptr radarSite_; + std::size_t cacheLimit_ {6u}; std::vector coordinates0_5Degree_; std::vector coordinates1Degree_; @@ -1135,7 +1136,7 @@ void RadarProductManagerImpl::UpdateRecentRecords( RadarProductRecordList& recentList, std::shared_ptr record) { - static constexpr std::size_t kRecentListMaxSize_ {2u}; + const std::size_t recentListMaxSize {cacheLimit_}; auto it = std::find(recentList.cbegin(), recentList.cend(), record); if (it != recentList.cbegin() && it != recentList.cend()) @@ -1150,7 +1151,7 @@ void RadarProductManagerImpl::UpdateRecentRecords( recentList.push_front(record); } - while (recentList.size() > kRecentListMaxSize_) + while (recentList.size() > recentListMaxSize) { // Remove from the end of the list while it's too big recentList.pop_back(); @@ -1215,6 +1216,11 @@ std::vector RadarProductManager::GetLevel3Products() return level3ProviderManager->provider_->GetAvailableProducts(); } +void RadarProductManager::SetCacheLimit(size_t cacheLimit) +{ + p->cacheLimit_ = cacheLimit; +} + void RadarProductManager::UpdateAvailableProducts() { std::lock_guard guard(p->level3ProductsInitializeMutex_); 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 34c2c6fb..ed8ba97b 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.hpp @@ -126,7 +126,15 @@ public: common::Level3ProductCategoryMap GetAvailableLevel3Categories(); std::vector GetLevel3Products(); - void UpdateAvailableProducts(); + + /** + * @brief Set the maximum number of products of each type that may be cached. + * + * @param [in] cacheLimit The maximum number of products of each type + */ + void SetCacheLimit(std::size_t cacheLimit); + + void UpdateAvailableProducts(); signals: void DataReloaded(std::shared_ptr record); diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 531dfe15..80d836fc 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -42,6 +42,13 @@ public: TimelineManager* self_; + std::pair + GetLoopStartAndEndTimes(); + void UpdateCacheLimit( + std::shared_ptr radarProductManager, + const std::set& volumeTimes); + void Pause(); void Play(); void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); @@ -223,6 +230,45 @@ void TimelineManager::Impl::Pause() } } +std::pair +TimelineManager::Impl::GetLoopStartAndEndTimes() +{ + // Determine loop end time + std::chrono::system_clock::time_point endTime; + + if (viewType_ == types::MapTime::Live || + pinnedTime_ == std::chrono::system_clock::time_point {}) + { + endTime = std::chrono::floor( + std::chrono::system_clock::now()); + } + else + { + endTime = pinnedTime_; + } + + // Determine loop start time and current position in the loop + std::chrono::system_clock::time_point startTime = endTime - loopTime_; + + return {startTime, endTime}; +} + +void TimelineManager::Impl::UpdateCacheLimit( + std::shared_ptr radarProductManager, + const std::set& volumeTimes) +{ + // Calculate the number of volume scans in the loop + auto [startTime, endTime] = GetLoopStartAndEndTimes(); + auto startIter = util::GetBoundedElementIterator(volumeTimes, startTime); + auto endIter = util::GetBoundedElementIterator(volumeTimes, endTime); + std::size_t numVolumeScans = std::distance(startIter, endIter) + 1; + + // Dynamically update maximum cached volume scans to 1.5x the loop length + radarProductManager->SetCacheLimit( + static_cast(numVolumeScans * 1.5)); +} + void TimelineManager::Impl::Play() { using namespace std::chrono_literals; @@ -244,22 +290,7 @@ void TimelineManager::Impl::Play() // Take a lock for time selection std::unique_lock lock {selectTimeMutex_}; - // Determine loop end time - std::chrono::system_clock::time_point endTime; - - if (viewType_ == types::MapTime::Live || - pinnedTime_ == std::chrono::system_clock::time_point {}) - { - endTime = std::chrono::floor( - std::chrono::system_clock::now()); - } - else - { - endTime = pinnedTime_; - } - - // Determine loop start time and current position in the loop - std::chrono::system_clock::time_point startTime = endTime - loopTime_; + auto [startTime, endTime] = GetLoopStartAndEndTimes(); std::chrono::system_clock::time_point currentTime = selectedTime_; std::chrono::system_clock::time_point newTime; @@ -353,6 +384,9 @@ void TimelineManager::Impl::SelectTime( 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); @@ -418,6 +452,9 @@ void TimelineManager::Impl::Step(Direction direction) return; } + // Dynamically update maximum cached volume scans + UpdateCacheLimit(radarProductManager, volumeTimes); + std::set::const_iterator it; if (adjustedTime_ == std::chrono::system_clock::time_point {}) From 608ee904b81947e233489179d525a536abfe0ff3 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 27 May 2023 23:35:47 -0500 Subject: [PATCH 37/50] Update animation toolbox limits - Correct to nearest value - Loop time 1-1440 minutes - Loop speed 1.00-99.99 --- .../scwx/qt/ui/animation_dock_widget.ui | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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 a072c2c5..562561f5 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -40,6 +40,9 @@ + + QAbstractSpinBox::CorrectToNearestValue + 0 @@ -81,6 +84,9 @@ + + QAbstractSpinBox::CorrectToNearestValue + HH:mm @@ -105,6 +111,15 @@ + + QAbstractSpinBox::CorrectToNearestValue + + + 1 + + + 1440 + 30 @@ -119,6 +134,12 @@ + + QAbstractSpinBox::CorrectToNearestValue + + + 1.000000000000000 + 1.000000000000000 From 1c159a39260f57f1a6813b3abadd90707c59cfa7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 28 May 2023 13:05:28 -0500 Subject: [PATCH 38/50] Use dedicated thread pool for radar product views --- scwx-qt/source/scwx/qt/view/level2_product_view.cpp | 5 ----- scwx-qt/source/scwx/qt/view/level2_product_view.hpp | 1 - scwx-qt/source/scwx/qt/view/level3_product_view.cpp | 6 ------ scwx-qt/source/scwx/qt/view/level3_product_view.hpp | 1 - scwx-qt/source/scwx/qt/view/radar_product_view.cpp | 8 ++++++++ scwx-qt/source/scwx/qt/view/radar_product_view.hpp | 2 +- 6 files changed, 9 insertions(+), 14 deletions(-) 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 01271814..53b9080c 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -290,11 +290,6 @@ void Level2ProductViewImpl::SetProduct(common::Level2Product product) } } -void Level2ProductView::Update() -{ - util::async([this]() { ComputeSweep(); }); -} - void Level2ProductView::UpdateColorTable() { if (p->momentDataBlock0_ == nullptr || // diff --git a/scwx-qt/source/scwx/qt/view/level2_product_view.hpp b/scwx-qt/source/scwx/qt/view/level2_product_view.hpp index a00dd0da..507e705d 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.hpp @@ -39,7 +39,6 @@ public: void LoadColorTable(std::shared_ptr colorTable) override; void SelectElevation(float elevation) override; void SelectProduct(const std::string& productName) override; - void Update() override; common::RadarProductGroup GetRadarProductGroup() const override; std::string GetRadarProductName() const override; diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp index c0a45ea2..5924ce35 100644 --- a/scwx-qt/source/scwx/qt/view/level3_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_product_view.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -161,11 +160,6 @@ void Level3ProductView::LoadColorTable( UpdateColorTable(); } -void Level3ProductView::Update() -{ - util::async([this]() { ComputeSweep(); }); -} - void Level3ProductView::UpdateColorTable() { logger_->debug("UpdateColorTable()"); diff --git a/scwx-qt/source/scwx/qt/view/level3_product_view.hpp b/scwx-qt/source/scwx/qt/view/level3_product_view.hpp index c9d299eb..0ea023db 100644 --- a/scwx-qt/source/scwx/qt/view/level3_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/level3_product_view.hpp @@ -32,7 +32,6 @@ public: std::uint16_t color_table_max() const override; void LoadColorTable(std::shared_ptr colorTable) override; - void Update() override; common::RadarProductGroup GetRadarProductGroup() const override; std::string GetRadarProductName() const override; diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp index 99cd78c6..a282e1f4 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -36,6 +37,8 @@ public: } ~RadarProductViewImpl() = default; + boost::asio::thread_pool threadPool_ {1}; + bool initialized_; std::mutex sweepMutex_; @@ -118,6 +121,11 @@ void RadarProductView::SelectTime(std::chrono::system_clock::time_point time) p->selectedTime_ = time; } +void RadarProductView::Update() +{ + boost::asio::post(p->threadPool_, [this]() { ComputeSweep(); }); +} + bool RadarProductView::IsInitialized() const { return p->initialized_; diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp index 04c9131d..8a589a48 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp @@ -51,7 +51,7 @@ public: virtual void SelectElevation(float elevation); virtual void SelectProduct(const std::string& productName) = 0; void SelectTime(std::chrono::system_clock::time_point time); - virtual void Update() = 0; + void Update(); bool IsInitialized() const; From 4e8b5ac452c130c8fa804b90a0a93d74a5a06d9a Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 28 May 2023 13:06:20 -0500 Subject: [PATCH 39/50] Fix to prevent accessing empty level 3 product records --- scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 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 272a23cb..a98a812f 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -1013,7 +1013,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord( auto it = level3ProductRecordsMap_.find(product); - if (it != level3ProductRecordsMap_.cend()) + if (it != level3ProductRecordsMap_.cend() && !it->second.empty()) { if (time == std::chrono::system_clock::time_point {}) { From 58a2d8982afb35e9a3aa87013e2890af1a949441 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 28 May 2023 20:18:06 -0500 Subject: [PATCH 40/50] Adjust date pruning threshold --- wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp index 6c749e82..f6dbf79a 100644 --- a/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp +++ b/wxdata/source/scwx/provider/aws_nexrad_data_provider.cpp @@ -20,8 +20,9 @@ static const std::string logPrefix_ = "scwx::provider::aws_nexrad_data_provider"; static const auto logger_ = util::Logger::Create(logPrefix_); -// Keep at least today, yesterday, and one more date -static const size_t kMinDatesBeforePruning_ = 4; +// Keep at least today, yesterday, and three more dates (archived volume scan +// list size) +static const size_t kMinDatesBeforePruning_ = 6; static const size_t kMaxObjects_ = 2500; class AwsNexradDataProvider::Impl From 45b0df3e0b6bd658f8f30d7d713b409665573f69 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 00:47:47 -0500 Subject: [PATCH 41/50] Fix text product start time when first P-VTEC uses 000000T0000Z --- scwx-qt/source/scwx/qt/model/alert_model.cpp | 3 +- .../scwx/awips/text_product_message.hpp | 9 +- .../scwx/awips/text_product_message.cpp | 83 +++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index 8ea4ccc1..793d67b4 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -421,8 +421,7 @@ AlertModelImpl::GetStartTime(const types::TextEventKey& key) if (messageList.size() > 0) { auto& firstMessage = messageList.front(); - auto firstSegment = firstMessage->segment(0); - return firstSegment->header_->vtecString_[0].pVtec_.event_begin(); + return firstMessage->segment_event_begin(0); } else { diff --git a/wxdata/include/scwx/awips/text_product_message.hpp b/wxdata/include/scwx/awips/text_product_message.hpp index ec90f82d..80ba4a16 100644 --- a/wxdata/include/scwx/awips/text_product_message.hpp +++ b/wxdata/include/scwx/awips/text_product_message.hpp @@ -91,11 +91,14 @@ public: std::shared_ptr wmo_header() const; std::vector mnd_header() const; std::vector overview_block() const; - size_t segment_count() const; + std::size_t segment_count() const; std::vector> segments() const; - std::shared_ptr segment(size_t s) const; + std::shared_ptr segment(std::size_t s) const; - size_t data_size() const; + std::chrono::system_clock::time_point + segment_event_begin(std::size_t s) const; + + std::size_t data_size() const; bool Parse(std::istream& is) override; diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index a27e593d..262ec01a 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -15,6 +16,7 @@ namespace awips { static const std::string logPrefix_ = "scwx::awips::text_product_message"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); // Issuance date/time takes one of the following forms: // * _xM__day_mon_
_year @@ -101,6 +103,87 @@ std::shared_ptr TextProductMessage::segment(size_t s) const return p->segments_[s]; } +std::chrono::system_clock::time_point +TextProductMessage::segment_event_begin(std::size_t s) const +{ + std::chrono::system_clock::time_point eventBegin {}; + + auto& header = segment(s)->header_; + if (header.has_value() && !header->vtecString_.empty()) + { + // Determine event begin from P-VTEC string + eventBegin = header->vtecString_[0].pVtec_.event_begin(); + + // If event begin is 000000T0000Z + if (eventBegin == std::chrono::system_clock::time_point {}) + { + using namespace std::chrono; + + // Determine event end from P-VTEC string + system_clock::time_point eventEnd = + header->vtecString_[0].pVtec_.event_end(); + + auto endDays = floor(eventEnd); + year_month_day endDate {endDays}; + + // Determine WMO date/time + std::string wmoDateTime = wmo_header()->date_time(); + + bool wmoDateTimeValid = false; + unsigned long dayOfMonth = 0; + unsigned long beginHour = 0; + unsigned long beginMinute = 0; + + try + { + // WMO date time is in the format DDHHMM + dayOfMonth = std::stoul(wmoDateTime.substr(0, 2)); + beginHour = std::stoul(wmoDateTime.substr(2, 2)); + beginMinute = std::stoul(wmoDateTime.substr(4, 2)); + wmoDateTimeValid = true; + } + catch (const std::exception&) + { + logger_->warn("Malformed WMO date/time: {}", wmoDateTime); + } + + if (wmoDateTimeValid) + { + // Combine end date year and month with WMO date time + eventBegin = + sys_days {endDate.year() / endDate.month() / day {dayOfMonth}} + + hours {beginHour} + minutes {beginMinute}; + + // If the begin date is after the end date, assume the start time + // was the previous month + if (eventBegin > eventEnd) + { + // If the current end month is January + if (endDate.month() == January) + { + // The begin month must be December of last year + eventBegin = sys_days {year {(endDate.year() - 1y).count()} / + December / day {dayOfMonth}} + + hours {beginHour} + minutes {beginMinute}; + } + else + { + // Back up one month + eventBegin = + sys_days {endDate.year() / + month {static_cast( + (endDate.month() - month {1}).count())} / + day {dayOfMonth}} + + hours {beginHour} + minutes {beginMinute}; + } + } + } + } + } + + return eventBegin; +} + size_t TextProductMessage::data_size() const { return 0; From bc21d7bf027101f8e9717b3b94ef17e0bfb3e074 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 00:53:12 -0500 Subject: [PATCH 42/50] Segment event begin GCC warning fixes --- wxdata/source/scwx/awips/text_product_message.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index 262ec01a..7d33e713 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -130,14 +130,15 @@ TextProductMessage::segment_event_begin(std::size_t s) const std::string wmoDateTime = wmo_header()->date_time(); bool wmoDateTimeValid = false; - unsigned long dayOfMonth = 0; + unsigned int dayOfMonth = 0; unsigned long beginHour = 0; unsigned long beginMinute = 0; try { // WMO date time is in the format DDHHMM - dayOfMonth = std::stoul(wmoDateTime.substr(0, 2)); + dayOfMonth = + static_cast(std::stoul(wmoDateTime.substr(0, 2))); beginHour = std::stoul(wmoDateTime.substr(2, 2)); beginMinute = std::stoul(wmoDateTime.substr(4, 2)); wmoDateTimeValid = true; @@ -162,9 +163,11 @@ TextProductMessage::segment_event_begin(std::size_t s) const if (endDate.month() == January) { // The begin month must be December of last year - eventBegin = sys_days {year {(endDate.year() - 1y).count()} / - December / day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; + eventBegin = + sys_days { + year {static_cast((endDate.year() - 1y).count())} / + December / day {dayOfMonth}} + + hours {beginHour} + minutes {beginMinute}; } else { From 6d165068e9ffe052dd42366cb51d8f280100faea Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 01:20:31 -0500 Subject: [PATCH 43/50] Add a grace period for expiring events in the past --- wxdata/source/scwx/awips/text_product_message.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index 7d33e713..9b2a9149 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -156,8 +156,9 @@ TextProductMessage::segment_event_begin(std::size_t s) const hours {beginHour} + minutes {beginMinute}; // If the begin date is after the end date, assume the start time - // was the previous month - if (eventBegin > eventEnd) + // was the previous month (give a 1 day grace period for expiring + // events in the past) + if (eventBegin > eventEnd + 24h) { // If the current end month is January if (endDate.month() == January) From 80f04be510b5ee12633f545ea2d3b3ae39ed230f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 13:00:50 -0500 Subject: [PATCH 44/50] Set loop defaults --- scwx-qt/source/scwx/qt/manager/timeline_manager.cpp | 2 +- scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index 80d836fc..a597e824 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -61,7 +61,7 @@ public: std::chrono::system_clock::time_point selectedTime_ {}; types::MapTime viewType_ {types::MapTime::Live}; std::chrono::minutes loopTime_ {30}; - double loopSpeed_ {1.0}; + double loopSpeed_ {5.0}; types::AnimationState animationState_ {types::AnimationState::Pause}; boost::asio::steady_timer animationTimer_ {scwx::util::io_context()}; 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 49035d12..d48ea020 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -85,6 +85,10 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : // Evaluate every 15 seconds, every second is unnecessary maxDateTimer->start(15000); + // Set loop defaults + ui->loopTimeSpinBox->setValue(30); + ui->loopSpeedSpinBox->setValue(5.0); + // Connect widget signals p->ConnectSignals(); } From 4963add9cc20c3fadb6cbf95028851b1f5b61c3e Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 13:37:21 -0500 Subject: [PATCH 45/50] Enable/disable auto update of radar data based on timeline selection --- scwx-qt/source/scwx/qt/main/main_window.cpp | 14 +++ .../scwx/qt/manager/timeline_manager.cpp | 13 ++ .../scwx/qt/manager/timeline_manager.hpp | 1 + scwx-qt/source/scwx/qt/map/map_widget.cpp | 9 +- scwx-qt/source/scwx/qt/map/map_widget.hpp | 1 + .../scwx/qt/ui/animation_dock_widget.cpp | 16 +++ .../scwx/qt/ui/animation_dock_widget.hpp | 1 + .../scwx/qt/ui/animation_dock_widget.ui | 113 ++++++++++++------ 8 files changed, 129 insertions(+), 39 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 43ef66af..e0b8254b 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -706,6 +706,20 @@ void MainWindowImpl::ConnectAnimationSignals() &manager::TimelineManager::AnimationStateUpdated, animationDockWidget_, &ui::AnimationDockWidget::UpdateAnimationState); + + connect(timelineManager_.get(), + &manager::TimelineManager::LiveStateUpdated, + animationDockWidget_, + &ui::AnimationDockWidget::UpdateLiveState); + connect(timelineManager_.get(), + &manager::TimelineManager::LiveStateUpdated, + [this](bool isLive) + { + for (auto map : maps_) + { + map->SetAutoUpdate(isLive); + } + }); } void MainWindowImpl::ConnectOtherSignals() diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp index a597e824..fd2413cd 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.cpp @@ -366,6 +366,7 @@ void TimelineManager::Impl::SelectTime( logger_->debug("Time updated: Live"); + emit self_->LiveStateUpdated(true); emit self_->VolumeTimeUpdated(selectedTime); emit self_->SelectedTimeUpdated(selectedTime); @@ -391,6 +392,9 @@ void TimelineManager::Impl::SelectTime( auto elementPtr = util::GetBoundedElementPointer(volumeTimes, selectedTime); + // The timeline is no longer live + emit self_->LiveStateUpdated(false); + if (elementPtr != nullptr) { @@ -469,6 +473,13 @@ void TimelineManager::Impl::Step(Direction direction) adjustedTime_); } + if (it == volumeTimes.cend()) + { + // Should not get here, but protect against an error + logger_->error("No suitable volume time found"); + return; + } + if (direction == Direction::Back) { // Only if we aren't at the beginning of the volume times set @@ -481,6 +492,7 @@ void TimelineManager::Impl::Step(Direction direction) logger_->debug("Volume time updated: {}", scwx::util::TimeString(adjustedTime_)); + emit self_->LiveStateUpdated(false); emit self_->VolumeTimeUpdated(adjustedTime_); emit self_->SelectedTimeUpdated(adjustedTime_); } @@ -497,6 +509,7 @@ void TimelineManager::Impl::Step(Direction direction) logger_->debug("Volume time updated: {}", scwx::util::TimeString(adjustedTime_)); + emit self_->LiveStateUpdated(false); emit self_->VolumeTimeUpdated(adjustedTime_); emit self_->SelectedTimeUpdated(adjustedTime_); } diff --git a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp index 2f3a3fd9..a29278b9 100644 --- a/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/timeline_manager.hpp @@ -44,6 +44,7 @@ signals: void VolumeTimeUpdated(std::chrono::system_clock::time_point dateTime); void AnimationStateUpdated(types::AnimationState state); + void LiveStateUpdated(bool isLive); void ViewTypeUpdated(types::MapTime viewType); private: diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index b7fe9ec1..779a16b4 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -62,6 +62,7 @@ public: overlayLayer_ {nullptr}, colorTableLayer_ {nullptr}, autoRefreshEnabled_ {true}, + autoUpdateEnabled_ {true}, selectedLevel2Product_ {common::Level2Product::Unknown}, lastPos_(), currentStyleIndex_ {0}, @@ -146,6 +147,7 @@ public: std::shared_ptr colorTableLayer_; bool autoRefreshEnabled_; + bool autoUpdateEnabled_; common::Level2Product selectedLevel2Product_; @@ -520,6 +522,11 @@ void MapWidget::SetAutoRefresh(bool enabled) } } +void MapWidget::SetAutoUpdate(bool enabled) +{ + p->autoUpdateEnabled_ = enabled; +} + void MapWidget::SetMapLocation(double latitude, double longitude, bool updateRadarSite) @@ -869,7 +876,7 @@ void MapWidgetImpl::RadarProductManagerConnect() const std::string& product, std::chrono::system_clock::time_point latestTime) { - if (autoRefreshEnabled_ && + if (autoUpdateEnabled_ && context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index 22b6da40..d6d0214b 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -89,6 +89,7 @@ public: void SetActive(bool isActive); void SetAutoRefresh(bool enabled); + void SetAutoUpdate(bool enabled); /** * @brief Sets the current map location. 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 d48ea020..f503c8bf 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -198,6 +198,22 @@ void AnimationDockWidget::UpdateAnimationState(types::AnimationState state) } } +void AnimationDockWidget::UpdateLiveState(bool isLive) +{ + static const QString prefix = tr("Auto Update"); + static const QString disabled = tr("Disabled"); + static const QString enabled = tr("Enabled"); + + if (isLive) + { + ui->autoUpdateLabel->setText(QString("%1: %2").arg(prefix).arg(enabled)); + } + else + { + ui->autoUpdateLabel->setText(QString("%1: %2").arg(prefix).arg(disabled)); + } +} + } // namespace ui } // namespace qt } // namespace scwx 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 a80d8912..705ca02f 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -30,6 +30,7 @@ public: public slots: void UpdateAnimationState(types::AnimationState state); + void UpdateLiveState(bool isLive); signals: void ViewTypeChanged(types::MapTime viewType); 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 562561f5..4e3502ca 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -21,6 +21,13 @@ Timeline + + + + Auto Update: Enabled + + + @@ -103,46 +110,76 @@ - - - Loop Time (Minutes) + + + QFrame::StyledPanel - - - - - - QAbstractSpinBox::CorrectToNearestValue - - - 1 - - - 1440 - - - 30 - - - - - - - Loop Speed - - - - - - - QAbstractSpinBox::CorrectToNearestValue - - - 1.000000000000000 - - - 1.000000000000000 + + QFrame::Raised + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Loop Time + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + min + + + 1 + + + 1440 + + + 30 + + + + + + + Loop Speed + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + x + + + 1.000000000000000 + + + 1.000000000000000 + + + + From d44075a5fd3ee6d375f121c4c97baed98d49a465 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 14:48:54 -0500 Subject: [PATCH 46/50] Don't disable loading of products when navigating the timeline, just don't select it --- scwx-qt/source/scwx/qt/map/map_widget.cpp | 31 +++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 779a16b4..3be5f28e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -876,7 +876,7 @@ void MapWidgetImpl::RadarProductManagerConnect() const std::string& product, std::chrono::system_clock::time_point latestTime) { - if (autoUpdateEnabled_ && + if (autoRefreshEnabled_ && context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) @@ -886,20 +886,23 @@ void MapWidgetImpl::RadarProductManagerConnect() std::make_shared(); // File request callback - connect( - request.get(), - &request::NexradFileRequest::RequestComplete, - this, - [this](std::shared_ptr request) - { - // Select loaded record - auto record = request->radar_product_record(); - - if (record != nullptr) + if (autoUpdateEnabled_) + { + connect( + request.get(), + &request::NexradFileRequest::RequestComplete, + this, + [this](std::shared_ptr request) { - widget_->SelectRadarProduct(record); - } - }); + // Select loaded record + auto record = request->radar_product_record(); + + if (record != nullptr) + { + widget_->SelectRadarProduct(record); + } + }); + } // Load file scwx::util::async( From fe831cf65a8fe1982dcbcaf4df497e1e7b089435 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 29 May 2023 15:51:06 -0500 Subject: [PATCH 47/50] Update CI for Linux packaging --- .github/workflows/ci.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca2a7f36..06b7a475 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,12 +127,34 @@ jobs: ninja supercell-wx wxtest cmake --install . --component supercell-wx - - name: Upload Artifacts + - name: Collect Artifacts + if: matrix.os == 'ubuntu-22.04' + shell: bash + run: | + pushd supercell-wx/ + cd lib/ + ln -s libssl.so.3 libssl.so + cd .. + mkdir -p plugins/sqldrivers/ + cd plugins/sqldrivers/ + cp "${RUNNER_WORKSPACE}/Qt/${{ matrix.qt_version }}/${{ matrix.qt_arch }}/plugins/sqldrivers/libqsqlite.so" . + popd + tar -czf supercell-wx-${{ matrix.artifact_suffix }}.tar.gz supercell-wx/ + + - name: Upload Artifacts (Windows) + if: matrix.os == 'windows-2022' uses: actions/upload-artifact@v3 with: name: supercell-wx-${{ matrix.artifact_suffix }} path: ${{ github.workspace }}/supercell-wx/ + - name: Upload Artifacts (Linux) + if: matrix.os == 'ubuntu-22.04' + uses: actions/upload-artifact@v3 + with: + name: supercell-wx-${{ matrix.artifact_suffix }} + path: ${{ github.workspace }}/supercell-wx-${{ matrix.artifact_suffix }}.tar.gz + - name: Test Supercell Wx working-directory: ${{ github.workspace }}/build run: ctest -C ${{ matrix.build_type }} --exclude-regex mbgl-test-runner From b18491b2a0de8f021482a920daeccbfcaf371849 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 30 May 2023 20:48:46 -0500 Subject: [PATCH 48/50] Enable debug artifacts for Windows release builds --- .github/workflows/ci.yml | 7 +++++++ external/mapbox-gl-native.cmake | 9 +++++++++ scwx-qt/scwx-qt.cmake | 9 +++++++++ wxdata/wxdata.cmake | 5 +++++ 4 files changed, 30 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06b7a475..3429ce50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,6 +148,13 @@ jobs: name: supercell-wx-${{ matrix.artifact_suffix }} path: ${{ github.workspace }}/supercell-wx/ + - name: Upload Debug Artifacts (Windows) + if: matrix.os == 'windows-2022' + uses: actions/upload-artifact@v3 + with: + name: supercell-wx-debug-${{ matrix.artifact_suffix }} + path: ${{ github.workspace }}/build/bin/*.pdb + - name: Upload Artifacts (Linux) if: matrix.os == 'ubuntu-22.04' uses: actions/upload-artifact@v3 diff --git a/external/mapbox-gl-native.cmake b/external/mapbox-gl-native.cmake index c48900c1..819dad6c 100644 --- a/external/mapbox-gl-native.cmake +++ b/external/mapbox-gl-native.cmake @@ -10,6 +10,15 @@ find_package(ZLIB) target_include_directories(mbgl-core PRIVATE ${ZLIB_INCLUDE_DIRS}) target_link_libraries(mbgl-core INTERFACE ${ZLIB_LIBRARIES}) +if (MSVC) + # Produce PDB file for debug + target_compile_options(mbgl-core PRIVATE "$<$:/Zi>") + target_compile_options(qmaplibregl PRIVATE "$<$:/Zi>") + target_link_options(qmaplibregl PRIVATE "$<$:/DEBUG>") + target_link_options(qmaplibregl PRIVATE "$<$:/OPT:REF>") + target_link_options(qmaplibregl PRIVATE "$<$:/OPT:ICF>") +endif() + set(MBGL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mapbox-gl-native/include ${CMAKE_CURRENT_SOURCE_DIR}/mapbox-gl-native/platform/qt/include PARENT_SCOPE) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 1cee1716..5558b857 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -388,6 +388,15 @@ target_compile_options(supercell-wx PRIVATE $<$>:-Wall -Wextra -Wpedantic -Werror> ) +if (MSVC) + # Produce PDB file for debug + target_compile_options(scwx-qt PRIVATE "$<$:/Zi>") + target_compile_options(supercell-wx PRIVATE "$<$:/Zi>") + target_link_options(supercell-wx PRIVATE "$<$:/DEBUG>") + target_link_options(supercell-wx PRIVATE "$<$:/OPT:REF>") + target_link_options(supercell-wx PRIVATE "$<$:/OPT:ICF>") +endif() + target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets Boost::json diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index d12b763a..e2ee86f9 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -233,6 +233,11 @@ target_compile_options(wxdata PRIVATE $<$>:-Wall -Wextra -Wpedantic -Werror> ) +if (MSVC) + # Produce PDB file for debug + target_compile_options(wxdata PRIVATE "$<$:/Zi>") +endif() + target_link_libraries(wxdata PUBLIC aws-cpp-sdk-core aws-cpp-sdk-s3 cpr::cpr From 1b49e317e40045eb8b02d001568311b893ca6b9f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 30 May 2023 23:28:02 -0500 Subject: [PATCH 49/50] Fix level 2 display --- scwx-qt/source/scwx/qt/main/main_window.cpp | 3 +- .../scwx/qt/manager/radar_product_manager.cpp | 66 +++++++++++++++++-- scwx-qt/source/scwx/qt/map/map_widget.cpp | 15 +++++ scwx-qt/source/scwx/qt/map/map_widget.hpp | 19 +++--- .../scwx/qt/view/level2_product_view.cpp | 3 +- .../scwx/qt/view/radar_product_view.cpp | 5 ++ .../scwx/qt/view/radar_product_view.hpp | 3 +- 7 files changed, 96 insertions(+), 18 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index e0b8254b..8b14dc85 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -849,7 +849,8 @@ void MainWindowImpl::SelectRadarProduct(map::MapWidget* mapWidget, UpdateRadarProductSettings(); } - mapWidget->SelectRadarProduct(group, productName, productCode); + mapWidget->SelectRadarProduct( + group, productName, productCode, mapWidget->GetSelectedTime()); } void MainWindowImpl::SetActiveMap(map::MapWidget* mapWidget) 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 a98a812f..24ba870d 100644 --- a/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/radar_product_manager.cpp @@ -205,6 +205,7 @@ public: std::shared_mutex& recordMutex, std::mutex& loadDataMutex, std::shared_ptr request); + void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time); static void LoadNexradFile(CreateNexradFileFunction load, @@ -944,6 +945,59 @@ void RadarProductManagerImpl::LoadNexradFile( }); } +void RadarProductManagerImpl::PopulateLevel2ProductTimes( + std::chrono::system_clock::time_point time) +{ + const auto today = std::chrono::floor(time); + const auto yesterday = today - std::chrono::days {1}; + const auto tomorrow = today + std::chrono::days {1}; + const auto dates = {yesterday, today, tomorrow}; + + std::set volumeTimes {}; + std::mutex volumeTimesMutex {}; + + // For yesterday, today and tomorrow (in parallel) + std::for_each(std::execution::par_unseq, + dates.begin(), + dates.end(), + [&, this](const auto& date) + { + // Don't query for a time point in the future + if (date > std::chrono::system_clock::now()) + { + return; + } + + // Query the provider for volume time points + auto timePoints = + level2ProviderManager_->provider_->GetTimePointsByDate( + date); + + // Lock the merged volume time list + std::unique_lock volumeTimesLock {volumeTimesMutex}; + + // Copy time points to the merged list + std::copy(timePoints.begin(), + timePoints.end(), + std::inserter(volumeTimes, volumeTimes.end())); + }); + + // Lock the level 2 product record map + std::unique_lock lock {level2ProductRecordMutex_}; + + // Merge volume times into map + std::transform( + volumeTimes.cbegin(), + volumeTimes.cend(), + std::inserter(level2ProductRecords_, level2ProductRecords_.begin()), + [](const std::chrono::system_clock::time_point& time) + { + return std::pair>( + time, std::weak_ptr {}); + }); +} + std::tuple, std::chrono::system_clock::time_point> RadarProductManagerImpl::GetLevel2ProductRecord( @@ -953,6 +1007,9 @@ RadarProductManagerImpl::GetLevel2ProductRecord( RadarProductRecordMap::const_pointer recordPtr {nullptr}; std::chrono::system_clock::time_point recordTime {time}; + // Ensure Level 2 product records are updated + PopulateLevel2ProductTimes(time); + if (!level2ProductRecords_.empty() && time == std::chrono::system_clock::time_point {}) { @@ -967,12 +1024,9 @@ RadarProductManagerImpl::GetLevel2ProductRecord( if (recordPtr != nullptr) { - if (time == std::chrono::system_clock::time_point {} || - time == recordPtr->first) - { - recordTime = recordPtr->first; - record = recordPtr->second.lock(); - } + // Don't check for an exact time match for level 2 products + recordTime = recordPtr->first; + record = recordPtr->second.lock(); } if (record == nullptr && diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 3be5f28e..c3aeebc7 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -316,6 +316,21 @@ std::shared_ptr MapWidget::GetRadarSite() const return radarSite; } +std::chrono::system_clock::time_point MapWidget::GetSelectedTime() const +{ + auto radarProductView = p->context_->radar_product_view(); + std::chrono::system_clock::time_point time; + + // If there is an active radar product view + if (radarProductView != nullptr) + { + // Select the time associated with the active radar product + time = radarProductView->GetSelectedTime(); + } + + return time; +} + std::uint16_t MapWidget::GetVcp() const { auto radarProductView = p->context_->radar_product_view(); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index d6d0214b..51cfb230 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -34,15 +34,16 @@ public: explicit MapWidget(const QMapLibreGL::Settings&); ~MapWidget(); - common::Level3ProductCategoryMap GetAvailableLevel3Categories(); - float GetElevation() const; - std::vector GetElevationCuts() const; - std::vector GetLevel3Products(); - std::string GetMapStyle() const; - common::RadarProductGroup GetRadarProductGroup() const; - std::string GetRadarProductName() const; - std::shared_ptr GetRadarSite() const; - std::uint16_t GetVcp() const; + common::Level3ProductCategoryMap GetAvailableLevel3Categories(); + float GetElevation() const; + std::vector GetElevationCuts() const; + std::vector GetLevel3Products(); + std::string GetMapStyle() const; + common::RadarProductGroup GetRadarProductGroup() const; + std::string GetRadarProductName() const; + std::shared_ptr GetRadarSite() const; + std::chrono::system_clock::time_point GetSelectedTime() const; + std::uint16_t GetVcp() const; void SelectElevation(float elevation); 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 53b9080c..ad4e6367 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -119,7 +119,8 @@ void Level2ProductView::ConnectRadarProductManager() { if (record->radar_product_group() == common::RadarProductGroup::Level2 && - record->time() == selected_time()) + std::chrono::floor(record->time()) == + selected_time()) { // If the data associated with the currently selected time is // reloaded, update the view diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp index a282e1f4..2f4c6f23 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.cpp @@ -146,6 +146,11 @@ RadarProductView::GetCfpMomentData() const return std::tie(data, dataSize, componentSize); } +std::chrono::system_clock::time_point RadarProductView::GetSelectedTime() const +{ + return p->selectedTime_; +} + void RadarProductView::ComputeSweep() { logger_->debug("ComputeSweep()"); diff --git a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp index 8a589a48..16d23a80 100644 --- a/scwx-qt/source/scwx/qt/view/radar_product_view.hpp +++ b/scwx-qt/source/scwx/qt/view/radar_product_view.hpp @@ -61,7 +61,8 @@ public: virtual std::tuple GetMomentData() const = 0; virtual std::tuple - GetCfpMomentData() const; + GetCfpMomentData() const; + std::chrono::system_clock::time_point GetSelectedTime() const; protected: virtual void ConnectRadarProductManager() = 0; From e80c4866a5e1705f94205c35973852b60f38fcac Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 30 May 2023 23:28:24 -0500 Subject: [PATCH 50/50] Fix crash when changing products --- scwx-qt/source/scwx/qt/view/level2_product_view.cpp | 6 +++++- scwx-qt/source/scwx/qt/view/level3_radial_view.cpp | 10 +++++++--- scwx-qt/source/scwx/qt/view/level3_raster_view.cpp | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) 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 ad4e6367..1cbd2e95 100644 --- a/scwx-qt/source/scwx/qt/view/level2_product_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level2_product_view.cpp @@ -108,7 +108,11 @@ Level2ProductView::Level2ProductView( { ConnectRadarProductManager(); } -Level2ProductView::~Level2ProductView() = default; + +Level2ProductView::~Level2ProductView() +{ + std::unique_lock sweepLock {sweep_mutex()}; +} void Level2ProductView::ConnectRadarProductManager() { diff --git a/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp b/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp index 4652c0a2..16e674d3 100644 --- a/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_radial_view.cpp @@ -50,7 +50,11 @@ Level3RadialView::Level3RadialView( p(std::make_unique()) { } -Level3RadialView::~Level3RadialView() = default; + +Level3RadialView::~Level3RadialView() +{ + std::unique_lock sweepLock {sweep_mutex()}; +} float Level3RadialView::range() const { @@ -150,8 +154,8 @@ void Level3RadialView::ComputeSweep() return; } - // A message with radial data should either have a Digital Radial Data Array - // Packet, or a Radial Data Array Packet (TODO) + // A message with radial data should either have a Digital Radial Data + // Array Packet, or a Radial Data Array Packet (TODO) std::shared_ptr digitalDataPacket = nullptr; std::shared_ptr radialDataPacket = nullptr; diff --git a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp index 1c150c70..f5d36185 100644 --- a/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp +++ b/scwx-qt/source/scwx/qt/view/level3_raster_view.cpp @@ -50,7 +50,11 @@ Level3RasterView::Level3RasterView( p(std::make_unique()) { } -Level3RasterView::~Level3RasterView() = default; + +Level3RasterView::~Level3RasterView() +{ + std::unique_lock sweepLock {sweep_mutex()}; +} float Level3RasterView::range() const {