Merge pull request #30 from dpaulat/feature/garbage-collect

Feature/garbage collect
This commit is contained in:
Dan Paulat 2023-04-13 05:55:26 -05:00 committed by GitHub
commit a851918ec2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 639 additions and 166 deletions

View file

@ -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();

View file

@ -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;

View file

@ -83,6 +83,8 @@
<string>&amp;Debug</string>
</property>
<addaction name="actionImGuiDebug"/>
<addaction name="separator"/>
<addaction name="actionDumpRadarProductRecords"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
@ -397,6 +399,11 @@
<string>&amp;GitHub Repository</string>
</property>
</action>
<action name="actionDumpRadarProductRecords">
<property name="text">
<string>Dump Radar &amp;Product Records</string>
</property>
</action>
</widget>
<resources>
<include location="../../../../scwx-qt.qrc"/>

View file

@ -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;
@ -54,7 +57,7 @@ static constexpr std::chrono::seconds kRetryInterval_ {15};
static std::unordered_map<std::string, std::weak_ptr<RadarProductManager>>
instanceMap_;
static std::mutex instanceMutex_;
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,
@ -200,9 +214,11 @@ public:
std::vector<float> coordinates1Degree_;
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());
@ -561,11 +694,14 @@ void RadarProductManagerImpl::LoadProviderData(
auto it = recordMap.find(time);
if (it != recordMap.cend())
{
existingRecord = it->second.lock();
if (existingRecord != nullptr)
{
logger_->debug(
"Data previously loaded, loading from data cache");
existingRecord = it->second;
}
}
}
@ -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)
{
record = nullptr;
if (time == std::chrono::system_clock::time_point {} ||
time == recordPtr->first)
{
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,
@ -799,16 +1002,23 @@ RadarProductManagerImpl::StoreRadarProductRecord(
auto it = level2ProductRecords_.find(timeInSeconds);
if (it != level2ProductRecords_.cend())
{
storedRecord = it->second.lock();
if (storedRecord != nullptr)
{
logger_->debug(
"Level 2 product previously loaded, loading from cache");
storedRecord = it->second;
}
else
}
if (storedRecord == nullptr)
{
storedRecord = record;
level2ProductRecords_[timeInSeconds] = record;
}
UpdateRecentRecords(level2ProductRecentRecords_, storedRecord);
}
else if (record->radar_product_group() == common::RadarProductGroup::Level3)
{
@ -818,24 +1028,59 @@ RadarProductManagerImpl::StoreRadarProductRecord(
auto it = productMap.find(timeInSeconds);
if (it != productMap.cend())
{
storedRecord = it->second.lock();
if (storedRecord != nullptr)
{
logger_->debug(
"Level 3 product previously loaded, loading from cache");
storedRecord = it->second;
}
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);

View file

@ -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,

View file

@ -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_);
}
}
}

View file

@ -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);
/**

View file

@ -105,10 +105,16 @@ RadarProductModelImpl::RadarProductModelImpl(RadarProductModel* self) :
}
}
// 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 {QString::fromStdString(
util::TimeString(latestTime))});
model_->AppendRow(productItem, new TreeItem {timeString});
}
},
Qt::QueuedConnection);
});

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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)
{

View file

@ -44,7 +44,6 @@ public:
explicit Level2ProductViewImpl(common::Level2Product product) :
product_ {product},
selectedElevation_ {0.0f},
selectedTime_ {},
elevationScan_ {nullptr},
momentDataBlock0_ {nullptr},
latitude_ {},
@ -73,7 +72,6 @@ public:
wsr88d::rda::DataBlockType dataBlockType_;
float selectedElevation_;
std::chrono::system_clock::time_point selectedTime_;
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;

View file

@ -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:

View file

@ -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
{

View file

@ -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:

View file

@ -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<uint8_t> dataMoments8_;
std::vector<std::uint8_t> dataMoments8_;
float latitude_;
float longitude_;
float range_;
uint16_t vcp_;
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");

View file

@ -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,

View file

@ -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");

View file

@ -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,

View file

@ -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);
}

View file

@ -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(
@ -49,7 +50,7 @@ public:
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;
void SelectTime(std::chrono::system_clock::time_point time);
virtual void Update() = 0;
bool IsInitialized() const;
@ -57,10 +58,14 @@ public:
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 ConnectRadarProductManager() = 0;
virtual void DisconnectRadarProductManager() = 0;
virtual void UpdateColorTable() = 0;
protected slots:

View file

@ -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);
}