mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 04:50:06 +00:00 
			
		
		
		
	Merge pull request #30 from dpaulat/feature/garbage-collect
Feature/garbage collect
This commit is contained in:
		
						commit
						a851918ec2
					
				
					 22 changed files with 639 additions and 166 deletions
				
			
		|  | @ -357,6 +357,11 @@ void MainWindow::on_actionImGuiDebug_triggered() | |||
|    p->imGuiDebugDialog_->show(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_actionDumpRadarProductRecords_triggered() | ||||
| { | ||||
|    manager::RadarProductManager::DumpRecords(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_actionUserManual_triggered() | ||||
| { | ||||
|    QDesktopServices::openUrl(QUrl {"https://supercell-wx.readthedocs.io/"}); | ||||
|  | @ -392,6 +397,67 @@ void MainWindow::on_resourceTreeExpandAllButton_clicked() | |||
|    ui->resourceTreeView->expandAll(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_resourceTreeView_doubleClicked(const QModelIndex& index) | ||||
| { | ||||
|    std::string selectedString {index.data().toString().toStdString()}; | ||||
|    std::chrono::system_clock::time_point time {}; | ||||
| 
 | ||||
|    logger_->debug("Selecting resource: {}", | ||||
|                   index.data().toString().toStdString()); | ||||
| 
 | ||||
|    static const std::string timeFormat {"%Y-%m-%d %H:%M:%S"}; | ||||
| 
 | ||||
|    std::istringstream in {selectedString}; | ||||
|    in >> std::chrono::parse(timeFormat, time); | ||||
| 
 | ||||
|    if (in.fail()) | ||||
|    { | ||||
|       // Not a time string, ignore double-click
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    QModelIndex parent1 = index.parent(); | ||||
|    QModelIndex parent2 = parent1.parent(); | ||||
|    QModelIndex parent3 = parent2.parent(); | ||||
| 
 | ||||
|    std::string radarSite {}; | ||||
|    std::string groupName {}; | ||||
|    std::string product {}; | ||||
| 
 | ||||
|    if (!parent2.isValid()) | ||||
|    { | ||||
|       // A time entry should be at the third or fourth level
 | ||||
|       logger_->error("Unexpected resource data"); | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    if (parent3.isValid()) | ||||
|    { | ||||
|       // Level 3 Product
 | ||||
|       radarSite = parent3.data().toString().toStdString(); | ||||
|       groupName = parent2.data().toString().toStdString(); | ||||
|       product   = parent1.data().toString().toStdString(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       // Level 2 Product
 | ||||
|       radarSite = parent2.data().toString().toStdString(); | ||||
|       groupName = parent1.data().toString().toStdString(); | ||||
|       // No product index
 | ||||
|    } | ||||
| 
 | ||||
|    common::RadarProductGroup group = common::GetRadarProductGroup(groupName); | ||||
| 
 | ||||
|    // Update radar site if different from currently selected
 | ||||
|    if (p->activeMap_->GetRadarSite()->id() != radarSite) | ||||
|    { | ||||
|       p->activeMap_->SelectRadarSite(radarSite); | ||||
|    } | ||||
| 
 | ||||
|    // Select the updated radar product
 | ||||
|    p->activeMap_->SelectRadarProduct(group, product, 0, time); | ||||
| } | ||||
| 
 | ||||
| void MainWindowImpl::ConfigureMapLayout() | ||||
| { | ||||
|    auto& generalSettings = manager::SettingsManager::general_settings(); | ||||
|  |  | |||
|  | @ -37,6 +37,7 @@ private slots: | |||
|    void on_actionSettings_triggered(); | ||||
|    void on_actionExit_triggered(); | ||||
|    void on_actionImGuiDebug_triggered(); | ||||
|    void on_actionDumpRadarProductRecords_triggered(); | ||||
|    void on_actionUserManual_triggered(); | ||||
|    void on_actionDiscord_triggered(); | ||||
|    void on_actionGitHubRepository_triggered(); | ||||
|  | @ -44,6 +45,7 @@ private slots: | |||
|    void on_radarSiteSelectButton_clicked(); | ||||
|    void on_resourceTreeCollapseAllButton_clicked(); | ||||
|    void on_resourceTreeExpandAllButton_clicked(); | ||||
|    void on_resourceTreeView_doubleClicked(const QModelIndex& index); | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<MainWindowImpl> p; | ||||
|  |  | |||
|  | @ -83,6 +83,8 @@ | |||
|      <string>&Debug</string> | ||||
|     </property> | ||||
|     <addaction name="actionImGuiDebug"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="actionDumpRadarProductRecords"/> | ||||
|    </widget> | ||||
|    <addaction name="menuFile"/> | ||||
|    <addaction name="menuView"/> | ||||
|  | @ -397,6 +399,11 @@ | |||
|     <string>&GitHub Repository</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="actionDumpRadarProductRecords"> | ||||
|    <property name="text"> | ||||
|     <string>Dump Radar &Product Records</string> | ||||
|    </property> | ||||
|   </action> | ||||
|  </widget> | ||||
|  <resources> | ||||
|   <include location="../../../../scwx-qt.qrc"/> | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| 
 | ||||
| #pragma warning(push, 0) | ||||
| #include <boost/asio/steady_timer.hpp> | ||||
| #include <boost/container_hash/hash.hpp> | ||||
| #include <boost/range/irange.hpp> | ||||
| #include <boost/timer/timer.hpp> | ||||
| #include <fmt/chrono.h> | ||||
|  | @ -36,8 +37,10 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); | |||
| typedef std::function<std::shared_ptr<wsr88d::NexradFile>()> | ||||
|    CreateNexradFileFunction; | ||||
| typedef std::map<std::chrono::system_clock::time_point, | ||||
|                  std::shared_ptr<types::RadarProductRecord>> | ||||
|                  std::weak_ptr<types::RadarProductRecord>> | ||||
|    RadarProductRecordMap; | ||||
| typedef std::list<std::shared_ptr<types::RadarProductRecord>> | ||||
|    RadarProductRecordList; | ||||
| 
 | ||||
| static constexpr uint32_t NUM_RADIAL_GATES_0_5_DEGREE = | ||||
|    common::MAX_0_5_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES; | ||||
|  | @ -53,8 +56,8 @@ static const std::string kDefaultLevel3Product_ {"N0B"}; | |||
| static constexpr std::chrono::seconds kRetryInterval_ {15}; | ||||
| 
 | ||||
| static std::unordered_map<std::string, std::weak_ptr<RadarProductManager>> | ||||
|                   instanceMap_; | ||||
| static std::mutex instanceMutex_; | ||||
|                          instanceMap_; | ||||
| static std::shared_mutex instanceMutex_; | ||||
| 
 | ||||
| static std::unordered_map<std::string, | ||||
|                           std::shared_ptr<types::RadarProductRecord>> | ||||
|  | @ -123,7 +126,9 @@ public: | |||
|        coordinates0_5Degree_ {}, | ||||
|        coordinates1Degree_ {}, | ||||
|        level2ProductRecords_ {}, | ||||
|        level2ProductRecentRecords_ {}, | ||||
|        level3ProductRecordsMap_ {}, | ||||
|        level3ProductRecentRecordsMap_ {}, | ||||
|        level2ProductRecordMutex_ {}, | ||||
|        level3ProductRecordMutex_ {}, | ||||
|        level2ProviderManager_ {std::make_shared<ProviderManager>( | ||||
|  | @ -159,6 +164,10 @@ public: | |||
|                        auto& [key, providerManager] = p; | ||||
|                        providerManager->Disable(); | ||||
|                     }); | ||||
| 
 | ||||
|       // Lock other mutexes before destroying, ensure loading is complete
 | ||||
|       std::unique_lock loadLevel2DataLock {loadLevel2DataMutex_}; | ||||
|       std::unique_lock loadLevel3DataLock {loadLevel3DataMutex_}; | ||||
|    } | ||||
| 
 | ||||
|    RadarProductManager* self_; | ||||
|  | @ -166,17 +175,22 @@ public: | |||
|    std::shared_ptr<ProviderManager> | ||||
|    GetLevel3ProviderManager(const std::string& product); | ||||
| 
 | ||||
|    void EnableRefresh(std::shared_ptr<ProviderManager> providerManager, | ||||
|    void EnableRefresh(boost::uuids::uuid               uuid, | ||||
|                       std::shared_ptr<ProviderManager> providerManager, | ||||
|                       bool                             enabled); | ||||
|    void RefreshData(std::shared_ptr<ProviderManager> providerManager); | ||||
| 
 | ||||
|    std::shared_ptr<types::RadarProductRecord> | ||||
|    std::tuple<std::shared_ptr<types::RadarProductRecord>, | ||||
|               std::chrono::system_clock::time_point> | ||||
|    GetLevel2ProductRecord(std::chrono::system_clock::time_point time); | ||||
|    std::shared_ptr<types::RadarProductRecord> | ||||
|    std::tuple<std::shared_ptr<types::RadarProductRecord>, | ||||
|               std::chrono::system_clock::time_point> | ||||
|    GetLevel3ProductRecord(const std::string&                    product, | ||||
|                           std::chrono::system_clock::time_point time); | ||||
|    std::shared_ptr<types::RadarProductRecord> | ||||
|    StoreRadarProductRecord(std::shared_ptr<types::RadarProductRecord> record); | ||||
|    void UpdateRecentRecords(RadarProductRecordList& recentList, | ||||
|                             std::shared_ptr<types::RadarProductRecord> record); | ||||
| 
 | ||||
|    void LoadProviderData(std::chrono::system_clock::time_point time, | ||||
|                          std::shared_ptr<ProviderManager>      providerManager, | ||||
|  | @ -199,10 +213,12 @@ public: | |||
|    std::vector<float> coordinates0_5Degree_; | ||||
|    std::vector<float> coordinates1Degree_; | ||||
| 
 | ||||
|    RadarProductRecordMap level2ProductRecords_; | ||||
|    RadarProductRecordMap  level2ProductRecords_; | ||||
|    RadarProductRecordList level2ProductRecentRecords_; | ||||
|    std::unordered_map<std::string, RadarProductRecordMap> | ||||
|       level3ProductRecordsMap_; | ||||
| 
 | ||||
|    std::unordered_map<std::string, RadarProductRecordList> | ||||
|                      level3ProductRecentRecordsMap_; | ||||
|    std::shared_mutex level2ProductRecordMutex_; | ||||
|    std::shared_mutex level3ProductRecordMutex_; | ||||
| 
 | ||||
|  | @ -218,6 +234,12 @@ public: | |||
| 
 | ||||
|    common::Level3ProductCategoryMap availableCategoryMap_; | ||||
|    std::shared_mutex                availableCategoryMutex_; | ||||
| 
 | ||||
|    std::unordered_map<boost::uuids::uuid, | ||||
|                       std::shared_ptr<ProviderManager>, | ||||
|                       boost::hash<boost::uuids::uuid>> | ||||
|               refreshMap_ {}; | ||||
|    std::mutex refreshMapMutex_ {}; | ||||
| }; | ||||
| 
 | ||||
| RadarProductManager::RadarProductManager(const std::string& radarId) : | ||||
|  | @ -248,6 +270,8 @@ std::string ProviderManager::name() const | |||
| 
 | ||||
| void ProviderManager::Disable() | ||||
| { | ||||
|    logger_->debug("Disabling refresh: {}", name()); | ||||
| 
 | ||||
|    std::unique_lock lock(refreshTimerMutex_); | ||||
|    refreshEnabled_ = false; | ||||
|    refreshTimer_.cancel(); | ||||
|  | @ -266,6 +290,61 @@ void RadarProductManager::Cleanup() | |||
|    } | ||||
| } | ||||
| 
 | ||||
| void RadarProductManager::DumpRecords() | ||||
| { | ||||
|    scwx::util::async( | ||||
|       [] | ||||
|       { | ||||
|          logger_->info("Record Dump"); | ||||
| 
 | ||||
|          std::shared_lock instanceLock {instanceMutex_}; | ||||
|          for (auto& instance : instanceMap_) | ||||
|          { | ||||
|             auto radarProductManager = instance.second.lock(); | ||||
|             if (radarProductManager != nullptr) | ||||
|             { | ||||
|                logger_->info(" {}", radarProductManager->radar_site()->id()); | ||||
|                logger_->info("  Level 2"); | ||||
| 
 | ||||
|                { | ||||
|                   std::shared_lock level2ProductLock { | ||||
|                      radarProductManager->p->level2ProductRecordMutex_}; | ||||
| 
 | ||||
|                   for (auto& record : | ||||
|                        radarProductManager->p->level2ProductRecords_) | ||||
|                   { | ||||
|                      logger_->info("   {}{}", | ||||
|                                    scwx::util::TimeString(record.first), | ||||
|                                    record.second.expired() ? " (expired)" : ""); | ||||
|                   } | ||||
|                } | ||||
| 
 | ||||
|                logger_->info("  Level 3"); | ||||
| 
 | ||||
|                { | ||||
|                   std::shared_lock level3ProductLock { | ||||
|                      radarProductManager->p->level3ProductRecordMutex_}; | ||||
| 
 | ||||
|                   for (auto& recordMap : | ||||
|                        radarProductManager->p->level3ProductRecordsMap_) | ||||
|                   { | ||||
|                      // Product Name
 | ||||
|                      logger_->info("   {}", recordMap.first); | ||||
| 
 | ||||
|                      for (auto& record : recordMap.second) | ||||
|                      { | ||||
|                         logger_->info("    {}{}", | ||||
|                                       scwx::util::TimeString(record.first), | ||||
|                                       record.second.expired() ? " (expired)" : | ||||
|                                                                 ""); | ||||
|                      } | ||||
|                   } | ||||
|                } | ||||
|             } | ||||
|          } | ||||
|       }); | ||||
| } | ||||
| 
 | ||||
| const std::vector<float>& | ||||
| RadarProductManager::coordinates(common::RadialSize radialSize) const | ||||
| { | ||||
|  | @ -413,11 +492,12 @@ RadarProductManagerImpl::GetLevel3ProviderManager(const std::string& product) | |||
| 
 | ||||
| void RadarProductManager::EnableRefresh(common::RadarProductGroup group, | ||||
|                                         const std::string&        product, | ||||
|                                         bool                      enabled) | ||||
|                                         bool                      enabled, | ||||
|                                         boost::uuids::uuid        uuid) | ||||
| { | ||||
|    if (group == common::RadarProductGroup::Level2) | ||||
|    { | ||||
|       p->EnableRefresh(p->level2ProviderManager_, enabled); | ||||
|       p->EnableRefresh(uuid, p->level2ProviderManager_, enabled); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|  | @ -437,16 +517,65 @@ void RadarProductManager::EnableRefresh(common::RadarProductGroup group, | |||
|                           availableProducts.cend(), | ||||
|                           product) != availableProducts.cend()) | ||||
|             { | ||||
|                p->EnableRefresh(providerManager, enabled); | ||||
|                p->EnableRefresh(uuid, providerManager, enabled); | ||||
|             } | ||||
|          }); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| void RadarProductManagerImpl::EnableRefresh( | ||||
|    std::shared_ptr<ProviderManager> providerManager, bool enabled) | ||||
|    boost::uuids::uuid               uuid, | ||||
|    std::shared_ptr<ProviderManager> providerManager, | ||||
|    bool                             enabled) | ||||
| { | ||||
|    if (providerManager->refreshEnabled_ != enabled) | ||||
|    // Lock the refresh map
 | ||||
|    std::unique_lock lock {refreshMapMutex_}; | ||||
| 
 | ||||
|    auto currentProviderManager = refreshMap_.find(uuid); | ||||
|    if (currentProviderManager != refreshMap_.cend()) | ||||
|    { | ||||
|       // If the enabling refresh for a different product, or disabling refresh
 | ||||
|       if (currentProviderManager->second != providerManager || !enabled) | ||||
|       { | ||||
|          // Determine number of entries in the map for the current provider
 | ||||
|          // manager
 | ||||
|          auto currentProviderManagerCount = std::count_if( | ||||
|             refreshMap_.cbegin(), | ||||
|             refreshMap_.cend(), | ||||
|             [&](const auto& provider) | ||||
|             { return provider.second == currentProviderManager->second; }); | ||||
| 
 | ||||
|          // If this is the last reference to the provider in the refresh map
 | ||||
|          if (currentProviderManagerCount == 1) | ||||
|          { | ||||
|             // Disable current provider
 | ||||
|             currentProviderManager->second->Disable(); | ||||
|          } | ||||
| 
 | ||||
|          // Dissociate uuid from current provider manager
 | ||||
|          refreshMap_.erase(currentProviderManager); | ||||
| 
 | ||||
|          // If we are enabling a new provider manager
 | ||||
|          if (enabled) | ||||
|          { | ||||
|             // Associate uuid to providerManager
 | ||||
|             refreshMap_.emplace(uuid, providerManager); | ||||
|          } | ||||
|       } | ||||
|    } | ||||
|    else if (enabled) | ||||
|    { | ||||
|       // We are enabling a new provider manager
 | ||||
|       // Associate uuid to provider manager
 | ||||
|       refreshMap_.emplace(uuid, providerManager); | ||||
|    } | ||||
| 
 | ||||
|    // Release the refresh map mutex
 | ||||
|    lock.unlock(); | ||||
| 
 | ||||
|    // We have already handled a disable request by this point. If enabling, and
 | ||||
|    // the provider manager refresh isn't already enabled, enable it.
 | ||||
|    if (enabled && providerManager->refreshEnabled_ != enabled) | ||||
|    { | ||||
|       providerManager->refreshEnabled_ = enabled; | ||||
| 
 | ||||
|  | @ -475,7 +604,7 @@ void RadarProductManagerImpl::RefreshData( | |||
| 
 | ||||
|          std::chrono::milliseconds interval = kRetryInterval_; | ||||
| 
 | ||||
|          if (newObjects > 0) | ||||
|          if (totalObjects > 0) | ||||
|          { | ||||
|             std::string key = providerManager->provider_->FindLatestKey(); | ||||
|             auto        latestTime = | ||||
|  | @ -491,10 +620,14 @@ void RadarProductManagerImpl::RefreshData( | |||
|                interval = kRetryInterval_; | ||||
|             } | ||||
| 
 | ||||
|             emit providerManager->NewDataAvailable( | ||||
|                providerManager->group_, providerManager->product_, latestTime); | ||||
|             if (newObjects > 0) | ||||
|             { | ||||
|                emit providerManager->NewDataAvailable(providerManager->group_, | ||||
|                                                       providerManager->product_, | ||||
|                                                       latestTime); | ||||
|             } | ||||
|          } | ||||
|          else if (providerManager->refreshEnabled_ && totalObjects == 0) | ||||
|          else if (providerManager->refreshEnabled_) | ||||
|          { | ||||
|             logger_->info("[{}] No data found, disabling refresh", | ||||
|                           providerManager->name()); | ||||
|  | @ -562,10 +695,13 @@ void RadarProductManagerImpl::LoadProviderData( | |||
|             auto it = recordMap.find(time); | ||||
|             if (it != recordMap.cend()) | ||||
|             { | ||||
|                logger_->debug( | ||||
|                   "Data previously loaded, loading from data cache"); | ||||
|                existingRecord = it->second.lock(); | ||||
| 
 | ||||
|                existingRecord = it->second; | ||||
|                if (existingRecord != nullptr) | ||||
|                { | ||||
|                   logger_->debug( | ||||
|                      "Data previously loaded, loading from data cache"); | ||||
|                } | ||||
|             } | ||||
|          } | ||||
| 
 | ||||
|  | @ -727,38 +863,70 @@ void RadarProductManagerImpl::LoadNexradFile( | |||
|       }); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<types::RadarProductRecord> | ||||
| std::tuple<std::shared_ptr<types::RadarProductRecord>, | ||||
|            std::chrono::system_clock::time_point> | ||||
| RadarProductManagerImpl::GetLevel2ProductRecord( | ||||
|    std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    std::shared_ptr<types::RadarProductRecord> record; | ||||
|    std::shared_ptr<types::RadarProductRecord> record {nullptr}; | ||||
|    RadarProductRecordMap::const_pointer       recordPtr {nullptr}; | ||||
|    std::chrono::system_clock::time_point      recordTime {time}; | ||||
| 
 | ||||
|    if (!level2ProductRecords_.empty() && | ||||
|        time == std::chrono::system_clock::time_point {}) | ||||
|    { | ||||
|       // If a default-initialized time point is given, return the latest record
 | ||||
|       record = level2ProductRecords_.rbegin()->second; | ||||
|       recordPtr = &(*level2ProductRecords_.rbegin()); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       // TODO: Round to minutes
 | ||||
|       record = scwx::util::GetBoundedElementValue(level2ProductRecords_, time); | ||||
|       recordPtr = | ||||
|          scwx::util::GetBoundedElementPointer(level2ProductRecords_, time); | ||||
|    } | ||||
| 
 | ||||
|       // Does the record contain the time we are looking for?
 | ||||
|       if (record != nullptr && (time < record->level2_file()->start_time())) | ||||
|    if (recordPtr != nullptr) | ||||
|    { | ||||
|       if (time == std::chrono::system_clock::time_point {} || | ||||
|           time == recordPtr->first) | ||||
|       { | ||||
|          record = nullptr; | ||||
|          recordTime = recordPtr->first; | ||||
|          record     = recordPtr->second.lock(); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    return record; | ||||
|    if (record == nullptr && | ||||
|        recordTime != std::chrono::system_clock::time_point {}) | ||||
|    { | ||||
|       // Product is expired, reload it
 | ||||
|       std::shared_ptr<request::NexradFileRequest> request = | ||||
|          std::make_shared<request::NexradFileRequest>(); | ||||
| 
 | ||||
|       QObject::connect( | ||||
|          request.get(), | ||||
|          &request::NexradFileRequest::RequestComplete, | ||||
|          self_, | ||||
|          [this](std::shared_ptr<request::NexradFileRequest> request) | ||||
|          { | ||||
|             if (request->radar_product_record() != nullptr) | ||||
|             { | ||||
|                emit self_->DataReloaded(request->radar_product_record()); | ||||
|             } | ||||
|          }); | ||||
| 
 | ||||
|       self_->LoadLevel2Data(recordTime, request); | ||||
|    } | ||||
| 
 | ||||
|    return {record, recordTime}; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<types::RadarProductRecord> | ||||
| std::tuple<std::shared_ptr<types::RadarProductRecord>, | ||||
|            std::chrono::system_clock::time_point> | ||||
| RadarProductManagerImpl::GetLevel3ProductRecord( | ||||
|    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}; | ||||
|    std::chrono::system_clock::time_point      recordTime {time}; | ||||
| 
 | ||||
|    std::unique_lock lock {level3ProductRecordMutex_}; | ||||
| 
 | ||||
|  | @ -770,15 +938,50 @@ RadarProductManagerImpl::GetLevel3ProductRecord( | |||
|       { | ||||
|          // If a default-initialized time point is given, return the latest
 | ||||
|          // record
 | ||||
|          record = it->second.rbegin()->second; | ||||
|          recordPtr = &(*it->second.rbegin()); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          record = scwx::util::GetBoundedElementValue(it->second, time); | ||||
|          recordPtr = scwx::util::GetBoundedElementPointer(it->second, time); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    return record; | ||||
|    // Lock is no longer needed
 | ||||
|    lock.unlock(); | ||||
| 
 | ||||
|    if (recordPtr != nullptr) | ||||
|    { | ||||
|       if (time == std::chrono::system_clock::time_point {} || | ||||
|           time == recordPtr->first) | ||||
|       { | ||||
|          recordTime = recordPtr->first; | ||||
|          record     = recordPtr->second.lock(); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    if (record == nullptr && | ||||
|        recordTime != std::chrono::system_clock::time_point {}) | ||||
|    { | ||||
|       // Product is expired, reload it
 | ||||
|       std::shared_ptr<request::NexradFileRequest> request = | ||||
|          std::make_shared<request::NexradFileRequest>(); | ||||
| 
 | ||||
|       QObject::connect( | ||||
|          request.get(), | ||||
|          &request::NexradFileRequest::RequestComplete, | ||||
|          self_, | ||||
|          [this](std::shared_ptr<request::NexradFileRequest> request) | ||||
|          { | ||||
|             if (request->radar_product_record() != nullptr) | ||||
|             { | ||||
|                emit self_->DataReloaded(request->radar_product_record()); | ||||
|             } | ||||
|          }); | ||||
| 
 | ||||
|       self_->LoadLevel3Data(product, recordTime, request); | ||||
|    } | ||||
| 
 | ||||
|    return {record, recordTime}; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<types::RadarProductRecord> | ||||
|  | @ -787,7 +990,7 @@ RadarProductManagerImpl::StoreRadarProductRecord( | |||
| { | ||||
|    logger_->debug("StoreRadarProductRecord()"); | ||||
| 
 | ||||
|    std::shared_ptr<types::RadarProductRecord> storedRecord = record; | ||||
|    std::shared_ptr<types::RadarProductRecord> storedRecord = nullptr; | ||||
| 
 | ||||
|    auto timeInSeconds = | ||||
|       std::chrono::time_point_cast<std::chrono::seconds, | ||||
|  | @ -800,15 +1003,22 @@ RadarProductManagerImpl::StoreRadarProductRecord( | |||
|       auto it = level2ProductRecords_.find(timeInSeconds); | ||||
|       if (it != level2ProductRecords_.cend()) | ||||
|       { | ||||
|          logger_->debug( | ||||
|             "Level 2 product previously loaded, loading from cache"); | ||||
|          storedRecord = it->second.lock(); | ||||
| 
 | ||||
|          storedRecord = it->second; | ||||
|          if (storedRecord != nullptr) | ||||
|          { | ||||
|             logger_->debug( | ||||
|                "Level 2 product previously loaded, loading from cache"); | ||||
|          } | ||||
|       } | ||||
|       else | ||||
| 
 | ||||
|       if (storedRecord == nullptr) | ||||
|       { | ||||
|          storedRecord                         = record; | ||||
|          level2ProductRecords_[timeInSeconds] = record; | ||||
|       } | ||||
| 
 | ||||
|       UpdateRecentRecords(level2ProductRecentRecords_, storedRecord); | ||||
|    } | ||||
|    else if (record->radar_product_group() == common::RadarProductGroup::Level3) | ||||
|    { | ||||
|  | @ -819,23 +1029,58 @@ RadarProductManagerImpl::StoreRadarProductRecord( | |||
|       auto it = productMap.find(timeInSeconds); | ||||
|       if (it != productMap.cend()) | ||||
|       { | ||||
|          logger_->debug( | ||||
|             "Level 3 product previously loaded, loading from cache"); | ||||
|          storedRecord = it->second.lock(); | ||||
| 
 | ||||
|          storedRecord = it->second; | ||||
|          if (storedRecord != nullptr) | ||||
|          { | ||||
|             logger_->debug( | ||||
|                "Level 3 product previously loaded, loading from cache"); | ||||
|          } | ||||
|       } | ||||
|       else | ||||
| 
 | ||||
|       if (storedRecord == nullptr) | ||||
|       { | ||||
|          storedRecord              = record; | ||||
|          productMap[timeInSeconds] = record; | ||||
|       } | ||||
| 
 | ||||
|       UpdateRecentRecords( | ||||
|          level3ProductRecentRecordsMap_[record->radar_product()], storedRecord); | ||||
|    } | ||||
| 
 | ||||
|    return storedRecord; | ||||
| } | ||||
| 
 | ||||
| void RadarProductManagerImpl::UpdateRecentRecords( | ||||
|    RadarProductRecordList&                    recentList, | ||||
|    std::shared_ptr<types::RadarProductRecord> record) | ||||
| { | ||||
|    static constexpr std::size_t kRecentListMaxSize_ {2u}; | ||||
| 
 | ||||
|    auto it = std::find(recentList.cbegin(), recentList.cend(), record); | ||||
|    if (it != recentList.cbegin() && it != recentList.cend()) | ||||
|    { | ||||
|       // If the record exists beyond the front of the list, remove it
 | ||||
|       recentList.erase(it); | ||||
|    } | ||||
| 
 | ||||
|    if (recentList.size() == 0 || it != recentList.cbegin()) | ||||
|    { | ||||
|       // Add the record to the front of the list, unless it's already there
 | ||||
|       recentList.push_front(record); | ||||
|    } | ||||
| 
 | ||||
|    while (recentList.size() > kRecentListMaxSize_) | ||||
|    { | ||||
|       // Remove from the end of the list while it's too big
 | ||||
|       recentList.pop_back(); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, | ||||
|            float, | ||||
|            std::vector<float>> | ||||
|            std::vector<float>, | ||||
|            std::chrono::system_clock::time_point> | ||||
| RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | ||||
|                                    float                      elevation, | ||||
|                                    std::chrono::system_clock::time_point time) | ||||
|  | @ -844,8 +1089,8 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | |||
|    float                                       elevationCut = 0.0f; | ||||
|    std::vector<float>                          elevationCuts; | ||||
| 
 | ||||
|    std::shared_ptr<types::RadarProductRecord> record = | ||||
|       p->GetLevel2ProductRecord(time); | ||||
|    std::shared_ptr<types::RadarProductRecord> record; | ||||
|    std::tie(record, time) = p->GetLevel2ProductRecord(time); | ||||
| 
 | ||||
|    if (record != nullptr) | ||||
|    { | ||||
|  | @ -854,24 +1099,25 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, | |||
|             dataBlockType, elevation, time); | ||||
|    } | ||||
| 
 | ||||
|    return std::tie(radarData, elevationCut, elevationCuts); | ||||
|    return {radarData, elevationCut, elevationCuts, time}; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<wsr88d::rpg::Level3Message> | ||||
| std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, | ||||
|            std::chrono::system_clock::time_point> | ||||
| RadarProductManager::GetLevel3Data(const std::string& product, | ||||
|                                    std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message = nullptr; | ||||
| 
 | ||||
|    std::shared_ptr<types::RadarProductRecord> record = | ||||
|       p->GetLevel3ProductRecord(product, time); | ||||
|    std::shared_ptr<types::RadarProductRecord> record; | ||||
|    std::tie(record, time) = p->GetLevel3ProductRecord(product, time); | ||||
| 
 | ||||
|    if (record != nullptr) | ||||
|    { | ||||
|       message = record->level3_file()->message(); | ||||
|    } | ||||
| 
 | ||||
|    return message; | ||||
|    return {message, time}; | ||||
| } | ||||
| 
 | ||||
| common::Level3ProductCategoryMap | ||||
|  | @ -970,7 +1216,7 @@ RadarProductManager::Instance(const std::string& radarSite) | |||
|    bool                                 instanceCreated = false; | ||||
| 
 | ||||
|    { | ||||
|       std::lock_guard<std::mutex> guard(instanceMutex_); | ||||
|       std::unique_lock lock {instanceMutex_}; | ||||
| 
 | ||||
|       // Look up instance weak pointer
 | ||||
|       auto it = instanceMap_.find(radarSite); | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <boost/uuid/nil_generator.hpp> | ||||
| #include <QObject> | ||||
| 
 | ||||
| namespace scwx | ||||
|  | @ -33,23 +34,63 @@ public: | |||
| 
 | ||||
|    static void Cleanup(); | ||||
| 
 | ||||
|    /**
 | ||||
|     * @brief Debug function to dump currently loaded products to the log. | ||||
|     */ | ||||
|    static void DumpRecords(); | ||||
| 
 | ||||
|    const std::vector<float>& coordinates(common::RadialSize radialSize) const; | ||||
|    float                     gate_size() const; | ||||
|    std::shared_ptr<config::RadarSite> radar_site() const; | ||||
| 
 | ||||
|    void Initialize(); | ||||
| 
 | ||||
|    /**
 | ||||
|     * @brief Enables or disables refresh associated with a unique identifier | ||||
|     * (UUID) for a given radar product group and product. | ||||
|     * | ||||
|     * Only a single product refresh can be enabled for a given UUID. If a second | ||||
|     * product refresh is enabled for the same UUID, the first product refresh is | ||||
|     * disabled (unless still enabled under a different UUID). | ||||
|     * | ||||
|     * @param [in] group Radar product group | ||||
|     * @param [in] product Radar product name | ||||
|     * @param [in] enabled Whether to enable refresh | ||||
|     * @param [in] uuid Unique identifier. Default is boost::uuids::nil_uuid(). | ||||
|     */ | ||||
|    void EnableRefresh(common::RadarProductGroup group, | ||||
|                       const std::string&        product, | ||||
|                       bool                      enabled); | ||||
|                       bool                      enabled, | ||||
|                       boost::uuids::uuid uuid = boost::uuids::nil_uuid()); | ||||
| 
 | ||||
|    /**
 | ||||
|     * @brief Get level 2 radar data for a data block type, elevation, and time. | ||||
|     * | ||||
|     * @param [in] dataBlockType Data block type | ||||
|     * @param [in] elevation Elevation tilt | ||||
|     * @param [in] time Radar product time | ||||
|     * | ||||
|     * @return Level 2 radar data, selected elevation cut, available elevation | ||||
|     * cuts and selected time | ||||
|     */ | ||||
|    std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, | ||||
|               float, | ||||
|               std::vector<float>> | ||||
|               std::vector<float>, | ||||
|               std::chrono::system_clock::time_point> | ||||
|    GetLevel2Data(wsr88d::rda::DataBlockType            dataBlockType, | ||||
|                  float                                 elevation, | ||||
|                  std::chrono::system_clock::time_point time = {}); | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> | ||||
|    /**
 | ||||
|     * @brief Get level 3 message data for a product and time. | ||||
|     * | ||||
|     * @param [in] product Radar product name | ||||
|     * @param [in] time Radar product time | ||||
|     * | ||||
|     * @return Level 3 message data and selected time | ||||
|     */ | ||||
|    std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, | ||||
|               std::chrono::system_clock::time_point> | ||||
|    GetLevel3Data(const std::string&                    product, | ||||
|                  std::chrono::system_clock::time_point time = {}); | ||||
| 
 | ||||
|  | @ -76,6 +117,7 @@ public: | |||
|    void                             UpdateAvailableProducts(); | ||||
| 
 | ||||
| signals: | ||||
|    void DataReloaded(std::shared_ptr<types::RadarProductRecord> record); | ||||
|    void Level3ProductsChanged(); | ||||
|    void NewDataAvailable(common::RadarProductGroup             group, | ||||
|                          const std::string&                    product, | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| 
 | ||||
| #include <backends/imgui_impl_opengl3.h> | ||||
| #include <backends/imgui_impl_qt.hpp> | ||||
| #include <boost/uuid/random_generator.hpp> | ||||
| #include <imgui.h> | ||||
| #include <QApplication> | ||||
| #include <QColor> | ||||
|  | @ -58,6 +59,7 @@ class MapWidgetImpl : public QObject | |||
| public: | ||||
|    explicit MapWidgetImpl(MapWidget*                   widget, | ||||
|                           const QMapLibreGL::Settings& settings) : | ||||
|        uuid_ {boost::uuids::random_generator()()}, | ||||
|        context_ {std::make_shared<MapContext>()}, | ||||
|        widget_ {widget}, | ||||
|        settings_(settings), | ||||
|  | @ -126,6 +128,8 @@ public: | |||
|    common::Level2Product | ||||
|    GetLevel2ProductOrDefault(const std::string& productName) const; | ||||
| 
 | ||||
|    boost::uuids::uuid uuid_; | ||||
| 
 | ||||
|    std::shared_ptr<MapContext> context_; | ||||
| 
 | ||||
|    MapWidget*                        widget_; | ||||
|  | @ -303,7 +307,7 @@ std::shared_ptr<config::RadarSite> MapWidget::GetRadarSite() const | |||
|    return radarSite; | ||||
| } | ||||
| 
 | ||||
| uint16_t MapWidget::GetVcp() const | ||||
| std::uint16_t MapWidget::GetVcp() const | ||||
| { | ||||
|    auto radarProductView = p->context_->radar_product_view(); | ||||
| 
 | ||||
|  | @ -313,7 +317,7 @@ uint16_t MapWidget::GetVcp() const | |||
|    } | ||||
|    else | ||||
|    { | ||||
|       return 0; | ||||
|       return 0u; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
|  | @ -330,7 +334,8 @@ void MapWidget::SelectElevation(float elevation) | |||
| 
 | ||||
| void MapWidget::SelectRadarProduct(common::RadarProductGroup group, | ||||
|                                    const std::string&        product, | ||||
|                                    int16_t                   productCode) | ||||
|                                    std::int16_t              productCode, | ||||
|                                    std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    bool radarProductViewCreated = false; | ||||
| 
 | ||||
|  | @ -380,8 +385,8 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, | |||
| 
 | ||||
|    if (radarProductView != nullptr) | ||||
|    { | ||||
|       // Always select the latest product available
 | ||||
|       radarProductView->SelectTime({}); | ||||
|       // Select the time associated with the request
 | ||||
|       radarProductView->SelectTime(time); | ||||
| 
 | ||||
|       if (radarProductViewCreated) | ||||
|       { | ||||
|  | @ -399,7 +404,8 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, | |||
| 
 | ||||
|    if (p->autoRefreshEnabled_) | ||||
|    { | ||||
|       p->radarProductManager_->EnableRefresh(group, productName, true); | ||||
|       p->radarProductManager_->EnableRefresh( | ||||
|          group, productName, true, p->uuid_); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
|  | @ -485,7 +491,8 @@ void MapWidget::SetAutoRefresh(bool enabled) | |||
|          p->radarProductManager_->EnableRefresh( | ||||
|             radarProductView->GetRadarProductGroup(), | ||||
|             radarProductView->GetRadarProductName(), | ||||
|             true); | ||||
|             true, | ||||
|             p->uuid_); | ||||
|       } | ||||
|    } | ||||
| } | ||||
|  |  | |||
|  | @ -41,12 +41,23 @@ public: | |||
|    common::RadarProductGroup          GetRadarProductGroup() const; | ||||
|    std::string                        GetRadarProductName() const; | ||||
|    std::shared_ptr<config::RadarSite> GetRadarSite() const; | ||||
|    uint16_t                           GetVcp() const; | ||||
|    std::uint16_t                      GetVcp() const; | ||||
| 
 | ||||
|    void SelectElevation(float elevation); | ||||
| 
 | ||||
|    /**
 | ||||
|     * @brief Selects a radar product. | ||||
|     * | ||||
|     * @param [in] group Radar product group | ||||
|     * @param [in] product Radar product name | ||||
|     * @param [in] productCode Radar product code (optional) | ||||
|     * @paran [in] time Product time. Default is the latest available. | ||||
|     */ | ||||
|    void SelectRadarProduct(common::RadarProductGroup group, | ||||
|                            const std::string&        product, | ||||
|                            int16_t                   productCode); | ||||
|                            std::int16_t              productCode      = 0, | ||||
|                            std::chrono::system_clock::time_point time = {}); | ||||
| 
 | ||||
|    void SelectRadarProduct(std::shared_ptr<types::RadarProductRecord> record); | ||||
| 
 | ||||
|    /**
 | ||||
|  |  | |||
|  | @ -105,10 +105,16 @@ RadarProductModelImpl::RadarProductModelImpl(RadarProductModel* self) : | |||
|                   } | ||||
|                } | ||||
| 
 | ||||
|                // Create leaf item for product time
 | ||||
|                model_->AppendRow(productItem, | ||||
|                                  new TreeItem {QString::fromStdString( | ||||
|                                     util::TimeString(latestTime))}); | ||||
|                // Find existing time item (e.g., 2023-04-10 10:11:12)
 | ||||
|                const QString timeString = | ||||
|                   QString::fromStdString(util::TimeString(latestTime)); | ||||
|                TreeItem* timeItem = productItem->FindChild(0, timeString); | ||||
| 
 | ||||
|                if (timeItem == nullptr) | ||||
|                { | ||||
|                   // Create leaf item for product time
 | ||||
|                   model_->AppendRow(productItem, new TreeItem {timeString}); | ||||
|                } | ||||
|             }, | ||||
|             Qt::QueuedConnection); | ||||
|       }); | ||||
|  |  | |||
|  | @ -119,8 +119,7 @@ void Level2ProductsWidgetImpl::UpdateProductSelection( | |||
| { | ||||
|    const std::string& productName = common::GetLevel2Name(product); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  productButtons_.cbegin(), | ||||
|    std::for_each(productButtons_.cbegin(), | ||||
|                  productButtons_.cend(), | ||||
|                  [&](auto& toolButton) | ||||
|                  { | ||||
|  |  | |||
|  | @ -51,7 +51,6 @@ public: | |||
| 
 | ||||
|    void NormalizeElevationButtons(); | ||||
|    void SelectElevation(float elevation); | ||||
|    void UpdateSettings(); | ||||
| 
 | ||||
|    Level2SettingsWidget* self_; | ||||
|    QLayout*              layout_; | ||||
|  | @ -135,8 +134,7 @@ void Level2SettingsWidget::UpdateElevationSelection(float elevation) | |||
|    QString buttonText {QString::number(elevation, 'f', 1) + | ||||
|                        common::Characters::DEGREE}; | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  p->elevationButtons_.cbegin(), | ||||
|    std::for_each(p->elevationButtons_.cbegin(), | ||||
|                  p->elevationButtons_.cend(), | ||||
|                  [&](auto& toolButton) | ||||
|                  { | ||||
|  |  | |||
|  | @ -258,8 +258,7 @@ void Level3ProductsWidgetImpl::UpdateCategorySelection( | |||
| { | ||||
|    const std::string& categoryName = common::GetLevel3CategoryName(category); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  categoryButtons_.cbegin(), | ||||
|    std::for_each(categoryButtons_.cbegin(), | ||||
|                  categoryButtons_.cend(), | ||||
|                  [&](auto& toolButton) | ||||
|                  { | ||||
|  |  | |||
|  | @ -44,7 +44,6 @@ public: | |||
|    explicit Level2ProductViewImpl(common::Level2Product product) : | ||||
|        product_ {product}, | ||||
|        selectedElevation_ {0.0f}, | ||||
|        selectedTime_ {}, | ||||
|        elevationScan_ {nullptr}, | ||||
|        momentDataBlock0_ {nullptr}, | ||||
|        latitude_ {}, | ||||
|  | @ -72,8 +71,7 @@ public: | |||
|    common::Level2Product      product_; | ||||
|    wsr88d::rda::DataBlockType dataBlockType_; | ||||
| 
 | ||||
|    float                                 selectedElevation_; | ||||
|    std::chrono::system_clock::time_point selectedTime_; | ||||
|    float selectedElevation_; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rda::ElevationScan>   elevationScan_; | ||||
|    std::shared_ptr<wsr88d::rda::MomentDataBlock> momentDataBlock0_; | ||||
|  | @ -108,9 +106,36 @@ Level2ProductView::Level2ProductView( | |||
|     RadarProductView(radarProductManager), | ||||
|     p(std::make_unique<Level2ProductViewImpl>(product)) | ||||
| { | ||||
|    ConnectRadarProductManager(); | ||||
| } | ||||
| Level2ProductView::~Level2ProductView() = default; | ||||
| 
 | ||||
| void Level2ProductView::ConnectRadarProductManager() | ||||
| { | ||||
|    connect(radar_product_manager().get(), | ||||
|            &manager::RadarProductManager::DataReloaded, | ||||
|            this, | ||||
|            [this](std::shared_ptr<types::RadarProductRecord> record) | ||||
|            { | ||||
|               if (record->radar_product_group() == | ||||
|                      common::RadarProductGroup::Level2 && | ||||
|                   record->time() == selected_time()) | ||||
|               { | ||||
|                  // If the data associated with the currently selected time is
 | ||||
|                  // reloaded, update the view
 | ||||
|                  Update(); | ||||
|               } | ||||
|            }); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductView::DisconnectRadarProductManager() | ||||
| { | ||||
|    disconnect(radar_product_manager().get(), | ||||
|               &manager::RadarProductManager::DataReloaded, | ||||
|               this, | ||||
|               nullptr); | ||||
| } | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| Level2ProductView::color_table() const | ||||
| { | ||||
|  | @ -243,11 +268,6 @@ void Level2ProductView::SelectProduct(const std::string& productName) | |||
|    p->SetProduct(productName); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductView::SelectTime(std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    p->selectedTime_ = time; | ||||
| } | ||||
| 
 | ||||
| void Level2ProductViewImpl::SetProduct(const std::string& productName) | ||||
| { | ||||
|    SetProduct(common::GetLevel2Product(productName)); | ||||
|  | @ -376,9 +396,18 @@ void Level2ProductView::ComputeSweep() | |||
|       radar_product_manager(); | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rda::ElevationScan> radarData; | ||||
|    std::tie(radarData, p->elevationCut_, p->elevationCuts_) = | ||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||
|    std::chrono::system_clock::time_point       foundTime; | ||||
|    std::tie(radarData, p->elevationCut_, p->elevationCuts_, foundTime) = | ||||
|       radarProductManager->GetLevel2Data( | ||||
|          p->dataBlockType_, p->selectedElevation_, p->selectedTime_); | ||||
|          p->dataBlockType_, p->selectedElevation_, requestedTime); | ||||
| 
 | ||||
|    // If a different time was found than what was requested, update it
 | ||||
|    if (requestedTime != foundTime) | ||||
|    { | ||||
|       SelectTime(foundTime); | ||||
|    } | ||||
| 
 | ||||
|    if (radarData == nullptr || radarData == p->elevationScan_) | ||||
|    { | ||||
|       return; | ||||
|  |  | |||
|  | @ -28,31 +28,34 @@ public: | |||
|    ~Level2ProductView(); | ||||
| 
 | ||||
|    const std::vector<boost::gil::rgba8_pixel_t>& color_table() const override; | ||||
|    uint16_t                              color_table_min() const override; | ||||
|    uint16_t                              color_table_max() const override; | ||||
|    std::uint16_t                         color_table_min() const override; | ||||
|    std::uint16_t                         color_table_max() const override; | ||||
|    float                                 elevation() const override; | ||||
|    float                                 range() const override; | ||||
|    std::chrono::system_clock::time_point sweep_time() const override; | ||||
|    uint16_t                              vcp() const override; | ||||
|    std::uint16_t                         vcp() const override; | ||||
|    const std::vector<float>&             vertices() const override; | ||||
| 
 | ||||
|    void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override; | ||||
|    void SelectElevation(float elevation) override; | ||||
|    void SelectProduct(const std::string& productName) override; | ||||
|    void SelectTime(std::chrono::system_clock::time_point time) override; | ||||
|    void Update() override; | ||||
| 
 | ||||
|    common::RadarProductGroup GetRadarProductGroup() const override; | ||||
|    std::string               GetRadarProductName() const override; | ||||
|    std::vector<float>        GetElevationCuts() const override; | ||||
|    std::tuple<const void*, size_t, size_t> GetMomentData() const override; | ||||
|    std::tuple<const void*, size_t, size_t> GetCfpMomentData() const override; | ||||
|    std::tuple<const void*, std::size_t, std::size_t> | ||||
|    GetMomentData() const override; | ||||
|    std::tuple<const void*, std::size_t, std::size_t> | ||||
|    GetCfpMomentData() const override; | ||||
| 
 | ||||
|    static std::shared_ptr<Level2ProductView> | ||||
|    Create(common::Level2Product                         product, | ||||
|           std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
| 
 | ||||
| protected: | ||||
|    void ConnectRadarProductManager() override; | ||||
|    void DisconnectRadarProductManager() override; | ||||
|    void UpdateColorTable() override; | ||||
| 
 | ||||
| protected slots: | ||||
|  |  | |||
|  | @ -59,9 +59,37 @@ Level3ProductView::Level3ProductView( | |||
|     RadarProductView(radarProductManager), | ||||
|     p(std::make_unique<Level3ProductViewImpl>(product)) | ||||
| { | ||||
|    ConnectRadarProductManager(); | ||||
| } | ||||
| Level3ProductView::~Level3ProductView() = default; | ||||
| 
 | ||||
| void Level3ProductView::ConnectRadarProductManager() | ||||
| { | ||||
|    connect(radar_product_manager().get(), | ||||
|            &manager::RadarProductManager::DataReloaded, | ||||
|            this, | ||||
|            [this](std::shared_ptr<types::RadarProductRecord> record) | ||||
|            { | ||||
|               if (record->radar_product_group() == | ||||
|                      common::RadarProductGroup::Level3 && | ||||
|                   record->radar_product() == p->product_ && | ||||
|                   record->time() == selected_time()) | ||||
|               { | ||||
|                  // If the data associated with the currently selected time is
 | ||||
|                  // reloaded, update the view
 | ||||
|                  Update(); | ||||
|               } | ||||
|            }); | ||||
| } | ||||
| 
 | ||||
| void Level3ProductView::DisconnectRadarProductManager() | ||||
| { | ||||
|    disconnect(radar_product_manager().get(), | ||||
|               &manager::RadarProductManager::DataReloaded, | ||||
|               this, | ||||
|               nullptr); | ||||
| } | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| Level3ProductView::color_table() const | ||||
| { | ||||
|  |  | |||
|  | @ -28,8 +28,8 @@ public: | |||
|    virtual ~Level3ProductView(); | ||||
| 
 | ||||
|    const std::vector<boost::gil::rgba8_pixel_t>& color_table() const override; | ||||
|    uint16_t color_table_min() const override; | ||||
|    uint16_t color_table_max() const override; | ||||
|    std::uint16_t color_table_min() const override; | ||||
|    std::uint16_t color_table_max() const override; | ||||
| 
 | ||||
|    void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override; | ||||
|    void Update() override; | ||||
|  | @ -45,6 +45,8 @@ protected: | |||
|    void set_graphic_product_message( | ||||
|       std::shared_ptr<wsr88d::rpg::GraphicProductMessage> gpm); | ||||
| 
 | ||||
|    void ConnectRadarProductManager() override; | ||||
|    void DisconnectRadarProductManager() override; | ||||
|    void UpdateColorTable() override; | ||||
| 
 | ||||
| private: | ||||
|  |  | |||
|  | @ -27,25 +27,18 @@ class Level3RadialViewImpl | |||
| { | ||||
| public: | ||||
|    explicit Level3RadialViewImpl() : | ||||
|        selectedTime_ {}, | ||||
|        latitude_ {}, | ||||
|        longitude_ {}, | ||||
|        range_ {}, | ||||
|        vcp_ {}, | ||||
|        sweepTime_ {} | ||||
|        latitude_ {}, longitude_ {}, range_ {}, vcp_ {}, sweepTime_ {} | ||||
|    { | ||||
|    } | ||||
|    ~Level3RadialViewImpl() = default; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point selectedTime_; | ||||
|    std::vector<float>        vertices_; | ||||
|    std::vector<std::uint8_t> dataMoments8_; | ||||
| 
 | ||||
|    std::vector<float>   vertices_; | ||||
|    std::vector<uint8_t> dataMoments8_; | ||||
| 
 | ||||
|    float    latitude_; | ||||
|    float    longitude_; | ||||
|    float    range_; | ||||
|    uint16_t vcp_; | ||||
|    float         latitude_; | ||||
|    float         longitude_; | ||||
|    float         range_; | ||||
|    std::uint16_t vcp_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point sweepTime_; | ||||
| }; | ||||
|  | @ -92,11 +85,6 @@ std::tuple<const void*, size_t, size_t> Level3RadialView::GetMomentData() const | |||
|    return std::tie(data, dataSize, componentSize); | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::SelectTime(std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    p->selectedTime_ = time; | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::ComputeSweep() | ||||
| { | ||||
|    logger_->debug("ComputeSweep()"); | ||||
|  | @ -109,9 +97,18 @@ void Level3RadialView::ComputeSweep() | |||
|       radar_product_manager(); | ||||
| 
 | ||||
|    // Retrieve message from Radar Product Manager
 | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message = | ||||
|       radarProductManager->GetLevel3Data(GetRadarProductName(), | ||||
|                                          p->selectedTime_); | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||
|    std::chrono::system_clock::time_point       foundTime; | ||||
|    std::tie(message, foundTime) = | ||||
|       radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); | ||||
| 
 | ||||
|    // If a different time was found than what was requested, update it
 | ||||
|    if (requestedTime != foundTime) | ||||
|    { | ||||
|       SelectTime(foundTime); | ||||
|    } | ||||
| 
 | ||||
|    if (message == nullptr) | ||||
|    { | ||||
|       logger_->debug("Level 3 data not found"); | ||||
|  |  | |||
|  | @ -27,12 +27,11 @@ public: | |||
| 
 | ||||
|    float                                 range() const override; | ||||
|    std::chrono::system_clock::time_point sweep_time() const override; | ||||
|    uint16_t                              vcp() const override; | ||||
|    std::uint16_t                         vcp() const override; | ||||
|    const std::vector<float>&             vertices() const override; | ||||
| 
 | ||||
|    void SelectTime(std::chrono::system_clock::time_point time) override; | ||||
| 
 | ||||
|    std::tuple<const void*, size_t, size_t> GetMomentData() const override; | ||||
|    std::tuple<const void*, std::size_t, std::size_t> | ||||
|    GetMomentData() const override; | ||||
| 
 | ||||
|    static std::shared_ptr<Level3RadialView> | ||||
|    Create(const std::string&                            product, | ||||
|  |  | |||
|  | @ -27,18 +27,11 @@ class Level3RasterViewImpl | |||
| { | ||||
| public: | ||||
|    explicit Level3RasterViewImpl() : | ||||
|        selectedTime_ {}, | ||||
|        latitude_ {}, | ||||
|        longitude_ {}, | ||||
|        range_ {}, | ||||
|        vcp_ {}, | ||||
|        sweepTime_ {} | ||||
|        latitude_ {}, longitude_ {}, range_ {}, vcp_ {}, sweepTime_ {} | ||||
|    { | ||||
|    } | ||||
|    ~Level3RasterViewImpl() = default; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point selectedTime_; | ||||
| 
 | ||||
|    std::vector<float>   vertices_; | ||||
|    std::vector<uint8_t> dataMoments8_; | ||||
| 
 | ||||
|  | @ -92,11 +85,6 @@ std::tuple<const void*, size_t, size_t> Level3RasterView::GetMomentData() const | |||
|    return std::tie(data, dataSize, componentSize); | ||||
| } | ||||
| 
 | ||||
| void Level3RasterView::SelectTime(std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    p->selectedTime_ = time; | ||||
| } | ||||
| 
 | ||||
| void Level3RasterView::ComputeSweep() | ||||
| { | ||||
|    logger_->debug("ComputeSweep()"); | ||||
|  | @ -109,9 +97,18 @@ void Level3RasterView::ComputeSweep() | |||
|       radar_product_manager(); | ||||
| 
 | ||||
|    // Retrieve message from Radar Product Manager
 | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message = | ||||
|       radarProductManager->GetLevel3Data(GetRadarProductName(), | ||||
|                                          p->selectedTime_); | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||
|    std::chrono::system_clock::time_point       foundTime; | ||||
|    std::tie(message, foundTime) = | ||||
|       radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); | ||||
| 
 | ||||
|    // If a different time was found than what was requested, update it
 | ||||
|    if (requestedTime != foundTime) | ||||
|    { | ||||
|       SelectTime(foundTime); | ||||
|    } | ||||
| 
 | ||||
|    if (message == nullptr) | ||||
|    { | ||||
|       logger_->debug("Level 3 data not found"); | ||||
|  |  | |||
|  | @ -27,12 +27,11 @@ public: | |||
| 
 | ||||
|    float                                 range() const override; | ||||
|    std::chrono::system_clock::time_point sweep_time() const override; | ||||
|    uint16_t                              vcp() const override; | ||||
|    std::uint16_t                         vcp() const override; | ||||
|    const std::vector<float>&             vertices() const override; | ||||
| 
 | ||||
|    void SelectTime(std::chrono::system_clock::time_point time) override; | ||||
| 
 | ||||
|    std::tuple<const void*, size_t, size_t> GetMomentData() const override; | ||||
|    std::tuple<const void*, std::size_t, std::size_t> | ||||
|    GetMomentData() const override; | ||||
| 
 | ||||
|    static std::shared_ptr<Level3RasterView> | ||||
|    Create(const std::string&                            product, | ||||
|  |  | |||
|  | @ -16,12 +16,12 @@ static const std::string logPrefix_ = "scwx::qt::view::radar_product_view"; | |||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||
| 
 | ||||
| // Default color table should be transparent to prevent flicker
 | ||||
| static const std::vector<boost::gil::rgba8_pixel_t> DEFAULT_COLOR_TABLE = { | ||||
| static const std::vector<boost::gil::rgba8_pixel_t> kDefaultColorTable_ = { | ||||
|    boost::gil::rgba8_pixel_t(0, 128, 0, 0), | ||||
|    boost::gil::rgba8_pixel_t(255, 192, 0, 0), | ||||
|    boost::gil::rgba8_pixel_t(255, 0, 0, 0)}; | ||||
| static const uint16_t DEFAULT_COLOR_TABLE_MIN = 2u; | ||||
| static const uint16_t DEFAULT_COLOR_TABLE_MAX = 255u; | ||||
| static const std::uint16_t kDefaultColorTableMin_ = 2u; | ||||
| static const std::uint16_t kDefaultColorTableMax_ = 255u; | ||||
| 
 | ||||
| class RadarProductViewImpl | ||||
| { | ||||
|  | @ -30,6 +30,7 @@ public: | |||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|        initialized_ {false}, | ||||
|        sweepMutex_ {}, | ||||
|        selectedTime_ {}, | ||||
|        radarProductManager_ {radarProductManager} | ||||
|    { | ||||
|    } | ||||
|  | @ -38,6 +39,8 @@ public: | |||
|    bool       initialized_; | ||||
|    std::mutex sweepMutex_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point selectedTime_; | ||||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||
| }; | ||||
| 
 | ||||
|  | @ -49,17 +52,17 @@ RadarProductView::~RadarProductView() = default; | |||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| RadarProductView::color_table() const | ||||
| { | ||||
|    return DEFAULT_COLOR_TABLE; | ||||
|    return kDefaultColorTable_; | ||||
| } | ||||
| 
 | ||||
| uint16_t RadarProductView::color_table_min() const | ||||
| std::uint16_t RadarProductView::color_table_min() const | ||||
| { | ||||
|    return DEFAULT_COLOR_TABLE_MIN; | ||||
|    return kDefaultColorTableMin_; | ||||
| } | ||||
| 
 | ||||
| uint16_t RadarProductView::color_table_max() const | ||||
| std::uint16_t RadarProductView::color_table_max() const | ||||
| { | ||||
|    return DEFAULT_COLOR_TABLE_MAX; | ||||
|    return kDefaultColorTableMax_; | ||||
| } | ||||
| 
 | ||||
| float RadarProductView::elevation() const | ||||
|  | @ -78,6 +81,11 @@ float RadarProductView::range() const | |||
|    return 0.0f; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point RadarProductView::selected_time() const | ||||
| { | ||||
|    return p->selectedTime_; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point RadarProductView::sweep_time() const | ||||
| { | ||||
|    return {}; | ||||
|  | @ -91,7 +99,9 @@ std::mutex& RadarProductView::sweep_mutex() | |||
| void RadarProductView::set_radar_product_manager( | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) | ||||
| { | ||||
|    DisconnectRadarProductManager(); | ||||
|    p->radarProductManager_ = radarProductManager; | ||||
|    ConnectRadarProductManager(); | ||||
| } | ||||
| 
 | ||||
| void RadarProductView::Initialize() | ||||
|  | @ -103,6 +113,11 @@ void RadarProductView::Initialize() | |||
| 
 | ||||
| void RadarProductView::SelectElevation(float /*elevation*/) {} | ||||
| 
 | ||||
| void RadarProductView::SelectTime(std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    p->selectedTime_ = time; | ||||
| } | ||||
| 
 | ||||
| bool RadarProductView::IsInitialized() const | ||||
| { | ||||
|    return p->initialized_; | ||||
|  | @ -113,12 +128,12 @@ std::vector<float> RadarProductView::GetElevationCuts() const | |||
|    return {}; | ||||
| } | ||||
| 
 | ||||
| std::tuple<const void*, size_t, size_t> | ||||
| std::tuple<const void*, std::size_t, std::size_t> | ||||
| RadarProductView::GetCfpMomentData() const | ||||
| { | ||||
|    const void* data          = nullptr; | ||||
|    size_t      dataSize      = 0; | ||||
|    size_t      componentSize = 0; | ||||
|    std::size_t dataSize      = 0; | ||||
|    std::size_t componentSize = 0; | ||||
| 
 | ||||
|    return std::tie(data, dataSize, componentSize); | ||||
| } | ||||
|  |  | |||
|  | @ -30,15 +30,16 @@ public: | |||
|    virtual ~RadarProductView(); | ||||
| 
 | ||||
|    virtual const std::vector<boost::gil::rgba8_pixel_t>& color_table() const; | ||||
|    virtual uint16_t                              color_table_min() const; | ||||
|    virtual uint16_t                              color_table_max() const; | ||||
|    virtual std::uint16_t                         color_table_min() const; | ||||
|    virtual std::uint16_t                         color_table_max() const; | ||||
|    virtual float                                 elevation() const; | ||||
|    virtual float                                 range() const; | ||||
|    virtual std::chrono::system_clock::time_point sweep_time() const; | ||||
|    virtual uint16_t                              vcp() const      = 0; | ||||
|    virtual std::uint16_t                         vcp() const      = 0; | ||||
|    virtual const std::vector<float>&             vertices() const = 0; | ||||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radar_product_manager() const; | ||||
|    std::chrono::system_clock::time_point         selected_time() const; | ||||
|    std::mutex&                                   sweep_mutex(); | ||||
| 
 | ||||
|    void set_radar_product_manager( | ||||
|  | @ -48,20 +49,24 @@ public: | |||
|    virtual void | ||||
|    LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) = 0; | ||||
|    virtual void SelectElevation(float elevation); | ||||
|    virtual void SelectProduct(const std::string& productName)          = 0; | ||||
|    virtual void SelectTime(std::chrono::system_clock::time_point time) = 0; | ||||
|    virtual void Update()                                               = 0; | ||||
|    virtual void SelectProduct(const std::string& productName) = 0; | ||||
|    void         SelectTime(std::chrono::system_clock::time_point time); | ||||
|    virtual void Update() = 0; | ||||
| 
 | ||||
|    bool IsInitialized() const; | ||||
| 
 | ||||
|    virtual common::RadarProductGroup GetRadarProductGroup() const = 0; | ||||
|    virtual std::string               GetRadarProductName() const  = 0; | ||||
|    virtual std::vector<float>        GetElevationCuts() const; | ||||
|    virtual std::tuple<const void*, size_t, size_t> GetMomentData() const = 0; | ||||
|    virtual std::tuple<const void*, size_t, size_t> GetCfpMomentData() const; | ||||
|    virtual std::tuple<const void*, std::size_t, std::size_t> | ||||
|    GetMomentData() const = 0; | ||||
|    virtual std::tuple<const void*, std::size_t, std::size_t> | ||||
|    GetCfpMomentData() const; | ||||
| 
 | ||||
| protected: | ||||
|    virtual void UpdateColorTable() = 0; | ||||
|    virtual void ConnectRadarProductManager()    = 0; | ||||
|    virtual void DisconnectRadarProductManager() = 0; | ||||
|    virtual void UpdateColorTable()              = 0; | ||||
| 
 | ||||
| protected slots: | ||||
|    virtual void ComputeSweep(); | ||||
|  |  | |||
|  | @ -8,10 +8,10 @@ namespace scwx | |||
| namespace util | ||||
| { | ||||
| 
 | ||||
| template<class Key, class T, class ReturnType = std::optional<T>> | ||||
| ReturnType GetBoundedElement(std::map<Key, T>& map, Key key) | ||||
| template<class Key, class T, class ReturnType = std::map<Key, T>::const_pointer> | ||||
| ReturnType GetBoundedElementPointer(std::map<Key, T>& map, const Key& key) | ||||
| { | ||||
|    ReturnType element; | ||||
|    ReturnType elementPtr {nullptr}; | ||||
| 
 | ||||
|    // Find the first element greater than the key requested
 | ||||
|    auto it = map.upper_bound(key); | ||||
|  | @ -24,26 +24,42 @@ ReturnType GetBoundedElement(std::map<Key, T>& map, Key key) | |||
|       { | ||||
|          // Get the element immediately preceding, this the element we are
 | ||||
|          // looking for
 | ||||
|          element = (--it)->second; | ||||
|          elementPtr = &(*(--it)); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          // The current element is a good substitute
 | ||||
|          element = it->second; | ||||
|          elementPtr = &(*it); | ||||
|       } | ||||
|    } | ||||
|    else if (map.size() > 0) | ||||
|    { | ||||
|       // An element with a key greater was not found. If it exists, it must be
 | ||||
|       // the last element.
 | ||||
|       element = map.rbegin()->second; | ||||
|       elementPtr = &(*map.rbegin()); | ||||
|    } | ||||
| 
 | ||||
|    return elementPtr; | ||||
| } | ||||
| 
 | ||||
| template<class Key, class T, class ReturnType = std::optional<T>> | ||||
| ReturnType GetBoundedElement(std::map<Key, T>& map, const Key& key) | ||||
| { | ||||
|    ReturnType element; | ||||
| 
 | ||||
|    typename std::map<Key, T>::pointer elementPtr = | ||||
|       GetBoundedElementPointer<Key, T, typename std::map<Key, T>::pointer>(map, | ||||
|                                                                            key); | ||||
|    if (elementPtr != nullptr) | ||||
|    { | ||||
|       element = elementPtr->second; | ||||
|    } | ||||
| 
 | ||||
|    return element; | ||||
| } | ||||
| 
 | ||||
| template<class Key, class T> | ||||
| inline T GetBoundedElementValue(std::map<Key, T>& map, Key key) | ||||
| inline T GetBoundedElementValue(std::map<Key, T>& map, const Key& key) | ||||
| { | ||||
|    return GetBoundedElement<Key, T, T>(map, key); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat