mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 01:40:04 +00:00 
			
		
		
		
	Compare commits
	
		
			43 commits
		
	
	
		
			de43670ec2
			...
			d218d05184
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d218d05184 | ||
|   | 2cc5e48102 | ||
|   | 4502b59830 | ||
|   | eed2736dde | ||
|   | d687d04d76 | ||
|   | 54fd86de5c | ||
|   | b8e9cb659e | ||
|   | 94dc5b547d | ||
|   | 2dc69544ec | ||
|   | f8ebd265e0 | ||
|   | a43c2df13f | ||
|   | a952d890e6 | ||
|   | 9416ff1546 | ||
|   | cb749e7b9e | ||
|   | 4fdf52b9b4 | ||
|   | 76a74922c5 | ||
|   | f34d11a7ea | ||
|   | 3fe7dd9eed | ||
|   | 985473a0a4 | ||
|   | 7fbd9e45a9 | ||
|   | 889b6e81be | ||
|   | acfb515e10 | ||
|   | 22a6ed33c1 | ||
|   | 95b9a03437 | ||
|   | 77ae293e87 | ||
|   | d6834127db | ||
|   | 1f8cd8ee39 | ||
|   | b0c7554f47 | ||
|   | 403a556b30 | ||
|   | 2beac20ee3 | ||
|   | 07adbb382d | ||
|   | 41bf7f731f | ||
|   | 931dd2d0a7 | ||
|   | 341096af1d | ||
|   | f679f37fc1 | ||
|   | a306fb4363 | ||
|   | 449d8cb796 | ||
|   | f4226b487d | ||
|   | 68f66c0c2f | ||
|   | df0d698837 | ||
|   | b6797eee7e | ||
|   | 3c5c9ea27e | ||
|   | 0e21884322 | 
					 32 changed files with 868 additions and 355 deletions
				
			
		
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -127,7 +127,7 @@ jobs: | ||||||
|     env: |     env: | ||||||
|       CC: ${{ matrix.env_cc }} |       CC: ${{ matrix.env_cc }} | ||||||
|       CXX: ${{ matrix.env_cxx }} |       CXX: ${{ matrix.env_cxx }} | ||||||
|       SCWX_VERSION: v0.5.1 |       SCWX_VERSION: v0.5.2 | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
| 
 | 
 | ||||||
|     steps: |     steps: | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0) | ||||||
| scwx_python_setup() | scwx_python_setup() | ||||||
| 
 | 
 | ||||||
| project(${PROJECT_NAME} | project(${PROJECT_NAME} | ||||||
|         VERSION      0.5.1 |         VERSION      0.5.2 | ||||||
|         DESCRIPTION  "Supercell Wx is a free, open source advanced weather radar viewer." |         DESCRIPTION  "Supercell Wx is a free, open source advanced weather radar viewer." | ||||||
|         HOMEPAGE_URL "https://github.com/dpaulat/supercell-wx" |         HOMEPAGE_URL "https://github.com/dpaulat/supercell-wx" | ||||||
|         LANGUAGES    C CXX) |         LANGUAGES    C CXX) | ||||||
|  | @ -32,7 +32,7 @@ set_property(DIRECTORY | ||||||
| set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_ALL_NO_LIB") | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_ALL_NO_LIB") | ||||||
| 
 | 
 | ||||||
| set(SCWX_DIR ${PROJECT_SOURCE_DIR}) | set(SCWX_DIR ${PROJECT_SOURCE_DIR}) | ||||||
| set(SCWX_VERSION "0.5.1") | set(SCWX_VERSION "0.5.2") | ||||||
| 
 | 
 | ||||||
| option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF) | option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -234,6 +234,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp | ||||||
|               source/scwx/qt/types/media_types.hpp |               source/scwx/qt/types/media_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/radar_product_types.hpp | ||||||
|               source/scwx/qt/types/text_event_key.hpp |               source/scwx/qt/types/text_event_key.hpp | ||||||
|               source/scwx/qt/types/text_types.hpp |               source/scwx/qt/types/text_types.hpp | ||||||
|               source/scwx/qt/types/texture_types.hpp |               source/scwx/qt/types/texture_types.hpp | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ | ||||||
| #include <boost/asio.hpp> | #include <boost/asio.hpp> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| #include <QApplication> | #include <QApplication> | ||||||
|  | #include <QLibraryInfo> | ||||||
| #include <QStandardPaths> | #include <QStandardPaths> | ||||||
| #include <QStyleHints> | #include <QStyleHints> | ||||||
| #include <QSurfaceFormat> | #include <QSurfaceFormat> | ||||||
|  | @ -71,6 +72,8 @@ int main(int argc, char* argv[]) | ||||||
|                  scwx::qt::main::kVersionString_, |                  scwx::qt::main::kVersionString_, | ||||||
|                  scwx::qt::main::kBuildNumber_, |                  scwx::qt::main::kBuildNumber_, | ||||||
|                  scwx::qt::main::kCommitString_); |                  scwx::qt::main::kCommitString_); | ||||||
|  |    logger_->info("Qt version {}", | ||||||
|  |                  QLibraryInfo::version().toString().toStdString()); | ||||||
| 
 | 
 | ||||||
|    InitializeOpenGL(); |    InitializeOpenGL(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -463,18 +463,28 @@ void MainWindow::keyReleaseEvent(QKeyEvent* ev) | ||||||
| void MainWindow::showEvent(QShowEvent* event) | void MainWindow::showEvent(QShowEvent* event) | ||||||
| { | { | ||||||
|    QMainWindow::showEvent(event); |    QMainWindow::showEvent(event); | ||||||
|    auto& uiSettings = settings::UiSettings::Instance(); |  | ||||||
| 
 | 
 | ||||||
|    // restore the geometry state
 |    static bool firstShowEvent = true; | ||||||
|    std::string uiGeometry = uiSettings.main_ui_geometry().GetValue(); |    bool        restored       = false; | ||||||
|    restoreGeometry( |  | ||||||
|       QByteArray::fromBase64(QByteArray::fromStdString(uiGeometry))); |  | ||||||
| 
 | 
 | ||||||
|    // restore the UI state
 |    if (firstShowEvent) | ||||||
|    std::string uiState = uiSettings.main_ui_state().GetValue(); |    { | ||||||
|  |       auto& uiSettings = settings::UiSettings::Instance(); | ||||||
|  | 
 | ||||||
|  |       // restore the geometry state
 | ||||||
|  |       const std::string uiGeometry = uiSettings.main_ui_geometry().GetValue(); | ||||||
|  |       restoreGeometry( | ||||||
|  |          QByteArray::fromBase64(QByteArray::fromStdString(uiGeometry))); | ||||||
|  | 
 | ||||||
|  |       // restore the UI state
 | ||||||
|  |       const std::string uiState = uiSettings.main_ui_state().GetValue(); | ||||||
|  | 
 | ||||||
|  |       restored = restoreState( | ||||||
|  |          QByteArray::fromBase64(QByteArray::fromStdString(uiState))); | ||||||
|  | 
 | ||||||
|  |       firstShowEvent = false; | ||||||
|  |    } | ||||||
| 
 | 
 | ||||||
|    bool restored = |  | ||||||
|       restoreState(QByteArray::fromBase64(QByteArray::fromStdString(uiState))); |  | ||||||
|    if (!restored) |    if (!restored) | ||||||
|    { |    { | ||||||
|       resizeDocks({ui->radarToolboxDock}, {194}, Qt::Horizontal); |       resizeDocks({ui->radarToolboxDock}, {194}, Qt::Horizontal); | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
| #include <execution> | #include <execution> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <shared_mutex> | #include <shared_mutex> | ||||||
|  | #include <unordered_map> | ||||||
| #include <unordered_set> | #include <unordered_set> | ||||||
| #include <utility> | #include <utility> | ||||||
| 
 | 
 | ||||||
|  | @ -36,11 +37,7 @@ | ||||||
| #   pragma warning(pop) | #   pragma warning(pop) | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::manager | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace manager |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = | static const std::string logPrefix_ = | ||||||
|  | @ -109,21 +106,27 @@ public: | ||||||
|                     group, product, isChunks_, latestTime); |                     group, product, isChunks_, latestTime); | ||||||
|               }); |               }); | ||||||
|    } |    } | ||||||
|    ~ProviderManager() { threadPool_.join(); }; |    ~ProviderManager() override | ||||||
|  |    { | ||||||
|  |       providerThreadPool_.stop(); | ||||||
|  |       providerThreadPool_.join(); | ||||||
|  |    }; | ||||||
| 
 | 
 | ||||||
|    std::string name() const; |    std::string name() const; | ||||||
| 
 | 
 | ||||||
|    void Disable(); |    void Disable(); | ||||||
|  |    void RefreshData(); | ||||||
|  |    void RefreshDataSync(); | ||||||
| 
 | 
 | ||||||
|    boost::asio::thread_pool threadPool_ {1u}; |    boost::asio::thread_pool providerThreadPool_ {2u}; | ||||||
| 
 | 
 | ||||||
|    const std::string                             radarId_; |    const std::string               radarId_; | ||||||
|    const common::RadarProductGroup               group_; |    const common::RadarProductGroup group_; | ||||||
|    const std::string                             product_; |    const std::string               product_; | ||||||
|    const bool                                    isChunks_; |    const bool                      isChunks_; | ||||||
|    bool                                          refreshEnabled_ {false}; |    bool                            refreshEnabled_ {false}; | ||||||
|    boost::asio::steady_timer                     refreshTimer_ {threadPool_}; |    boost::asio::steady_timer       refreshTimer_ {providerThreadPool_}; | ||||||
|    std::mutex                                    refreshTimerMutex_ {}; |    std::mutex                      refreshTimerMutex_ {}; | ||||||
|    std::shared_ptr<provider::NexradDataProvider> provider_ {nullptr}; |    std::shared_ptr<provider::NexradDataProvider> provider_ {nullptr}; | ||||||
|    size_t                                        refreshCount_ {0}; |    size_t                                        refreshCount_ {0}; | ||||||
| 
 | 
 | ||||||
|  | @ -184,11 +187,9 @@ public: | ||||||
|                        auto& [key, providerManager] = p; |                        auto& [key, providerManager] = p; | ||||||
|                        providerManager->Disable(); |                        providerManager->Disable(); | ||||||
|                     }); |                     }); | ||||||
|  |       lock.unlock(); | ||||||
| 
 | 
 | ||||||
|       // Lock other mutexes before destroying, ensure loading is complete
 |       threadPool_.stop(); | ||||||
|       std::unique_lock loadLevel2DataLock {loadLevel2DataMutex_}; |  | ||||||
|       std::unique_lock loadLevel3DataLock {loadLevel3DataMutex_}; |  | ||||||
| 
 |  | ||||||
|       threadPool_.join(); |       threadPool_.join(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  | @ -203,14 +204,14 @@ public: | ||||||
|       boost::uuids::uuid                                uuid, |       boost::uuids::uuid                                uuid, | ||||||
|       const std::set<std::shared_ptr<ProviderManager>>& providerManagers, |       const std::set<std::shared_ptr<ProviderManager>>& providerManagers, | ||||||
|       bool                                              enabled); |       bool                                              enabled); | ||||||
|    void RefreshData(std::shared_ptr<ProviderManager> providerManager); |  | ||||||
|    void RefreshDataSync(std::shared_ptr<ProviderManager> providerManager); |  | ||||||
| 
 | 
 | ||||||
|    std::map<std::chrono::system_clock::time_point, |    std::tuple<std::map<std::chrono::system_clock::time_point, | ||||||
|             std::shared_ptr<types::RadarProductRecord>> |                        std::shared_ptr<types::RadarProductRecord>>, | ||||||
|  |               types::RadarProductLoadStatus> | ||||||
|    GetLevel2ProductRecords(std::chrono::system_clock::time_point time); |    GetLevel2ProductRecords(std::chrono::system_clock::time_point time); | ||||||
|    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, | ||||||
|  |               types::RadarProductLoadStatus> | ||||||
|    GetLevel3ProductRecord(const std::string&                    product, |    GetLevel3ProductRecord(const std::string&                    product, | ||||||
|                           std::chrono::system_clock::time_point time); |                           std::chrono::system_clock::time_point time); | ||||||
|    std::shared_ptr<types::RadarProductRecord> |    std::shared_ptr<types::RadarProductRecord> | ||||||
|  | @ -224,15 +225,24 @@ public: | ||||||
|       std::mutex&                                        mutex, |       std::mutex&                                        mutex, | ||||||
|       std::chrono::system_clock::time_point              time); |       std::chrono::system_clock::time_point              time); | ||||||
|    void |    void | ||||||
|         LoadProviderData(std::chrono::system_clock::time_point time, |    LoadProviderData(std::chrono::system_clock::time_point time, | ||||||
|                          std::shared_ptr<ProviderManager>      providerManager, |                     std::shared_ptr<ProviderManager>      providerManager, | ||||||
|                          RadarProductRecordMap&                recordMap, |                     RadarProductRecordMap&                recordMap, | ||||||
|                          std::shared_mutex&                    recordMutex, |                     std::shared_mutex&                    recordMutex, | ||||||
|                          std::mutex&                           loadDataMutex, |                     std::mutex&                           loadDataMutex, | ||||||
|                          const std::shared_ptr<request::NexradFileRequest>& request); |                     const std::shared_ptr<request::NexradFileRequest>& request); | ||||||
|    void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time); | 
 | ||||||
|  |    bool AreLevel2ProductTimesPopulated( | ||||||
|  |       std::chrono::system_clock::time_point time) const; | ||||||
|  |    bool | ||||||
|  |    AreLevel3ProductTimesPopulated(const std::string&                    product, | ||||||
|  |                                   std::chrono::system_clock::time_point time); | ||||||
|  | 
 | ||||||
|  |    void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time, | ||||||
|  |                                    bool update = true); | ||||||
|    void PopulateLevel3ProductTimes(const std::string& product, |    void PopulateLevel3ProductTimes(const std::string& product, | ||||||
|                                    std::chrono::system_clock::time_point time); |                                    std::chrono::system_clock::time_point time, | ||||||
|  |                                    bool update = true); | ||||||
| 
 | 
 | ||||||
|    void UpdateAvailableProductsSync(); |    void UpdateAvailableProductsSync(); | ||||||
| 
 | 
 | ||||||
|  | @ -243,11 +253,16 @@ public: | ||||||
|                         const float         gateRangeOffset, |                         const float         gateRangeOffset, | ||||||
|                         std::vector<float>& outputCoordinates); |                         std::vector<float>& outputCoordinates); | ||||||
| 
 | 
 | ||||||
|  |    static bool AreProductTimesPopulated( | ||||||
|  |       const std::shared_ptr<ProviderManager>& providerManager, | ||||||
|  |       std::chrono::system_clock::time_point   time); | ||||||
|  | 
 | ||||||
|    static void |    static void | ||||||
|    PopulateProductTimes(std::shared_ptr<ProviderManager> providerManager, |    PopulateProductTimes(std::shared_ptr<ProviderManager> providerManager, | ||||||
|                         RadarProductRecordMap&           productRecordMap, |                         RadarProductRecordMap&           productRecordMap, | ||||||
|                         std::shared_mutex&               productRecordMutex, |                         std::shared_mutex&               productRecordMutex, | ||||||
|                         std::chrono::system_clock::time_point time); |                         std::chrono::system_clock::time_point time, | ||||||
|  |                         bool                                  update); | ||||||
| 
 | 
 | ||||||
|    static void |    static void | ||||||
|    LoadNexradFile(CreateNexradFileFunction                           load, |    LoadNexradFile(CreateNexradFileFunction                           load, | ||||||
|  | @ -771,28 +786,27 @@ void RadarProductManagerImpl::EnableRefresh( | ||||||
|          if (providerManager->refreshEnabled_ != enabled) |          if (providerManager->refreshEnabled_ != enabled) | ||||||
|          { |          { | ||||||
|             providerManager->refreshEnabled_ = enabled; |             providerManager->refreshEnabled_ = enabled; | ||||||
|             RefreshData(providerManager); |             providerManager->RefreshData(); | ||||||
|          } |          } | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RadarProductManagerImpl::RefreshData( | void ProviderManager::RefreshData() | ||||||
|    std::shared_ptr<ProviderManager> providerManager) |  | ||||||
| { | { | ||||||
|    logger_->trace("RefreshData: {}", providerManager->name()); |    logger_->trace("RefreshData: {}", name()); | ||||||
| 
 | 
 | ||||||
|    { |    { | ||||||
|       std::unique_lock lock(providerManager->refreshTimerMutex_); |       const std::unique_lock lock(refreshTimerMutex_); | ||||||
|       providerManager->refreshTimer_.cancel(); |       refreshTimer_.cancel(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    boost::asio::post(threadPool_, |    boost::asio::post(providerThreadPool_, | ||||||
|                      [=, this]() |                      [this]() | ||||||
|                      { |                      { | ||||||
|                         try |                         try | ||||||
|                         { |                         { | ||||||
|                            RefreshDataSync(providerManager); |                            RefreshDataSync(); | ||||||
|                         } |                         } | ||||||
|                         catch (const std::exception& ex) |                         catch (const std::exception& ex) | ||||||
|                         { |                         { | ||||||
|  | @ -801,27 +815,24 @@ void RadarProductManagerImpl::RefreshData( | ||||||
|                      }); |                      }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RadarProductManagerImpl::RefreshDataSync( | void ProviderManager::RefreshDataSync() | ||||||
|    std::shared_ptr<ProviderManager> providerManager) |  | ||||||
| { | { | ||||||
|    using namespace std::chrono_literals; |    using namespace std::chrono_literals; | ||||||
| 
 | 
 | ||||||
|    auto [newObjects, totalObjects] = providerManager->provider_->Refresh(); |    auto [newObjects, totalObjects] = provider_->Refresh(); | ||||||
| 
 | 
 | ||||||
|    // Level2 chunked data is updated quickly and uses a faster interval
 |    // Level2 chunked data is updated quickly and uses a faster interval
 | ||||||
|    const std::chrono::milliseconds fastRetryInterval = |    const std::chrono::milliseconds fastRetryInterval = | ||||||
|       providerManager->isChunks_ ? kFastRetryIntervalChunks_ : |       isChunks_ ? kFastRetryIntervalChunks_ : kFastRetryInterval_; | ||||||
|                                    kFastRetryInterval_; |  | ||||||
|    const std::chrono::milliseconds slowRetryInterval = |    const std::chrono::milliseconds slowRetryInterval = | ||||||
|       providerManager->isChunks_ ? kSlowRetryIntervalChunks_ : |       isChunks_ ? kSlowRetryIntervalChunks_ : kSlowRetryInterval_; | ||||||
|                                    kSlowRetryInterval_; |  | ||||||
|    std::chrono::milliseconds interval = fastRetryInterval; |    std::chrono::milliseconds interval = fastRetryInterval; | ||||||
| 
 | 
 | ||||||
|    if (totalObjects > 0) |    if (totalObjects > 0) | ||||||
|    { |    { | ||||||
|       auto latestTime        = providerManager->provider_->FindLatestTime(); |       auto latestTime        = provider_->FindLatestTime(); | ||||||
|       auto updatePeriod      = providerManager->provider_->update_period(); |       auto updatePeriod      = provider_->update_period(); | ||||||
|       auto lastModified      = providerManager->provider_->last_modified(); |       auto lastModified      = provider_->last_modified(); | ||||||
|       auto sinceLastModified = scwx::util::time::now() - lastModified; |       auto sinceLastModified = scwx::util::time::now() - lastModified; | ||||||
| 
 | 
 | ||||||
|       // For the default interval, assume products are updated at a
 |       // For the default interval, assume products are updated at a
 | ||||||
|  | @ -830,7 +841,11 @@ void RadarProductManagerImpl::RefreshDataSync( | ||||||
|       interval = std::chrono::duration_cast<std::chrono::milliseconds>( |       interval = std::chrono::duration_cast<std::chrono::milliseconds>( | ||||||
|          updatePeriod - sinceLastModified); |          updatePeriod - sinceLastModified); | ||||||
| 
 | 
 | ||||||
|       if (updatePeriod > 0s && sinceLastModified > updatePeriod * 5) |       // Allow 5 update periods before considering the data stale
 | ||||||
|  |       constexpr std::size_t kUpdatePeriodStaleCount = 5; | ||||||
|  | 
 | ||||||
|  |       if (updatePeriod > 0s && | ||||||
|  |           sinceLastModified > updatePeriod * kUpdatePeriodStaleCount) | ||||||
|       { |       { | ||||||
|          // If it has been at least 5 update periods since the file has
 |          // If it has been at least 5 update periods since the file has
 | ||||||
|          // been last modified, slow the retry period
 |          // been last modified, slow the retry period
 | ||||||
|  | @ -844,46 +859,43 @@ void RadarProductManagerImpl::RefreshDataSync( | ||||||
| 
 | 
 | ||||||
|       if (newObjects > 0) |       if (newObjects > 0) | ||||||
|       { |       { | ||||||
|          Q_EMIT providerManager->NewDataAvailable( |          Q_EMIT NewDataAvailable(group_, product_, latestTime); | ||||||
|             providerManager->group_, providerManager->product_, latestTime); |  | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|    else if (providerManager->refreshEnabled_) |    else if (refreshEnabled_) | ||||||
|    { |    { | ||||||
|       logger_->info("[{}] No data found", providerManager->name()); |       logger_->info("[{}] No data found", name()); | ||||||
| 
 | 
 | ||||||
|       // If no data is found, retry at the slow retry interval
 |       // If no data is found, retry at the slow retry interval
 | ||||||
|       interval = slowRetryInterval; |       interval = slowRetryInterval; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    std::unique_lock const lock(providerManager->refreshTimerMutex_); |    std::unique_lock const lock(refreshTimerMutex_); | ||||||
| 
 | 
 | ||||||
|    if (providerManager->refreshEnabled_) |    if (refreshEnabled_) | ||||||
|    { |    { | ||||||
|       logger_->trace( |       logger_->trace( | ||||||
|          "[{}] Scheduled refresh in {:%M:%S}", |          "[{}] Scheduled refresh in {:%M:%S}", | ||||||
|          providerManager->name(), |          name(), | ||||||
|          std::chrono::duration_cast<std::chrono::seconds>(interval)); |          std::chrono::duration_cast<std::chrono::seconds>(interval)); | ||||||
| 
 | 
 | ||||||
|       { |       { | ||||||
|          providerManager->refreshTimer_.expires_after(interval); |          refreshTimer_.expires_after(interval); | ||||||
|          providerManager->refreshTimer_.async_wait( |          refreshTimer_.async_wait( | ||||||
|             [=, this](const boost::system::error_code& e) |             [this](const boost::system::error_code& e) | ||||||
|             { |             { | ||||||
|                if (e == boost::system::errc::success) |                if (e == boost::system::errc::success) | ||||||
|                { |                { | ||||||
|                   RefreshData(providerManager); |                   RefreshData(); | ||||||
|                } |                } | ||||||
|                else if (e == boost::asio::error::operation_aborted) |                else if (e == boost::asio::error::operation_aborted) | ||||||
|                { |                { | ||||||
|                   logger_->debug("[{}] Data refresh timer cancelled", |                   logger_->debug("[{}] Data refresh timer cancelled", name()); | ||||||
|                                  providerManager->name()); |  | ||||||
|                } |                } | ||||||
|                else |                else | ||||||
|                { |                { | ||||||
|                   logger_->warn("[{}] Data refresh timer error: {}", |                   logger_->warn( | ||||||
|                                 providerManager->name(), |                      "[{}] Data refresh timer error: {}", name(), e.message()); | ||||||
|                                 e.message()); |  | ||||||
|                } |                } | ||||||
|             }); |             }); | ||||||
|       } |       } | ||||||
|  | @ -934,32 +946,32 @@ RadarProductManager::GetActiveVolumeTimes( | ||||||
|       [&](const std::shared_ptr<provider::NexradDataProvider>& provider) |       [&](const std::shared_ptr<provider::NexradDataProvider>& provider) | ||||||
|       { |       { | ||||||
|          // For yesterday, today and tomorrow (in parallel)
 |          // For yesterday, today and tomorrow (in parallel)
 | ||||||
|          std::for_each(std::execution::par, |          std::for_each( | ||||||
|                        dates.begin(), |             std::execution::par, | ||||||
|                        dates.end(), |             dates.begin(), | ||||||
|                        [&](const auto& date) |             dates.end(), | ||||||
|                        { |             [&](const auto& date) | ||||||
|                           // Don't query for a time point in the future
 |             { | ||||||
|                           if (date > scwx::util::time::now()) |                // Don't query for a time point in the future
 | ||||||
|                           { |                if (date > scwx::util::time::now()) | ||||||
|                              return; |                { | ||||||
|                           } |                   return; | ||||||
|  |                } | ||||||
| 
 | 
 | ||||||
|                           // Query the provider for volume time points
 |                // Query the provider for volume time points
 | ||||||
|                           auto timePoints = provider->GetTimePointsByDate(date); |                auto timePoints = provider->GetTimePointsByDate(date, true); | ||||||
| 
 | 
 | ||||||
|                           // TODO: Note, this will miss volume times present in
 |                // TODO: Note, this will miss volume times present in Level 2
 | ||||||
|                           // Level 2 products with a second scan
 |                // products with a second scan
 | ||||||
| 
 | 
 | ||||||
|                           // Lock the merged volume time list
 |                // Lock the merged volume time list
 | ||||||
|                           std::unique_lock volumeTimesLock {volumeTimesMutex}; |                const std::unique_lock volumeTimesLock {volumeTimesMutex}; | ||||||
| 
 | 
 | ||||||
|                           // Copy time points to the merged list
 |                // Copy time points to the merged list
 | ||||||
|                           std::copy( |                std::copy(timePoints.begin(), | ||||||
|                              timePoints.begin(), |                          timePoints.end(), | ||||||
|                              timePoints.end(), |                          std::inserter(volumeTimes, volumeTimes.end())); | ||||||
|                              std::inserter(volumeTimes, volumeTimes.end())); |             }); | ||||||
|                        }); |  | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|    // Return merged volume times list
 |    // Return merged volume times list
 | ||||||
|  | @ -1202,21 +1214,75 @@ void RadarProductManagerImpl::LoadNexradFile( | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool RadarProductManagerImpl::AreLevel2ProductTimesPopulated( | ||||||
|  |    std::chrono::system_clock::time_point time) const | ||||||
|  | { | ||||||
|  |    return AreProductTimesPopulated(level2ProviderManager_, time); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RadarProductManagerImpl::AreLevel3ProductTimesPopulated( | ||||||
|  |    const std::string& product, std::chrono::system_clock::time_point time) | ||||||
|  | { | ||||||
|  |    // Get provider manager
 | ||||||
|  |    const auto level3ProviderManager = GetLevel3ProviderManager(product); | ||||||
|  | 
 | ||||||
|  |    return AreProductTimesPopulated(level3ProviderManager, time); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RadarProductManagerImpl::AreProductTimesPopulated( | ||||||
|  |    const std::shared_ptr<ProviderManager>& providerManager, | ||||||
|  |    std::chrono::system_clock::time_point   time) | ||||||
|  | { | ||||||
|  |    auto today = std::chrono::floor<std::chrono::days>(time); | ||||||
|  | 
 | ||||||
|  |    bool productTimesPopulated = true; | ||||||
|  | 
 | ||||||
|  |    // Assume a query for the epoch is a query for now
 | ||||||
|  |    if (today == std::chrono::system_clock::time_point {}) | ||||||
|  |    { | ||||||
|  |       today = std::chrono::floor<std::chrono::days>(scwx::util::time::now()); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    const auto yesterday = today - std::chrono::days {1}; | ||||||
|  |    const auto tomorrow  = today + std::chrono::days {1}; | ||||||
|  |    const auto dates     = {yesterday, today, tomorrow}; | ||||||
|  | 
 | ||||||
|  |    for (auto& date : dates) | ||||||
|  |    { | ||||||
|  |       // Don't query for a time point in the future
 | ||||||
|  |       if (date > scwx::util::time::now()) | ||||||
|  |       { | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!providerManager->provider_->IsDateCached(date)) | ||||||
|  |       { | ||||||
|  |          productTimesPopulated = false; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return productTimesPopulated; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RadarProductManagerImpl::PopulateLevel2ProductTimes( | void RadarProductManagerImpl::PopulateLevel2ProductTimes( | ||||||
|    std::chrono::system_clock::time_point time) |    std::chrono::system_clock::time_point time, bool update) | ||||||
| { | { | ||||||
|    PopulateProductTimes(level2ProviderManager_, |    PopulateProductTimes(level2ProviderManager_, | ||||||
|                         level2ProductRecords_, |                         level2ProductRecords_, | ||||||
|                         level2ProductRecordMutex_, |                         level2ProductRecordMutex_, | ||||||
|                         time); |                         time, | ||||||
|  |                         update); | ||||||
|    PopulateProductTimes(level2ChunksProviderManager_, |    PopulateProductTimes(level2ChunksProviderManager_, | ||||||
|                         level2ProductRecords_, |                         level2ProductRecords_, | ||||||
|                         level2ProductRecordMutex_, |                         level2ProductRecordMutex_, | ||||||
|                         time); |                         time, | ||||||
|  |                         update); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RadarProductManagerImpl::PopulateLevel3ProductTimes( | void RadarProductManagerImpl::PopulateLevel3ProductTimes( | ||||||
|    const std::string& product, std::chrono::system_clock::time_point time) |    const std::string&                    product, | ||||||
|  |    std::chrono::system_clock::time_point time, | ||||||
|  |    bool                                  update) | ||||||
| { | { | ||||||
|    // Get provider manager
 |    // Get provider manager
 | ||||||
|    auto level3ProviderManager = GetLevel3ProviderManager(product); |    auto level3ProviderManager = GetLevel3ProviderManager(product); | ||||||
|  | @ -1229,21 +1295,38 @@ void RadarProductManagerImpl::PopulateLevel3ProductTimes( | ||||||
|    PopulateProductTimes(level3ProviderManager, |    PopulateProductTimes(level3ProviderManager, | ||||||
|                         level3ProductRecords, |                         level3ProductRecords, | ||||||
|                         level3ProductRecordMutex_, |                         level3ProductRecordMutex_, | ||||||
|                         time); |                         time, | ||||||
|  |                         update); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RadarProductManagerImpl::PopulateProductTimes( | void RadarProductManagerImpl::PopulateProductTimes( | ||||||
|    std::shared_ptr<ProviderManager>      providerManager, |    std::shared_ptr<ProviderManager>      providerManager, | ||||||
|    RadarProductRecordMap&                productRecordMap, |    RadarProductRecordMap&                productRecordMap, | ||||||
|    std::shared_mutex&                    productRecordMutex, |    std::shared_mutex&                    productRecordMutex, | ||||||
|    std::chrono::system_clock::time_point time) |    std::chrono::system_clock::time_point time, | ||||||
|  |    bool                                  update) | ||||||
| { | { | ||||||
|    const auto today = std::chrono::floor<std::chrono::days>(time); |    if (update) | ||||||
|  |    { | ||||||
|  |       logger_->debug("Populating product times: {}, {}, {}", | ||||||
|  |                      common::GetRadarProductGroupName(providerManager->group_), | ||||||
|  |                      providerManager->product_, | ||||||
|  |                      scwx::util::time::TimeString(time)); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       logger_->trace("Populating cached product times: {}, {}, {}", | ||||||
|  |                      common::GetRadarProductGroupName(providerManager->group_), | ||||||
|  |                      providerManager->product_, | ||||||
|  |                      scwx::util::time::TimeString(time)); | ||||||
|  |    } | ||||||
| 
 | 
 | ||||||
|    // Don't query for the epoch
 |    auto today = std::chrono::floor<std::chrono::days>(time); | ||||||
|  | 
 | ||||||
|  |    // Assume a query for the epoch is a query for now
 | ||||||
|    if (today == std::chrono::system_clock::time_point {}) |    if (today == std::chrono::system_clock::time_point {}) | ||||||
|    { |    { | ||||||
|       return; |       today = std::chrono::floor<std::chrono::days>(scwx::util::time::now()); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    const auto yesterday = today - std::chrono::days {1}; |    const auto yesterday = today - std::chrono::days {1}; | ||||||
|  | @ -1267,7 +1350,8 @@ void RadarProductManagerImpl::PopulateProductTimes( | ||||||
| 
 | 
 | ||||||
|                     // Query the provider for volume time points
 |                     // Query the provider for volume time points
 | ||||||
|                     auto timePoints = |                     auto timePoints = | ||||||
|                        providerManager->provider_->GetTimePointsByDate(date); |                        providerManager->provider_->GetTimePointsByDate(date, | ||||||
|  |                                                                        update); | ||||||
| 
 | 
 | ||||||
|                     // Lock the merged volume time list
 |                     // Lock the merged volume time list
 | ||||||
|                     std::unique_lock volumeTimesLock {volumeTimesMutex}; |                     std::unique_lock volumeTimesLock {volumeTimesMutex}; | ||||||
|  | @ -1293,8 +1377,9 @@ void RadarProductManagerImpl::PopulateProductTimes( | ||||||
|                   }); |                   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::map<std::chrono::system_clock::time_point, | std::tuple<std::map<std::chrono::system_clock::time_point, | ||||||
|          std::shared_ptr<types::RadarProductRecord>> |                     std::shared_ptr<types::RadarProductRecord>>, | ||||||
|  |            types::RadarProductLoadStatus> | ||||||
| RadarProductManagerImpl::GetLevel2ProductRecords( | RadarProductManagerImpl::GetLevel2ProductRecords( | ||||||
|    std::chrono::system_clock::time_point time) |    std::chrono::system_clock::time_point time) | ||||||
| { | { | ||||||
|  | @ -1302,9 +1387,40 @@ RadarProductManagerImpl::GetLevel2ProductRecords( | ||||||
|             std::shared_ptr<types::RadarProductRecord>> |             std::shared_ptr<types::RadarProductRecord>> | ||||||
|                                                      records {}; |                                                      records {}; | ||||||
|    std::vector<RadarProductRecordMap::const_pointer> recordPtrs {}; |    std::vector<RadarProductRecordMap::const_pointer> recordPtrs {}; | ||||||
|  |    types::RadarProductLoadStatus                     status { | ||||||
|  |       types::RadarProductLoadStatus::ListingProducts}; | ||||||
|  | 
 | ||||||
|  |    std::size_t recordPtrCount = 0u; | ||||||
|  |    std::size_t recordCount    = 0u; | ||||||
| 
 | 
 | ||||||
|    // Ensure Level 2 product records are updated
 |    // Ensure Level 2 product records are updated
 | ||||||
|    PopulateLevel2ProductTimes(time); |    if (!AreLevel2ProductTimesPopulated(time)) | ||||||
|  |    { | ||||||
|  |       logger_->debug("Level 2 product times need populated: {}", | ||||||
|  |                      scwx::util::time::TimeString(time)); | ||||||
|  | 
 | ||||||
|  |       // Populate level 2 product times asynchronously
 | ||||||
|  |       boost::asio::post(threadPool_, | ||||||
|  |                         [time, this]() | ||||||
|  |                         { | ||||||
|  |                            // Populate product times
 | ||||||
|  |                            PopulateLevel2ProductTimes(time); | ||||||
|  | 
 | ||||||
|  |                            // Signal finished
 | ||||||
|  |                            Q_EMIT self_->ProductTimesPopulated( | ||||||
|  |                               common::RadarProductGroup::Level2, "", time); | ||||||
|  |                         }); | ||||||
|  | 
 | ||||||
|  |       // Return listing products status
 | ||||||
|  |       return {records, status}; | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       PopulateLevel2ProductTimes(time, false); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Advance to loading product
 | ||||||
|  |    status = types::RadarProductLoadStatus::LoadingProduct; | ||||||
| 
 | 
 | ||||||
|    { |    { | ||||||
|       std::shared_lock lock {level2ProductRecordMutex_}; |       std::shared_lock lock {level2ProductRecordMutex_}; | ||||||
|  | @ -1343,9 +1459,29 @@ RadarProductManagerImpl::GetLevel2ProductRecords( | ||||||
| 
 | 
 | ||||||
|       if (recordPtr != nullptr) |       if (recordPtr != nullptr) | ||||||
|       { |       { | ||||||
|  |          using namespace std::chrono_literals; | ||||||
|  | 
 | ||||||
|          // Don't check for an exact time match for level 2 products
 |          // Don't check for an exact time match for level 2 products
 | ||||||
|          recordTime = recordPtr->first; |          recordTime = recordPtr->first; | ||||||
|          record     = recordPtr->second.lock(); | 
 | ||||||
|  |          if ( | ||||||
|  |             // For latest data, ensure it is from the last 24 hours
 | ||||||
|  |             (time == std::chrono::system_clock::time_point {} && | ||||||
|  |              (recordTime > scwx::util::time::now() - 24h || | ||||||
|  |               recordTime == time)) || | ||||||
|  |             // For time queries, ensure data is within 24 hours of the request
 | ||||||
|  |             (time != std::chrono::system_clock::time_point {} && | ||||||
|  |              std::chrono::abs(recordTime - time) < 24h)) | ||||||
|  |          { | ||||||
|  |             record = recordPtr->second.lock(); | ||||||
|  |             ++recordPtrCount; | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             // Reset the record
 | ||||||
|  |             recordPtr  = nullptr; | ||||||
|  |             recordTime = time; | ||||||
|  |          } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (recordPtr != nullptr && record == nullptr && |       if (recordPtr != nullptr && record == nullptr && | ||||||
|  | @ -1368,29 +1504,73 @@ RadarProductManagerImpl::GetLevel2ProductRecords( | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|          self_->LoadLevel2Data(recordTime, request); |          self_->LoadLevel2Data(recordTime, request); | ||||||
|  | 
 | ||||||
|  |          // Status is already set to LoadingProduct
 | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (record != nullptr) |       if (record != nullptr) | ||||||
|       { |       { | ||||||
|          // Return valid records
 |          // Return valid records
 | ||||||
|          records.insert_or_assign(recordTime, record); |          records.insert_or_assign(recordTime, record); | ||||||
|  |          ++recordCount; | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return records; |    if (recordPtrCount == 0) | ||||||
|  |    { | ||||||
|  |       // If all records are empty, the product is not available
 | ||||||
|  |       status = types::RadarProductLoadStatus::ProductNotAvailable; | ||||||
|  |    } | ||||||
|  |    else if (recordCount == recordPtrCount) | ||||||
|  |    { | ||||||
|  |       // If all records were populated, the product has been loaded
 | ||||||
|  |       status = types::RadarProductLoadStatus::ProductLoaded; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return {records, status}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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, | ||||||
|  |            types::RadarProductLoadStatus> | ||||||
| RadarProductManagerImpl::GetLevel3ProductRecord( | RadarProductManagerImpl::GetLevel3ProductRecord( | ||||||
|    const std::string& product, std::chrono::system_clock::time_point time) |    const std::string& product, std::chrono::system_clock::time_point time) | ||||||
| { | { | ||||||
|    std::shared_ptr<types::RadarProductRecord> record {nullptr}; |    std::shared_ptr<types::RadarProductRecord> record {nullptr}; | ||||||
|    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}; | ||||||
|  |    types::RadarProductLoadStatus              status { | ||||||
|  |       types::RadarProductLoadStatus::ListingProducts}; | ||||||
| 
 | 
 | ||||||
|    // Ensure Level 3 product records are updated
 |    // Ensure Level 3 product records are updated
 | ||||||
|    PopulateLevel3ProductTimes(product, time); |    if (!AreLevel3ProductTimesPopulated(product, time)) | ||||||
|  |    { | ||||||
|  |       logger_->debug("Level 3 product times need populated: {}, {}", | ||||||
|  |                      product, | ||||||
|  |                      scwx::util::time::TimeString(time)); | ||||||
|  | 
 | ||||||
|  |       // Populate level 3 product times asynchronously
 | ||||||
|  |       boost::asio::post(threadPool_, | ||||||
|  |                         [product, time, this]() | ||||||
|  |                         { | ||||||
|  |                            // Populate product times
 | ||||||
|  |                            PopulateLevel3ProductTimes(product, time); | ||||||
|  | 
 | ||||||
|  |                            // Signal finished
 | ||||||
|  |                            Q_EMIT self_->ProductTimesPopulated( | ||||||
|  |                               common::RadarProductGroup::Level3, product, time); | ||||||
|  |                         }); | ||||||
|  | 
 | ||||||
|  |       // Return listing products status
 | ||||||
|  |       return {record, recordTime, status}; | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       PopulateLevel3ProductTimes(product, time, false); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Advance to loading product
 | ||||||
|  |    status = types::RadarProductLoadStatus::LoadingProduct; | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock {level3ProductRecordMutex_}; |    std::unique_lock lock {level3ProductRecordMutex_}; | ||||||
| 
 | 
 | ||||||
|  | @ -1415,9 +1595,27 @@ RadarProductManagerImpl::GetLevel3ProductRecord( | ||||||
| 
 | 
 | ||||||
|    if (recordPtr != nullptr) |    if (recordPtr != nullptr) | ||||||
|    { |    { | ||||||
|  |       using namespace std::chrono_literals; | ||||||
|  | 
 | ||||||
|       // Don't check for an exact time match for level 3 products
 |       // Don't check for an exact time match for level 3 products
 | ||||||
|       recordTime = recordPtr->first; |       recordTime = recordPtr->first; | ||||||
|       record     = recordPtr->second.lock(); | 
 | ||||||
|  |       if ( | ||||||
|  |          // For latest data, ensure it is from the last 24 hours
 | ||||||
|  |          (time == std::chrono::system_clock::time_point {} && | ||||||
|  |           (recordTime > scwx::util::time::now() - 24h || recordTime == time)) || | ||||||
|  |          // For time queries, ensure data is within 24 hours of the request
 | ||||||
|  |          (time != std::chrono::system_clock::time_point {} && | ||||||
|  |           std::chrono::abs(recordTime - time) < 24h)) | ||||||
|  |       { | ||||||
|  |          record = recordPtr->second.lock(); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // Reset the record
 | ||||||
|  |          recordPtr  = nullptr; | ||||||
|  |          recordTime = time; | ||||||
|  |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (recordPtr != nullptr && record == nullptr && |    if (recordPtr != nullptr && record == nullptr && | ||||||
|  | @ -1440,9 +1638,22 @@ RadarProductManagerImpl::GetLevel3ProductRecord( | ||||||
|          }); |          }); | ||||||
| 
 | 
 | ||||||
|       self_->LoadLevel3Data(product, recordTime, request); |       self_->LoadLevel3Data(product, recordTime, request); | ||||||
|  | 
 | ||||||
|  |       // Status is already set to LoadingProduct
 | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return {record, recordTime}; |    if (recordPtr == nullptr) | ||||||
|  |    { | ||||||
|  |       // If the record is empty, the product is not available
 | ||||||
|  |       status = types::RadarProductLoadStatus::ProductNotAvailable; | ||||||
|  |    } | ||||||
|  |    else if (record != nullptr) | ||||||
|  |    { | ||||||
|  |       // If the record was populated, the product has been loaded
 | ||||||
|  |       status = types::RadarProductLoadStatus::ProductLoaded; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return {record, recordTime, status}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::shared_ptr<types::RadarProductRecord> | std::shared_ptr<types::RadarProductRecord> | ||||||
|  | @ -1543,7 +1754,8 @@ void RadarProductManagerImpl::UpdateRecentRecords( | ||||||
| std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, | std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, | ||||||
|            float, |            float, | ||||||
|            std::vector<float>, |            std::vector<float>, | ||||||
|            std::chrono::system_clock::time_point> |            std::chrono::system_clock::time_point, | ||||||
|  |            types::RadarProductLoadStatus> | ||||||
| RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|                                    float                      elevation, |                                    float                      elevation, | ||||||
|                                    std::chrono::system_clock::time_point time) |                                    std::chrono::system_clock::time_point time) | ||||||
|  | @ -1552,6 +1764,8 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|    float                                       elevationCut = 0.0f; |    float                                       elevationCut = 0.0f; | ||||||
|    std::vector<float>                          elevationCuts {}; |    std::vector<float>                          elevationCuts {}; | ||||||
|    std::chrono::system_clock::time_point       foundTime {}; |    std::chrono::system_clock::time_point       foundTime {}; | ||||||
|  |    types::RadarProductLoadStatus               loadStatus { | ||||||
|  |       types::RadarProductLoadStatus::ProductNotLoaded}; | ||||||
| 
 | 
 | ||||||
|    const bool        isEpox = time == std::chrono::system_clock::time_point {}; |    const bool        isEpox = time == std::chrono::system_clock::time_point {}; | ||||||
|    bool              needArchive   = true; |    bool              needArchive   = true; | ||||||
|  | @ -1587,6 +1801,7 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|          if (foundTime >= firstValidChunkTime) |          if (foundTime >= firstValidChunkTime) | ||||||
|          { |          { | ||||||
|             needArchive = false; |             needArchive = false; | ||||||
|  |             loadStatus  = types::RadarProductLoadStatus::ProductLoaded; | ||||||
|          } |          } | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|  | @ -1594,7 +1809,11 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|    // It is not in the chunk provider, so get it from the archive
 |    // It is not in the chunk provider, so get it from the archive
 | ||||||
|    if (needArchive) |    if (needArchive) | ||||||
|    { |    { | ||||||
|       auto records = p->GetLevel2ProductRecords(time); |       std::map<std::chrono::system_clock::time_point, | ||||||
|  |                std::shared_ptr<types::RadarProductRecord>> | ||||||
|  |          records; | ||||||
|  | 
 | ||||||
|  |       std::tie(records, loadStatus) = p->GetLevel2ProductRecords(time); | ||||||
|       for (auto& recordPair : records) |       for (auto& recordPair : records) | ||||||
|       { |       { | ||||||
|          auto& record = recordPair.second; |          auto& record = recordPair.second; | ||||||
|  | @ -1639,25 +1858,35 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return {radarData, elevationCut, elevationCuts, foundTime}; |    if (loadStatus == types::RadarProductLoadStatus::ProductLoaded && | ||||||
|  |        radarData == nullptr) | ||||||
|  |    { | ||||||
|  |       // If all data was available for the time point, but there is no matching
 | ||||||
|  |       // radar data, consider this as no product available
 | ||||||
|  |       loadStatus = types::RadarProductLoadStatus::ProductNotAvailable; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return {radarData, elevationCut, elevationCuts, foundTime, loadStatus}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, | std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, | ||||||
|            std::chrono::system_clock::time_point> |            std::chrono::system_clock::time_point, | ||||||
|  |            types::RadarProductLoadStatus> | ||||||
| RadarProductManager::GetLevel3Data(const std::string& product, | RadarProductManager::GetLevel3Data(const std::string& product, | ||||||
|                                    std::chrono::system_clock::time_point time) |                                    std::chrono::system_clock::time_point time) | ||||||
| { | { | ||||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message = nullptr; |    std::shared_ptr<wsr88d::rpg::Level3Message> message = nullptr; | ||||||
|  |    types::RadarProductLoadStatus               status {}; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<types::RadarProductRecord> record; |    std::shared_ptr<types::RadarProductRecord> record; | ||||||
|    std::tie(record, time) = p->GetLevel3ProductRecord(product, time); |    std::tie(record, time, status) = p->GetLevel3ProductRecord(product, time); | ||||||
| 
 | 
 | ||||||
|    if (record != nullptr) |    if (record != nullptr) | ||||||
|    { |    { | ||||||
|       message = record->level3_file()->message(); |       message = record->level3_file()->message(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return {message, time}; |    return {message, time, status}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| common::Level3ProductCategoryMap | common::Level3ProductCategoryMap | ||||||
|  | @ -1809,6 +2038,4 @@ RadarProductManager::Instance(const std::string& radarSite) | ||||||
| 
 | 
 | ||||||
| #include "radar_product_manager.moc" | #include "radar_product_manager.moc" | ||||||
| 
 | 
 | ||||||
| } // namespace manager
 | } // namespace scwx::qt::manager
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -5,23 +5,19 @@ | ||||||
| #include <scwx/qt/config/radar_site.hpp> | #include <scwx/qt/config/radar_site.hpp> | ||||||
| #include <scwx/qt/request/nexrad_file_request.hpp> | #include <scwx/qt/request/nexrad_file_request.hpp> | ||||||
| #include <scwx/qt/types/radar_product_record.hpp> | #include <scwx/qt/types/radar_product_record.hpp> | ||||||
|  | #include <scwx/qt/types/radar_product_types.hpp> | ||||||
| #include <scwx/util/time.hpp> | #include <scwx/util/time.hpp> | ||||||
| #include <scwx/wsr88d/ar2v_file.hpp> | #include <scwx/wsr88d/ar2v_file.hpp> | ||||||
| #include <scwx/wsr88d/level3_file.hpp> | #include <scwx/wsr88d/level3_file.hpp> | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <set> | #include <set> | ||||||
| #include <unordered_map> |  | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| #include <boost/uuid/nil_generator.hpp> | #include <boost/uuid/nil_generator.hpp> | ||||||
| #include <QObject> | #include <QObject> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::manager | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace manager |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class RadarProductManagerImpl; | class RadarProductManagerImpl; | ||||||
|  | @ -89,12 +85,13 @@ public: | ||||||
|     * @param [in] time Radar product time |     * @param [in] time Radar product time | ||||||
|     * |     * | ||||||
|     * @return Level 2 radar data, selected elevation cut, available elevation |     * @return Level 2 radar data, selected elevation cut, available elevation | ||||||
|     * cuts and selected time |     * cuts, selected time and product load status | ||||||
|     */ |     */ | ||||||
|    std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, |    std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, | ||||||
|               float, |               float, | ||||||
|               std::vector<float>, |               std::vector<float>, | ||||||
|               std::chrono::system_clock::time_point> |               std::chrono::system_clock::time_point, | ||||||
|  |               types::RadarProductLoadStatus> | ||||||
|    GetLevel2Data(wsr88d::rda::DataBlockType            dataBlockType, |    GetLevel2Data(wsr88d::rda::DataBlockType            dataBlockType, | ||||||
|                  float                                 elevation, |                  float                                 elevation, | ||||||
|                  std::chrono::system_clock::time_point time = {}); |                  std::chrono::system_clock::time_point time = {}); | ||||||
|  | @ -105,10 +102,11 @@ public: | ||||||
|     * @param [in] product Radar product name |     * @param [in] product Radar product name | ||||||
|     * @param [in] time Radar product time |     * @param [in] time Radar product time | ||||||
|     * |     * | ||||||
|     * @return Level 3 message data and selected time |     * @return Level 3 message data, selected time and product load status | ||||||
|     */ |     */ | ||||||
|    std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, |    std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, | ||||||
|               std::chrono::system_clock::time_point> |               std::chrono::system_clock::time_point, | ||||||
|  |               types::RadarProductLoadStatus> | ||||||
|    GetLevel3Data(const std::string&                    product, |    GetLevel3Data(const std::string&                    product, | ||||||
|                  std::chrono::system_clock::time_point time = {}); |                  std::chrono::system_clock::time_point time = {}); | ||||||
| 
 | 
 | ||||||
|  | @ -151,6 +149,9 @@ signals: | ||||||
|                          bool                                  isChunks, |                          bool                                  isChunks, | ||||||
|                          std::chrono::system_clock::time_point latestTime); |                          std::chrono::system_clock::time_point latestTime); | ||||||
|    void IncomingLevel2ElevationChanged(std::optional<float> incomingElevation); |    void IncomingLevel2ElevationChanged(std::optional<float> incomingElevation); | ||||||
|  |    void ProductTimesPopulated(common::RadarProductGroup             group, | ||||||
|  |                               const std::string&                    product, | ||||||
|  |                               std::chrono::system_clock::time_point queryTime); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<RadarProductManagerImpl> p; |    std::unique_ptr<RadarProductManagerImpl> p; | ||||||
|  | @ -158,6 +159,4 @@ private: | ||||||
|    friend class RadarProductManagerImpl; |    friend class RadarProductManagerImpl; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace manager
 | } // namespace scwx::qt::manager
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -53,8 +53,13 @@ public: | ||||||
|       // Lock mutexes before destroying
 |       // Lock mutexes before destroying
 | ||||||
|       std::unique_lock animationTimerLock {animationTimerMutex_}; |       std::unique_lock animationTimerLock {animationTimerMutex_}; | ||||||
|       animationTimer_.cancel(); |       animationTimer_.cancel(); | ||||||
|  |       animationTimerLock.unlock(); | ||||||
| 
 | 
 | ||||||
|       std::unique_lock selectTimeLock {selectTimeMutex_}; |       selectThreadPool_.stop(); | ||||||
|  |       playThreadPool_.stop(); | ||||||
|  | 
 | ||||||
|  |       selectThreadPool_.join(); | ||||||
|  |       playThreadPool_.join(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    TimelineManager* self_; |    TimelineManager* self_; | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ | ||||||
| #include <scwx/qt/util/color.hpp> | #include <scwx/qt/util/color.hpp> | ||||||
| #include <scwx/qt/util/tooltip.hpp> | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/time.hpp> |  | ||||||
| 
 | 
 | ||||||
|  | #include <atomic> | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <ranges> | #include <ranges> | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
| #include <shared_mutex> | #include <shared_mutex> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| #include <unordered_set> | #include <unordered_set> | ||||||
|  | #include <utility> | ||||||
| 
 | 
 | ||||||
| #include <boost/algorithm/string/join.hpp> | #include <boost/algorithm/string/join.hpp> | ||||||
| #include <boost/asio/system_timer.hpp> | #include <boost/asio/system_timer.hpp> | ||||||
|  | @ -22,7 +23,6 @@ | ||||||
| #include <boost/container/stable_vector.hpp> | #include <boost/container/stable_vector.hpp> | ||||||
| #include <boost/container_hash/hash.hpp> | #include <boost/container_hash/hash.hpp> | ||||||
| #include <QEvent> | #include <QEvent> | ||||||
| #include <utility> |  | ||||||
| 
 | 
 | ||||||
| namespace scwx::qt::map | namespace scwx::qt::map | ||||||
| { | { | ||||||
|  | @ -153,9 +153,11 @@ public: | ||||||
|    ~Impl() |    ~Impl() | ||||||
|    { |    { | ||||||
|       std::unique_lock refreshLock(refreshMutex_); |       std::unique_lock refreshLock(refreshMutex_); | ||||||
|  |       refreshEnabled_ = false; | ||||||
|       refreshTimer_.cancel(); |       refreshTimer_.cancel(); | ||||||
|       refreshLock.unlock(); |       refreshLock.unlock(); | ||||||
| 
 | 
 | ||||||
|  |       threadPool_.stop(); | ||||||
|       threadPool_.join(); |       threadPool_.join(); | ||||||
| 
 | 
 | ||||||
|       receiver_ = nullptr; |       receiver_ = nullptr; | ||||||
|  | @ -213,6 +215,7 @@ public: | ||||||
| 
 | 
 | ||||||
|    AlertLayer* self_; |    AlertLayer* self_; | ||||||
| 
 | 
 | ||||||
|  |    std::atomic<bool>         refreshEnabled_ {true}; | ||||||
|    boost::asio::system_timer refreshTimer_ {threadPool_}; |    boost::asio::system_timer refreshTimer_ {threadPool_}; | ||||||
|    std::mutex                refreshMutex_; |    std::mutex                refreshMutex_; | ||||||
| 
 | 
 | ||||||
|  | @ -582,7 +585,8 @@ void AlertLayer::Impl::ScheduleRefresh() | ||||||
| 
 | 
 | ||||||
|    // Expires at the top of the next minute
 |    // Expires at the top of the next minute
 | ||||||
|    std::chrono::system_clock::time_point now = |    std::chrono::system_clock::time_point now = | ||||||
|       std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); |       std::chrono::floor<std::chrono::minutes>( | ||||||
|  |          std::chrono::system_clock::now()); | ||||||
|    refreshTimer_.expires_at(now + 1min); |    refreshTimer_.expires_at(now + 1min); | ||||||
| 
 | 
 | ||||||
|    refreshTimer_.async_wait( |    refreshTimer_.async_wait( | ||||||
|  | @ -599,7 +603,11 @@ void AlertLayer::Impl::ScheduleRefresh() | ||||||
|          else |          else | ||||||
|          { |          { | ||||||
|             Q_EMIT self_->NeedsRendering(); |             Q_EMIT self_->NeedsRendering(); | ||||||
|             ScheduleRefresh(); | 
 | ||||||
|  |             if (refreshEnabled_) | ||||||
|  |             { | ||||||
|  |                ScheduleRefresh(); | ||||||
|  |             } | ||||||
|          } |          } | ||||||
|       }); |       }); | ||||||
| } | } | ||||||
|  | @ -883,7 +891,7 @@ void AlertLayer::Impl::HandleGeoLinesEvent( | ||||||
| 
 | 
 | ||||||
|    switch (ev->type()) |    switch (ev->type()) | ||||||
|    { |    { | ||||||
|    case QEvent::Type::MouseButtonPress: |    case QEvent::Type::MouseButtonRelease: | ||||||
|    { |    { | ||||||
|       auto it = segmentsByLine_.find(di); |       auto it = segmentsByLine_.find(di); | ||||||
|       if (it != segmentsByLine_.cend()) |       if (it != segmentsByLine_.cend()) | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ static const std::unordered_map<MapProvider, MapProviderInfo> mapProviderInfo_ { | ||||||
|            .drawBelow_ {mapboxDrawBelow_}}, |            .drawBelow_ {mapboxDrawBelow_}}, | ||||||
|           {.name_ {"Mineral"}, |           {.name_ {"Mineral"}, | ||||||
|            .url_ {"mapbox://styles/mapbox/cjtep62gq54l21frr1whf27ak"}, |            .url_ {"mapbox://styles/mapbox/cjtep62gq54l21frr1whf27ak"}, | ||||||
|            .drawBelow_ {mapboxDrawBelow_}}, |            .drawBelow_ {"tunnel"}}, | ||||||
|           {.name_ {"Minimo"}, |           {.name_ {"Minimo"}, | ||||||
|            .url_ { |            .url_ { | ||||||
|               "mapbox://styles/mapbox-map-design/cksjc2nsq1bg117pnekb655h1"}, |               "mapbox://styles/mapbox-map-design/cksjc2nsq1bg117pnekb655h1"}, | ||||||
|  |  | ||||||
|  | @ -92,8 +92,15 @@ public: | ||||||
|    Impl(const Impl&&)            = delete; |    Impl(const Impl&&)            = delete; | ||||||
|    Impl& operator=(const Impl&&) = delete; |    Impl& operator=(const Impl&&) = delete; | ||||||
| 
 | 
 | ||||||
|  |    void RenderProductName(const std::shared_ptr<MapContext>& mapContext); | ||||||
|  |    void | ||||||
|  |         RenderProductDetails(const std::shared_ptr<MapContext>& mapContext, | ||||||
|  |                              const QMapLibre::CustomLayerRenderParameters& params); | ||||||
|  |    void RenderAttribution(const std::shared_ptr<MapContext>& mapContext, | ||||||
|  |                           const QMapLibre::CustomLayerRenderParameters& params); | ||||||
|  | 
 | ||||||
|    void SetupGeoIcons(); |    void SetupGeoIcons(); | ||||||
|    void SetCusorLocation(common::Coordinate coordinate); |    void SetCursorLocation(common::Coordinate coordinate); | ||||||
| 
 | 
 | ||||||
|    OverlayLayer* self_; |    OverlayLayer* self_; | ||||||
| 
 | 
 | ||||||
|  | @ -173,7 +180,7 @@ OverlayLayer::~OverlayLayer() | ||||||
|    p->cursorScaleConnection_.disconnect(); |    p->cursorScaleConnection_.disconnect(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OverlayLayer::Impl::SetCusorLocation(common::Coordinate coordinate) | void OverlayLayer::Impl::SetCursorLocation(common::Coordinate coordinate) | ||||||
| { | { | ||||||
|    geoIcons_->SetIconLocation( |    geoIcons_->SetIconLocation( | ||||||
|       cursorIcon_, coordinate.latitude_, coordinate.longitude_); |       cursorIcon_, coordinate.latitude_, coordinate.longitude_); | ||||||
|  | @ -388,7 +395,7 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|    p->geoIcons_->SetIconVisible(p->cursorIcon_, cursorIconVisible); |    p->geoIcons_->SetIconVisible(p->cursorIcon_, cursorIconVisible); | ||||||
|    if (cursorIconVisible) |    if (cursorIconVisible) | ||||||
|    { |    { | ||||||
|       p->SetCusorLocation(mapContext->mouse_coordinate()); |       p->SetCursorLocation(mapContext->mouse_coordinate()); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Location Icon
 |    // Location Icon
 | ||||||
|  | @ -425,6 +432,58 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  |    p->RenderProductName(mapContext); | ||||||
|  |    p->RenderProductDetails(mapContext, params); | ||||||
|  | 
 | ||||||
|  |    // Map Center Icon
 | ||||||
|  |    if (params.width != p->lastWidth_ || params.height != p->lastHeight_) | ||||||
|  |    { | ||||||
|  |       static constexpr double xPosition = 0.5; | ||||||
|  |       static constexpr double yPosition = 0.5; | ||||||
|  | 
 | ||||||
|  |       // Draw the icon in the center of the widget
 | ||||||
|  |       p->icons_->SetIconLocation(p->mapCenterIcon_, | ||||||
|  |                                  params.width * xPosition, | ||||||
|  |                                  params.height * yPosition); | ||||||
|  |    } | ||||||
|  |    p->icons_->SetIconVisible(p->mapCenterIcon_, | ||||||
|  |                              generalSettings.show_map_center().GetValue()); | ||||||
|  | 
 | ||||||
|  |    const QMargins colorTableMargins = mapContext->color_table_margins(); | ||||||
|  |    if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_) | ||||||
|  |    { | ||||||
|  |       static constexpr int xOffset = 10; | ||||||
|  |       static constexpr int yOffset = 10; | ||||||
|  | 
 | ||||||
|  |       // Draw map logo with a 10x10 indent from the bottom left
 | ||||||
|  |       p->icons_->SetIconLocation(p->mapLogoIcon_, | ||||||
|  |                                  colorTableMargins.left() + xOffset, | ||||||
|  |                                  colorTableMargins.bottom() + yOffset); | ||||||
|  |    } | ||||||
|  |    p->icons_->SetIconVisible(p->mapLogoIcon_, | ||||||
|  |                              generalSettings.show_map_logo().GetValue()); | ||||||
|  | 
 | ||||||
|  |    DrawLayer::RenderWithoutImGui(params); | ||||||
|  | 
 | ||||||
|  |    p->RenderAttribution(mapContext, params); | ||||||
|  | 
 | ||||||
|  |    p->firstRender_           = false; | ||||||
|  |    p->lastWidth_             = params.width; | ||||||
|  |    p->lastHeight_            = params.height; | ||||||
|  |    p->lastBearing_           = params.bearing; | ||||||
|  |    p->lastFontSize_          = ImGui::GetFontSize(); | ||||||
|  |    p->lastColorTableMargins_ = colorTableMargins; | ||||||
|  | 
 | ||||||
|  |    ImGuiFrameEnd(); | ||||||
|  | 
 | ||||||
|  |    SCWX_GL_CHECK_ERROR(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void OverlayLayer::Impl::RenderProductName( | ||||||
|  |    const std::shared_ptr<MapContext>& mapContext) | ||||||
|  | { | ||||||
|  |    auto radarProductView = mapContext->radar_product_view(); | ||||||
|  | 
 | ||||||
|    if (radarProductView != nullptr) |    if (radarProductView != nullptr) | ||||||
|    { |    { | ||||||
|       // Render product name
 |       // Render product name
 | ||||||
|  | @ -456,14 +515,68 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|          ImGui::End(); |          ImGui::End(); | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|    if (p->sweepTimeString_.length() > 0) | void OverlayLayer::Impl::RenderProductDetails( | ||||||
|  |    const std::shared_ptr<MapContext>&            mapContext, | ||||||
|  |    const QMapLibre::CustomLayerRenderParameters& params) | ||||||
|  | { | ||||||
|  |    auto radarProductView = mapContext->radar_product_view(); | ||||||
|  | 
 | ||||||
|  |    ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), 0.0f}, | ||||||
|  |                            ImGuiCond_Always, | ||||||
|  |                            ImVec2 {1.0f, 0.0f}); | ||||||
|  | 
 | ||||||
|  |    bool                          productNotAvailable = false; | ||||||
|  |    types::RadarProductLoadStatus newLoadStatus = | ||||||
|  |       types::RadarProductLoadStatus::ProductNotLoaded; | ||||||
|  | 
 | ||||||
|  |    if (radarProductView != nullptr) | ||||||
|  |    { | ||||||
|  |       newLoadStatus = radarProductView->load_status(); | ||||||
|  | 
 | ||||||
|  |       switch (newLoadStatus) | ||||||
|  |       { | ||||||
|  |       case types::RadarProductLoadStatus::ProductNotAvailable: | ||||||
|  |          productNotAvailable = true; | ||||||
|  |          break; | ||||||
|  | 
 | ||||||
|  |       default: | ||||||
|  |          productNotAvailable = false; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (productNotAvailable) | ||||||
|  |    { | ||||||
|  |       ImGui::Begin("Product Not Available", | ||||||
|  |                    nullptr, | ||||||
|  |                    ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | | ||||||
|  |                       ImGuiWindowFlags_AlwaysAutoResize); | ||||||
|  | 
 | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); | ||||||
|  |       ImGui::TextUnformatted("NO DATA AVAILABLE"); | ||||||
|  |       ImGui::PopStyleColor(); | ||||||
|  |       if (ImGui::BeginItemTooltip()) | ||||||
|  |       { | ||||||
|  |          static constexpr float  kFontSizeFactor_  = 20.0f; | ||||||
|  |          static constexpr double kMaxWidthPercent_ = 0.8; | ||||||
|  | 
 | ||||||
|  |          ImGui::PushTextWrapPos( | ||||||
|  |             std::min(ImGui::GetFontSize() * kFontSizeFactor_, | ||||||
|  |                      static_cast<float>(params.width * kMaxWidthPercent_))); | ||||||
|  |          ImGui::TextUnformatted( | ||||||
|  |             "No data found for the selected product and time. Please select a " | ||||||
|  |             "different product, or update your time selection."); | ||||||
|  |          ImGui::PopTextWrapPos(); | ||||||
|  |          ImGui::EndTooltip(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       ImGui::End(); | ||||||
|  |    } | ||||||
|  |    else if (sweepTimeString_.length() > 0) | ||||||
|    { |    { | ||||||
|       // Render time
 |       // Render time
 | ||||||
|       ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), 0.0f}, |       ImGui::Begin("Product Details", | ||||||
|                               ImGuiCond_Always, |  | ||||||
|                               ImVec2 {1.0f, 0.0f}); |  | ||||||
|       ImGui::Begin("Sweep Time", |  | ||||||
|                    nullptr, |                    nullptr, | ||||||
|                    ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | |                    ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | | ||||||
|                       ImGuiWindowFlags_AlwaysAutoResize); |                       ImGuiWindowFlags_AlwaysAutoResize); | ||||||
|  | @ -471,12 +584,12 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|       if (radarProductView != nullptr && ImGui::IsWindowHovered()) |       if (radarProductView != nullptr && ImGui::IsWindowHovered()) | ||||||
|       { |       { | ||||||
|          // Show a detailed product description when the sweep time is hovered
 |          // Show a detailed product description when the sweep time is hovered
 | ||||||
|          p->sweepTimePicked_ = true; |          sweepTimePicked_ = true; | ||||||
| 
 | 
 | ||||||
|          auto fields = radarProductView->GetDescriptionFields(); |          auto fields = radarProductView->GetDescriptionFields(); | ||||||
|          if (fields.empty()) |          if (fields.empty()) | ||||||
|          { |          { | ||||||
|             ImGui::TextUnformatted(p->sweepTimeString_.c_str()); |             ImGui::TextUnformatted(sweepTimeString_.c_str()); | ||||||
|          } |          } | ||||||
|          else |          else | ||||||
|          { |          { | ||||||
|  | @ -496,34 +609,19 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|       } |       } | ||||||
|       else |       else | ||||||
|       { |       { | ||||||
|          ImGui::TextUnformatted(p->sweepTimeString_.c_str()); |          ImGui::TextUnformatted(sweepTimeString_.c_str()); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       ImGui::End(); |       ImGui::End(); | ||||||
|    } |    } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|    // Map Center Icon
 | void OverlayLayer::Impl::RenderAttribution( | ||||||
|    if (params.width != p->lastWidth_ || params.height != p->lastHeight_) |    const std::shared_ptr<MapContext>&            mapContext, | ||||||
|    { |    const QMapLibre::CustomLayerRenderParameters& params) | ||||||
|       // Draw the icon in the center of the widget
 | { | ||||||
|       p->icons_->SetIconLocation( |  | ||||||
|          p->mapCenterIcon_, params.width / 2.0, params.height / 2.0); |  | ||||||
|    } |  | ||||||
|    p->icons_->SetIconVisible(p->mapCenterIcon_, |  | ||||||
|                              generalSettings.show_map_center().GetValue()); |  | ||||||
| 
 |  | ||||||
|    const QMargins colorTableMargins = mapContext->color_table_margins(); |    const QMargins colorTableMargins = mapContext->color_table_margins(); | ||||||
|    if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_) |    auto&          generalSettings   = settings::GeneralSettings::Instance(); | ||||||
|    { |  | ||||||
|       // Draw map logo with a 10x10 indent from the bottom left
 |  | ||||||
|       p->icons_->SetIconLocation(p->mapLogoIcon_, |  | ||||||
|                                  10 + colorTableMargins.left(), |  | ||||||
|                                  10 + colorTableMargins.bottom()); |  | ||||||
|    } |  | ||||||
|    p->icons_->SetIconVisible(p->mapLogoIcon_, |  | ||||||
|                              generalSettings.show_map_logo().GetValue()); |  | ||||||
| 
 |  | ||||||
|    DrawLayer::RenderWithoutImGui(params); |  | ||||||
| 
 | 
 | ||||||
|    auto mapCopyrights = mapContext->map_copyrights(); |    auto mapCopyrights = mapContext->map_copyrights(); | ||||||
|    if (mapCopyrights.length() > 0 && |    if (mapCopyrights.length() > 0 && | ||||||
|  | @ -532,13 +630,19 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|       auto attributionFont = manager::FontManager::Instance().GetImGuiFont( |       auto attributionFont = manager::FontManager::Instance().GetImGuiFont( | ||||||
|          types::FontCategory::Attribution); |          types::FontCategory::Attribution); | ||||||
| 
 | 
 | ||||||
|       ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), |       static constexpr float kWindowBgAlpha_  = 0.5f; | ||||||
|                                       static_cast<float>(params.height) - |       static constexpr float kWindowPaddingX_ = 3.0f; | ||||||
|                                          colorTableMargins.bottom()}, |       static constexpr float kWindowPaddingY_ = 2.0f; | ||||||
|                               ImGuiCond_Always, | 
 | ||||||
|                               ImVec2 {1.0f, 1.0f}); |       ImGui::SetNextWindowPos( | ||||||
|       ImGui::SetNextWindowBgAlpha(0.5f); |          ImVec2 { | ||||||
|       ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {3.0f, 2.0f}); |             static_cast<float>(params.width), | ||||||
|  |             static_cast<float>(params.height - colorTableMargins.bottom())}, | ||||||
|  |          ImGuiCond_Always, | ||||||
|  |          ImVec2 {1.0f, 1.0f}); | ||||||
|  |       ImGui::SetNextWindowBgAlpha(kWindowBgAlpha_); | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, | ||||||
|  |                           ImVec2 {kWindowPaddingX_, kWindowPaddingY_}); | ||||||
|       ImGui::PushFont(attributionFont.first->font(), |       ImGui::PushFont(attributionFont.first->font(), | ||||||
|                       attributionFont.second.value()); |                       attributionFont.second.value()); | ||||||
|       ImGui::Begin("Attribution", |       ImGui::Begin("Attribution", | ||||||
|  | @ -550,17 +654,6 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext, | ||||||
|       ImGui::PopFont(); |       ImGui::PopFont(); | ||||||
|       ImGui::PopStyleVar(); |       ImGui::PopStyleVar(); | ||||||
|    } |    } | ||||||
| 
 |  | ||||||
|    p->firstRender_           = false; |  | ||||||
|    p->lastWidth_             = params.width; |  | ||||||
|    p->lastHeight_            = params.height; |  | ||||||
|    p->lastBearing_           = params.bearing; |  | ||||||
|    p->lastFontSize_          = ImGui::GetFontSize(); |  | ||||||
|    p->lastColorTableMargins_ = colorTableMargins; |  | ||||||
| 
 |  | ||||||
|    ImGuiFrameEnd(); |  | ||||||
| 
 |  | ||||||
|    SCWX_GL_CHECK_ERROR(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OverlayLayer::Deinitialize() | void OverlayLayer::Deinitialize() | ||||||
|  |  | ||||||
|  | @ -63,6 +63,9 @@ public: | ||||||
| 
 | 
 | ||||||
|    bool colorTableNeedsUpdate_ {false}; |    bool colorTableNeedsUpdate_ {false}; | ||||||
|    bool sweepNeedsUpdate_ {false}; |    bool sweepNeedsUpdate_ {false}; | ||||||
|  | 
 | ||||||
|  |    types::RadarProductLoadStatus latchedLoadStatus_ { | ||||||
|  |       types::RadarProductLoadStatus::ProductNotAvailable}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| RadarProductLayer::RadarProductLayer(std::shared_ptr<gl::GlContext> glContext) : | RadarProductLayer::RadarProductLayer(std::shared_ptr<gl::GlContext> glContext) : | ||||||
|  | @ -146,6 +149,26 @@ void RadarProductLayer::Initialize( | ||||||
|            &view::RadarProductView::SweepComputed, |            &view::RadarProductView::SweepComputed, | ||||||
|            this, |            this, | ||||||
|            [this]() { p->sweepNeedsUpdate_ = true; }); |            [this]() { p->sweepNeedsUpdate_ = true; }); | ||||||
|  |    connect(radarProductView.get(), | ||||||
|  |            &view::RadarProductView::SweepNotComputed, | ||||||
|  |            this, | ||||||
|  |            [this](types::NoUpdateReason reason) | ||||||
|  |            { | ||||||
|  |               if (reason == types::NoUpdateReason::NotAvailable) | ||||||
|  |               { | ||||||
|  |                  // Ensure the radar product is hidden by re-rendering
 | ||||||
|  |                  Q_EMIT NeedsRendering(); | ||||||
|  |               } | ||||||
|  |               if (reason == types::NoUpdateReason::NoChange) | ||||||
|  |               { | ||||||
|  |                  if (p->latchedLoadStatus_ == | ||||||
|  |                      types::RadarProductLoadStatus::ProductNotAvailable) | ||||||
|  |                  { | ||||||
|  |                     // Ensure the radar product is shown by re-rendering
 | ||||||
|  |                     Q_EMIT NeedsRendering(); | ||||||
|  |                  } | ||||||
|  |               } | ||||||
|  |            }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RadarProductLayer::UpdateSweep( | void RadarProductLayer::UpdateSweep( | ||||||
|  | @ -189,10 +212,10 @@ void RadarProductLayer::UpdateSweep( | ||||||
|    glEnableVertexAttribArray(0); |    glEnableVertexAttribArray(0); | ||||||
| 
 | 
 | ||||||
|    // Buffer data moments
 |    // Buffer data moments
 | ||||||
|    const GLvoid* data; |    const GLvoid* data {}; | ||||||
|    GLsizeiptr    dataSize; |    GLsizeiptr    dataSize {}; | ||||||
|    size_t        componentSize; |    size_t        componentSize {}; | ||||||
|    GLenum        type; |    GLenum        type {}; | ||||||
| 
 | 
 | ||||||
|    std::tie(data, dataSize, componentSize) = radarProductView->GetMomentData(); |    std::tie(data, dataSize, componentSize) = radarProductView->GetMomentData(); | ||||||
| 
 | 
 | ||||||
|  | @ -215,10 +238,10 @@ void RadarProductLayer::UpdateSweep( | ||||||
|    glEnableVertexAttribArray(1); |    glEnableVertexAttribArray(1); | ||||||
| 
 | 
 | ||||||
|    // Buffer CFP data
 |    // Buffer CFP data
 | ||||||
|    const GLvoid* cfpData; |    const GLvoid* cfpData {}; | ||||||
|    GLsizeiptr    cfpDataSize; |    GLsizeiptr    cfpDataSize {}; | ||||||
|    size_t        cfpComponentSize; |    size_t        cfpComponentSize {}; | ||||||
|    GLenum        cfpType; |    GLenum        cfpType {}; | ||||||
| 
 | 
 | ||||||
|    std::tie(cfpData, cfpDataSize, cfpComponentSize) = |    std::tie(cfpData, cfpDataSize, cfpComponentSize) = | ||||||
|       radarProductView->GetCfpMomentData(); |       radarProductView->GetCfpMomentData(); | ||||||
|  | @ -258,7 +281,6 @@ void RadarProductLayer::Render( | ||||||
|    const std::shared_ptr<MapContext>&            mapContext, |    const std::shared_ptr<MapContext>&            mapContext, | ||||||
|    const QMapLibre::CustomLayerRenderParameters& params) |    const QMapLibre::CustomLayerRenderParameters& params) | ||||||
| { | { | ||||||
| 
 |  | ||||||
|    p->shaderProgram_->Use(); |    p->shaderProgram_->Use(); | ||||||
| 
 | 
 | ||||||
|    // Set OpenGL blend mode for transparency
 |    // Set OpenGL blend mode for transparency
 | ||||||
|  | @ -281,34 +303,65 @@ void RadarProductLayer::Render( | ||||||
|       UpdateSweep(mapContext); |       UpdateSweep(mapContext); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    const float scale = std::pow(2.0, params.zoom) * 2.0f * |    const std::shared_ptr<view::RadarProductView> radarProductView = | ||||||
|                        mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; |       mapContext->radar_product_view(); | ||||||
|    const float xScale = scale / params.width; |  | ||||||
|    const float yScale = scale / params.height; |  | ||||||
| 
 | 
 | ||||||
|    glm::mat4 uMVPMatrix(1.0f); |    bool                          sweepVisible = false; | ||||||
|    uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); |    types::RadarProductLoadStatus newLoadStatus = | ||||||
|    uMVPMatrix = glm::rotate(uMVPMatrix, |       types::RadarProductLoadStatus::ProductNotLoaded; | ||||||
|                             glm::radians<float>(params.bearing), |  | ||||||
|                             glm::vec3(0.0f, 0.0f, 1.0f)); |  | ||||||
| 
 | 
 | ||||||
|    glUniform2fv(p->uMapScreenCoordLocation_, |    if (radarProductView != nullptr) | ||||||
|                 1, |    { | ||||||
|                 glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( |       newLoadStatus = radarProductView->load_status(); | ||||||
|                    {params.latitude, params.longitude}))); |  | ||||||
| 
 | 
 | ||||||
|    glUniformMatrix4fv( |       switch (newLoadStatus) | ||||||
|       p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); |       { | ||||||
|  |       case types::RadarProductLoadStatus::ProductNotAvailable: | ||||||
|  |          sweepVisible = false; | ||||||
|  |          break; | ||||||
| 
 | 
 | ||||||
|    glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0); |       case types::RadarProductLoadStatus::ListingProducts: | ||||||
|  |          sweepVisible = p->latchedLoadStatus_ != | ||||||
|  |                         types::RadarProductLoadStatus::ProductNotAvailable; | ||||||
|  |          break; | ||||||
| 
 | 
 | ||||||
|    glUniform1ui(p->uDataMomentOffsetLocation_, p->rangeMin_); |       default: | ||||||
|    glUniform1f(p->uDataMomentScaleLocation_, p->scale_); |          sweepVisible = true; | ||||||
|  |       } | ||||||
|  |    } | ||||||
| 
 | 
 | ||||||
|    glActiveTexture(GL_TEXTURE0); |    if (sweepVisible) | ||||||
|    glBindTexture(GL_TEXTURE_1D, p->texture_); |    { | ||||||
|    glBindVertexArray(p->vao_); |       const double scale = std::pow(2.0, params.zoom) * 2.0 * | ||||||
|    glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(p->numVertices_)); |                            mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; | ||||||
|  |       const auto xScale = static_cast<float>(scale / params.width); | ||||||
|  |       const auto yScale = static_cast<float>(scale / params.height); | ||||||
|  | 
 | ||||||
|  |       glm::mat4 uMVPMatrix(1.0f); | ||||||
|  |       uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); | ||||||
|  |       uMVPMatrix = glm::rotate(uMVPMatrix, | ||||||
|  |                                glm::radians(static_cast<float>(params.bearing)), | ||||||
|  |                                glm::vec3(0.0f, 0.0f, 1.0f)); | ||||||
|  | 
 | ||||||
|  |       glUniform2fv(p->uMapScreenCoordLocation_, | ||||||
|  |                    1, | ||||||
|  |                    glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( | ||||||
|  |                       {params.latitude, params.longitude}))); | ||||||
|  | 
 | ||||||
|  |       glUniformMatrix4fv( | ||||||
|  |          p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); | ||||||
|  | 
 | ||||||
|  |       glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0); | ||||||
|  | 
 | ||||||
|  |       glUniform1ui(p->uDataMomentOffsetLocation_, p->rangeMin_); | ||||||
|  |       glUniform1f(p->uDataMomentScaleLocation_, p->scale_); | ||||||
|  | 
 | ||||||
|  |       glActiveTexture(GL_TEXTURE0); | ||||||
|  |       glBindTexture(GL_TEXTURE_1D, p->texture_); | ||||||
|  |       glBindVertexArray(p->vao_); | ||||||
|  | 
 | ||||||
|  |       glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(p->numVertices_)); | ||||||
|  |    } | ||||||
| 
 | 
 | ||||||
|    if (wireframeEnabled) |    if (wireframeEnabled) | ||||||
|    { |    { | ||||||
|  | @ -316,6 +369,16 @@ void RadarProductLayer::Render( | ||||||
|       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); |       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  |    if (radarProductView != nullptr && | ||||||
|  |        // Don't latch a transition from Not Available to Listing Products
 | ||||||
|  |        !(p->latchedLoadStatus_ == | ||||||
|  |             types::RadarProductLoadStatus::ProductNotAvailable && | ||||||
|  |          newLoadStatus == types::RadarProductLoadStatus::ListingProducts)) | ||||||
|  |    { | ||||||
|  |       // Latch last load status
 | ||||||
|  |       p->latchedLoadStatus_ = newLoadStatus; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|    SCWX_GL_CHECK_ERROR(); |    SCWX_GL_CHECK_ERROR(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -543,7 +606,7 @@ void RadarProductLayer::UpdateColorTable( | ||||||
|    const uint16_t rangeMin = radarProductView->color_table_min(); |    const uint16_t rangeMin = radarProductView->color_table_min(); | ||||||
|    const uint16_t rangeMax = radarProductView->color_table_max(); |    const uint16_t rangeMax = radarProductView->color_table_max(); | ||||||
| 
 | 
 | ||||||
|    const float scale = rangeMax - rangeMin; |    const auto scale = static_cast<float>(rangeMax - rangeMin); | ||||||
| 
 | 
 | ||||||
|    glActiveTexture(GL_TEXTURE0); |    glActiveTexture(GL_TEXTURE0); | ||||||
|    glBindTexture(GL_TEXTURE_1D, p->texture_); |    glBindTexture(GL_TEXTURE_1D, p->texture_); | ||||||
|  |  | ||||||
|  | @ -86,8 +86,10 @@ public: | ||||||
|                } |                } | ||||||
|                else |                else | ||||||
|                { |                { | ||||||
|                   // TODO: Validate level 3 product
 |                   // Validate level 3 product
 | ||||||
|                   return true; |                   const auto level3Product = | ||||||
|  |                      common::GetLevel3ProductByAwipsId(value); | ||||||
|  |                   return !level3Product.empty() && level3Product != "?"; | ||||||
|                } |                } | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ enum class NoUpdateReason | ||||||
| { | { | ||||||
|    NoChange, |    NoChange, | ||||||
|    NotLoaded, |    NotLoaded, | ||||||
|  |    NotAvailable, | ||||||
|    InvalidProduct, |    InvalidProduct, | ||||||
|    InvalidData |    InvalidData | ||||||
| }; | }; | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								scwx-qt/source/scwx/qt/types/radar_product_types.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								scwx-qt/source/scwx/qt/types/radar_product_types.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <cstdint> | ||||||
|  | 
 | ||||||
|  | namespace scwx::qt::types | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | enum class RadarProductLoadStatus : std::uint8_t | ||||||
|  | { | ||||||
|  |    ProductNotLoaded, | ||||||
|  |    ProductLoaded, | ||||||
|  |    ListingProducts, | ||||||
|  |    LoadingProduct, | ||||||
|  |    ProductNotAvailable | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -93,9 +93,9 @@ void UpdateDialog::Impl::HandleAsset(const types::gh::ReleaseAsset& asset) | ||||||
| #if defined(_WIN32) | #if defined(_WIN32) | ||||||
| 
 | 
 | ||||||
| #   if defined(_M_AMD64) | #   if defined(_M_AMD64) | ||||||
|    static constexpr std::string assetSuffix = "-x64.msi"; |    static const std::string assetSuffix = "-x64.msi"; | ||||||
| #   else | #   else | ||||||
|    static constexpr std::string assetSuffix = "-arm64.msi"; |    static const std::string assetSuffix = "-arm64.msi"; | ||||||
| #   endif | #   endif | ||||||
| 
 | 
 | ||||||
|    if (asset.name_.ends_with(assetSuffix)) |    if (asset.name_.ends_with(assetSuffix)) | ||||||
|  |  | ||||||
|  | @ -8,14 +8,13 @@ | ||||||
| #include <scwx/util/threads.hpp> | #include <scwx/util/threads.hpp> | ||||||
| #include <scwx/util/time.hpp> | #include <scwx/util/time.hpp> | ||||||
| 
 | 
 | ||||||
|  | #include <atomic> | ||||||
|  | #include <mutex> | ||||||
|  | 
 | ||||||
| #include <boost/range/irange.hpp> | #include <boost/range/irange.hpp> | ||||||
| #include <boost/timer/timer.hpp> | #include <boost/timer/timer.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::view | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace view |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::view::level2_product_view"; | static const std::string logPrefix_ = "scwx::qt::view::level2_product_view"; | ||||||
|  | @ -164,11 +163,13 @@ public: | ||||||
| 
 | 
 | ||||||
|    float                    latitude_; |    float                    latitude_; | ||||||
|    float                    longitude_; |    float                    longitude_; | ||||||
|    float                    elevationCut_; |    std::atomic<float>       elevationCut_; | ||||||
|    std::vector<float>       elevationCuts_; |    std::vector<float>       elevationCuts_; | ||||||
|    units::kilometers<float> range_; |    units::kilometers<float> range_; | ||||||
|    uint16_t                 vcp_; |    uint16_t                 vcp_; | ||||||
| 
 | 
 | ||||||
|  |    std::mutex elevationCutsMutex_ {}; | ||||||
|  | 
 | ||||||
|    std::chrono::system_clock::time_point sweepTime_; |    std::chrono::system_clock::time_point sweepTime_; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<common::ColorTable>    colorTable_; |    std::shared_ptr<common::ColorTable>    colorTable_; | ||||||
|  | @ -214,6 +215,22 @@ void Level2ProductView::ConnectRadarProductManager() | ||||||
|                  Update(); |                  Update(); | ||||||
|               } |               } | ||||||
|            }); |            }); | ||||||
|  | 
 | ||||||
|  |    connect(radar_product_manager().get(), | ||||||
|  |            &manager::RadarProductManager::ProductTimesPopulated, | ||||||
|  |            this, | ||||||
|  |            [this](common::RadarProductGroup group, | ||||||
|  |                   const std::string& /* product */, | ||||||
|  |                   std::chrono::system_clock::time_point queryTime) | ||||||
|  |            { | ||||||
|  |               if (group == common::RadarProductGroup::Level2 && | ||||||
|  |                   queryTime == selected_time()) | ||||||
|  |               { | ||||||
|  |                  // If the data associated with the currently selected time is
 | ||||||
|  |                  // reloaded, update the view
 | ||||||
|  |                  Update(); | ||||||
|  |               } | ||||||
|  |            }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Level2ProductView::DisconnectRadarProductManager() | void Level2ProductView::DisconnectRadarProductManager() | ||||||
|  | @ -356,6 +373,7 @@ std::string Level2ProductView::GetRadarProductName() const | ||||||
| 
 | 
 | ||||||
| std::vector<float> Level2ProductView::GetElevationCuts() const | std::vector<float> Level2ProductView::GetElevationCuts() const | ||||||
| { | { | ||||||
|  |    const std::unique_lock lock {p->elevationCutsMutex_}; | ||||||
|    return p->elevationCuts_; |    return p->elevationCuts_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -552,13 +570,26 @@ void Level2ProductView::ComputeSweep() | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<wsr88d::rda::ElevationScan> radarData; |    std::shared_ptr<wsr88d::rda::ElevationScan> radarData; | ||||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; |    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||||
|    std::tie(radarData, p->elevationCut_, p->elevationCuts_, std::ignore) = |    types::RadarProductLoadStatus               loadStatus {}; | ||||||
|  | 
 | ||||||
|  |    std::vector<float> newElevationCuts {}; | ||||||
|  |    std::tie( | ||||||
|  |       radarData, p->elevationCut_, newElevationCuts, std::ignore, loadStatus) = | ||||||
|       radarProductManager->GetLevel2Data( |       radarProductManager->GetLevel2Data( | ||||||
|          p->dataBlockType_, p->selectedElevation_, requestedTime); |          p->dataBlockType_, p->selectedElevation_, requestedTime); | ||||||
| 
 | 
 | ||||||
|  |    std::unique_lock elevationCutsLock {p->elevationCutsMutex_}; | ||||||
|  |    p->elevationCuts_ = newElevationCuts; | ||||||
|  |    elevationCutsLock.unlock(); | ||||||
|  | 
 | ||||||
|  |    set_load_status(loadStatus); | ||||||
|  | 
 | ||||||
|    if (radarData == nullptr) |    if (radarData == nullptr) | ||||||
|    { |    { | ||||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); |       Q_EMIT SweepNotComputed( | ||||||
|  |          loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ? | ||||||
|  |             types::NoUpdateReason::NotAvailable : | ||||||
|  |             types::NoUpdateReason::NotLoaded); | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
|    if ((radarData == p->elevationScan_) && |    if ((radarData == p->elevationScan_) && | ||||||
|  | @ -1369,7 +1400,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const | ||||||
|             auto nextRadial = radarData->find((i + 1) % numRadials); |             auto nextRadial = radarData->find((i + 1) % numRadials); | ||||||
|             if (nextRadial != radarData->cend()) |             if (nextRadial != radarData->cend()) | ||||||
|             { |             { | ||||||
|                nextAngle    = nextRadial->second->azimuth_angle(); |                nextAngle = nextRadial->second->azimuth_angle(); | ||||||
| 
 | 
 | ||||||
|                // Level 2 angles are the center of the bins.
 |                // Level 2 angles are the center of the bins.
 | ||||||
|                const units::degrees<float> deltaAngle = |                const units::degrees<float> deltaAngle = | ||||||
|  | @ -1564,6 +1595,4 @@ std::shared_ptr<Level2ProductView> Level2ProductView::Create( | ||||||
|    return std::make_shared<Level2ProductView>(product, radarProductManager); |    return std::make_shared<Level2ProductView>(product, radarProductManager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace view
 | } // namespace scwx::qt::view
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -11,17 +11,12 @@ | ||||||
| #include <scwx/wsr88d/rpg/radial_data_packet.hpp> | #include <scwx/wsr88d/rpg/radial_data_packet.hpp> | ||||||
| 
 | 
 | ||||||
| #include <limits> | #include <limits> | ||||||
| #include <unordered_set> |  | ||||||
| 
 | 
 | ||||||
| #include <boost/range/irange.hpp> | #include <boost/range/irange.hpp> | ||||||
| #include <boost/timer/timer.hpp> | #include <boost/timer/timer.hpp> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::view | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace view |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::view::level3_product_view"; | static const std::string logPrefix_ = "scwx::qt::view::level3_product_view"; | ||||||
|  | @ -151,6 +146,22 @@ void Level3ProductView::ConnectRadarProductManager() | ||||||
|                  Update(); |                  Update(); | ||||||
|               } |               } | ||||||
|            }); |            }); | ||||||
|  | 
 | ||||||
|  |    connect(radar_product_manager().get(), | ||||||
|  |            &manager::RadarProductManager::ProductTimesPopulated, | ||||||
|  |            this, | ||||||
|  |            [this](common::RadarProductGroup             group, | ||||||
|  |                   const std::string&                    product, | ||||||
|  |                   std::chrono::system_clock::time_point queryTime) | ||||||
|  |            { | ||||||
|  |               if (group == common::RadarProductGroup::Level3 && | ||||||
|  |                   product == p->product_ && queryTime == selected_time()) | ||||||
|  |               { | ||||||
|  |                  // If the data associated with the currently selected time is
 | ||||||
|  |                  // reloaded, update the view
 | ||||||
|  |                  Update(); | ||||||
|  |               } | ||||||
|  |            }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Level3ProductView::DisconnectRadarProductManager() | void Level3ProductView::DisconnectRadarProductManager() | ||||||
|  | @ -596,6 +607,4 @@ bool Level3ProductView::IgnoreUnits() const | ||||||
|    return false; |    return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace view
 | } // namespace scwx::qt::view
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -10,11 +10,7 @@ | ||||||
| #include <boost/range/irange.hpp> | #include <boost/range/irange.hpp> | ||||||
| #include <boost/timer/timer.hpp> | #include <boost/timer/timer.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::view | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace view |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::view::level3_radial_view"; | static const std::string logPrefix_ = "scwx::qt::view::level3_radial_view"; | ||||||
|  | @ -31,15 +27,7 @@ static constexpr std::uint32_t VALUES_PER_VERTEX = 2u; | ||||||
| class Level3RadialView::Impl | class Level3RadialView::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit Impl(Level3RadialView* self) : |    explicit Impl(Level3RadialView* self) : self_ {self} {} | ||||||
|        self_ {self}, |  | ||||||
|        latitude_ {}, |  | ||||||
|        longitude_ {}, |  | ||||||
|        range_ {}, |  | ||||||
|        vcp_ {}, |  | ||||||
|        sweepTime_ {} |  | ||||||
|    { |  | ||||||
|    } |  | ||||||
|    ~Impl() { threadPool_.join(); }; |    ~Impl() { threadPool_.join(); }; | ||||||
| 
 | 
 | ||||||
|    void ComputeCoordinates( |    void ComputeCoordinates( | ||||||
|  | @ -65,13 +53,13 @@ public: | ||||||
|    bool lastShowSmoothedRangeFolding_ {false}; |    bool lastShowSmoothedRangeFolding_ {false}; | ||||||
|    bool lastSmoothingEnabled_ {false}; |    bool lastSmoothingEnabled_ {false}; | ||||||
| 
 | 
 | ||||||
|    float         latitude_; |    float                latitude_ {}; | ||||||
|    float         longitude_; |    float                longitude_ {}; | ||||||
|    std::optional<float> elevation_ {}; |    std::optional<float> elevation_ {}; | ||||||
|    float         range_; |    float                range_ {}; | ||||||
|    std::uint16_t vcp_; |    std::uint16_t        vcp_ {}; | ||||||
| 
 | 
 | ||||||
|    std::chrono::system_clock::time_point sweepTime_; |    std::chrono::system_clock::time_point sweepTime_ {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Level3RadialView::Level3RadialView( | Level3RadialView::Level3RadialView( | ||||||
|  | @ -148,9 +136,12 @@ void Level3RadialView::ComputeSweep() | ||||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; |    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; |    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||||
|    std::chrono::system_clock::time_point       foundTime; |    std::chrono::system_clock::time_point       foundTime; | ||||||
|    std::tie(message, foundTime) = |    types::RadarProductLoadStatus               loadStatus {}; | ||||||
|  |    std::tie(message, foundTime, loadStatus) = | ||||||
|       radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); |       radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); | ||||||
| 
 | 
 | ||||||
|  |    set_load_status(loadStatus); | ||||||
|  | 
 | ||||||
|    // If a different time was found than what was requested, update it
 |    // If a different time was found than what was requested, update it
 | ||||||
|    if (requestedTime != foundTime) |    if (requestedTime != foundTime) | ||||||
|    { |    { | ||||||
|  | @ -160,7 +151,10 @@ void Level3RadialView::ComputeSweep() | ||||||
|    if (message == nullptr) |    if (message == nullptr) | ||||||
|    { |    { | ||||||
|       logger_->debug("Level 3 data not found"); |       logger_->debug("Level 3 data not found"); | ||||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); |       Q_EMIT SweepNotComputed( | ||||||
|  |          loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ? | ||||||
|  |             types::NoUpdateReason::NotAvailable : | ||||||
|  |             types::NoUpdateReason::NotLoaded); | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  | @ -752,6 +746,4 @@ std::shared_ptr<Level3RadialView> Level3RadialView::Create( | ||||||
|    return std::make_shared<Level3RadialView>(product, radarProductManager); |    return std::make_shared<Level3RadialView>(product, radarProductManager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace view
 | } // namespace scwx::qt::view
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -10,11 +10,7 @@ | ||||||
| #include <boost/timer/timer.hpp> | #include <boost/timer/timer.hpp> | ||||||
| #include <units/angle.h> | #include <units/angle.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::view | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace view |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::view::level3_raster_view"; | static const std::string logPrefix_ = "scwx::qt::view::level3_raster_view"; | ||||||
|  | @ -125,9 +121,12 @@ void Level3RasterView::ComputeSweep() | ||||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; |    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; |    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||||
|    std::chrono::system_clock::time_point       foundTime; |    std::chrono::system_clock::time_point       foundTime; | ||||||
|    std::tie(message, foundTime) = |    types::RadarProductLoadStatus               loadStatus {}; | ||||||
|  |    std::tie(message, foundTime, loadStatus) = | ||||||
|       radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); |       radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); | ||||||
| 
 | 
 | ||||||
|  |    set_load_status(loadStatus); | ||||||
|  | 
 | ||||||
|    // If a different time was found than what was requested, update it
 |    // If a different time was found than what was requested, update it
 | ||||||
|    if (requestedTime != foundTime) |    if (requestedTime != foundTime) | ||||||
|    { |    { | ||||||
|  | @ -137,7 +136,10 @@ void Level3RasterView::ComputeSweep() | ||||||
|    if (message == nullptr) |    if (message == nullptr) | ||||||
|    { |    { | ||||||
|       logger_->debug("Level 3 data not found"); |       logger_->debug("Level 3 data not found"); | ||||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); |       Q_EMIT SweepNotComputed( | ||||||
|  |          loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ? | ||||||
|  |             types::NoUpdateReason::NotAvailable : | ||||||
|  |             types::NoUpdateReason::NotLoaded); | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  | @ -538,6 +540,4 @@ std::shared_ptr<Level3RasterView> Level3RasterView::Create( | ||||||
|    return std::make_shared<Level3RasterView>(product, radarProductManager); |    return std::make_shared<Level3RasterView>(product, radarProductManager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace view
 | } // namespace scwx::qt::view
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -8,11 +8,7 @@ | ||||||
| #include <boost/asio.hpp> | #include <boost/asio.hpp> | ||||||
| #include <boost/uuid/random_generator.hpp> | #include <boost/uuid/random_generator.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::qt::view | ||||||
| { |  | ||||||
| namespace qt |  | ||||||
| { |  | ||||||
| namespace view |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = "scwx::qt::view::overlay_product_view"; | static const std::string logPrefix_ = "scwx::qt::view::overlay_product_view"; | ||||||
|  | @ -128,6 +124,22 @@ void OverlayProductView::Impl::ConnectRadarProductManager() | ||||||
|          } |          } | ||||||
|       }, |       }, | ||||||
|       Qt::QueuedConnection); |       Qt::QueuedConnection); | ||||||
|  | 
 | ||||||
|  |    connect(radarProductManager_.get(), | ||||||
|  |            &manager::RadarProductManager::ProductTimesPopulated, | ||||||
|  |            self_, | ||||||
|  |            [this](common::RadarProductGroup             group, | ||||||
|  |                   const std::string&                    product, | ||||||
|  |                   std::chrono::system_clock::time_point queryTime) | ||||||
|  |            { | ||||||
|  |               if (group == common::RadarProductGroup::Level3 && | ||||||
|  |                   product == kNst_ && queryTime == selectedTime_) | ||||||
|  |               { | ||||||
|  |                  // If the data associated with the currently selected time is
 | ||||||
|  |                  // reloaded, update the view
 | ||||||
|  |                  Update(product); | ||||||
|  |               } | ||||||
|  |            }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OverlayProductView::Impl::DisconnectRadarProductManager() | void OverlayProductView::Impl::DisconnectRadarProductManager() | ||||||
|  | @ -286,7 +298,7 @@ void OverlayProductView::Impl::Update(const std::string& product) | ||||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; |    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||||
|    std::chrono::system_clock::time_point       requestedTime {selectedTime_}; |    std::chrono::system_clock::time_point       requestedTime {selectedTime_}; | ||||||
|    std::chrono::system_clock::time_point       foundTime; |    std::chrono::system_clock::time_point       foundTime; | ||||||
|    std::tie(message, foundTime) = |    std::tie(message, foundTime, std::ignore) = | ||||||
|       radarProductManager_->GetLevel3Data(product, requestedTime); |       radarProductManager_->GetLevel3Data(product, requestedTime); | ||||||
| 
 | 
 | ||||||
|    // If a different time was found than what was requested, update it
 |    // If a different time was found than what was requested, update it
 | ||||||
|  | @ -329,6 +341,4 @@ void OverlayProductView::SetAutoUpdate(bool enabled) | ||||||
|    p->autoUpdateEnabled_ = enabled; |    p->autoUpdateEnabled_ = enabled; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace view
 | } // namespace scwx::qt::view
 | ||||||
| } // namespace qt
 |  | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -58,6 +58,8 @@ public: | ||||||
|    std::chrono::system_clock::time_point selectedTime_; |    std::chrono::system_clock::time_point selectedTime_; | ||||||
|    bool                                  showSmoothedRangeFolding_ {false}; |    bool                                  showSmoothedRangeFolding_ {false}; | ||||||
|    bool                                  smoothingEnabled_ {false}; |    bool                                  smoothingEnabled_ {false}; | ||||||
|  |    types::RadarProductLoadStatus         loadStatus_ { | ||||||
|  |       types::RadarProductLoadStatus::ProductNotLoaded}; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; |    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||||
| 
 | 
 | ||||||
|  | @ -90,6 +92,11 @@ std::optional<float> RadarProductView::elevation() const | ||||||
|    return {}; |    return {}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | types::RadarProductLoadStatus RadarProductView::load_status() const | ||||||
|  | { | ||||||
|  |    return p->loadStatus_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| std::shared_ptr<manager::RadarProductManager> | std::shared_ptr<manager::RadarProductManager> | ||||||
| RadarProductView::radar_product_manager() const | RadarProductView::radar_product_manager() const | ||||||
| { | { | ||||||
|  | @ -126,6 +133,11 @@ std::mutex& RadarProductView::sweep_mutex() | ||||||
|    return p->sweepMutex_; |    return p->sweepMutex_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void RadarProductView::set_load_status(types::RadarProductLoadStatus loadStatus) | ||||||
|  | { | ||||||
|  |    p->loadStatus_ = loadStatus; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RadarProductView::set_radar_product_manager( | void RadarProductView::set_radar_product_manager( | ||||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) |    std::shared_ptr<manager::RadarProductManager> radarProductManager) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -38,11 +38,12 @@ public: | ||||||
|    [[nodiscard]] virtual std::shared_ptr<common::ColorTable> |    [[nodiscard]] virtual std::shared_ptr<common::ColorTable> | ||||||
|    color_table() const = 0; |    color_table() const = 0; | ||||||
|    [[nodiscard]] virtual const std::vector<boost::gil::rgba8_pixel_t>& |    [[nodiscard]] virtual const std::vector<boost::gil::rgba8_pixel_t>& | ||||||
|                                               color_table_lut() const; |                                                color_table_lut() const; | ||||||
|    [[nodiscard]] virtual std::uint16_t        color_table_min() const; |    [[nodiscard]] virtual std::uint16_t         color_table_min() const; | ||||||
|    [[nodiscard]] virtual std::uint16_t        color_table_max() const; |    [[nodiscard]] virtual std::uint16_t         color_table_max() const; | ||||||
|    [[nodiscard]] virtual std::optional<float> elevation() const; |    [[nodiscard]] virtual std::optional<float>  elevation() const; | ||||||
|    [[nodiscard]] virtual float                range() const; |    [[nodiscard]] types::RadarProductLoadStatus load_status() const; | ||||||
|  |    [[nodiscard]] virtual float                 range() const; | ||||||
|    [[nodiscard]] virtual std::chrono::system_clock::time_point |    [[nodiscard]] virtual std::chrono::system_clock::time_point | ||||||
|                                                    sweep_time() const; |                                                    sweep_time() const; | ||||||
|    [[nodiscard]] virtual float                     unit_scale() const = 0; |    [[nodiscard]] virtual float                     unit_scale() const = 0; | ||||||
|  | @ -98,6 +99,8 @@ protected: | ||||||
|    virtual void DisconnectRadarProductManager() = 0; |    virtual void DisconnectRadarProductManager() = 0; | ||||||
|    virtual void UpdateColorTableLut()           = 0; |    virtual void UpdateColorTableLut()           = 0; | ||||||
| 
 | 
 | ||||||
|  |    void set_load_status(types::RadarProductLoadStatus loadStatus); | ||||||
|  | 
 | ||||||
| protected slots: | protected slots: | ||||||
|    virtual void ComputeSweep(); |    virtual void ComputeSweep(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| Subproject commit c68bee74549963e9a02e0fa998efad0f10f8256b | Subproject commit fd8bc8bf1d07474886ce6773abffab4d315d0cd0 | ||||||
|  | @ -76,7 +76,7 @@ TEST(AwsLevel3DataProvider, GetTimePointsByDate) | ||||||
| 
 | 
 | ||||||
|    AwsLevel3DataProvider provider("KLSX", "N0Q"); |    AwsLevel3DataProvider provider("KLSX", "N0Q"); | ||||||
| 
 | 
 | ||||||
|    auto timePoints = provider.GetTimePointsByDate(date); |    auto timePoints = provider.GetTimePointsByDate(date, true); | ||||||
| 
 | 
 | ||||||
|    EXPECT_GT(timePoints.size(), 0); |    EXPECT_GT(timePoints.size(), 0); | ||||||
|    for (auto timePoint : timePoints) |    for (auto timePoint : timePoints) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| id: net.supercellwx.app | id: net.supercellwx.app | ||||||
| version: '0.5.1' | version: '0.5.2' | ||||||
| runtime: "org.freedesktop.Platform" | runtime: "org.freedesktop.Platform" | ||||||
| runtime-version: "23.08" | runtime-version: "23.08" | ||||||
| sdk: "org.freedesktop.Sdk" | sdk: "org.freedesktop.Sdk" | ||||||
|  |  | ||||||
|  | @ -46,7 +46,9 @@ public: | ||||||
|    std::string FindLatestKey() override; |    std::string FindLatestKey() override; | ||||||
|    std::chrono::system_clock::time_point FindLatestTime() override; |    std::chrono::system_clock::time_point FindLatestTime() override; | ||||||
|    std::vector<std::chrono::system_clock::time_point> |    std::vector<std::chrono::system_clock::time_point> | ||||||
|    GetTimePointsByDate(std::chrono::system_clock::time_point date) override; |         GetTimePointsByDate(std::chrono::system_clock::time_point date, | ||||||
|  |                             bool                                  update) override; | ||||||
|  |    bool IsDateCached(std::chrono::system_clock::time_point date) override; | ||||||
|    std::tuple<bool, size_t, size_t> |    std::tuple<bool, size_t, size_t> | ||||||
|    ListObjects(std::chrono::system_clock::time_point date) override; |    ListObjects(std::chrono::system_clock::time_point date) override; | ||||||
|    std::shared_ptr<wsr88d::NexradFile> |    std::shared_ptr<wsr88d::NexradFile> | ||||||
|  |  | ||||||
|  | @ -2,17 +2,12 @@ | ||||||
| 
 | 
 | ||||||
| #include <scwx/provider/nexrad_data_provider.hpp> | #include <scwx/provider/nexrad_data_provider.hpp> | ||||||
| 
 | 
 | ||||||
| namespace Aws | namespace Aws::S3 | ||||||
| { |  | ||||||
| namespace S3 |  | ||||||
| { | { | ||||||
| class S3Client; | class S3Client; | ||||||
| } // namespace S3
 | } // namespace Aws::S3
 | ||||||
| } // namespace Aws
 |  | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::provider | ||||||
| { |  | ||||||
| namespace provider |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | @ -32,20 +27,23 @@ public: | ||||||
|    AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept; |    AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept; | ||||||
|    AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept; |    AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept; | ||||||
| 
 | 
 | ||||||
|    size_t cache_size() const override; |    [[nodiscard]] std::size_t cache_size() const override; | ||||||
| 
 | 
 | ||||||
|    std::chrono::system_clock::time_point last_modified() const override; |    [[nodiscard]] std::chrono::system_clock::time_point | ||||||
|    std::chrono::seconds                  update_period() const override; |                                       last_modified() const override; | ||||||
|  |    [[nodiscard]] std::chrono::seconds update_period() const override; | ||||||
| 
 | 
 | ||||||
|    std::string FindKey(std::chrono::system_clock::time_point time) override; |    std::string FindKey(std::chrono::system_clock::time_point time) override; | ||||||
|    std::string FindLatestKey() override; |    std::string FindLatestKey() override; | ||||||
|    std::chrono::system_clock::time_point FindLatestTime() override; |    std::chrono::system_clock::time_point FindLatestTime() override; | ||||||
|    std::vector<std::chrono::system_clock::time_point> |    std::vector<std::chrono::system_clock::time_point> | ||||||
|    GetTimePointsByDate(std::chrono::system_clock::time_point date) override; |         GetTimePointsByDate(std::chrono::system_clock::time_point date, | ||||||
|  |                             bool                                  update) override; | ||||||
|  |    bool IsDateCached(std::chrono::system_clock::time_point date) override; | ||||||
|    std::tuple<bool, size_t, size_t> |    std::tuple<bool, size_t, size_t> | ||||||
|    ListObjects(std::chrono::system_clock::time_point date) override; |    ListObjects(std::chrono::system_clock::time_point date) override; | ||||||
|    std::shared_ptr<wsr88d::NexradFile> |    std::shared_ptr<wsr88d::NexradFile> | ||||||
|                              LoadObjectByKey(const std::string& key) override; |    LoadObjectByKey(const std::string& key) override; | ||||||
|    std::shared_ptr<wsr88d::NexradFile> |    std::shared_ptr<wsr88d::NexradFile> | ||||||
|    LoadObjectByTime(std::chrono::system_clock::time_point time) override; |    LoadObjectByTime(std::chrono::system_clock::time_point time) override; | ||||||
|    std::pair<size_t, size_t> Refresh() override; |    std::pair<size_t, size_t> Refresh() override; | ||||||
|  | @ -61,5 +59,4 @@ private: | ||||||
|    std::unique_ptr<Impl> p; |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace provider
 | } // namespace scwx::provider
 | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -7,9 +7,7 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::provider | ||||||
| { |  | ||||||
| namespace provider |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class NexradDataProvider | class NexradDataProvider | ||||||
|  | @ -24,7 +22,7 @@ public: | ||||||
|    NexradDataProvider(NexradDataProvider&&) noexcept; |    NexradDataProvider(NexradDataProvider&&) noexcept; | ||||||
|    NexradDataProvider& operator=(NexradDataProvider&&) noexcept; |    NexradDataProvider& operator=(NexradDataProvider&&) noexcept; | ||||||
| 
 | 
 | ||||||
|    virtual size_t cache_size() const = 0; |    [[nodiscard]] virtual size_t cache_size() const = 0; | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Gets the last modified time. This is equal to the most recent object's |     * Gets the last modified time. This is equal to the most recent object's | ||||||
|  | @ -32,7 +30,8 @@ public: | ||||||
|     * |     * | ||||||
|     * @return Last modified time |     * @return Last modified time | ||||||
|     */ |     */ | ||||||
|    virtual std::chrono::system_clock::time_point last_modified() const = 0; |    [[nodiscard]] virtual std::chrono::system_clock::time_point | ||||||
|  |    last_modified() const = 0; | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Gets the current update period. This is equal to the difference between |     * Gets the current update period. This is equal to the difference between | ||||||
|  | @ -41,7 +40,7 @@ public: | ||||||
|     * |     * | ||||||
|     * @return Update period |     * @return Update period | ||||||
|     */ |     */ | ||||||
|    virtual std::chrono::seconds update_period() const = 0; |    [[nodiscard]] virtual std::chrono::seconds update_period() const = 0; | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Finds the most recent key in the cache, no later than the time provided. |     * Finds the most recent key in the cache, no later than the time provided. | ||||||
|  | @ -116,7 +115,7 @@ public: | ||||||
|     * |     * | ||||||
|     * @return NEXRAD data time point |     * @return NEXRAD data time point | ||||||
|     */ |     */ | ||||||
|    virtual std::chrono::system_clock::time_point |    [[nodiscard]] virtual std::chrono::system_clock::time_point | ||||||
|    GetTimePointByKey(const std::string& key) const = 0; |    GetTimePointByKey(const std::string& key) const = 0; | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|  | @ -124,11 +123,22 @@ public: | ||||||
|     * to the cache if required. |     * to the cache if required. | ||||||
|     * |     * | ||||||
|     * @param date Date for which to get NEXRAD data time points |     * @param date Date for which to get NEXRAD data time points | ||||||
|  |     * @param update Whether or not to list and add data not present in the cache | ||||||
|     * |     * | ||||||
|     * @return NEXRAD data time points |     * @return NEXRAD data time points | ||||||
|     */ |     */ | ||||||
|    virtual std::vector<std::chrono::system_clock::time_point> |    virtual std::vector<std::chrono::system_clock::time_point> | ||||||
|    GetTimePointsByDate(std::chrono::system_clock::time_point date) = 0; |    GetTimePointsByDate(std::chrono::system_clock::time_point date, | ||||||
|  |                        bool                                  update) = 0; | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Determines if time points for the requested date are cached. | ||||||
|  |     * | ||||||
|  |     * @param date Date for which to query the cache | ||||||
|  |     * | ||||||
|  |     * @return Whether or not the requested date is cached | ||||||
|  |     */ | ||||||
|  |    virtual bool IsDateCached(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 | ||||||
|  | @ -148,5 +158,4 @@ private: | ||||||
|    std::unique_ptr<Impl> p; |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace provider
 | } // namespace scwx::provider
 | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
|  | @ -289,11 +289,18 @@ AwsLevel2ChunksDataProvider::FindLatestTime() | ||||||
| 
 | 
 | ||||||
| std::vector<std::chrono::system_clock::time_point> | std::vector<std::chrono::system_clock::time_point> | ||||||
| AwsLevel2ChunksDataProvider::GetTimePointsByDate( | AwsLevel2ChunksDataProvider::GetTimePointsByDate( | ||||||
|    std::chrono::system_clock::time_point /*date*/) |    std::chrono::system_clock::time_point /* date */, bool /* update */) | ||||||
| { | { | ||||||
|    return {}; |    return {}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool AwsLevel2ChunksDataProvider::IsDateCached( | ||||||
|  |    std::chrono::system_clock::time_point /* date */) | ||||||
|  | { | ||||||
|  |    // No cache, default to true
 | ||||||
|  |    return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| std::chrono::system_clock::time_point | std::chrono::system_clock::time_point | ||||||
| AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix) | AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ static const std::string logPrefix_ = | ||||||
|    "scwx::provider::aws_level2_data_provider"; |    "scwx::provider::aws_level2_data_provider"; | ||||||
| static const auto logger_ = util::Logger::Create(logPrefix_); | static const auto logger_ = util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static const std::string kDefaultBucketName_ = "noaa-nexrad-level2"; | static const std::string kDefaultBucketName_ = "unidata-nexrad-level2"; | ||||||
| static const std::string kDefaultRegion_     = "us-east-1"; | static const std::string kDefaultRegion_     = "us-east-1"; | ||||||
| 
 | 
 | ||||||
| class AwsLevel2DataProvider::Impl | class AwsLevel2DataProvider::Impl | ||||||
|  |  | ||||||
|  | @ -15,9 +15,7 @@ | ||||||
| #include <aws/s3/model/ListObjectsV2Request.h> | #include <aws/s3/model/ListObjectsV2Request.h> | ||||||
| #include <fmt/chrono.h> | #include <fmt/chrono.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx::provider | ||||||
| { |  | ||||||
| namespace provider |  | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = | static const std::string logPrefix_ = | ||||||
|  | @ -177,7 +175,7 @@ std::chrono::system_clock::time_point AwsNexradDataProvider::FindLatestTime() | ||||||
| 
 | 
 | ||||||
| std::vector<std::chrono::system_clock::time_point> | std::vector<std::chrono::system_clock::time_point> | ||||||
| AwsNexradDataProvider::GetTimePointsByDate( | AwsNexradDataProvider::GetTimePointsByDate( | ||||||
|    std::chrono::system_clock::time_point date) |    std::chrono::system_clock::time_point date, bool update) | ||||||
| { | { | ||||||
|    const auto day = std::chrono::floor<std::chrono::days>(date); |    const auto day = std::chrono::floor<std::chrono::days>(date); | ||||||
| 
 | 
 | ||||||
|  | @ -188,23 +186,26 @@ AwsNexradDataProvider::GetTimePointsByDate( | ||||||
|    std::shared_lock lock(p->objectsMutex_); |    std::shared_lock lock(p->objectsMutex_); | ||||||
| 
 | 
 | ||||||
|    // Is the date present in the date list?
 |    // Is the date present in the date list?
 | ||||||
|    bool currentDatePresent; |    bool currentDatePresent = false; | ||||||
|    auto currentDateIterator = |    auto currentDateIterator = | ||||||
|       std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day); |       std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day); | ||||||
|    if (currentDateIterator == p->objectDates_.cend()) |    if (currentDateIterator == p->objectDates_.cend()) | ||||||
|    { |    { | ||||||
|       // Temporarily unlock mutex
 |       if (update) | ||||||
|       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); |          // Temporarily unlock mutex
 | ||||||
|       } |          lock.unlock(); | ||||||
| 
 | 
 | ||||||
|       // Re-lock mutex
 |          // List objects, since the date is not present in the date list
 | ||||||
|       lock.lock(); |          const auto [success, newObjects, totalObjects] = ListObjects(date); | ||||||
|  |          if (success) | ||||||
|  |          { | ||||||
|  |             p->UpdateObjectDates(date); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Re-lock mutex
 | ||||||
|  |          lock.lock(); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       currentDatePresent = false; |       currentDatePresent = false; | ||||||
|    } |    } | ||||||
|  | @ -214,8 +215,8 @@ AwsNexradDataProvider::GetTimePointsByDate( | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Determine objects to retrieve
 |    // Determine objects to retrieve
 | ||||||
|    auto objectsBegin = p->objects_.lower_bound(day); |    const auto objectsBegin = p->objects_.lower_bound(day); | ||||||
|    auto objectsEnd   = p->objects_.lower_bound(day + std::chrono::days {1}); |    const auto objectsEnd = p->objects_.lower_bound(day + std::chrono::days {1}); | ||||||
| 
 | 
 | ||||||
|    // Copy time points to destination vector
 |    // Copy time points to destination vector
 | ||||||
|    std::transform(objectsBegin, |    std::transform(objectsBegin, | ||||||
|  | @ -236,6 +237,20 @@ AwsNexradDataProvider::GetTimePointsByDate( | ||||||
|    return timePoints; |    return timePoints; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool AwsNexradDataProvider::IsDateCached( | ||||||
|  |    std::chrono::system_clock::time_point date) | ||||||
|  | { | ||||||
|  |    const auto day = std::chrono::floor<std::chrono::days>(date); | ||||||
|  | 
 | ||||||
|  |    const std::shared_lock lock(p->objectsMutex_); | ||||||
|  | 
 | ||||||
|  |    // Is the date present in the date list?
 | ||||||
|  |    const auto currentDateIterator = | ||||||
|  |       std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day); | ||||||
|  | 
 | ||||||
|  |    return currentDateIterator != p->objectDates_.cend(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| std::tuple<bool, size_t, size_t> | 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) | ||||||
| { | { | ||||||
|  | @ -446,5 +461,4 @@ void AwsNexradDataProvider::Impl::UpdateObjectDates( | ||||||
|    objectDates_.push_back(day); |    objectDates_.push_back(day); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace provider
 | } // namespace scwx::provider
 | ||||||
| } // namespace scwx
 |  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue