mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-11-01 02:30:04 +00:00
Merge pull request #52 from dpaulat/feature/timeline-animation
Initial Timeline Animation Implementation
This commit is contained in:
commit
287903f180
41 changed files with 1970 additions and 113 deletions
31
.github/workflows/ci.yml
vendored
31
.github/workflows/ci.yml
vendored
|
|
@ -127,12 +127,41 @@ jobs:
|
||||||
ninja supercell-wx wxtest
|
ninja supercell-wx wxtest
|
||||||
cmake --install . --component supercell-wx
|
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
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: supercell-wx-${{ matrix.artifact_suffix }}
|
name: supercell-wx-${{ matrix.artifact_suffix }}
|
||||||
path: ${{ github.workspace }}/supercell-wx/
|
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
|
||||||
|
with:
|
||||||
|
name: supercell-wx-${{ matrix.artifact_suffix }}
|
||||||
|
path: ${{ github.workspace }}/supercell-wx-${{ matrix.artifact_suffix }}.tar.gz
|
||||||
|
|
||||||
- name: Test Supercell Wx
|
- name: Test Supercell Wx
|
||||||
working-directory: ${{ github.workspace }}/build
|
working-directory: ${{ github.workspace }}/build
|
||||||
run: ctest -C ${{ matrix.build_type }} --exclude-regex mbgl-test-runner
|
run: ctest -C ${{ matrix.build_type }} --exclude-regex mbgl-test-runner
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,15 @@ set_property(DIRECTORY
|
||||||
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||||
conanfile.txt)
|
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}
|
conan_cmake_install(PATH_OR_REFERENCE ${PROJECT_SOURCE_DIR}
|
||||||
BUILD missing
|
BUILD missing
|
||||||
|
|
|
||||||
9
external/mapbox-gl-native.cmake
vendored
9
external/mapbox-gl-native.cmake
vendored
|
|
@ -10,6 +10,15 @@ find_package(ZLIB)
|
||||||
target_include_directories(mbgl-core PRIVATE ${ZLIB_INCLUDE_DIRS})
|
target_include_directories(mbgl-core PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||||
target_link_libraries(mbgl-core INTERFACE ${ZLIB_LIBRARIES})
|
target_link_libraries(mbgl-core INTERFACE ${ZLIB_LIBRARIES})
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
# Produce PDB file for debug
|
||||||
|
target_compile_options(mbgl-core PRIVATE "$<$<CONFIG:Release>:/Zi>")
|
||||||
|
target_compile_options(qmaplibregl PRIVATE "$<$<CONFIG:Release>:/Zi>")
|
||||||
|
target_link_options(qmaplibregl PRIVATE "$<$<CONFIG:Release>:/DEBUG>")
|
||||||
|
target_link_options(qmaplibregl PRIVATE "$<$<CONFIG:Release>:/OPT:REF>")
|
||||||
|
target_link_options(qmaplibregl PRIVATE "$<$<CONFIG:Release>:/OPT:ICF>")
|
||||||
|
endif()
|
||||||
|
|
||||||
set(MBGL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mapbox-gl-native/include
|
set(MBGL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mapbox-gl-native/include
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/mapbox-gl-native/platform/qt/include PARENT_SCOPE)
|
${CMAKE_CURRENT_SOURCE_DIR}/mapbox-gl-native/platform/qt/include PARENT_SCOPE)
|
||||||
|
|
||||||
|
|
|
||||||
1
scwx-qt/res/icons/font-awesome-6/pause-solid.svg
Normal file
1
scwx-qt/res/icons/font-awesome-6/pause-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="pause" class="svg-inline--fa fa-pause" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M272 63.1l-32 0c-26.51 0-48 21.49-48 47.1v288c0 26.51 21.49 48 48 48L272 448c26.51 0 48-21.49 48-48v-288C320 85.49 298.5 63.1 272 63.1zM80 63.1l-32 0c-26.51 0-48 21.49-48 48v288C0 426.5 21.49 448 48 448l32 0c26.51 0 48-21.49 48-48v-288C128 85.49 106.5 63.1 80 63.1z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 487 B |
1
scwx-qt/res/icons/font-awesome-6/play-solid.svg
Normal file
1
scwx-qt/res/icons/font-awesome-6/play-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="play" class="svg-inline--fa fa-play" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M176 480C148.6 480 128 457.6 128 432v-352c0-25.38 20.4-47.98 48.01-47.98c8.686 0 17.35 2.352 25.02 7.031l288 176C503.3 223.8 512 239.3 512 256s-8.703 32.23-22.97 40.95l-288 176C193.4 477.6 184.7 480 176 480z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 427 B |
|
|
@ -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/resource_manager.hpp
|
||||||
source/scwx/qt/manager/settings_manager.hpp
|
source/scwx/qt/manager/settings_manager.hpp
|
||||||
source/scwx/qt/manager/text_event_manager.hpp
|
source/scwx/qt/manager/text_event_manager.hpp
|
||||||
|
source/scwx/qt/manager/timeline_manager.hpp
|
||||||
source/scwx/qt/manager/update_manager.hpp)
|
source/scwx/qt/manager/update_manager.hpp)
|
||||||
set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp
|
set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp
|
||||||
source/scwx/qt/manager/radar_product_manager_notifier.cpp
|
source/scwx/qt/manager/radar_product_manager_notifier.cpp
|
||||||
source/scwx/qt/manager/resource_manager.cpp
|
source/scwx/qt/manager/resource_manager.cpp
|
||||||
source/scwx/qt/manager/settings_manager.cpp
|
source/scwx/qt/manager/settings_manager.cpp
|
||||||
source/scwx/qt/manager/text_event_manager.cpp
|
source/scwx/qt/manager/text_event_manager.cpp
|
||||||
|
source/scwx/qt/manager/timeline_manager.cpp
|
||||||
source/scwx/qt/manager/update_manager.cpp)
|
source/scwx/qt/manager/update_manager.cpp)
|
||||||
set(HDR_MAP source/scwx/qt/map/alert_layer.hpp
|
set(HDR_MAP source/scwx/qt/map/alert_layer.hpp
|
||||||
source/scwx/qt/map/color_table_layer.hpp
|
source/scwx/qt/map/color_table_layer.hpp
|
||||||
|
|
@ -132,16 +134,19 @@ set(SRC_SETTINGS source/scwx/qt/settings/general_settings.cpp
|
||||||
set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
|
set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
|
||||||
source/scwx/qt/types/font_types.hpp
|
source/scwx/qt/types/font_types.hpp
|
||||||
source/scwx/qt/types/github_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/qt_types.hpp
|
||||||
source/scwx/qt/types/radar_product_record.hpp
|
source/scwx/qt/types/radar_product_record.hpp
|
||||||
source/scwx/qt/types/text_event_key.hpp)
|
source/scwx/qt/types/text_event_key.hpp)
|
||||||
set(SRC_TYPES source/scwx/qt/types/alert_types.cpp
|
set(SRC_TYPES source/scwx/qt/types/alert_types.cpp
|
||||||
source/scwx/qt/types/github_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/radar_product_record.cpp
|
||||||
source/scwx/qt/types/text_event_key.cpp)
|
source/scwx/qt/types/text_event_key.cpp)
|
||||||
set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
|
set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
|
||||||
source/scwx/qt/ui/alert_dialog.hpp
|
source/scwx/qt/ui/alert_dialog.hpp
|
||||||
source/scwx/qt/ui/alert_dock_widget.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/flow_layout.hpp
|
||||||
source/scwx/qt/ui/imgui_debug_dialog.hpp
|
source/scwx/qt/ui/imgui_debug_dialog.hpp
|
||||||
source/scwx/qt/ui/imgui_debug_widget.hpp
|
source/scwx/qt/ui/imgui_debug_widget.hpp
|
||||||
|
|
@ -154,6 +159,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
|
||||||
set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
|
set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
|
||||||
source/scwx/qt/ui/alert_dialog.cpp
|
source/scwx/qt/ui/alert_dialog.cpp
|
||||||
source/scwx/qt/ui/alert_dock_widget.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/flow_layout.cpp
|
||||||
source/scwx/qt/ui/imgui_debug_dialog.cpp
|
source/scwx/qt/ui/imgui_debug_dialog.cpp
|
||||||
source/scwx/qt/ui/imgui_debug_widget.cpp
|
source/scwx/qt/ui/imgui_debug_widget.cpp
|
||||||
|
|
@ -166,6 +172,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
|
||||||
set(UI_UI source/scwx/qt/ui/about_dialog.ui
|
set(UI_UI source/scwx/qt/ui/about_dialog.ui
|
||||||
source/scwx/qt/ui/alert_dialog.ui
|
source/scwx/qt/ui/alert_dialog.ui
|
||||||
source/scwx/qt/ui/alert_dock_widget.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/imgui_debug_dialog.ui
|
||||||
source/scwx/qt/ui/radar_site_dialog.ui
|
source/scwx/qt/ui/radar_site_dialog.ui
|
||||||
source/scwx/qt/ui/settings_dialog.ui
|
source/scwx/qt/ui/settings_dialog.ui
|
||||||
|
|
@ -179,7 +186,8 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp
|
||||||
source/scwx/qt/util/streams.hpp
|
source/scwx/qt/util/streams.hpp
|
||||||
source/scwx/qt/util/texture_atlas.hpp
|
source/scwx/qt/util/texture_atlas.hpp
|
||||||
source/scwx/qt/util/q_file_buffer.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
|
set(SRC_UTIL source/scwx/qt/util/color.cpp
|
||||||
source/scwx/qt/util/file.cpp
|
source/scwx/qt/util/file.cpp
|
||||||
source/scwx/qt/util/font.cpp
|
source/scwx/qt/util/font.cpp
|
||||||
|
|
@ -188,7 +196,8 @@ set(SRC_UTIL source/scwx/qt/util/color.cpp
|
||||||
source/scwx/qt/util/json.cpp
|
source/scwx/qt/util/json.cpp
|
||||||
source/scwx/qt/util/texture_atlas.cpp
|
source/scwx/qt/util/texture_atlas.cpp
|
||||||
source/scwx/qt/util/q_file_buffer.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
|
set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp
|
||||||
source/scwx/qt/view/level3_product_view.hpp
|
source/scwx/qt/view/level3_product_view.hpp
|
||||||
source/scwx/qt/view/level3_radial_view.hpp
|
source/scwx/qt/view/level3_radial_view.hpp
|
||||||
|
|
@ -379,6 +388,15 @@ target_compile_options(supercell-wx PRIVATE
|
||||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>
|
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
# Produce PDB file for debug
|
||||||
|
target_compile_options(scwx-qt PRIVATE "$<$<CONFIG:Release>:/Zi>")
|
||||||
|
target_compile_options(supercell-wx PRIVATE "$<$<CONFIG:Release>:/Zi>")
|
||||||
|
target_link_options(supercell-wx PRIVATE "$<$<CONFIG:Release>:/DEBUG>")
|
||||||
|
target_link_options(supercell-wx PRIVATE "$<$<CONFIG:Release>:/OPT:REF>")
|
||||||
|
target_link_options(supercell-wx PRIVATE "$<$<CONFIG:Release>:/OPT:ICF>")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets
|
target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
|
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
|
||||||
Boost::json
|
Boost::json
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@
|
||||||
<file>res/icons/font-awesome-6/gears-solid.svg</file>
|
<file>res/icons/font-awesome-6/gears-solid.svg</file>
|
||||||
<file>res/icons/font-awesome-6/github.svg</file>
|
<file>res/icons/font-awesome-6/github.svg</file>
|
||||||
<file>res/icons/font-awesome-6/palette-solid.svg</file>
|
<file>res/icons/font-awesome-6/palette-solid.svg</file>
|
||||||
|
<file>res/icons/font-awesome-6/pause-solid.svg</file>
|
||||||
|
<file>res/icons/font-awesome-6/play-solid.svg</file>
|
||||||
<file>res/icons/font-awesome-6/rotate-left-solid.svg</file>
|
<file>res/icons/font-awesome-6/rotate-left-solid.svg</file>
|
||||||
<file>res/icons/font-awesome-6/sliders-solid.svg</file>
|
<file>res/icons/font-awesome-6/sliders-solid.svg</file>
|
||||||
<file>res/icons/font-awesome-6/square-minus-regular.svg</file>
|
<file>res/icons/font-awesome-6/square-minus-regular.svg</file>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <scwx/qt/manager/radar_product_manager.hpp>
|
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||||
#include <scwx/qt/manager/settings_manager.hpp>
|
#include <scwx/qt/manager/settings_manager.hpp>
|
||||||
#include <scwx/qt/manager/text_event_manager.hpp>
|
#include <scwx/qt/manager/text_event_manager.hpp>
|
||||||
|
#include <scwx/qt/manager/timeline_manager.hpp>
|
||||||
#include <scwx/qt/manager/update_manager.hpp>
|
#include <scwx/qt/manager/update_manager.hpp>
|
||||||
#include <scwx/qt/map/map_provider.hpp>
|
#include <scwx/qt/map/map_provider.hpp>
|
||||||
#include <scwx/qt/map/map_widget.hpp>
|
#include <scwx/qt/map/map_widget.hpp>
|
||||||
|
|
@ -15,6 +16,7 @@
|
||||||
#include <scwx/qt/ui/alert_dock_widget.hpp>
|
#include <scwx/qt/ui/alert_dock_widget.hpp>
|
||||||
#include <scwx/qt/ui/flow_layout.hpp>
|
#include <scwx/qt/ui/flow_layout.hpp>
|
||||||
#include <scwx/qt/ui/about_dialog.hpp>
|
#include <scwx/qt/ui/about_dialog.hpp>
|
||||||
|
#include <scwx/qt/ui/animation_dock_widget.hpp>
|
||||||
#include <scwx/qt/ui/imgui_debug_dialog.hpp>
|
#include <scwx/qt/ui/imgui_debug_dialog.hpp>
|
||||||
#include <scwx/qt/ui/level2_products_widget.hpp>
|
#include <scwx/qt/ui/level2_products_widget.hpp>
|
||||||
#include <scwx/qt/ui/level2_settings_widget.hpp>
|
#include <scwx/qt/ui/level2_settings_widget.hpp>
|
||||||
|
|
@ -62,6 +64,7 @@ public:
|
||||||
level2SettingsWidget_ {nullptr},
|
level2SettingsWidget_ {nullptr},
|
||||||
level3ProductsWidget_ {nullptr},
|
level3ProductsWidget_ {nullptr},
|
||||||
alertDockWidget_ {nullptr},
|
alertDockWidget_ {nullptr},
|
||||||
|
animationDockWidget_ {nullptr},
|
||||||
aboutDialog_ {nullptr},
|
aboutDialog_ {nullptr},
|
||||||
imGuiDebugDialog_ {nullptr},
|
imGuiDebugDialog_ {nullptr},
|
||||||
radarSiteDialog_ {nullptr},
|
radarSiteDialog_ {nullptr},
|
||||||
|
|
@ -69,6 +72,7 @@ public:
|
||||||
updateDialog_ {nullptr},
|
updateDialog_ {nullptr},
|
||||||
radarProductModel_ {nullptr},
|
radarProductModel_ {nullptr},
|
||||||
textEventManager_ {manager::TextEventManager::Instance()},
|
textEventManager_ {manager::TextEventManager::Instance()},
|
||||||
|
timelineManager_ {manager::TimelineManager::Instance()},
|
||||||
updateManager_ {manager::UpdateManager::Instance()},
|
updateManager_ {manager::UpdateManager::Instance()},
|
||||||
maps_ {},
|
maps_ {},
|
||||||
elevationCuts_ {},
|
elevationCuts_ {},
|
||||||
|
|
@ -109,6 +113,7 @@ public:
|
||||||
|
|
||||||
void AsyncSetup();
|
void AsyncSetup();
|
||||||
void ConfigureMapLayout();
|
void ConfigureMapLayout();
|
||||||
|
void ConnectAnimationSignals();
|
||||||
void ConnectMapSignals();
|
void ConnectMapSignals();
|
||||||
void ConnectOtherSignals();
|
void ConnectOtherSignals();
|
||||||
void HandleFocusChange(QWidget* focused);
|
void HandleFocusChange(QWidget* focused);
|
||||||
|
|
@ -139,6 +144,7 @@ public:
|
||||||
ui::Level3ProductsWidget* level3ProductsWidget_;
|
ui::Level3ProductsWidget* level3ProductsWidget_;
|
||||||
|
|
||||||
ui::AlertDockWidget* alertDockWidget_;
|
ui::AlertDockWidget* alertDockWidget_;
|
||||||
|
ui::AnimationDockWidget* animationDockWidget_;
|
||||||
ui::AboutDialog* aboutDialog_;
|
ui::AboutDialog* aboutDialog_;
|
||||||
ui::ImGuiDebugDialog* imGuiDebugDialog_;
|
ui::ImGuiDebugDialog* imGuiDebugDialog_;
|
||||||
ui::RadarSiteDialog* radarSiteDialog_;
|
ui::RadarSiteDialog* radarSiteDialog_;
|
||||||
|
|
@ -147,6 +153,7 @@ public:
|
||||||
|
|
||||||
std::unique_ptr<model::RadarProductModel> radarProductModel_;
|
std::unique_ptr<model::RadarProductModel> radarProductModel_;
|
||||||
std::shared_ptr<manager::TextEventManager> textEventManager_;
|
std::shared_ptr<manager::TextEventManager> textEventManager_;
|
||||||
|
std::shared_ptr<manager::TimelineManager> timelineManager_;
|
||||||
std::shared_ptr<manager::UpdateManager> updateManager_;
|
std::shared_ptr<manager::UpdateManager> updateManager_;
|
||||||
|
|
||||||
std::vector<map::MapWidget*> maps_;
|
std::vector<map::MapWidget*> maps_;
|
||||||
|
|
@ -182,12 +189,23 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||||
p->alertDockWidget_->setVisible(false);
|
p->alertDockWidget_->setVisible(false);
|
||||||
addDockWidget(Qt::BottomDockWidgetArea, p->alertDockWidget_);
|
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
|
// Configure Menu
|
||||||
ui->menuView->insertAction(ui->actionRadarToolbox,
|
ui->menuView->insertAction(ui->actionRadarToolbox,
|
||||||
ui->radarToolboxDock->toggleViewAction());
|
ui->radarToolboxDock->toggleViewAction());
|
||||||
ui->radarToolboxDock->toggleViewAction()->setText(tr("Radar &Toolbox"));
|
ui->radarToolboxDock->toggleViewAction()->setText(tr("Radar &Toolbox"));
|
||||||
ui->actionRadarToolbox->setVisible(false);
|
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->menuView->insertAction(ui->actionResourceExplorer,
|
||||||
ui->resourceExplorerDock->toggleViewAction());
|
ui->resourceExplorerDock->toggleViewAction());
|
||||||
ui->resourceExplorerDock->toggleViewAction()->setText(
|
ui->resourceExplorerDock->toggleViewAction()->setText(
|
||||||
|
|
@ -257,6 +275,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||||
|
|
||||||
p->PopulateMapStyles();
|
p->PopulateMapStyles();
|
||||||
p->ConnectMapSignals();
|
p->ConnectMapSignals();
|
||||||
|
p->ConnectAnimationSignals();
|
||||||
p->ConnectOtherSignals();
|
p->ConnectOtherSignals();
|
||||||
p->HandleFocusChange(p->activeMap_);
|
p->HandleFocusChange(p->activeMap_);
|
||||||
p->AsyncSetup();
|
p->AsyncSetup();
|
||||||
|
|
@ -273,7 +292,7 @@ void MainWindow::showEvent(QShowEvent* event)
|
||||||
{
|
{
|
||||||
QMainWindow::showEvent(event);
|
QMainWindow::showEvent(event);
|
||||||
|
|
||||||
resizeDocks({ui->radarToolboxDock}, {150}, Qt::Horizontal);
|
resizeDocks({ui->radarToolboxDock}, {188}, Qt::Horizontal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionOpenNexrad_triggered()
|
void MainWindow::on_actionOpenNexrad_triggered()
|
||||||
|
|
@ -634,6 +653,75 @@ 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::AnimationPlayPause);
|
||||||
|
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::VolumeTimeUpdated,
|
||||||
|
[this](std::chrono::system_clock::time_point dateTime)
|
||||||
|
{
|
||||||
|
for (auto map : maps_)
|
||||||
|
{
|
||||||
|
map->SelectTime(dateTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(timelineManager_.get(),
|
||||||
|
&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()
|
void MainWindowImpl::ConnectOtherSignals()
|
||||||
{
|
{
|
||||||
connect(qApp,
|
connect(qApp,
|
||||||
|
|
@ -761,7 +849,8 @@ void MainWindowImpl::SelectRadarProduct(map::MapWidget* mapWidget,
|
||||||
UpdateRadarProductSettings();
|
UpdateRadarProductSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
mapWidget->SelectRadarProduct(group, productName, productCode);
|
mapWidget->SelectRadarProduct(
|
||||||
|
group, productName, productCode, mapWidget->GetSelectedTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindowImpl::SetActiveMap(map::MapWidget* mapWidget)
|
void MainWindowImpl::SetActiveMap(map::MapWidget* mapWidget)
|
||||||
|
|
@ -841,11 +930,15 @@ void MainWindowImpl::UpdateRadarSite()
|
||||||
mainWindow_->ui->radarSiteValueLabel->setText(radarSite->id().c_str());
|
mainWindow_->ui->radarSiteValueLabel->setText(radarSite->id().c_str());
|
||||||
mainWindow_->ui->radarLocationLabel->setText(
|
mainWindow_->ui->radarLocationLabel->setText(
|
||||||
radarSite->location_name().c_str());
|
radarSite->location_name().c_str());
|
||||||
|
|
||||||
|
timelineManager_->SetRadarSite(radarSite->id());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mainWindow_->ui->radarSiteValueLabel->setVisible(false);
|
mainWindow_->ui->radarSiteValueLabel->setVisible(false);
|
||||||
mainWindow_->ui->radarLocationLabel->setVisible(false);
|
mainWindow_->ui->radarLocationLabel->setVisible(false);
|
||||||
|
|
||||||
|
timelineManager_->SetRadarSite("?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@
|
||||||
<string>&View</string>
|
<string>&View</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionRadarToolbox"/>
|
<addaction name="actionRadarToolbox"/>
|
||||||
|
<addaction name="actionAnimationToolbox"/>
|
||||||
<addaction name="actionResourceExplorer"/>
|
<addaction name="actionResourceExplorer"/>
|
||||||
<addaction name="actionAlerts"/>
|
<addaction name="actionAlerts"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
|
@ -359,7 +360,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionRadarToolbox">
|
<action name="actionRadarToolbox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Radar Toolbox</string>
|
<string>Radar &Toolbox</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionResourceExplorer">
|
<action name="actionResourceExplorer">
|
||||||
|
|
@ -429,6 +430,11 @@
|
||||||
<string>&Check for Updates</string>
|
<string>&Check for Updates</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionAnimationToolbox">
|
||||||
|
<property name="text">
|
||||||
|
<string>A&nimation Toolbox</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../../../scwx-qt.qrc"/>
|
<include location="../../../../scwx-qt.qrc"/>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <execution>
|
#include <execution>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
# pragma warning(push, 0)
|
# pragma warning(push, 0)
|
||||||
|
|
@ -204,6 +205,7 @@ public:
|
||||||
std::shared_mutex& recordMutex,
|
std::shared_mutex& recordMutex,
|
||||||
std::mutex& loadDataMutex,
|
std::mutex& loadDataMutex,
|
||||||
std::shared_ptr<request::NexradFileRequest> request);
|
std::shared_ptr<request::NexradFileRequest> request);
|
||||||
|
void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
LoadNexradFile(CreateNexradFileFunction load,
|
LoadNexradFile(CreateNexradFileFunction load,
|
||||||
|
|
@ -215,6 +217,7 @@ public:
|
||||||
bool level3ProductsInitialized_;
|
bool level3ProductsInitialized_;
|
||||||
|
|
||||||
std::shared_ptr<config::RadarSite> radarSite_;
|
std::shared_ptr<config::RadarSite> radarSite_;
|
||||||
|
std::size_t cacheLimit_ {6u};
|
||||||
|
|
||||||
std::vector<float> coordinates0_5Degree_;
|
std::vector<float> coordinates0_5Degree_;
|
||||||
std::vector<float> coordinates1Degree_;
|
std::vector<float> coordinates1Degree_;
|
||||||
|
|
@ -245,7 +248,7 @@ public:
|
||||||
std::shared_ptr<ProviderManager>,
|
std::shared_ptr<ProviderManager>,
|
||||||
boost::hash<boost::uuids::uuid>>
|
boost::hash<boost::uuids::uuid>>
|
||||||
refreshMap_ {};
|
refreshMap_ {};
|
||||||
std::mutex refreshMapMutex_ {};
|
std::shared_mutex refreshMapMutex_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
RadarProductManager::RadarProductManager(const std::string& radarId) :
|
RadarProductManager::RadarProductManager(const std::string& radarId) :
|
||||||
|
|
@ -676,6 +679,79 @@ void RadarProductManagerImpl::RefreshData(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<std::chrono::system_clock::time_point>
|
||||||
|
RadarProductManager::GetActiveVolumeTimes(
|
||||||
|
std::chrono::system_clock::time_point time)
|
||||||
|
{
|
||||||
|
std::unordered_set<std::shared_ptr<provider::NexradDataProvider>>
|
||||||
|
providers {};
|
||||||
|
std::set<std::chrono::system_clock::time_point> 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_};
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
const auto today = std::chrono::floor<std::chrono::days>(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,
|
||||||
|
providers.begin(),
|
||||||
|
providers.end(),
|
||||||
|
[&](const std::shared_ptr<provider::NexradDataProvider>& provider)
|
||||||
|
{
|
||||||
|
// For yesterday, today and tomorrow (in parallel)
|
||||||
|
std::for_each(std::execution::par_unseq,
|
||||||
|
dates.begin(),
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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(
|
void RadarProductManagerImpl::LoadProviderData(
|
||||||
std::chrono::system_clock::time_point time,
|
std::chrono::system_clock::time_point time,
|
||||||
std::shared_ptr<ProviderManager> providerManager,
|
std::shared_ptr<ProviderManager> providerManager,
|
||||||
|
|
@ -869,6 +945,59 @@ void RadarProductManagerImpl::LoadNexradFile(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RadarProductManagerImpl::PopulateLevel2ProductTimes(
|
||||||
|
std::chrono::system_clock::time_point time)
|
||||||
|
{
|
||||||
|
const auto today = std::chrono::floor<std::chrono::days>(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<std::chrono::system_clock::time_point> 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<std::chrono::system_clock::time_point,
|
||||||
|
std::weak_ptr<types::RadarProductRecord>>(
|
||||||
|
time, std::weak_ptr<types::RadarProductRecord> {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<std::shared_ptr<types::RadarProductRecord>,
|
std::tuple<std::shared_ptr<types::RadarProductRecord>,
|
||||||
std::chrono::system_clock::time_point>
|
std::chrono::system_clock::time_point>
|
||||||
RadarProductManagerImpl::GetLevel2ProductRecord(
|
RadarProductManagerImpl::GetLevel2ProductRecord(
|
||||||
|
|
@ -878,6 +1007,9 @@ RadarProductManagerImpl::GetLevel2ProductRecord(
|
||||||
RadarProductRecordMap::const_pointer recordPtr {nullptr};
|
RadarProductRecordMap::const_pointer recordPtr {nullptr};
|
||||||
std::chrono::system_clock::time_point recordTime {time};
|
std::chrono::system_clock::time_point recordTime {time};
|
||||||
|
|
||||||
|
// Ensure Level 2 product records are updated
|
||||||
|
PopulateLevel2ProductTimes(time);
|
||||||
|
|
||||||
if (!level2ProductRecords_.empty() &&
|
if (!level2ProductRecords_.empty() &&
|
||||||
time == std::chrono::system_clock::time_point {})
|
time == std::chrono::system_clock::time_point {})
|
||||||
{
|
{
|
||||||
|
|
@ -892,13 +1024,10 @@ RadarProductManagerImpl::GetLevel2ProductRecord(
|
||||||
|
|
||||||
if (recordPtr != nullptr)
|
if (recordPtr != nullptr)
|
||||||
{
|
{
|
||||||
if (time == std::chrono::system_clock::time_point {} ||
|
// Don't check for an exact time match for level 2 products
|
||||||
time == recordPtr->first)
|
|
||||||
{
|
|
||||||
recordTime = recordPtr->first;
|
recordTime = recordPtr->first;
|
||||||
record = recordPtr->second.lock();
|
record = recordPtr->second.lock();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (record == nullptr &&
|
if (record == nullptr &&
|
||||||
recordTime != std::chrono::system_clock::time_point {})
|
recordTime != std::chrono::system_clock::time_point {})
|
||||||
|
|
@ -938,7 +1067,7 @@ RadarProductManagerImpl::GetLevel3ProductRecord(
|
||||||
|
|
||||||
auto it = level3ProductRecordsMap_.find(product);
|
auto it = level3ProductRecordsMap_.find(product);
|
||||||
|
|
||||||
if (it != level3ProductRecordsMap_.cend())
|
if (it != level3ProductRecordsMap_.cend() && !it->second.empty())
|
||||||
{
|
{
|
||||||
if (time == std::chrono::system_clock::time_point {})
|
if (time == std::chrono::system_clock::time_point {})
|
||||||
{
|
{
|
||||||
|
|
@ -1061,7 +1190,7 @@ void RadarProductManagerImpl::UpdateRecentRecords(
|
||||||
RadarProductRecordList& recentList,
|
RadarProductRecordList& recentList,
|
||||||
std::shared_ptr<types::RadarProductRecord> record)
|
std::shared_ptr<types::RadarProductRecord> record)
|
||||||
{
|
{
|
||||||
static constexpr std::size_t kRecentListMaxSize_ {2u};
|
const std::size_t recentListMaxSize {cacheLimit_};
|
||||||
|
|
||||||
auto it = std::find(recentList.cbegin(), recentList.cend(), record);
|
auto it = std::find(recentList.cbegin(), recentList.cend(), record);
|
||||||
if (it != recentList.cbegin() && it != recentList.cend())
|
if (it != recentList.cbegin() && it != recentList.cend())
|
||||||
|
|
@ -1076,7 +1205,7 @@ void RadarProductManagerImpl::UpdateRecentRecords(
|
||||||
recentList.push_front(record);
|
recentList.push_front(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (recentList.size() > kRecentListMaxSize_)
|
while (recentList.size() > recentListMaxSize)
|
||||||
{
|
{
|
||||||
// Remove from the end of the list while it's too big
|
// Remove from the end of the list while it's too big
|
||||||
recentList.pop_back();
|
recentList.pop_back();
|
||||||
|
|
@ -1141,6 +1270,11 @@ std::vector<std::string> RadarProductManager::GetLevel3Products()
|
||||||
return level3ProviderManager->provider_->GetAvailableProducts();
|
return level3ProviderManager->provider_->GetAvailableProducts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RadarProductManager::SetCacheLimit(size_t cacheLimit)
|
||||||
|
{
|
||||||
|
p->cacheLimit_ = cacheLimit;
|
||||||
|
}
|
||||||
|
|
||||||
void RadarProductManager::UpdateAvailableProducts()
|
void RadarProductManager::UpdateAvailableProducts()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(p->level3ProductsInitializeMutex_);
|
std::lock_guard<std::mutex> guard(p->level3ProductsInitializeMutex_);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <scwx/wsr88d/level3_file.hpp>
|
#include <scwx/wsr88d/level3_file.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -63,6 +64,17 @@ public:
|
||||||
bool enabled,
|
bool enabled,
|
||||||
boost::uuids::uuid uuid = boost::uuids::nil_uuid());
|
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 previous, current and next day.
|
||||||
|
*
|
||||||
|
* @param [in] time Current date to provide to volume time query
|
||||||
|
*
|
||||||
|
* @return Merged list of active volume times
|
||||||
|
*/
|
||||||
|
std::set<std::chrono::system_clock::time_point>
|
||||||
|
GetActiveVolumeTimes(std::chrono::system_clock::time_point time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get level 2 radar data for a data block type, elevation, and time.
|
* @brief Get level 2 radar data for a data block type, elevation, and time.
|
||||||
*
|
*
|
||||||
|
|
@ -114,6 +126,14 @@ public:
|
||||||
|
|
||||||
common::Level3ProductCategoryMap GetAvailableLevel3Categories();
|
common::Level3ProductCategoryMap GetAvailableLevel3Categories();
|
||||||
std::vector<std::string> GetLevel3Products();
|
std::vector<std::string> GetLevel3Products();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
void UpdateAvailableProducts();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
541
scwx-qt/source/scwx/qt/manager/timeline_manager.cpp
Normal file
541
scwx-qt/source/scwx/qt/manager/timeline_manager.cpp
Normal file
|
|
@ -0,0 +1,541 @@
|
||||||
|
#include <scwx/qt/manager/timeline_manager.hpp>
|
||||||
|
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||||
|
#include <scwx/util/logger.hpp>
|
||||||
|
#include <scwx/util/map.hpp>
|
||||||
|
#include <scwx/util/threads.hpp>
|
||||||
|
#include <scwx/util/time.hpp>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <boost/asio/steady_timer.hpp>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
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_);
|
||||||
|
|
||||||
|
enum class Direction
|
||||||
|
{
|
||||||
|
Back,
|
||||||
|
Next
|
||||||
|
};
|
||||||
|
|
||||||
|
class TimelineManager::Impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Impl(TimelineManager* self) : self_ {self} {}
|
||||||
|
|
||||||
|
~Impl()
|
||||||
|
{
|
||||||
|
// Lock mutexes before destroying
|
||||||
|
std::unique_lock animationTimerLock {animationTimerMutex_};
|
||||||
|
animationTimer_.cancel();
|
||||||
|
|
||||||
|
std::unique_lock selectTimeLock {selectTimeMutex_};
|
||||||
|
}
|
||||||
|
|
||||||
|
TimelineManager* self_;
|
||||||
|
|
||||||
|
std::pair<std::chrono::system_clock::time_point,
|
||||||
|
std::chrono::system_clock::time_point>
|
||||||
|
GetLoopStartAndEndTimes();
|
||||||
|
void UpdateCacheLimit(
|
||||||
|
std::shared_ptr<manager::RadarProductManager> radarProductManager,
|
||||||
|
const std::set<std::chrono::system_clock::time_point>& volumeTimes);
|
||||||
|
|
||||||
|
void Pause();
|
||||||
|
void Play();
|
||||||
|
void SelectTime(std::chrono::system_clock::time_point selectedTime = {});
|
||||||
|
void Step(Direction direction);
|
||||||
|
|
||||||
|
std::string radarSite_ {"?"};
|
||||||
|
std::string previousRadarSite_ {"?"};
|
||||||
|
std::chrono::system_clock::time_point pinnedTime_ {};
|
||||||
|
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_ {5.0};
|
||||||
|
|
||||||
|
types::AnimationState animationState_ {types::AnimationState::Pause};
|
||||||
|
boost::asio::steady_timer animationTimer_ {scwx::util::io_context()};
|
||||||
|
std::mutex animationTimerMutex_ {};
|
||||||
|
|
||||||
|
std::mutex selectTimeMutex_ {};
|
||||||
|
};
|
||||||
|
|
||||||
|
TimelineManager::TimelineManager() : p(std::make_unique<Impl>(this)) {}
|
||||||
|
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;
|
||||||
|
|
||||||
|
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(
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (loopSpeed < 1.0)
|
||||||
|
{
|
||||||
|
loopSpeed = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->loopSpeed_ = loopSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineManager::AnimationStepBegin()
|
||||||
|
{
|
||||||
|
logger_->debug("AnimationStepBegin");
|
||||||
|
|
||||||
|
p->Pause();
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
logger_->debug("AnimationStepBack");
|
||||||
|
|
||||||
|
p->Pause();
|
||||||
|
p->Step(Direction::Back);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineManager::AnimationPlayPause()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
p->SelectTime();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the selected view type is archive, select using the pinned time
|
||||||
|
p->SelectTime(p->pinnedTime_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::chrono::system_clock::time_point,
|
||||||
|
std::chrono::system_clock::time_point>
|
||||||
|
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::minutes>(
|
||||||
|
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<manager::RadarProductManager> radarProductManager,
|
||||||
|
const std::set<std::chrono::system_clock::time_point>& 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<std::size_t>(numVolumeScans * 1.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
scwx::util::async(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
// Take a lock for time selection
|
||||||
|
std::unique_lock lock {selectTimeMutex_};
|
||||||
|
|
||||||
|
auto [startTime, endTime] = GetLoopStartAndEndTimes();
|
||||||
|
std::chrono::system_clock::time_point currentTime = selectedTime_;
|
||||||
|
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
|
||||||
|
newTime = startTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the currently selected time is in the loop, increment
|
||||||
|
newTime = currentTime + 1min;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_};
|
||||||
|
|
||||||
|
animationTimer_.expires_after(interval);
|
||||||
|
animationTimer_.async_wait(
|
||||||
|
[this](const boost::system::error_code& e)
|
||||||
|
{
|
||||||
|
if (e == boost::system::errc::success)
|
||||||
|
{
|
||||||
|
if (animationState_ == types::AnimationState::Play)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (selectedTime_ == selectedTime && radarSite_ == previousRadarSite_)
|
||||||
|
{
|
||||||
|
// 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_->LiveStateUpdated(true);
|
||||||
|
emit self_->VolumeTimeUpdated(selectedTime);
|
||||||
|
emit self_->SelectedTimeUpdated(selectedTime);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scwx::util::async(
|
||||||
|
[=, this]()
|
||||||
|
{
|
||||||
|
// Take a lock for time selection
|
||||||
|
std::unique_lock lock {selectTimeMutex_};
|
||||||
|
|
||||||
|
// Request active volume times
|
||||||
|
auto radarProductManager =
|
||||||
|
manager::RadarProductManager::Instance(radarSite_);
|
||||||
|
auto volumeTimes =
|
||||||
|
radarProductManager->GetActiveVolumeTimes(selectedTime);
|
||||||
|
|
||||||
|
// Dynamically update maximum cached volume scans
|
||||||
|
UpdateCacheLimit(radarProductManager, volumeTimes);
|
||||||
|
|
||||||
|
// Find the best match bounded time
|
||||||
|
auto elementPtr =
|
||||||
|
util::GetBoundedElementPointer(volumeTimes, selectedTime);
|
||||||
|
|
||||||
|
// The timeline is no longer live
|
||||||
|
emit self_->LiveStateUpdated(false);
|
||||||
|
|
||||||
|
if (elementPtr != nullptr)
|
||||||
|
{
|
||||||
|
|
||||||
|
// If the adjusted time changed, or if a new radar site has been
|
||||||
|
// selected
|
||||||
|
if (adjustedTime_ != *elementPtr ||
|
||||||
|
radarSite_ != previousRadarSite_)
|
||||||
|
{
|
||||||
|
// If the time was found, select it
|
||||||
|
adjustedTime_ = *elementPtr;
|
||||||
|
|
||||||
|
logger_->debug("Volume time updated: {}",
|
||||||
|
scwx::util::TimeString(adjustedTime_));
|
||||||
|
|
||||||
|
emit self_->VolumeTimeUpdated(adjustedTime_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No volume time was found
|
||||||
|
logger_->info("No volume scan found for {}",
|
||||||
|
scwx::util::TimeString(selectedTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_->trace("Selected time updated: {}",
|
||||||
|
scwx::util::TimeString(selectedTime));
|
||||||
|
|
||||||
|
selectedTime_ = selectedTime;
|
||||||
|
emit self_->SelectedTimeUpdated(selectedTime);
|
||||||
|
|
||||||
|
previousRadarSite_ = radarSite_;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineManager::Impl::Step(Direction direction)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (volumeTimes.empty())
|
||||||
|
{
|
||||||
|
logger_->debug("No products to step through");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamically update maximum cached volume scans
|
||||||
|
UpdateCacheLimit(radarProductManager, volumeTimes);
|
||||||
|
|
||||||
|
std::set<std::chrono::system_clock::time_point>::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 = std::prev(volumeTimes.cend());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get the current element in the set
|
||||||
|
it = scwx::util::GetBoundedElementIterator(volumeTimes,
|
||||||
|
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
|
||||||
|
if (it != volumeTimes.cbegin())
|
||||||
|
{
|
||||||
|
// Select the previous time
|
||||||
|
adjustedTime_ = *(--it);
|
||||||
|
selectedTime_ = adjustedTime_;
|
||||||
|
|
||||||
|
logger_->debug("Volume time updated: {}",
|
||||||
|
scwx::util::TimeString(adjustedTime_));
|
||||||
|
|
||||||
|
emit self_->LiveStateUpdated(false);
|
||||||
|
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_->LiveStateUpdated(false);
|
||||||
|
emit self_->VolumeTimeUpdated(adjustedTime_);
|
||||||
|
emit self_->SelectedTimeUpdated(adjustedTime_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TimelineManager> TimelineManager::Instance()
|
||||||
|
{
|
||||||
|
static std::weak_ptr<TimelineManager> timelineManagerReference_ {};
|
||||||
|
static std::mutex instanceMutex_ {};
|
||||||
|
|
||||||
|
std::unique_lock lock(instanceMutex_);
|
||||||
|
|
||||||
|
std::shared_ptr<TimelineManager> timelineManager =
|
||||||
|
timelineManagerReference_.lock();
|
||||||
|
|
||||||
|
if (timelineManager == nullptr)
|
||||||
|
{
|
||||||
|
timelineManager = std::make_shared<TimelineManager>();
|
||||||
|
timelineManagerReference_ = timelineManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timelineManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace manager
|
||||||
|
} // namespace qt
|
||||||
|
} // namespace scwx
|
||||||
57
scwx-qt/source/scwx/qt/manager/timeline_manager.hpp
Normal file
57
scwx-qt/source/scwx/qt/manager/timeline_manager.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scwx/qt/types/map_types.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace scwx
|
||||||
|
{
|
||||||
|
namespace qt
|
||||||
|
{
|
||||||
|
namespace manager
|
||||||
|
{
|
||||||
|
|
||||||
|
class TimelineManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TimelineManager();
|
||||||
|
~TimelineManager();
|
||||||
|
|
||||||
|
static std::shared_ptr<TimelineManager> Instance();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void SetRadarSite(const std::string& radarSite);
|
||||||
|
|
||||||
|
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 AnimationPlayPause();
|
||||||
|
void AnimationStepNext();
|
||||||
|
void AnimationStepEnd();
|
||||||
|
|
||||||
|
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 LiveStateUpdated(bool isLive);
|
||||||
|
void ViewTypeUpdated(types::MapTime viewType);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
std::unique_ptr<Impl> p;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace manager
|
||||||
|
} // namespace qt
|
||||||
|
} // namespace scwx
|
||||||
|
|
@ -62,8 +62,8 @@ public:
|
||||||
overlayLayer_ {nullptr},
|
overlayLayer_ {nullptr},
|
||||||
colorTableLayer_ {nullptr},
|
colorTableLayer_ {nullptr},
|
||||||
autoRefreshEnabled_ {true},
|
autoRefreshEnabled_ {true},
|
||||||
|
autoUpdateEnabled_ {true},
|
||||||
selectedLevel2Product_ {common::Level2Product::Unknown},
|
selectedLevel2Product_ {common::Level2Product::Unknown},
|
||||||
selectedTime_ {},
|
|
||||||
lastPos_(),
|
lastPos_(),
|
||||||
currentStyleIndex_ {0},
|
currentStyleIndex_ {0},
|
||||||
currentStyle_ {nullptr},
|
currentStyle_ {nullptr},
|
||||||
|
|
@ -147,9 +147,9 @@ public:
|
||||||
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
||||||
|
|
||||||
bool autoRefreshEnabled_;
|
bool autoRefreshEnabled_;
|
||||||
|
bool autoUpdateEnabled_;
|
||||||
|
|
||||||
common::Level2Product selectedLevel2Product_;
|
common::Level2Product selectedLevel2Product_;
|
||||||
std::chrono::system_clock::time_point selectedTime_;
|
|
||||||
|
|
||||||
QPointF lastPos_;
|
QPointF lastPos_;
|
||||||
std::size_t currentStyleIndex_;
|
std::size_t currentStyleIndex_;
|
||||||
|
|
@ -316,6 +316,21 @@ std::shared_ptr<config::RadarSite> MapWidget::GetRadarSite() const
|
||||||
return radarSite;
|
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
|
std::uint16_t MapWidget::GetVcp() const
|
||||||
{
|
{
|
||||||
auto radarProductView = p->context_->radar_product_view();
|
auto radarProductView = p->context_->radar_product_view();
|
||||||
|
|
@ -434,9 +449,8 @@ void MapWidget::SelectRadarProduct(
|
||||||
scwx::util::TimeString(time));
|
scwx::util::TimeString(time));
|
||||||
|
|
||||||
p->SetRadarSite(radarId);
|
p->SetRadarSite(radarId);
|
||||||
p->selectedTime_ = time;
|
|
||||||
|
|
||||||
SelectRadarProduct(group, product, productCode);
|
SelectRadarProduct(group, product, productCode, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapWidget::SelectRadarSite(const std::string& id, bool updateCoordinates)
|
void MapWidget::SelectRadarSite(const std::string& id, bool updateCoordinates)
|
||||||
|
|
@ -478,6 +492,23 @@ void MapWidget::SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite,
|
||||||
AddLayers();
|
AddLayers();
|
||||||
|
|
||||||
// TODO: Disable refresh from old site
|
// 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,6 +537,11 @@ void MapWidget::SetAutoRefresh(bool enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MapWidget::SetAutoUpdate(bool enabled)
|
||||||
|
{
|
||||||
|
p->autoUpdateEnabled_ = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
void MapWidget::SetMapLocation(double latitude,
|
void MapWidget::SetMapLocation(double latitude,
|
||||||
double longitude,
|
double longitude,
|
||||||
bool updateRadarSite)
|
bool updateRadarSite)
|
||||||
|
|
@ -865,6 +901,8 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
std::make_shared<request::NexradFileRequest>();
|
std::make_shared<request::NexradFileRequest>();
|
||||||
|
|
||||||
// File request callback
|
// File request callback
|
||||||
|
if (autoUpdateEnabled_)
|
||||||
|
{
|
||||||
connect(
|
connect(
|
||||||
request.get(),
|
request.get(),
|
||||||
&request::NexradFileRequest::RequestComplete,
|
&request::NexradFileRequest::RequestComplete,
|
||||||
|
|
@ -879,6 +917,7 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
widget_->SelectRadarProduct(record);
|
widget_->SelectRadarProduct(record);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Load file
|
// Load file
|
||||||
scwx::util::async(
|
scwx::util::async(
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ public:
|
||||||
common::RadarProductGroup GetRadarProductGroup() const;
|
common::RadarProductGroup GetRadarProductGroup() const;
|
||||||
std::string GetRadarProductName() const;
|
std::string GetRadarProductName() const;
|
||||||
std::shared_ptr<config::RadarSite> GetRadarSite() const;
|
std::shared_ptr<config::RadarSite> GetRadarSite() const;
|
||||||
|
std::chrono::system_clock::time_point GetSelectedTime() const;
|
||||||
std::uint16_t GetVcp() const;
|
std::uint16_t GetVcp() const;
|
||||||
|
|
||||||
void SelectElevation(float elevation);
|
void SelectElevation(float elevation);
|
||||||
|
|
@ -80,8 +81,16 @@ public:
|
||||||
void SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite,
|
void SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite,
|
||||||
bool updateCoordinates = true);
|
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 SetActive(bool isActive);
|
||||||
void SetAutoRefresh(bool enabled);
|
void SetAutoRefresh(bool enabled);
|
||||||
|
void SetAutoUpdate(bool enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets the current map location.
|
* @brief Sets the current map location.
|
||||||
|
|
@ -132,6 +141,7 @@ signals:
|
||||||
double bearing,
|
double bearing,
|
||||||
double pitch);
|
double pitch);
|
||||||
void MapStyleChanged(const std::string& styleName);
|
void MapStyleChanged(const std::string& styleName);
|
||||||
|
void RadarSiteUpdated(std::shared_ptr<config::RadarSite> radarSite);
|
||||||
void RadarSweepUpdated();
|
void RadarSweepUpdated();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -421,8 +421,7 @@ AlertModelImpl::GetStartTime(const types::TextEventKey& key)
|
||||||
if (messageList.size() > 0)
|
if (messageList.size() > 0)
|
||||||
{
|
{
|
||||||
auto& firstMessage = messageList.front();
|
auto& firstMessage = messageList.front();
|
||||||
auto firstSegment = firstMessage->segment(0);
|
return firstMessage->segment_event_begin(0);
|
||||||
return firstSegment->header_->vtecString_[0].pVtec_.event_begin();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
22
scwx-qt/source/scwx/qt/types/map_types.cpp
Normal file
22
scwx-qt/source/scwx/qt/types/map_types.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#include <scwx/qt/types/map_types.hpp>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace scwx
|
||||||
|
{
|
||||||
|
namespace qt
|
||||||
|
{
|
||||||
|
namespace types
|
||||||
|
{
|
||||||
|
|
||||||
|
static const std::unordered_map<MapTime, std::string> mapTimeName_ {
|
||||||
|
{MapTime::Live, "Live"}, {MapTime::Archive, "Archive"}};
|
||||||
|
|
||||||
|
std::string GetMapTimeName(MapTime mapTime)
|
||||||
|
{
|
||||||
|
return mapTimeName_.at(mapTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace types
|
||||||
|
} // namespace qt
|
||||||
|
} // namespace scwx
|
||||||
28
scwx-qt/source/scwx/qt/types/map_types.hpp
Normal file
28
scwx-qt/source/scwx/qt/types/map_types.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace scwx
|
||||||
|
{
|
||||||
|
namespace qt
|
||||||
|
{
|
||||||
|
namespace types
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class AnimationState
|
||||||
|
{
|
||||||
|
Play,
|
||||||
|
Pause
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MapTime
|
||||||
|
{
|
||||||
|
Live,
|
||||||
|
Archive
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string GetMapTimeName(MapTime mapTime);
|
||||||
|
|
||||||
|
} // namespace types
|
||||||
|
} // namespace qt
|
||||||
|
} // namespace scwx
|
||||||
219
scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp
Normal file
219
scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
#include "animation_dock_widget.hpp"
|
||||||
|
#include "ui_animation_dock_widget.h"
|
||||||
|
|
||||||
|
#include <scwx/qt/util/time.hpp>
|
||||||
|
#include <scwx/util/logger.hpp>
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
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(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_;
|
||||||
|
|
||||||
|
types::AnimationState animationState_ {types::AnimationState::Pause};
|
||||||
|
|
||||||
|
std::chrono::sys_days selectedDate_ {};
|
||||||
|
std::chrono::seconds selectedTime_ {};
|
||||||
|
|
||||||
|
void ConnectSignals();
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimationDockWidget::AnimationDockWidget(QWidget* parent) :
|
||||||
|
QDockWidget(parent),
|
||||||
|
p {std::make_unique<AnimationDockWidgetImpl>(this)},
|
||||||
|
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();
|
||||||
|
QDate currentDate = currentDateTime.date();
|
||||||
|
QTime currentTime = currentDateTime.time();
|
||||||
|
ui->dateEdit->setDate(currentDate);
|
||||||
|
ui->timeEdit->setTime(currentTime);
|
||||||
|
ui->dateEdit->setMaximumDate(currentDateTime.date());
|
||||||
|
p->selectedDate_ = util::SysDays(currentDate);
|
||||||
|
p->selectedTime_ =
|
||||||
|
std::chrono::seconds(currentTime.msecsSinceStartOfDay() / 1000);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Set loop defaults
|
||||||
|
ui->loopTimeSpinBox->setValue(30);
|
||||||
|
ui->loopSpeedSpinBox->setValue(5.0);
|
||||||
|
|
||||||
|
// Connect widget signals
|
||||||
|
p->ConnectSignals();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_ = util::SysDays(date);
|
||||||
|
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]() { emit self_->AnimationPlaySelected(); });
|
||||||
|
QObject::connect(self_->ui->stepNextButton,
|
||||||
|
&QAbstractButton::clicked,
|
||||||
|
self_,
|
||||||
|
[this]() { emit self_->AnimationStepNextSelected(); });
|
||||||
|
QObject::connect(self_->ui->endButton,
|
||||||
|
&QAbstractButton::clicked,
|
||||||
|
self_,
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
56
scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp
Normal file
56
scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scwx/qt/types/map_types.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <QDockWidget>
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void UpdateAnimationState(types::AnimationState state);
|
||||||
|
void UpdateLiveState(bool isLive);
|
||||||
|
|
||||||
|
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 AnimationPlaySelected();
|
||||||
|
void AnimationStepNextSelected();
|
||||||
|
void AnimationStepEndSelected();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AnimationDockWidgetImpl;
|
||||||
|
std::unique_ptr<AnimationDockWidgetImpl> p;
|
||||||
|
Ui::AnimationDockWidget* ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace qt
|
||||||
|
} // namespace scwx
|
||||||
290
scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui
Normal file
290
scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AnimationDockWidget</class>
|
||||||
|
<widget class="QDockWidget" name="AnimationDockWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>200</width>
|
||||||
|
<height>337</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Animation Toolbox</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="dockWidgetContents">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="timelineGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Timeline</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="autoUpdateLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto Update: Enabled</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="liveViewRadioButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Live View</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="archiveViewRadioButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Archive View</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDateEdit" name="dateEdit">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="minimumDateTime">
|
||||||
|
<datetime>
|
||||||
|
<hour>0</hour>
|
||||||
|
<minute>0</minute>
|
||||||
|
<second>0</second>
|
||||||
|
<year>1991</year>
|
||||||
|
<month>6</month>
|
||||||
|
<day>1</day>
|
||||||
|
</datetime>
|
||||||
|
</property>
|
||||||
|
<property name="displayFormat">
|
||||||
|
<string>yyyy-MM-dd</string>
|
||||||
|
</property>
|
||||||
|
<property name="calendarPopup">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QFrame" name="frame">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QTimeEdit" name="timeEdit">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="displayFormat">
|
||||||
|
<string>HH:mm</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>UTC</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QFrame" name="frame_3">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="loopTimeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Loop Time</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QSpinBox" name="loopTimeSpinBox">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string> min</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1440</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="loopSpeedLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Loop Speed</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="loopSpeedSpinBox">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string>x</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QFrame" name="frame_2">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="beginButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../scwx-qt.qrc">
|
||||||
|
<normaloff>:/res/icons/font-awesome-6/backward-step-solid.svg</normaloff>:/res/icons/font-awesome-6/backward-step-solid.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="stepBackButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../scwx-qt.qrc">
|
||||||
|
<normaloff>:/res/icons/font-awesome-6/angle-left-solid.svg</normaloff>:/res/icons/font-awesome-6/angle-left-solid.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="playButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../scwx-qt.qrc">
|
||||||
|
<normaloff>:/res/icons/font-awesome-6/play-solid.svg</normaloff>:/res/icons/font-awesome-6/play-solid.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="stepNextButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../scwx-qt.qrc">
|
||||||
|
<normaloff>:/res/icons/font-awesome-6/angle-right-solid.svg</normaloff>:/res/icons/font-awesome-6/angle-right-solid.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="endButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../scwx-qt.qrc">
|
||||||
|
<normaloff>:/res/icons/font-awesome-6/forward-step-solid.svg</normaloff>:/res/icons/font-awesome-6/forward-step-solid.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../../../../scwx-qt.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
24
scwx-qt/source/scwx/qt/util/time.cpp
Normal file
24
scwx-qt/source/scwx/qt/util/time.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#include <scwx/qt/util/time.hpp>
|
||||||
|
|
||||||
|
namespace scwx
|
||||||
|
{
|
||||||
|
namespace qt
|
||||||
|
{
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
|
||||||
|
std::chrono::sys_days SysDays(const QDate& date)
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
using sys_days = time_point<system_clock, days>;
|
||||||
|
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
|
||||||
25
scwx-qt/source/scwx/qt/util/time.hpp
Normal file
25
scwx-qt/source/scwx/qt/util/time.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -108,7 +108,11 @@ Level2ProductView::Level2ProductView(
|
||||||
{
|
{
|
||||||
ConnectRadarProductManager();
|
ConnectRadarProductManager();
|
||||||
}
|
}
|
||||||
Level2ProductView::~Level2ProductView() = default;
|
|
||||||
|
Level2ProductView::~Level2ProductView()
|
||||||
|
{
|
||||||
|
std::unique_lock sweepLock {sweep_mutex()};
|
||||||
|
}
|
||||||
|
|
||||||
void Level2ProductView::ConnectRadarProductManager()
|
void Level2ProductView::ConnectRadarProductManager()
|
||||||
{
|
{
|
||||||
|
|
@ -119,7 +123,8 @@ void Level2ProductView::ConnectRadarProductManager()
|
||||||
{
|
{
|
||||||
if (record->radar_product_group() ==
|
if (record->radar_product_group() ==
|
||||||
common::RadarProductGroup::Level2 &&
|
common::RadarProductGroup::Level2 &&
|
||||||
record->time() == selected_time())
|
std::chrono::floor<std::chrono::seconds>(record->time()) ==
|
||||||
|
selected_time())
|
||||||
{
|
{
|
||||||
// If the data associated with the currently selected time is
|
// If the data associated with the currently selected time is
|
||||||
// reloaded, update the view
|
// reloaded, update the view
|
||||||
|
|
@ -290,11 +295,6 @@ void Level2ProductViewImpl::SetProduct(common::Level2Product product)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Level2ProductView::Update()
|
|
||||||
{
|
|
||||||
util::async([this]() { ComputeSweep(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void Level2ProductView::UpdateColorTable()
|
void Level2ProductView::UpdateColorTable()
|
||||||
{
|
{
|
||||||
if (p->momentDataBlock0_ == nullptr || //
|
if (p->momentDataBlock0_ == nullptr || //
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ public:
|
||||||
void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override;
|
void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override;
|
||||||
void SelectElevation(float elevation) override;
|
void SelectElevation(float elevation) override;
|
||||||
void SelectProduct(const std::string& productName) override;
|
void SelectProduct(const std::string& productName) override;
|
||||||
void Update() override;
|
|
||||||
|
|
||||||
common::RadarProductGroup GetRadarProductGroup() const override;
|
common::RadarProductGroup GetRadarProductGroup() const override;
|
||||||
std::string GetRadarProductName() const override;
|
std::string GetRadarProductName() const override;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include <scwx/qt/view/level3_product_view.hpp>
|
#include <scwx/qt/view/level3_product_view.hpp>
|
||||||
#include <scwx/common/constants.hpp>
|
#include <scwx/common/constants.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/threads.hpp>
|
|
||||||
#include <scwx/util/time.hpp>
|
#include <scwx/util/time.hpp>
|
||||||
#include <scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp>
|
#include <scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp>
|
||||||
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
|
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
|
||||||
|
|
@ -161,11 +160,6 @@ void Level3ProductView::LoadColorTable(
|
||||||
UpdateColorTable();
|
UpdateColorTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Level3ProductView::Update()
|
|
||||||
{
|
|
||||||
util::async([this]() { ComputeSweep(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void Level3ProductView::UpdateColorTable()
|
void Level3ProductView::UpdateColorTable()
|
||||||
{
|
{
|
||||||
logger_->debug("UpdateColorTable()");
|
logger_->debug("UpdateColorTable()");
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ public:
|
||||||
std::uint16_t color_table_max() const override;
|
std::uint16_t color_table_max() const override;
|
||||||
|
|
||||||
void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override;
|
void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override;
|
||||||
void Update() override;
|
|
||||||
|
|
||||||
common::RadarProductGroup GetRadarProductGroup() const override;
|
common::RadarProductGroup GetRadarProductGroup() const override;
|
||||||
std::string GetRadarProductName() const override;
|
std::string GetRadarProductName() const override;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,11 @@ Level3RadialView::Level3RadialView(
|
||||||
p(std::make_unique<Level3RadialViewImpl>())
|
p(std::make_unique<Level3RadialViewImpl>())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
Level3RadialView::~Level3RadialView() = default;
|
|
||||||
|
Level3RadialView::~Level3RadialView()
|
||||||
|
{
|
||||||
|
std::unique_lock sweepLock {sweep_mutex()};
|
||||||
|
}
|
||||||
|
|
||||||
float Level3RadialView::range() const
|
float Level3RadialView::range() const
|
||||||
{
|
{
|
||||||
|
|
@ -150,8 +154,8 @@ void Level3RadialView::ComputeSweep()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A message with radial data should either have a Digital Radial Data Array
|
// A message with radial data should either have a Digital Radial Data
|
||||||
// Packet, or a Radial Data Array Packet (TODO)
|
// Array Packet, or a Radial Data Array Packet (TODO)
|
||||||
std::shared_ptr<wsr88d::rpg::DigitalRadialDataArrayPacket>
|
std::shared_ptr<wsr88d::rpg::DigitalRadialDataArrayPacket>
|
||||||
digitalDataPacket = nullptr;
|
digitalDataPacket = nullptr;
|
||||||
std::shared_ptr<wsr88d::rpg::RadialDataPacket> radialDataPacket = nullptr;
|
std::shared_ptr<wsr88d::rpg::RadialDataPacket> radialDataPacket = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,11 @@ Level3RasterView::Level3RasterView(
|
||||||
p(std::make_unique<Level3RasterViewImpl>())
|
p(std::make_unique<Level3RasterViewImpl>())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
Level3RasterView::~Level3RasterView() = default;
|
|
||||||
|
Level3RasterView::~Level3RasterView()
|
||||||
|
{
|
||||||
|
std::unique_lock sweepLock {sweep_mutex()};
|
||||||
|
}
|
||||||
|
|
||||||
float Level3RasterView::range() const
|
float Level3RasterView::range() const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include <scwx/common/constants.hpp>
|
#include <scwx/common/constants.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
#include <boost/range/irange.hpp>
|
#include <boost/range/irange.hpp>
|
||||||
#include <boost/timer/timer.hpp>
|
#include <boost/timer/timer.hpp>
|
||||||
|
|
||||||
|
|
@ -36,6 +37,8 @@ public:
|
||||||
}
|
}
|
||||||
~RadarProductViewImpl() = default;
|
~RadarProductViewImpl() = default;
|
||||||
|
|
||||||
|
boost::asio::thread_pool threadPool_ {1};
|
||||||
|
|
||||||
bool initialized_;
|
bool initialized_;
|
||||||
std::mutex sweepMutex_;
|
std::mutex sweepMutex_;
|
||||||
|
|
||||||
|
|
@ -118,6 +121,11 @@ void RadarProductView::SelectTime(std::chrono::system_clock::time_point time)
|
||||||
p->selectedTime_ = time;
|
p->selectedTime_ = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RadarProductView::Update()
|
||||||
|
{
|
||||||
|
boost::asio::post(p->threadPool_, [this]() { ComputeSweep(); });
|
||||||
|
}
|
||||||
|
|
||||||
bool RadarProductView::IsInitialized() const
|
bool RadarProductView::IsInitialized() const
|
||||||
{
|
{
|
||||||
return p->initialized_;
|
return p->initialized_;
|
||||||
|
|
@ -138,6 +146,11 @@ RadarProductView::GetCfpMomentData() const
|
||||||
return std::tie(data, dataSize, componentSize);
|
return std::tie(data, dataSize, componentSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point RadarProductView::GetSelectedTime() const
|
||||||
|
{
|
||||||
|
return p->selectedTime_;
|
||||||
|
}
|
||||||
|
|
||||||
void RadarProductView::ComputeSweep()
|
void RadarProductView::ComputeSweep()
|
||||||
{
|
{
|
||||||
logger_->debug("ComputeSweep()");
|
logger_->debug("ComputeSweep()");
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ public:
|
||||||
virtual void SelectElevation(float elevation);
|
virtual void SelectElevation(float elevation);
|
||||||
virtual void SelectProduct(const std::string& productName) = 0;
|
virtual void SelectProduct(const std::string& productName) = 0;
|
||||||
void SelectTime(std::chrono::system_clock::time_point time);
|
void SelectTime(std::chrono::system_clock::time_point time);
|
||||||
virtual void Update() = 0;
|
void Update();
|
||||||
|
|
||||||
bool IsInitialized() const;
|
bool IsInitialized() const;
|
||||||
|
|
||||||
|
|
@ -62,6 +62,7 @@ public:
|
||||||
GetMomentData() const = 0;
|
GetMomentData() const = 0;
|
||||||
virtual std::tuple<const void*, std::size_t, std::size_t>
|
virtual std::tuple<const void*, std::size_t, std::size_t>
|
||||||
GetCfpMomentData() const;
|
GetCfpMomentData() const;
|
||||||
|
std::chrono::system_clock::time_point GetSelectedTime() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void ConnectRadarProductManager() = 0;
|
virtual void ConnectRadarProductManager() = 0;
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,26 @@ TEST(AwsLevel3DataProvider, GetAvailableProducts)
|
||||||
EXPECT_GT(products.size(), 0);
|
EXPECT_GT(products.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(AwsLevel3DataProvider, GetTimePointsByDate)
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
using sys_days = time_point<system_clock, days>;
|
||||||
|
|
||||||
|
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)
|
TEST(AwsLevel3DataProvider, TimePointValid)
|
||||||
{
|
{
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,14 @@ public:
|
||||||
std::shared_ptr<WmoHeader> wmo_header() const;
|
std::shared_ptr<WmoHeader> wmo_header() const;
|
||||||
std::vector<std::string> mnd_header() const;
|
std::vector<std::string> mnd_header() const;
|
||||||
std::vector<std::string> overview_block() const;
|
std::vector<std::string> overview_block() const;
|
||||||
size_t segment_count() const;
|
std::size_t segment_count() const;
|
||||||
std::vector<std::shared_ptr<const Segment>> segments() const;
|
std::vector<std::shared_ptr<const Segment>> segments() const;
|
||||||
std::shared_ptr<const Segment> segment(size_t s) const;
|
std::shared_ptr<const Segment> 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;
|
bool Parse(std::istream& is) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,20 @@ public:
|
||||||
AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept;
|
AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept;
|
||||||
AwsNexradDataProvider& operator=(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::system_clock::time_point last_modified() const override;
|
||||||
std::chrono::seconds update_period() const;
|
std::chrono::seconds update_period() const override;
|
||||||
|
|
||||||
std::string FindKey(std::chrono::system_clock::time_point time);
|
std::string FindKey(std::chrono::system_clock::time_point time) override;
|
||||||
std::string FindLatestKey();
|
std::string FindLatestKey() override;
|
||||||
std::pair<size_t, size_t>
|
std::vector<std::chrono::system_clock::time_point>
|
||||||
ListObjects(std::chrono::system_clock::time_point date);
|
GetTimePointsByDate(std::chrono::system_clock::time_point date) override;
|
||||||
std::shared_ptr<wsr88d::NexradFile> LoadObjectByKey(const std::string& key);
|
std::tuple<bool, size_t, size_t>
|
||||||
std::pair<size_t, size_t> Refresh();
|
ListObjects(std::chrono::system_clock::time_point date) override;
|
||||||
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
LoadObjectByKey(const std::string& key) override;
|
||||||
|
std::pair<size_t, size_t> Refresh() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<Aws::S3::S3Client> client();
|
std::shared_ptr<Aws::S3::S3Client> client();
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,11 @@ public:
|
||||||
*
|
*
|
||||||
* @param date Date for which to list objects
|
* @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
|
* - Total objects found for the given date
|
||||||
*/
|
*/
|
||||||
virtual std::pair<size_t, size_t>
|
virtual std::tuple<bool, size_t, size_t>
|
||||||
ListObjects(std::chrono::system_clock::time_point date) = 0;
|
ListObjects(std::chrono::system_clock::time_point date) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -101,6 +102,17 @@ public:
|
||||||
virtual std::chrono::system_clock::time_point
|
virtual std::chrono::system_clock::time_point
|
||||||
GetTimePointByKey(const std::string& key) const = 0;
|
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<std::chrono::system_clock::time_point>
|
||||||
|
GetTimePointsByDate(std::chrono::system_clock::time_point date) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests available NEXRAD products for the current radar site, and adds
|
* Requests available NEXRAD products for the current radar site, and adds
|
||||||
* the list to the cache.
|
* the list to the cache.
|
||||||
|
|
|
||||||
|
|
@ -8,35 +8,50 @@ namespace scwx
|
||||||
namespace util
|
namespace util
|
||||||
{
|
{
|
||||||
|
|
||||||
template<class Key, class T, class ReturnType = std::map<Key, T>::const_pointer>
|
template<class Container>
|
||||||
ReturnType GetBoundedElementPointer(std::map<Key, T>& map, const Key& key)
|
Container::const_iterator
|
||||||
|
GetBoundedElementIterator(Container& container,
|
||||||
|
const typename Container::key_type& key)
|
||||||
{
|
{
|
||||||
ReturnType elementPtr {nullptr};
|
|
||||||
|
|
||||||
// Find the first element greater than the key requested
|
// Find the first element greater than the key requested
|
||||||
auto it = map.upper_bound(key);
|
typename Container::const_iterator it = container.upper_bound(key);
|
||||||
|
|
||||||
// An element with a key greater was found
|
// An element with a key greater was found
|
||||||
if (it != map.cend())
|
if (it != container.cend())
|
||||||
{
|
{
|
||||||
// Are there elements prior to this element?
|
// 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
|
// Get the element immediately preceding, this the element we are
|
||||||
// looking for
|
// looking for
|
||||||
elementPtr = &(*(--it));
|
--it;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The current element is a good substitute
|
// The current element is a good substitute
|
||||||
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
|
// An element with a key greater was not found. If it exists, it must be
|
||||||
// the last element.
|
// the last element. Decrement the end iterator.
|
||||||
elementPtr = &(*map.rbegin());
|
--it;
|
||||||
|
}
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Container, class ReturnType = Container::const_pointer>
|
||||||
|
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;
|
return elementPtr;
|
||||||
|
|
@ -47,8 +62,9 @@ ReturnType GetBoundedElement(std::map<Key, T>& map, const Key& key)
|
||||||
{
|
{
|
||||||
ReturnType element;
|
ReturnType element;
|
||||||
|
|
||||||
typename std::map<Key, T>::pointer elementPtr =
|
typename std::map<Key, T>::const_pointer elementPtr =
|
||||||
GetBoundedElementPointer<Key, T, typename std::map<Key, T>::pointer>(map,
|
GetBoundedElementPointer<std::map<Key, T>,
|
||||||
|
typename std::map<Key, T>::const_pointer>(map,
|
||||||
key);
|
key);
|
||||||
if (elementPtr != nullptr)
|
if (elementPtr != nullptr)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <scwx/awips/text_product_message.hpp>
|
#include <scwx/awips/text_product_message.hpp>
|
||||||
#include <scwx/common/characters.hpp>
|
#include <scwx/common/characters.hpp>
|
||||||
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/streams.hpp>
|
#include <scwx/util/streams.hpp>
|
||||||
|
|
||||||
#include <istream>
|
#include <istream>
|
||||||
|
|
@ -15,6 +16,7 @@ namespace awips
|
||||||
{
|
{
|
||||||
|
|
||||||
static const std::string logPrefix_ = "scwx::awips::text_product_message";
|
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:
|
// Issuance date/time takes one of the following forms:
|
||||||
// * <hhmm>_xM_<tz>_day_mon_<dd>_year
|
// * <hhmm>_xM_<tz>_day_mon_<dd>_year
|
||||||
|
|
@ -101,6 +103,91 @@ std::shared_ptr<const Segment> TextProductMessage::segment(size_t s) const
|
||||||
return p->segments_[s];
|
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<days>(eventEnd);
|
||||||
|
year_month_day endDate {endDays};
|
||||||
|
|
||||||
|
// Determine WMO date/time
|
||||||
|
std::string wmoDateTime = wmo_header()->date_time();
|
||||||
|
|
||||||
|
bool wmoDateTimeValid = false;
|
||||||
|
unsigned int dayOfMonth = 0;
|
||||||
|
unsigned long beginHour = 0;
|
||||||
|
unsigned long beginMinute = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// WMO date time is in the format DDHHMM
|
||||||
|
dayOfMonth =
|
||||||
|
static_cast<unsigned int>(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 (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)
|
||||||
|
{
|
||||||
|
// The begin month must be December of last year
|
||||||
|
eventBegin =
|
||||||
|
sys_days {
|
||||||
|
year {static_cast<int>((endDate.year() - 1y).count())} /
|
||||||
|
December / day {dayOfMonth}} +
|
||||||
|
hours {beginHour} + minutes {beginMinute};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Back up one month
|
||||||
|
eventBegin =
|
||||||
|
sys_days {endDate.year() /
|
||||||
|
month {static_cast<unsigned int>(
|
||||||
|
(endDate.month() - month {1}).count())} /
|
||||||
|
day {dayOfMonth}} +
|
||||||
|
hours {beginHour} + minutes {beginMinute};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventBegin;
|
||||||
|
}
|
||||||
|
|
||||||
size_t TextProductMessage::data_size() const
|
size_t TextProductMessage::data_size() const
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ AwsLevel2DataProvider::operator=(AwsLevel2DataProvider&&) noexcept = default;
|
||||||
std::string
|
std::string
|
||||||
AwsLevel2DataProvider::GetPrefix(std::chrono::system_clock::time_point date)
|
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_);
|
return fmt::format("{0:%Y/%m/%d}/{1}/", fmt::gmtime(date), p->radarSite_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,11 @@ AwsLevel3DataProvider::operator=(AwsLevel3DataProvider&&) noexcept = default;
|
||||||
std::string
|
std::string
|
||||||
AwsLevel3DataProvider::GetPrefix(std::chrono::system_clock::time_point date)
|
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(
|
return fmt::format(
|
||||||
"{0}_{1}_{2:%Y_%m_%d}_", p->siteId_, p->product_, fmt::gmtime(date));
|
"{0}_{1}_{2:%Y_%m_%d}_", p->siteId_, p->product_, fmt::gmtime(date));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <aws/s3/model/GetObjectRequest.h>
|
#include <aws/s3/model/GetObjectRequest.h>
|
||||||
#include <aws/s3/model/ListObjectsV2Request.h>
|
#include <aws/s3/model/ListObjectsV2Request.h>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
|
|
@ -19,8 +20,9 @@ static const std::string logPrefix_ =
|
||||||
"scwx::provider::aws_nexrad_data_provider";
|
"scwx::provider::aws_nexrad_data_provider";
|
||||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||||
|
|
||||||
// Keep at least today, yesterday, and one more date
|
// Keep at least today, yesterday, and three more dates (archived volume scan
|
||||||
static const size_t kMinDatesBeforePruning_ = 4;
|
// list size)
|
||||||
|
static const size_t kMinDatesBeforePruning_ = 6;
|
||||||
static const size_t kMaxObjects_ = 2500;
|
static const size_t kMaxObjects_ = 2500;
|
||||||
|
|
||||||
class AwsNexradDataProvider::Impl
|
class AwsNexradDataProvider::Impl
|
||||||
|
|
@ -156,7 +158,61 @@ std::string AwsNexradDataProvider::FindLatestKey()
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<size_t, size_t>
|
std::vector<std::chrono::system_clock::time_point>
|
||||||
|
AwsNexradDataProvider::GetTimePointsByDate(
|
||||||
|
std::chrono::system_clock::time_point date)
|
||||||
|
{
|
||||||
|
const auto day = std::chrono::floor<std::chrono::days>(date);
|
||||||
|
|
||||||
|
std::vector<std::chrono::system_clock::time_point> timePoints {};
|
||||||
|
|
||||||
|
logger_->trace("GetTimePointsByDate: {}", util::TimeString(date));
|
||||||
|
|
||||||
|
std::shared_lock lock(p->objectsMutex_);
|
||||||
|
|
||||||
|
// Is the date present in the date list?
|
||||||
|
auto currentDateIterator =
|
||||||
|
std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day);
|
||||||
|
if (currentDateIterator == p->objectDates_.cend())
|
||||||
|
{
|
||||||
|
// Temporarily unlock mutex
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
// List objects, since the date is not present in the date list
|
||||||
|
auto [success, newObjects, totalObjects] = ListObjects(date);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
p->UpdateObjectDates(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; });
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<bool, size_t, size_t>
|
||||||
AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date)
|
AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date)
|
||||||
{
|
{
|
||||||
const std::string prefix {GetPrefix(date)};
|
const std::string prefix {GetPrefix(date)};
|
||||||
|
|
@ -222,7 +278,7 @@ AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date)
|
||||||
outcome.GetError().GetMessage());
|
outcome.GetError().GetMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_pair(newObjects, totalObjects);
|
return {outcome.IsSuccess(), newObjects, totalObjects};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<wsr88d::NexradFile>
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
|
@ -269,7 +325,7 @@ std::pair<size_t, size_t> AwsNexradDataProvider::Refresh()
|
||||||
// yesterday, to ensure we haven't missed any objects near midnight
|
// yesterday, to ensure we haven't missed any objects near midnight
|
||||||
if (p->refreshDate_ < today)
|
if (p->refreshDate_ < today)
|
||||||
{
|
{
|
||||||
auto [newObjects, totalObjects] = ListObjects(yesterday);
|
auto [success, newObjects, totalObjects] = ListObjects(yesterday);
|
||||||
allNewObjects = newObjects;
|
allNewObjects = newObjects;
|
||||||
allTotalObjects = totalObjects;
|
allTotalObjects = totalObjects;
|
||||||
if (totalObjects > 0)
|
if (totalObjects > 0)
|
||||||
|
|
@ -278,7 +334,7 @@ std::pair<size_t, size_t> AwsNexradDataProvider::Refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [newObjects, totalObjects] = ListObjects(today);
|
auto [success, newObjects, totalObjects] = ListObjects(today);
|
||||||
allNewObjects += newObjects;
|
allNewObjects += newObjects;
|
||||||
allTotalObjects += totalObjects;
|
allTotalObjects += totalObjects;
|
||||||
if (totalObjects > 0)
|
if (totalObjects > 0)
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,11 @@ target_compile_options(wxdata PRIVATE
|
||||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>
|
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic -Werror>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
# Produce PDB file for debug
|
||||||
|
target_compile_options(wxdata PRIVATE "$<$<CONFIG:Release>:/Zi>")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(wxdata PUBLIC aws-cpp-sdk-core
|
target_link_libraries(wxdata PUBLIC aws-cpp-sdk-core
|
||||||
aws-cpp-sdk-s3
|
aws-cpp-sdk-s3
|
||||||
cpr::cpr
|
cpr::cpr
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue