mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 20:30:05 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "d218d051846ac6ca2c3f303bd9b21fe6fb12f27d" and "de43670ec21c61738f63f3b5d84fe0af72e3abb2" have entirely different histories.
		
	
	
		
			d218d05184
			...
			de43670ec2
		
	
		
					 32 changed files with 355 additions and 868 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.2 |       SCWX_VERSION: v0.5.1 | ||||||
|     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.2 |         VERSION      0.5.1 | ||||||
|         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.2") | set(SCWX_VERSION "0.5.1") | ||||||
| 
 | 
 | ||||||
| option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF) | option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -234,7 +234,6 @@ 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,7 +27,6 @@ | ||||||
| #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> | ||||||
|  | @ -72,8 +71,6 @@ 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,28 +463,18 @@ 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(); | ||||||
| 
 | 
 | ||||||
|    static bool firstShowEvent = true; |    // restore the geometry state
 | ||||||
|    bool        restored       = false; |    std::string uiGeometry = uiSettings.main_ui_geometry().GetValue(); | ||||||
|  |    restoreGeometry( | ||||||
|  |       QByteArray::fromBase64(QByteArray::fromStdString(uiGeometry))); | ||||||
| 
 | 
 | ||||||
|    if (firstShowEvent) |    // restore the UI state
 | ||||||
|    { |    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,7 +15,6 @@ | ||||||
| #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> | ||||||
| 
 | 
 | ||||||
|  | @ -37,7 +36,11 @@ | ||||||
| #   pragma warning(pop) | #   pragma warning(pop) | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace scwx::qt::manager | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = | static const std::string logPrefix_ = | ||||||
|  | @ -106,27 +109,21 @@ public: | ||||||
|                     group, product, isChunks_, latestTime); |                     group, product, isChunks_, latestTime); | ||||||
|               }); |               }); | ||||||
|    } |    } | ||||||
|    ~ProviderManager() override |    ~ProviderManager() { threadPool_.join(); }; | ||||||
|    { |  | ||||||
|       providerThreadPool_.stop(); |  | ||||||
|       providerThreadPool_.join(); |  | ||||||
|    }; |  | ||||||
| 
 | 
 | ||||||
|    std::string name() const; |    std::string name() const; | ||||||
| 
 | 
 | ||||||
|    void Disable(); |    void Disable(); | ||||||
|    void RefreshData(); |  | ||||||
|    void RefreshDataSync(); |  | ||||||
| 
 | 
 | ||||||
|    boost::asio::thread_pool providerThreadPool_ {2u}; |    boost::asio::thread_pool threadPool_ {1u}; | ||||||
| 
 | 
 | ||||||
|    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_ {providerThreadPool_}; |    boost::asio::steady_timer                     refreshTimer_ {threadPool_}; | ||||||
|    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}; | ||||||
| 
 | 
 | ||||||
|  | @ -187,9 +184,11 @@ public: | ||||||
|                        auto& [key, providerManager] = p; |                        auto& [key, providerManager] = p; | ||||||
|                        providerManager->Disable(); |                        providerManager->Disable(); | ||||||
|                     }); |                     }); | ||||||
|       lock.unlock(); |  | ||||||
| 
 | 
 | ||||||
|       threadPool_.stop(); |       // Lock other mutexes before destroying, ensure loading is complete
 | ||||||
|  |       std::unique_lock loadLevel2DataLock {loadLevel2DataMutex_}; | ||||||
|  |       std::unique_lock loadLevel3DataLock {loadLevel3DataMutex_}; | ||||||
|  | 
 | ||||||
|       threadPool_.join(); |       threadPool_.join(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  | @ -204,14 +203,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::tuple<std::map<std::chrono::system_clock::time_point, |    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> | ||||||
|  | @ -225,24 +224,15 @@ 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(); | ||||||
| 
 | 
 | ||||||
|  | @ -253,16 +243,11 @@ 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, | ||||||
|  | @ -786,27 +771,28 @@ void RadarProductManagerImpl::EnableRefresh( | ||||||
|          if (providerManager->refreshEnabled_ != enabled) |          if (providerManager->refreshEnabled_ != enabled) | ||||||
|          { |          { | ||||||
|             providerManager->refreshEnabled_ = enabled; |             providerManager->refreshEnabled_ = enabled; | ||||||
|             providerManager->RefreshData(); |             RefreshData(providerManager); | ||||||
|          } |          } | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ProviderManager::RefreshData() | void RadarProductManagerImpl::RefreshData( | ||||||
|  |    std::shared_ptr<ProviderManager> providerManager) | ||||||
| { | { | ||||||
|    logger_->trace("RefreshData: {}", name()); |    logger_->trace("RefreshData: {}", providerManager->name()); | ||||||
| 
 | 
 | ||||||
|    { |    { | ||||||
|       const std::unique_lock lock(refreshTimerMutex_); |       std::unique_lock lock(providerManager->refreshTimerMutex_); | ||||||
|       refreshTimer_.cancel(); |       providerManager->refreshTimer_.cancel(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    boost::asio::post(providerThreadPool_, |    boost::asio::post(threadPool_, | ||||||
|                      [this]() |                      [=, this]() | ||||||
|                      { |                      { | ||||||
|                         try |                         try | ||||||
|                         { |                         { | ||||||
|                            RefreshDataSync(); |                            RefreshDataSync(providerManager); | ||||||
|                         } |                         } | ||||||
|                         catch (const std::exception& ex) |                         catch (const std::exception& ex) | ||||||
|                         { |                         { | ||||||
|  | @ -815,24 +801,27 @@ void ProviderManager::RefreshData() | ||||||
|                      }); |                      }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ProviderManager::RefreshDataSync() | void RadarProductManagerImpl::RefreshDataSync( | ||||||
|  |    std::shared_ptr<ProviderManager> providerManager) | ||||||
| { | { | ||||||
|    using namespace std::chrono_literals; |    using namespace std::chrono_literals; | ||||||
| 
 | 
 | ||||||
|    auto [newObjects, totalObjects] = provider_->Refresh(); |    auto [newObjects, totalObjects] = providerManager->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 = | ||||||
|       isChunks_ ? kFastRetryIntervalChunks_ : kFastRetryInterval_; |       providerManager->isChunks_ ? kFastRetryIntervalChunks_ : | ||||||
|  |                                    kFastRetryInterval_; | ||||||
|    const std::chrono::milliseconds slowRetryInterval = |    const std::chrono::milliseconds slowRetryInterval = | ||||||
|       isChunks_ ? kSlowRetryIntervalChunks_ : kSlowRetryInterval_; |       providerManager->isChunks_ ? kSlowRetryIntervalChunks_ : | ||||||
|  |                                    kSlowRetryInterval_; | ||||||
|    std::chrono::milliseconds interval = fastRetryInterval; |    std::chrono::milliseconds interval = fastRetryInterval; | ||||||
| 
 | 
 | ||||||
|    if (totalObjects > 0) |    if (totalObjects > 0) | ||||||
|    { |    { | ||||||
|       auto latestTime        = provider_->FindLatestTime(); |       auto latestTime        = providerManager->provider_->FindLatestTime(); | ||||||
|       auto updatePeriod      = provider_->update_period(); |       auto updatePeriod      = providerManager->provider_->update_period(); | ||||||
|       auto lastModified      = provider_->last_modified(); |       auto lastModified      = providerManager->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
 | ||||||
|  | @ -841,11 +830,7 @@ void ProviderManager::RefreshDataSync() | ||||||
|       interval = std::chrono::duration_cast<std::chrono::milliseconds>( |       interval = std::chrono::duration_cast<std::chrono::milliseconds>( | ||||||
|          updatePeriod - sinceLastModified); |          updatePeriod - sinceLastModified); | ||||||
| 
 | 
 | ||||||
|       // Allow 5 update periods before considering the data stale
 |       if (updatePeriod > 0s && sinceLastModified > updatePeriod * 5) | ||||||
|       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
 | ||||||
|  | @ -859,43 +844,46 @@ void ProviderManager::RefreshDataSync() | ||||||
| 
 | 
 | ||||||
|       if (newObjects > 0) |       if (newObjects > 0) | ||||||
|       { |       { | ||||||
|          Q_EMIT NewDataAvailable(group_, product_, latestTime); |          Q_EMIT providerManager->NewDataAvailable( | ||||||
|  |             providerManager->group_, providerManager->product_, latestTime); | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|    else if (refreshEnabled_) |    else if (providerManager->refreshEnabled_) | ||||||
|    { |    { | ||||||
|       logger_->info("[{}] No data found", name()); |       logger_->info("[{}] No data found", providerManager->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(refreshTimerMutex_); |    std::unique_lock const lock(providerManager->refreshTimerMutex_); | ||||||
| 
 | 
 | ||||||
|    if (refreshEnabled_) |    if (providerManager->refreshEnabled_) | ||||||
|    { |    { | ||||||
|       logger_->trace( |       logger_->trace( | ||||||
|          "[{}] Scheduled refresh in {:%M:%S}", |          "[{}] Scheduled refresh in {:%M:%S}", | ||||||
|          name(), |          providerManager->name(), | ||||||
|          std::chrono::duration_cast<std::chrono::seconds>(interval)); |          std::chrono::duration_cast<std::chrono::seconds>(interval)); | ||||||
| 
 | 
 | ||||||
|       { |       { | ||||||
|          refreshTimer_.expires_after(interval); |          providerManager->refreshTimer_.expires_after(interval); | ||||||
|          refreshTimer_.async_wait( |          providerManager->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(); |                   RefreshData(providerManager); | ||||||
|                } |                } | ||||||
|                else if (e == boost::asio::error::operation_aborted) |                else if (e == boost::asio::error::operation_aborted) | ||||||
|                { |                { | ||||||
|                   logger_->debug("[{}] Data refresh timer cancelled", name()); |                   logger_->debug("[{}] Data refresh timer cancelled", | ||||||
|  |                                  providerManager->name()); | ||||||
|                } |                } | ||||||
|                else |                else | ||||||
|                { |                { | ||||||
|                   logger_->warn( |                   logger_->warn("[{}] Data refresh timer error: {}", | ||||||
|                      "[{}] Data refresh timer error: {}", name(), e.message()); |                                 providerManager->name(), | ||||||
|  |                                 e.message()); | ||||||
|                } |                } | ||||||
|             }); |             }); | ||||||
|       } |       } | ||||||
|  | @ -946,32 +934,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::for_each(std::execution::par, | ||||||
|             std::execution::par, |                        dates.begin(), | ||||||
|             dates.begin(), |                        dates.end(), | ||||||
|             dates.end(), |                        [&](const auto& date) | ||||||
|             [&](const auto& date) |                        { | ||||||
|             { |                           // Don't query for a time point in the future
 | ||||||
|                // Don't query for a time point in the future
 |                           if (date > scwx::util::time::now()) | ||||||
|                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, true); |                           auto timePoints = provider->GetTimePointsByDate(date); | ||||||
| 
 | 
 | ||||||
|                // TODO: Note, this will miss volume times present in Level 2
 |                           // TODO: Note, this will miss volume times present in
 | ||||||
|                // products with a second scan
 |                           // Level 2 products with a second scan
 | ||||||
| 
 | 
 | ||||||
|                // Lock the merged volume time list
 |                           // Lock the merged volume time list
 | ||||||
|                const std::unique_lock volumeTimesLock {volumeTimesMutex}; |                           std::unique_lock volumeTimesLock {volumeTimesMutex}; | ||||||
| 
 | 
 | ||||||
|                // Copy time points to the merged list
 |                           // Copy time points to the merged list
 | ||||||
|                std::copy(timePoints.begin(), |                           std::copy( | ||||||
|                          timePoints.end(), |                              timePoints.begin(), | ||||||
|                          std::inserter(volumeTimes, volumeTimes.end())); |                              timePoints.end(), | ||||||
|             }); |                              std::inserter(volumeTimes, volumeTimes.end())); | ||||||
|  |                        }); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|    // Return merged volume times list
 |    // Return merged volume times list
 | ||||||
|  | @ -1214,75 +1202,21 @@ 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, bool update) |    std::chrono::system_clock::time_point time) | ||||||
| { | { | ||||||
|    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, |    const std::string& product, std::chrono::system_clock::time_point time) | ||||||
|    std::chrono::system_clock::time_point time, |  | ||||||
|    bool                                  update) |  | ||||||
| { | { | ||||||
|    // Get provider manager
 |    // Get provider manager
 | ||||||
|    auto level3ProviderManager = GetLevel3ProviderManager(product); |    auto level3ProviderManager = GetLevel3ProviderManager(product); | ||||||
|  | @ -1295,38 +1229,21 @@ 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) |  | ||||||
| { | { | ||||||
|    if (update) |    const auto today = std::chrono::floor<std::chrono::days>(time); | ||||||
|    { |  | ||||||
|       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)); |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    auto today = std::chrono::floor<std::chrono::days>(time); |    // Don't query for the epoch
 | ||||||
| 
 |  | ||||||
|    // 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 {}) | ||||||
|    { |    { | ||||||
|       today = std::chrono::floor<std::chrono::days>(scwx::util::time::now()); |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    const auto yesterday = today - std::chrono::days {1}; |    const auto yesterday = today - std::chrono::days {1}; | ||||||
|  | @ -1350,8 +1267,7 @@ 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}; | ||||||
|  | @ -1377,9 +1293,8 @@ void RadarProductManagerImpl::PopulateProductTimes( | ||||||
|                   }); |                   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::tuple<std::map<std::chrono::system_clock::time_point, | 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) | ||||||
| { | { | ||||||
|  | @ -1387,40 +1302,9 @@ 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
 | ||||||
|    if (!AreLevel2ProductTimesPopulated(time)) |    PopulateLevel2ProductTimes(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_}; | ||||||
|  | @ -1459,29 +1343,9 @@ 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 && | ||||||
|  | @ -1504,73 +1368,29 @@ 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; |  | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (recordPtrCount == 0) |    return records; | ||||||
|    { |  | ||||||
|       // 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
 | ||||||
|    if (!AreLevel3ProductTimesPopulated(product, time)) |    PopulateLevel3ProductTimes(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_}; | ||||||
| 
 | 
 | ||||||
|  | @ -1595,27 +1415,9 @@ 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 && | ||||||
|  | @ -1638,22 +1440,9 @@ RadarProductManagerImpl::GetLevel3ProductRecord( | ||||||
|          }); |          }); | ||||||
| 
 | 
 | ||||||
|       self_->LoadLevel3Data(product, recordTime, request); |       self_->LoadLevel3Data(product, recordTime, request); | ||||||
| 
 |  | ||||||
|       // Status is already set to LoadingProduct
 |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (recordPtr == nullptr) |    return {record, recordTime}; | ||||||
|    { |  | ||||||
|       // 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> | ||||||
|  | @ -1754,8 +1543,7 @@ 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) | ||||||
|  | @ -1764,8 +1552,6 @@ 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; | ||||||
|  | @ -1801,7 +1587,6 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|          if (foundTime >= firstValidChunkTime) |          if (foundTime >= firstValidChunkTime) | ||||||
|          { |          { | ||||||
|             needArchive = false; |             needArchive = false; | ||||||
|             loadStatus  = types::RadarProductLoadStatus::ProductLoaded; |  | ||||||
|          } |          } | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|  | @ -1809,11 +1594,7 @@ 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) | ||||||
|    { |    { | ||||||
|       std::map<std::chrono::system_clock::time_point, |       auto records = p->GetLevel2ProductRecords(time); | ||||||
|                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; | ||||||
|  | @ -1858,35 +1639,25 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (loadStatus == types::RadarProductLoadStatus::ProductLoaded && |    return {radarData, elevationCut, elevationCuts, foundTime}; | ||||||
|        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, status) = p->GetLevel3ProductRecord(product, time); |    std::tie(record, time) = p->GetLevel3ProductRecord(product, time); | ||||||
| 
 | 
 | ||||||
|    if (record != nullptr) |    if (record != nullptr) | ||||||
|    { |    { | ||||||
|       message = record->level3_file()->message(); |       message = record->level3_file()->message(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return {message, time, status}; |    return {message, time}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| common::Level3ProductCategoryMap | common::Level3ProductCategoryMap | ||||||
|  | @ -2038,4 +1809,6 @@ RadarProductManager::Instance(const std::string& radarSite) | ||||||
| 
 | 
 | ||||||
| #include "radar_product_manager.moc" | #include "radar_product_manager.moc" | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::manager
 | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -5,19 +5,23 @@ | ||||||
| #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::qt::manager | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class RadarProductManagerImpl; | class RadarProductManagerImpl; | ||||||
|  | @ -85,13 +89,12 @@ 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, selected time and product load status |     * cuts and selected time | ||||||
|     */ |     */ | ||||||
|    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 = {}); | ||||||
|  | @ -102,11 +105,10 @@ 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, selected time and product load status |     * @return Level 3 message data and selected time | ||||||
|     */ |     */ | ||||||
|    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 = {}); | ||||||
| 
 | 
 | ||||||
|  | @ -149,9 +151,6 @@ 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; | ||||||
|  | @ -159,4 +158,6 @@ private: | ||||||
|    friend class RadarProductManagerImpl; |    friend class RadarProductManagerImpl; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::manager
 | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -53,13 +53,8 @@ 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(); |  | ||||||
| 
 | 
 | ||||||
|       selectThreadPool_.stop(); |       std::unique_lock selectTimeLock {selectTimeMutex_}; | ||||||
|       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,7 +15,6 @@ | ||||||
| #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> | ||||||
|  | @ -23,6 +22,7 @@ | ||||||
| #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,11 +153,9 @@ 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; | ||||||
|  | @ -215,7 +213,6 @@ 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_; | ||||||
| 
 | 
 | ||||||
|  | @ -585,8 +582,7 @@ 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>( |       std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); | ||||||
|          std::chrono::system_clock::now()); |  | ||||||
|    refreshTimer_.expires_at(now + 1min); |    refreshTimer_.expires_at(now + 1min); | ||||||
| 
 | 
 | ||||||
|    refreshTimer_.async_wait( |    refreshTimer_.async_wait( | ||||||
|  | @ -603,11 +599,7 @@ void AlertLayer::Impl::ScheduleRefresh() | ||||||
|          else |          else | ||||||
|          { |          { | ||||||
|             Q_EMIT self_->NeedsRendering(); |             Q_EMIT self_->NeedsRendering(); | ||||||
| 
 |             ScheduleRefresh(); | ||||||
|             if (refreshEnabled_) |  | ||||||
|             { |  | ||||||
|                ScheduleRefresh(); |  | ||||||
|             } |  | ||||||
|          } |          } | ||||||
|       }); |       }); | ||||||
| } | } | ||||||
|  | @ -891,7 +883,7 @@ void AlertLayer::Impl::HandleGeoLinesEvent( | ||||||
| 
 | 
 | ||||||
|    switch (ev->type()) |    switch (ev->type()) | ||||||
|    { |    { | ||||||
|    case QEvent::Type::MouseButtonRelease: |    case QEvent::Type::MouseButtonPress: | ||||||
|    { |    { | ||||||
|       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_ {"tunnel"}}, |            .drawBelow_ {mapboxDrawBelow_}}, | ||||||
|           {.name_ {"Minimo"}, |           {.name_ {"Minimo"}, | ||||||
|            .url_ { |            .url_ { | ||||||
|               "mapbox://styles/mapbox-map-design/cksjc2nsq1bg117pnekb655h1"}, |               "mapbox://styles/mapbox-map-design/cksjc2nsq1bg117pnekb655h1"}, | ||||||
|  |  | ||||||
|  | @ -92,15 +92,8 @@ 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 SetCursorLocation(common::Coordinate coordinate); |    void SetCusorLocation(common::Coordinate coordinate); | ||||||
| 
 | 
 | ||||||
|    OverlayLayer* self_; |    OverlayLayer* self_; | ||||||
| 
 | 
 | ||||||
|  | @ -180,7 +173,7 @@ OverlayLayer::~OverlayLayer() | ||||||
|    p->cursorScaleConnection_.disconnect(); |    p->cursorScaleConnection_.disconnect(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OverlayLayer::Impl::SetCursorLocation(common::Coordinate coordinate) | void OverlayLayer::Impl::SetCusorLocation(common::Coordinate coordinate) | ||||||
| { | { | ||||||
|    geoIcons_->SetIconLocation( |    geoIcons_->SetIconLocation( | ||||||
|       cursorIcon_, coordinate.latitude_, coordinate.longitude_); |       cursorIcon_, coordinate.latitude_, coordinate.longitude_); | ||||||
|  | @ -395,7 +388,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->SetCursorLocation(mapContext->mouse_coordinate()); |       p->SetCusorLocation(mapContext->mouse_coordinate()); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Location Icon
 |    // Location Icon
 | ||||||
|  | @ -432,58 +425,6 @@ 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
 | ||||||
|  | @ -515,68 +456,14 @@ void OverlayLayer::Impl::RenderProductName( | ||||||
|          ImGui::End(); |          ImGui::End(); | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void OverlayLayer::Impl::RenderProductDetails( |    if (p->sweepTimeString_.length() > 0) | ||||||
|    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::Begin("Product Details", |       ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), 0.0f}, | ||||||
|  |                               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); | ||||||
|  | @ -584,12 +471,12 @@ void OverlayLayer::Impl::RenderProductDetails( | ||||||
|       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
 | ||||||
|          sweepTimePicked_ = true; |          p->sweepTimePicked_ = true; | ||||||
| 
 | 
 | ||||||
|          auto fields = radarProductView->GetDescriptionFields(); |          auto fields = radarProductView->GetDescriptionFields(); | ||||||
|          if (fields.empty()) |          if (fields.empty()) | ||||||
|          { |          { | ||||||
|             ImGui::TextUnformatted(sweepTimeString_.c_str()); |             ImGui::TextUnformatted(p->sweepTimeString_.c_str()); | ||||||
|          } |          } | ||||||
|          else |          else | ||||||
|          { |          { | ||||||
|  | @ -609,19 +496,34 @@ void OverlayLayer::Impl::RenderProductDetails( | ||||||
|       } |       } | ||||||
|       else |       else | ||||||
|       { |       { | ||||||
|          ImGui::TextUnformatted(sweepTimeString_.c_str()); |          ImGui::TextUnformatted(p->sweepTimeString_.c_str()); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       ImGui::End(); |       ImGui::End(); | ||||||
|    } |    } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void OverlayLayer::Impl::RenderAttribution( |    // Map Center Icon
 | ||||||
|    const std::shared_ptr<MapContext>&            mapContext, |    if (params.width != p->lastWidth_ || params.height != p->lastHeight_) | ||||||
|    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(); | ||||||
|    auto&          generalSettings   = settings::GeneralSettings::Instance(); |    if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_) | ||||||
|  |    { | ||||||
|  |       // 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 && | ||||||
|  | @ -630,19 +532,13 @@ void OverlayLayer::Impl::RenderAttribution( | ||||||
|       auto attributionFont = manager::FontManager::Instance().GetImGuiFont( |       auto attributionFont = manager::FontManager::Instance().GetImGuiFont( | ||||||
|          types::FontCategory::Attribution); |          types::FontCategory::Attribution); | ||||||
| 
 | 
 | ||||||
|       static constexpr float kWindowBgAlpha_  = 0.5f; |       ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), | ||||||
|       static constexpr float kWindowPaddingX_ = 3.0f; |                                       static_cast<float>(params.height) - | ||||||
|       static constexpr float kWindowPaddingY_ = 2.0f; |                                          colorTableMargins.bottom()}, | ||||||
| 
 |                               ImGuiCond_Always, | ||||||
|       ImGui::SetNextWindowPos( |                               ImVec2 {1.0f, 1.0f}); | ||||||
|          ImVec2 { |       ImGui::SetNextWindowBgAlpha(0.5f); | ||||||
|             static_cast<float>(params.width), |       ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {3.0f, 2.0f}); | ||||||
|             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", | ||||||
|  | @ -654,6 +550,17 @@ void OverlayLayer::Impl::RenderAttribution( | ||||||
|       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,9 +63,6 @@ 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) : | ||||||
|  | @ -149,26 +146,6 @@ 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( | ||||||
|  | @ -212,10 +189,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(); | ||||||
| 
 | 
 | ||||||
|  | @ -238,10 +215,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(); | ||||||
|  | @ -281,6 +258,7 @@ 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
 | ||||||
|  | @ -303,65 +281,34 @@ void RadarProductLayer::Render( | ||||||
|       UpdateSweep(mapContext); |       UpdateSweep(mapContext); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    const std::shared_ptr<view::RadarProductView> radarProductView = |    const float scale = std::pow(2.0, params.zoom) * 2.0f * | ||||||
|       mapContext->radar_product_view(); |                        mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; | ||||||
|  |    const float xScale = scale / params.width; | ||||||
|  |    const float yScale = scale / params.height; | ||||||
| 
 | 
 | ||||||
|    bool                          sweepVisible = false; |    glm::mat4 uMVPMatrix(1.0f); | ||||||
|    types::RadarProductLoadStatus newLoadStatus = |    uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); | ||||||
|       types::RadarProductLoadStatus::ProductNotLoaded; |    uMVPMatrix = glm::rotate(uMVPMatrix, | ||||||
|  |                             glm::radians<float>(params.bearing), | ||||||
|  |                             glm::vec3(0.0f, 0.0f, 1.0f)); | ||||||
| 
 | 
 | ||||||
|    if (radarProductView != nullptr) |    glUniform2fv(p->uMapScreenCoordLocation_, | ||||||
|    { |                 1, | ||||||
|       newLoadStatus = radarProductView->load_status(); |                 glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( | ||||||
|  |                    {params.latitude, params.longitude}))); | ||||||
| 
 | 
 | ||||||
|       switch (newLoadStatus) |    glUniformMatrix4fv( | ||||||
|       { |       p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); | ||||||
|       case types::RadarProductLoadStatus::ProductNotAvailable: |  | ||||||
|          sweepVisible = false; |  | ||||||
|          break; |  | ||||||
| 
 | 
 | ||||||
|       case types::RadarProductLoadStatus::ListingProducts: |    glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0); | ||||||
|          sweepVisible = p->latchedLoadStatus_ != |  | ||||||
|                         types::RadarProductLoadStatus::ProductNotAvailable; |  | ||||||
|          break; |  | ||||||
| 
 | 
 | ||||||
|       default: |    glUniform1ui(p->uDataMomentOffsetLocation_, p->rangeMin_); | ||||||
|          sweepVisible = true; |    glUniform1f(p->uDataMomentScaleLocation_, p->scale_); | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    if (sweepVisible) |    glActiveTexture(GL_TEXTURE0); | ||||||
|    { |    glBindTexture(GL_TEXTURE_1D, p->texture_); | ||||||
|       const double scale = std::pow(2.0, params.zoom) * 2.0 * |    glBindVertexArray(p->vao_); | ||||||
|                            mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; |    glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(p->numVertices_)); | ||||||
|       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) | ||||||
|    { |    { | ||||||
|  | @ -369,16 +316,6 @@ 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(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -606,7 +543,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 auto scale = static_cast<float>(rangeMax - rangeMin); |    const float scale = rangeMax - rangeMin; | ||||||
| 
 | 
 | ||||||
|    glActiveTexture(GL_TEXTURE0); |    glActiveTexture(GL_TEXTURE0); | ||||||
|    glBindTexture(GL_TEXTURE_1D, p->texture_); |    glBindTexture(GL_TEXTURE_1D, p->texture_); | ||||||
|  |  | ||||||
|  | @ -86,10 +86,8 @@ public: | ||||||
|                } |                } | ||||||
|                else |                else | ||||||
|                { |                { | ||||||
|                   // Validate level 3 product
 |                   // TODO: Validate level 3 product
 | ||||||
|                   const auto level3Product = |                   return true; | ||||||
|                      common::GetLevel3ProductByAwipsId(value); |  | ||||||
|                   return !level3Product.empty() && level3Product != "?"; |  | ||||||
|                } |                } | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,7 +25,6 @@ enum class NoUpdateReason | ||||||
| { | { | ||||||
|    NoChange, |    NoChange, | ||||||
|    NotLoaded, |    NotLoaded, | ||||||
|    NotAvailable, |  | ||||||
|    InvalidProduct, |    InvalidProduct, | ||||||
|    InvalidData |    InvalidData | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,17 +0,0 @@ | ||||||
| #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 const std::string assetSuffix = "-x64.msi"; |    static constexpr std::string assetSuffix = "-x64.msi"; | ||||||
| #   else | #   else | ||||||
|    static const std::string assetSuffix = "-arm64.msi"; |    static constexpr std::string assetSuffix = "-arm64.msi"; | ||||||
| #   endif | #   endif | ||||||
| 
 | 
 | ||||||
|    if (asset.name_.ends_with(assetSuffix)) |    if (asset.name_.ends_with(assetSuffix)) | ||||||
|  |  | ||||||
|  | @ -8,13 +8,14 @@ | ||||||
| #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::qt::view | namespace scwx | ||||||
|  | { | ||||||
|  | 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"; | ||||||
|  | @ -163,13 +164,11 @@ public: | ||||||
| 
 | 
 | ||||||
|    float                    latitude_; |    float                    latitude_; | ||||||
|    float                    longitude_; |    float                    longitude_; | ||||||
|    std::atomic<float>       elevationCut_; |    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_; | ||||||
|  | @ -215,22 +214,6 @@ 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() | ||||||
|  | @ -373,7 +356,6 @@ 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_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -570,26 +552,13 @@ 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()}; | ||||||
|    types::RadarProductLoadStatus               loadStatus {}; |    std::tie(radarData, p->elevationCut_, p->elevationCuts_, std::ignore) = | ||||||
| 
 |  | ||||||
|    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( |       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); | ||||||
|          loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ? |  | ||||||
|             types::NoUpdateReason::NotAvailable : |  | ||||||
|             types::NoUpdateReason::NotLoaded); |  | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
|    if ((radarData == p->elevationScan_) && |    if ((radarData == p->elevationScan_) && | ||||||
|  | @ -1400,7 +1369,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 = | ||||||
|  | @ -1595,4 +1564,6 @@ std::shared_ptr<Level2ProductView> Level2ProductView::Create( | ||||||
|    return std::make_shared<Level2ProductView>(product, radarProductManager); |    return std::make_shared<Level2ProductView>(product, radarProductManager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::view
 | } // namespace view
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -11,12 +11,17 @@ | ||||||
| #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::qt::view | namespace scwx | ||||||
|  | { | ||||||
|  | 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"; | ||||||
|  | @ -146,22 +151,6 @@ 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() | ||||||
|  | @ -607,4 +596,6 @@ bool Level3ProductView::IgnoreUnits() const | ||||||
|    return false; |    return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::view
 | } // namespace view
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -10,7 +10,11 @@ | ||||||
| #include <boost/range/irange.hpp> | #include <boost/range/irange.hpp> | ||||||
| #include <boost/timer/timer.hpp> | #include <boost/timer/timer.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx::qt::view | namespace scwx | ||||||
|  | { | ||||||
|  | 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"; | ||||||
|  | @ -27,7 +31,15 @@ static constexpr std::uint32_t VALUES_PER_VERTEX = 2u; | ||||||
| class Level3RadialView::Impl | class Level3RadialView::Impl | ||||||
| { | { | ||||||
| public: | public: | ||||||
|    explicit Impl(Level3RadialView* self) : self_ {self} {} |    explicit Impl(Level3RadialView* self) : | ||||||
|  |        self_ {self}, | ||||||
|  |        latitude_ {}, | ||||||
|  |        longitude_ {}, | ||||||
|  |        range_ {}, | ||||||
|  |        vcp_ {}, | ||||||
|  |        sweepTime_ {} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|    ~Impl() { threadPool_.join(); }; |    ~Impl() { threadPool_.join(); }; | ||||||
| 
 | 
 | ||||||
|    void ComputeCoordinates( |    void ComputeCoordinates( | ||||||
|  | @ -53,13 +65,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( | ||||||
|  | @ -136,12 +148,9 @@ 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; | ||||||
|    types::RadarProductLoadStatus               loadStatus {}; |    std::tie(message, foundTime) = | ||||||
|    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) | ||||||
|    { |    { | ||||||
|  | @ -151,10 +160,7 @@ 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( |       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); | ||||||
|          loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ? |  | ||||||
|             types::NoUpdateReason::NotAvailable : |  | ||||||
|             types::NoUpdateReason::NotLoaded); |  | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  | @ -746,4 +752,6 @@ std::shared_ptr<Level3RadialView> Level3RadialView::Create( | ||||||
|    return std::make_shared<Level3RadialView>(product, radarProductManager); |    return std::make_shared<Level3RadialView>(product, radarProductManager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::view
 | } // namespace view
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -10,7 +10,11 @@ | ||||||
| #include <boost/timer/timer.hpp> | #include <boost/timer/timer.hpp> | ||||||
| #include <units/angle.h> | #include <units/angle.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx::qt::view | namespace scwx | ||||||
|  | { | ||||||
|  | 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"; | ||||||
|  | @ -121,12 +125,9 @@ 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; | ||||||
|    types::RadarProductLoadStatus               loadStatus {}; |    std::tie(message, foundTime) = | ||||||
|    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) | ||||||
|    { |    { | ||||||
|  | @ -136,10 +137,7 @@ 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( |       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); | ||||||
|          loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ? |  | ||||||
|             types::NoUpdateReason::NotAvailable : |  | ||||||
|             types::NoUpdateReason::NotLoaded); |  | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|  | @ -540,4 +538,6 @@ std::shared_ptr<Level3RasterView> Level3RasterView::Create( | ||||||
|    return std::make_shared<Level3RasterView>(product, radarProductManager); |    return std::make_shared<Level3RasterView>(product, radarProductManager); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::view
 | } // namespace view
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -8,7 +8,11 @@ | ||||||
| #include <boost/asio.hpp> | #include <boost/asio.hpp> | ||||||
| #include <boost/uuid/random_generator.hpp> | #include <boost/uuid/random_generator.hpp> | ||||||
| 
 | 
 | ||||||
| namespace scwx::qt::view | namespace scwx | ||||||
|  | { | ||||||
|  | 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"; | ||||||
|  | @ -124,22 +128,6 @@ 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() | ||||||
|  | @ -298,7 +286,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::ignore) = |    std::tie(message, foundTime) = | ||||||
|       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
 | ||||||
|  | @ -341,4 +329,6 @@ void OverlayProductView::SetAutoUpdate(bool enabled) | ||||||
|    p->autoUpdateEnabled_ = enabled; |    p->autoUpdateEnabled_ = enabled; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::qt::view
 | } // namespace view
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -58,8 +58,6 @@ 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_; | ||||||
| 
 | 
 | ||||||
|  | @ -92,11 +90,6 @@ 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 | ||||||
| { | { | ||||||
|  | @ -133,11 +126,6 @@ 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,12 +38,11 @@ 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]] types::RadarProductLoadStatus load_status() const; |    [[nodiscard]] virtual float                range() 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; | ||||||
|  | @ -99,8 +98,6 @@ 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 fd8bc8bf1d07474886ce6773abffab4d315d0cd0 | Subproject commit c68bee74549963e9a02e0fa998efad0f10f8256b | ||||||
|  | @ -76,7 +76,7 @@ TEST(AwsLevel3DataProvider, GetTimePointsByDate) | ||||||
| 
 | 
 | ||||||
|    AwsLevel3DataProvider provider("KLSX", "N0Q"); |    AwsLevel3DataProvider provider("KLSX", "N0Q"); | ||||||
| 
 | 
 | ||||||
|    auto timePoints = provider.GetTimePointsByDate(date, true); |    auto timePoints = provider.GetTimePointsByDate(date); | ||||||
| 
 | 
 | ||||||
|    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.2' | version: '0.5.1' | ||||||
| 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,9 +46,7 @@ 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, |    GetTimePointsByDate(std::chrono::system_clock::time_point date) override; | ||||||
|                             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,12 +2,17 @@ | ||||||
| 
 | 
 | ||||||
| #include <scwx/provider/nexrad_data_provider.hpp> | #include <scwx/provider/nexrad_data_provider.hpp> | ||||||
| 
 | 
 | ||||||
| namespace Aws::S3 | namespace Aws | ||||||
|  | { | ||||||
|  | namespace S3 | ||||||
| { | { | ||||||
| class S3Client; | class S3Client; | ||||||
| } // namespace Aws::S3
 | } // namespace S3
 | ||||||
|  | } // namespace Aws
 | ||||||
| 
 | 
 | ||||||
| namespace scwx::provider | namespace scwx | ||||||
|  | { | ||||||
|  | namespace provider | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | @ -27,23 +32,20 @@ public: | ||||||
|    AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept; |    AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept; | ||||||
|    AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept; |    AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept; | ||||||
| 
 | 
 | ||||||
|    [[nodiscard]] std::size_t cache_size() const override; |    size_t cache_size() const override; | ||||||
| 
 | 
 | ||||||
|    [[nodiscard]] std::chrono::system_clock::time_point |    std::chrono::system_clock::time_point last_modified() const override; | ||||||
|                                       last_modified() const override; |    std::chrono::seconds                  update_period() 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, |    GetTimePointsByDate(std::chrono::system_clock::time_point date) override; | ||||||
|                             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; | ||||||
|  | @ -59,4 +61,5 @@ private: | ||||||
|    std::unique_ptr<Impl> p; |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::provider
 | } // namespace provider
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,9 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
| namespace scwx::provider | namespace scwx | ||||||
|  | { | ||||||
|  | namespace provider | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class NexradDataProvider | class NexradDataProvider | ||||||
|  | @ -22,7 +24,7 @@ public: | ||||||
|    NexradDataProvider(NexradDataProvider&&) noexcept; |    NexradDataProvider(NexradDataProvider&&) noexcept; | ||||||
|    NexradDataProvider& operator=(NexradDataProvider&&) noexcept; |    NexradDataProvider& operator=(NexradDataProvider&&) noexcept; | ||||||
| 
 | 
 | ||||||
|    [[nodiscard]] virtual size_t cache_size() const = 0; |    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 | ||||||
|  | @ -30,8 +32,7 @@ public: | ||||||
|     * |     * | ||||||
|     * @return Last modified time |     * @return Last modified time | ||||||
|     */ |     */ | ||||||
|    [[nodiscard]] virtual std::chrono::system_clock::time_point |    virtual std::chrono::system_clock::time_point last_modified() const = 0; | ||||||
|    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 | ||||||
|  | @ -40,7 +41,7 @@ public: | ||||||
|     * |     * | ||||||
|     * @return Update period |     * @return Update period | ||||||
|     */ |     */ | ||||||
|    [[nodiscard]] virtual std::chrono::seconds update_period() const = 0; |    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. | ||||||
|  | @ -115,7 +116,7 @@ public: | ||||||
|     * |     * | ||||||
|     * @return NEXRAD data time point |     * @return NEXRAD data time point | ||||||
|     */ |     */ | ||||||
|    [[nodiscard]] virtual std::chrono::system_clock::time_point |    virtual std::chrono::system_clock::time_point | ||||||
|    GetTimePointByKey(const std::string& key) const = 0; |    GetTimePointByKey(const std::string& key) const = 0; | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|  | @ -123,22 +124,11 @@ 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, |    GetTimePointsByDate(std::chrono::system_clock::time_point date) = 0; | ||||||
|                        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 | ||||||
|  | @ -158,4 +148,5 @@ private: | ||||||
|    std::unique_ptr<Impl> p; |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::provider
 | } // namespace provider
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
|  | @ -289,18 +289,11 @@ 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 */, bool /* update */) |    std::chrono::system_clock::time_point /*date*/) | ||||||
| { | { | ||||||
|    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_ = "unidata-nexrad-level2"; | static const std::string kDefaultBucketName_ = "noaa-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,7 +15,9 @@ | ||||||
| #include <aws/s3/model/ListObjectsV2Request.h> | #include <aws/s3/model/ListObjectsV2Request.h> | ||||||
| #include <fmt/chrono.h> | #include <fmt/chrono.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx::provider | namespace scwx | ||||||
|  | { | ||||||
|  | namespace provider | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| static const std::string logPrefix_ = | static const std::string logPrefix_ = | ||||||
|  | @ -175,7 +177,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, bool update) |    std::chrono::system_clock::time_point date) | ||||||
| { | { | ||||||
|    const auto day = std::chrono::floor<std::chrono::days>(date); |    const auto day = std::chrono::floor<std::chrono::days>(date); | ||||||
| 
 | 
 | ||||||
|  | @ -186,27 +188,24 @@ 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 = false; |    bool currentDatePresent; | ||||||
|    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()) | ||||||
|    { |    { | ||||||
|       if (update) |       // Temporarily unlock mutex
 | ||||||
|  |       lock.unlock(); | ||||||
|  | 
 | ||||||
|  |       // List objects, since the date is not present in the date list
 | ||||||
|  |       auto [success, newObjects, totalObjects] = ListObjects(date); | ||||||
|  |       if (success) | ||||||
|       { |       { | ||||||
|          // Temporarily unlock mutex
 |          p->UpdateObjectDates(date); | ||||||
|          lock.unlock(); |  | ||||||
| 
 |  | ||||||
|          // List objects, since the date is not present in the date list
 |  | ||||||
|          const auto [success, newObjects, totalObjects] = ListObjects(date); |  | ||||||
|          if (success) |  | ||||||
|          { |  | ||||||
|             p->UpdateObjectDates(date); |  | ||||||
|          } |  | ||||||
| 
 |  | ||||||
|          // Re-lock mutex
 |  | ||||||
|          lock.lock(); |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       // Re-lock mutex
 | ||||||
|  |       lock.lock(); | ||||||
|  | 
 | ||||||
|       currentDatePresent = false; |       currentDatePresent = false; | ||||||
|    } |    } | ||||||
|    else |    else | ||||||
|  | @ -215,8 +214,8 @@ AwsNexradDataProvider::GetTimePointsByDate( | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Determine objects to retrieve
 |    // Determine objects to retrieve
 | ||||||
|    const auto objectsBegin = p->objects_.lower_bound(day); |    auto objectsBegin = p->objects_.lower_bound(day); | ||||||
|    const auto objectsEnd = p->objects_.lower_bound(day + std::chrono::days {1}); |    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, | ||||||
|  | @ -237,20 +236,6 @@ 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) | ||||||
| { | { | ||||||
|  | @ -461,4 +446,5 @@ void AwsNexradDataProvider::Impl::UpdateObjectDates( | ||||||
|    objectDates_.push_back(day); |    objectDates_.push_back(day); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace scwx::provider
 | } // namespace provider
 | ||||||
|  | } // namespace scwx
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue