mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 23:30:04 +00:00
Merge pull request #404 from AdenKoperczak/level_2_chunks
Level 2 chunks
This commit is contained in:
commit
58f2609fe7
22 changed files with 1509 additions and 202 deletions
|
|
@ -963,6 +963,13 @@ void MainWindowImpl::ConnectMapSignals()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
connect(
|
||||||
|
mapWidget,
|
||||||
|
&map::MapWidget::IncomingLevel2ElevationChanged,
|
||||||
|
this,
|
||||||
|
[this](std::optional<float>)
|
||||||
|
{ level2SettingsWidget_->UpdateSettings(activeMap_); },
|
||||||
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <scwx/qt/types/time_types.hpp>
|
#include <scwx/qt/types/time_types.hpp>
|
||||||
#include <scwx/qt/util/geographic_lib.hpp>
|
#include <scwx/qt/util/geographic_lib.hpp>
|
||||||
#include <scwx/common/constants.hpp>
|
#include <scwx/common/constants.hpp>
|
||||||
|
#include <scwx/provider/aws_level2_chunks_data_provider.hpp>
|
||||||
#include <scwx/provider/nexrad_data_provider_factory.hpp>
|
#include <scwx/provider/nexrad_data_provider_factory.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/map.hpp>
|
#include <scwx/util/map.hpp>
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
# pragma warning(push, 0)
|
# pragma warning(push, 0)
|
||||||
|
|
@ -66,7 +68,9 @@ static const std::string kDefaultLevel3Product_ {"N0B"};
|
||||||
static constexpr std::size_t kTimerPlaces_ {6u};
|
static constexpr std::size_t kTimerPlaces_ {6u};
|
||||||
|
|
||||||
static constexpr std::chrono::seconds kFastRetryInterval_ {15};
|
static constexpr std::chrono::seconds kFastRetryInterval_ {15};
|
||||||
|
static constexpr std::chrono::seconds kFastRetryIntervalChunks_ {3};
|
||||||
static constexpr std::chrono::seconds kSlowRetryInterval_ {120};
|
static constexpr std::chrono::seconds kSlowRetryInterval_ {120};
|
||||||
|
static constexpr std::chrono::seconds kSlowRetryIntervalChunks_ {20};
|
||||||
|
|
||||||
static std::unordered_map<std::string, std::weak_ptr<RadarProductManager>>
|
static std::unordered_map<std::string, std::weak_ptr<RadarProductManager>>
|
||||||
instanceMap_;
|
instanceMap_;
|
||||||
|
|
@ -84,21 +88,25 @@ class ProviderManager : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ProviderManager(RadarProductManager* self,
|
explicit ProviderManager(RadarProductManager* self,
|
||||||
const std::string& radarId,
|
std::string radarId,
|
||||||
common::RadarProductGroup group) :
|
|
||||||
ProviderManager(self, radarId, group, "???")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
explicit ProviderManager(RadarProductManager* self,
|
|
||||||
const std::string& radarId,
|
|
||||||
common::RadarProductGroup group,
|
common::RadarProductGroup group,
|
||||||
const std::string& product) :
|
std::string product = "???",
|
||||||
radarId_ {radarId}, group_ {group}, product_ {product}
|
bool isChunks = false) :
|
||||||
|
radarId_ {std::move(radarId)},
|
||||||
|
group_ {group},
|
||||||
|
product_ {std::move(product)},
|
||||||
|
isChunks_ {isChunks}
|
||||||
{
|
{
|
||||||
connect(this,
|
connect(this,
|
||||||
&ProviderManager::NewDataAvailable,
|
&ProviderManager::NewDataAvailable,
|
||||||
self,
|
self,
|
||||||
&RadarProductManager::NewDataAvailable);
|
[this, self](common::RadarProductGroup group,
|
||||||
|
const std::string& product,
|
||||||
|
std::chrono::system_clock::time_point latestTime)
|
||||||
|
{
|
||||||
|
Q_EMIT self->NewDataAvailable(
|
||||||
|
group, product, isChunks_, latestTime);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
~ProviderManager() { threadPool_.join(); };
|
~ProviderManager() { threadPool_.join(); };
|
||||||
|
|
||||||
|
|
@ -111,10 +119,12 @@ public:
|
||||||
const std::string radarId_;
|
const std::string radarId_;
|
||||||
const common::RadarProductGroup group_;
|
const common::RadarProductGroup group_;
|
||||||
const std::string product_;
|
const std::string product_;
|
||||||
|
const bool isChunks_;
|
||||||
bool refreshEnabled_ {false};
|
bool refreshEnabled_ {false};
|
||||||
boost::asio::steady_timer refreshTimer_ {threadPool_};
|
boost::asio::steady_timer refreshTimer_ {threadPool_};
|
||||||
std::mutex refreshTimerMutex_ {};
|
std::mutex refreshTimerMutex_ {};
|
||||||
std::shared_ptr<provider::NexradDataProvider> provider_ {nullptr};
|
std::shared_ptr<provider::NexradDataProvider> provider_ {nullptr};
|
||||||
|
size_t refreshCount_ {0};
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void NewDataAvailable(common::RadarProductGroup group,
|
void NewDataAvailable(common::RadarProductGroup group,
|
||||||
|
|
@ -133,7 +143,9 @@ public:
|
||||||
level3ProductsInitialized_ {false},
|
level3ProductsInitialized_ {false},
|
||||||
radarSite_ {config::RadarSite::Get(radarId)},
|
radarSite_ {config::RadarSite::Get(radarId)},
|
||||||
level2ProviderManager_ {std::make_shared<ProviderManager>(
|
level2ProviderManager_ {std::make_shared<ProviderManager>(
|
||||||
self_, radarId_, common::RadarProductGroup::Level2)}
|
self_, radarId_, common::RadarProductGroup::Level2)},
|
||||||
|
level2ChunksProviderManager_ {std::make_shared<ProviderManager>(
|
||||||
|
self_, radarId_, common::RadarProductGroup::Level2, "???", true)}
|
||||||
{
|
{
|
||||||
if (radarSite_ == nullptr)
|
if (radarSite_ == nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -143,10 +155,24 @@ public:
|
||||||
|
|
||||||
level2ProviderManager_->provider_ =
|
level2ProviderManager_->provider_ =
|
||||||
provider::NexradDataProviderFactory::CreateLevel2DataProvider(radarId);
|
provider::NexradDataProviderFactory::CreateLevel2DataProvider(radarId);
|
||||||
|
level2ChunksProviderManager_->provider_ =
|
||||||
|
provider::NexradDataProviderFactory::CreateLevel2ChunksDataProvider(
|
||||||
|
radarId);
|
||||||
|
|
||||||
|
auto level2ChunksProvider =
|
||||||
|
std::dynamic_pointer_cast<provider::AwsLevel2ChunksDataProvider>(
|
||||||
|
level2ChunksProviderManager_->provider_);
|
||||||
|
if (level2ChunksProvider != nullptr)
|
||||||
|
{
|
||||||
|
level2ChunksProvider->SetLevel2DataProvider(
|
||||||
|
std::dynamic_pointer_cast<provider::AwsLevel2DataProvider>(
|
||||||
|
level2ProviderManager_->provider_));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
~RadarProductManagerImpl()
|
~RadarProductManagerImpl()
|
||||||
{
|
{
|
||||||
level2ProviderManager_->Disable();
|
level2ProviderManager_->Disable();
|
||||||
|
level2ChunksProviderManager_->Disable();
|
||||||
|
|
||||||
std::shared_lock lock(level3ProviderManagerMutex_);
|
std::shared_lock lock(level3ProviderManagerMutex_);
|
||||||
std::for_each(std::execution::par_unseq,
|
std::for_each(std::execution::par_unseq,
|
||||||
|
|
@ -172,8 +198,9 @@ public:
|
||||||
std::shared_ptr<ProviderManager>
|
std::shared_ptr<ProviderManager>
|
||||||
GetLevel3ProviderManager(const std::string& product);
|
GetLevel3ProviderManager(const std::string& product);
|
||||||
|
|
||||||
void EnableRefresh(boost::uuids::uuid uuid,
|
void EnableRefresh(
|
||||||
std::shared_ptr<ProviderManager> providerManager,
|
boost::uuids::uuid uuid,
|
||||||
|
const std::set<std::shared_ptr<ProviderManager>>& providerManagers,
|
||||||
bool enabled);
|
bool enabled);
|
||||||
void RefreshData(std::shared_ptr<ProviderManager> providerManager);
|
void RefreshData(std::shared_ptr<ProviderManager> providerManager);
|
||||||
void RefreshDataSync(std::shared_ptr<ProviderManager> providerManager);
|
void RefreshDataSync(std::shared_ptr<ProviderManager> providerManager);
|
||||||
|
|
@ -250,6 +277,7 @@ public:
|
||||||
std::shared_mutex level3ProductRecordMutex_ {};
|
std::shared_mutex level3ProductRecordMutex_ {};
|
||||||
|
|
||||||
std::shared_ptr<ProviderManager> level2ProviderManager_;
|
std::shared_ptr<ProviderManager> level2ProviderManager_;
|
||||||
|
std::shared_ptr<ProviderManager> level2ChunksProviderManager_;
|
||||||
std::unordered_map<std::string, std::shared_ptr<ProviderManager>>
|
std::unordered_map<std::string, std::shared_ptr<ProviderManager>>
|
||||||
level3ProviderManagerMap_ {};
|
level3ProviderManagerMap_ {};
|
||||||
std::shared_mutex level3ProviderManagerMutex_ {};
|
std::shared_mutex level3ProviderManagerMutex_ {};
|
||||||
|
|
@ -262,8 +290,10 @@ public:
|
||||||
common::Level3ProductCategoryMap availableCategoryMap_ {};
|
common::Level3ProductCategoryMap availableCategoryMap_ {};
|
||||||
std::shared_mutex availableCategoryMutex_ {};
|
std::shared_mutex availableCategoryMutex_ {};
|
||||||
|
|
||||||
|
std::optional<float> incomingLevel2Elevation_ {};
|
||||||
|
|
||||||
std::unordered_map<boost::uuids::uuid,
|
std::unordered_map<boost::uuids::uuid,
|
||||||
std::shared_ptr<ProviderManager>,
|
std::set<std::shared_ptr<ProviderManager>>,
|
||||||
boost::hash<boost::uuids::uuid>>
|
boost::hash<boost::uuids::uuid>>
|
||||||
refreshMap_ {};
|
refreshMap_ {};
|
||||||
std::shared_mutex refreshMapMutex_ {};
|
std::shared_mutex refreshMapMutex_ {};
|
||||||
|
|
@ -441,6 +471,11 @@ float RadarProductManager::gate_size() const
|
||||||
return (is_tdwr()) ? 150.0f : 250.0f;
|
return (is_tdwr()) ? 150.0f : 250.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<float> RadarProductManager::incoming_level_2_elevation() const
|
||||||
|
{
|
||||||
|
return p->incomingLevel2Elevation_;
|
||||||
|
}
|
||||||
|
|
||||||
std::string RadarProductManager::radar_id() const
|
std::string RadarProductManager::radar_id() const
|
||||||
{
|
{
|
||||||
return p->radarId_;
|
return p->radarId_;
|
||||||
|
|
@ -637,7 +672,10 @@ void RadarProductManager::EnableRefresh(common::RadarProductGroup group,
|
||||||
{
|
{
|
||||||
if (group == common::RadarProductGroup::Level2)
|
if (group == common::RadarProductGroup::Level2)
|
||||||
{
|
{
|
||||||
p->EnableRefresh(uuid, p->level2ProviderManager_, enabled);
|
p->EnableRefresh(
|
||||||
|
uuid,
|
||||||
|
{p->level2ProviderManager_, p->level2ChunksProviderManager_},
|
||||||
|
enabled);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -660,7 +698,7 @@ void RadarProductManager::EnableRefresh(common::RadarProductGroup group,
|
||||||
availableProducts.cend(),
|
availableProducts.cend(),
|
||||||
product) != availableProducts.cend())
|
product) != availableProducts.cend())
|
||||||
{
|
{
|
||||||
p->EnableRefresh(uuid, providerManager, enabled);
|
p->EnableRefresh(uuid, {providerManager}, enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& ex)
|
catch (const std::exception& ex)
|
||||||
|
|
@ -673,49 +711,44 @@ void RadarProductManager::EnableRefresh(common::RadarProductGroup group,
|
||||||
|
|
||||||
void RadarProductManagerImpl::EnableRefresh(
|
void RadarProductManagerImpl::EnableRefresh(
|
||||||
boost::uuids::uuid uuid,
|
boost::uuids::uuid uuid,
|
||||||
std::shared_ptr<ProviderManager> providerManager,
|
const std::set<std::shared_ptr<ProviderManager>>& providerManagers,
|
||||||
bool enabled)
|
bool enabled)
|
||||||
{
|
{
|
||||||
// Lock the refresh map
|
// Lock the refresh map
|
||||||
std::unique_lock lock {refreshMapMutex_};
|
std::unique_lock lock {refreshMapMutex_};
|
||||||
|
|
||||||
auto currentProviderManager = refreshMap_.find(uuid);
|
auto currentProviderManagers = refreshMap_.find(uuid);
|
||||||
if (currentProviderManager != refreshMap_.cend())
|
if (currentProviderManagers != refreshMap_.cend())
|
||||||
{
|
{
|
||||||
// If the enabling refresh for a different product, or disabling refresh
|
for (const auto& currentProviderManager : currentProviderManagers->second)
|
||||||
if (currentProviderManager->second != providerManager || !enabled)
|
{
|
||||||
|
currentProviderManager->refreshCount_ -= 1;
|
||||||
|
// If the enabling refresh for a different product, or disabling
|
||||||
|
// refresh
|
||||||
|
if (!providerManagers.contains(currentProviderManager) || !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 this is the last reference to the provider in the refresh map
|
||||||
if (currentProviderManagerCount == 1)
|
if (currentProviderManager->refreshCount_ == 0)
|
||||||
{
|
{
|
||||||
// Disable current provider
|
// Disable current provider
|
||||||
currentProviderManager->second->Disable();
|
currentProviderManager->Disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dissociate uuid from current provider manager
|
// Dissociate uuid from current provider managers
|
||||||
refreshMap_.erase(currentProviderManager);
|
refreshMap_.erase(currentProviderManagers);
|
||||||
|
}
|
||||||
|
|
||||||
// If we are enabling a new provider manager
|
|
||||||
if (enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
// Associate uuid to providerManager
|
// We are enabling provider managers
|
||||||
refreshMap_.emplace(uuid, providerManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (enabled)
|
|
||||||
{
|
|
||||||
// We are enabling a new provider manager
|
|
||||||
// Associate uuid to provider manager
|
// Associate uuid to provider manager
|
||||||
refreshMap_.emplace(uuid, providerManager);
|
refreshMap_.emplace(uuid, providerManagers);
|
||||||
|
for (const auto& providerManager : providerManagers)
|
||||||
|
{
|
||||||
|
providerManager->refreshCount_ += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the refresh map mutex
|
// Release the refresh map mutex
|
||||||
|
|
@ -723,21 +756,23 @@ void RadarProductManagerImpl::EnableRefresh(
|
||||||
|
|
||||||
// We have already handled a disable request by this point. If enabling, and
|
// We have already handled a disable request by this point. If enabling, and
|
||||||
// the provider manager refresh isn't already enabled, enable it.
|
// the provider manager refresh isn't already enabled, enable it.
|
||||||
if (enabled && providerManager->refreshEnabled_ != enabled)
|
|
||||||
{
|
|
||||||
providerManager->refreshEnabled_ = enabled;
|
|
||||||
|
|
||||||
if (enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
|
for (const auto& providerManager : providerManagers)
|
||||||
|
{
|
||||||
|
if (providerManager->refreshEnabled_ != enabled)
|
||||||
|
{
|
||||||
|
providerManager->refreshEnabled_ = enabled;
|
||||||
RefreshData(providerManager);
|
RefreshData(providerManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RadarProductManagerImpl::RefreshData(
|
void RadarProductManagerImpl::RefreshData(
|
||||||
std::shared_ptr<ProviderManager> providerManager)
|
std::shared_ptr<ProviderManager> providerManager)
|
||||||
{
|
{
|
||||||
logger_->debug("RefreshData: {}", providerManager->name());
|
logger_->trace("RefreshData: {}", providerManager->name());
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock lock(providerManager->refreshTimerMutex_);
|
std::unique_lock lock(providerManager->refreshTimerMutex_);
|
||||||
|
|
@ -765,13 +800,18 @@ void RadarProductManagerImpl::RefreshDataSync(
|
||||||
|
|
||||||
auto [newObjects, totalObjects] = providerManager->provider_->Refresh();
|
auto [newObjects, totalObjects] = providerManager->provider_->Refresh();
|
||||||
|
|
||||||
std::chrono::milliseconds interval = kFastRetryInterval_;
|
// Level2 chunked data is updated quickly and uses a faster interval
|
||||||
|
const std::chrono::milliseconds fastRetryInterval =
|
||||||
|
providerManager->isChunks_ ? kFastRetryIntervalChunks_ :
|
||||||
|
kFastRetryInterval_;
|
||||||
|
const std::chrono::milliseconds slowRetryInterval =
|
||||||
|
providerManager->isChunks_ ? kSlowRetryIntervalChunks_ :
|
||||||
|
kSlowRetryInterval_;
|
||||||
|
std::chrono::milliseconds interval = fastRetryInterval;
|
||||||
|
|
||||||
if (totalObjects > 0)
|
if (totalObjects > 0)
|
||||||
{
|
{
|
||||||
std::string key = providerManager->provider_->FindLatestKey();
|
auto latestTime = providerManager->provider_->FindLatestTime();
|
||||||
auto latestTime = providerManager->provider_->GetTimePointByKey(key);
|
|
||||||
|
|
||||||
auto updatePeriod = providerManager->provider_->update_period();
|
auto updatePeriod = providerManager->provider_->update_period();
|
||||||
auto lastModified = providerManager->provider_->last_modified();
|
auto lastModified = providerManager->provider_->last_modified();
|
||||||
auto sinceLastModified = std::chrono::system_clock::now() - lastModified;
|
auto sinceLastModified = std::chrono::system_clock::now() - lastModified;
|
||||||
|
|
@ -786,12 +826,12 @@ void RadarProductManagerImpl::RefreshDataSync(
|
||||||
{
|
{
|
||||||
// If it has been at least 5 update periods since the file has
|
// If it has been at least 5 update periods since the file has
|
||||||
// been last modified, slow the retry period
|
// been last modified, slow the retry period
|
||||||
interval = kSlowRetryInterval_;
|
interval = slowRetryInterval;
|
||||||
}
|
}
|
||||||
else if (interval < std::chrono::milliseconds {kFastRetryInterval_})
|
else if (interval < std::chrono::milliseconds {fastRetryInterval})
|
||||||
{
|
{
|
||||||
// The interval should be no quicker than the fast retry interval
|
// The interval should be no quicker than the fast retry interval
|
||||||
interval = kFastRetryInterval_;
|
interval = fastRetryInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newObjects > 0)
|
if (newObjects > 0)
|
||||||
|
|
@ -805,14 +845,14 @@ void RadarProductManagerImpl::RefreshDataSync(
|
||||||
logger_->info("[{}] No data found", providerManager->name());
|
logger_->info("[{}] No data found", providerManager->name());
|
||||||
|
|
||||||
// If no data is found, retry at the slow retry interval
|
// If no data is found, retry at the slow retry interval
|
||||||
interval = kSlowRetryInterval_;
|
interval = slowRetryInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_lock const lock(providerManager->refreshTimerMutex_);
|
std::unique_lock const lock(providerManager->refreshTimerMutex_);
|
||||||
|
|
||||||
if (providerManager->refreshEnabled_)
|
if (providerManager->refreshEnabled_)
|
||||||
{
|
{
|
||||||
logger_->debug(
|
logger_->trace(
|
||||||
"[{}] Scheduled refresh in {:%M:%S}",
|
"[{}] Scheduled refresh in {:%M:%S}",
|
||||||
providerManager->name(),
|
providerManager->name(),
|
||||||
std::chrono::duration_cast<std::chrono::seconds>(interval));
|
std::chrono::duration_cast<std::chrono::seconds>(interval));
|
||||||
|
|
@ -861,10 +901,13 @@ RadarProductManager::GetActiveVolumeTimes(
|
||||||
std::shared_lock refreshLock {p->refreshMapMutex_};
|
std::shared_lock refreshLock {p->refreshMapMutex_};
|
||||||
|
|
||||||
// For each entry in the refresh map (refresh is enabled)
|
// For each entry in the refresh map (refresh is enabled)
|
||||||
for (auto& refreshEntry : p->refreshMap_)
|
for (auto& refreshSet : p->refreshMap_)
|
||||||
|
{
|
||||||
|
for (const auto& refreshEntry : refreshSet.second)
|
||||||
{
|
{
|
||||||
// Add the provider for the current entry
|
// Add the provider for the current entry
|
||||||
providers.insert(refreshEntry.second->provider_);
|
providers.insert(refreshEntry->provider_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock the refresh map
|
// Unlock the refresh map
|
||||||
|
|
@ -923,7 +966,7 @@ void RadarProductManagerImpl::LoadProviderData(
|
||||||
std::mutex& loadDataMutex,
|
std::mutex& loadDataMutex,
|
||||||
const std::shared_ptr<request::NexradFileRequest>& request)
|
const std::shared_ptr<request::NexradFileRequest>& request)
|
||||||
{
|
{
|
||||||
logger_->debug("LoadProviderData: {}, {}",
|
logger_->trace("LoadProviderData: {}, {}",
|
||||||
providerManager->name(),
|
providerManager->name(),
|
||||||
scwx::util::TimeString(time));
|
scwx::util::TimeString(time));
|
||||||
|
|
||||||
|
|
@ -943,7 +986,7 @@ void RadarProductManagerImpl::LoadProviderData(
|
||||||
|
|
||||||
if (existingRecord != nullptr)
|
if (existingRecord != nullptr)
|
||||||
{
|
{
|
||||||
logger_->debug(
|
logger_->trace(
|
||||||
"Data previously loaded, loading from data cache");
|
"Data previously loaded, loading from data cache");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -951,13 +994,8 @@ void RadarProductManagerImpl::LoadProviderData(
|
||||||
|
|
||||||
if (existingRecord == nullptr)
|
if (existingRecord == nullptr)
|
||||||
{
|
{
|
||||||
std::string key = providerManager->provider_->FindKey(time);
|
nexradFile = providerManager->provider_->LoadObjectByTime(time);
|
||||||
|
if (nexradFile == nullptr)
|
||||||
if (!key.empty())
|
|
||||||
{
|
|
||||||
nexradFile = providerManager->provider_->LoadObjectByKey(key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
logger_->warn("Attempting to load object without key: {}",
|
logger_->warn("Attempting to load object without key: {}",
|
||||||
scwx::util::TimeString(time));
|
scwx::util::TimeString(time));
|
||||||
|
|
@ -979,7 +1017,7 @@ void RadarProductManager::LoadLevel2Data(
|
||||||
std::chrono::system_clock::time_point time,
|
std::chrono::system_clock::time_point time,
|
||||||
const std::shared_ptr<request::NexradFileRequest>& request)
|
const std::shared_ptr<request::NexradFileRequest>& request)
|
||||||
{
|
{
|
||||||
logger_->debug("LoadLevel2Data: {}", scwx::util::TimeString(time));
|
logger_->trace("LoadLevel2Data: {}", scwx::util::TimeString(time));
|
||||||
|
|
||||||
p->LoadProviderData(time,
|
p->LoadProviderData(time,
|
||||||
p->level2ProviderManager_,
|
p->level2ProviderManager_,
|
||||||
|
|
@ -1163,6 +1201,10 @@ void RadarProductManagerImpl::PopulateLevel2ProductTimes(
|
||||||
level2ProductRecords_,
|
level2ProductRecords_,
|
||||||
level2ProductRecordMutex_,
|
level2ProductRecordMutex_,
|
||||||
time);
|
time);
|
||||||
|
PopulateProductTimes(level2ChunksProviderManager_,
|
||||||
|
level2ProductRecords_,
|
||||||
|
level2ProductRecordMutex_,
|
||||||
|
time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RadarProductManagerImpl::PopulateLevel3ProductTimes(
|
void RadarProductManagerImpl::PopulateLevel3ProductTimes(
|
||||||
|
|
@ -1399,7 +1441,7 @@ std::shared_ptr<types::RadarProductRecord>
|
||||||
RadarProductManagerImpl::StoreRadarProductRecord(
|
RadarProductManagerImpl::StoreRadarProductRecord(
|
||||||
std::shared_ptr<types::RadarProductRecord> record)
|
std::shared_ptr<types::RadarProductRecord> record)
|
||||||
{
|
{
|
||||||
logger_->debug("StoreRadarProductRecord()");
|
logger_->trace("StoreRadarProductRecord()");
|
||||||
|
|
||||||
std::shared_ptr<types::RadarProductRecord> storedRecord = nullptr;
|
std::shared_ptr<types::RadarProductRecord> storedRecord = nullptr;
|
||||||
|
|
||||||
|
|
@ -1418,7 +1460,7 @@ RadarProductManagerImpl::StoreRadarProductRecord(
|
||||||
|
|
||||||
if (storedRecord != nullptr)
|
if (storedRecord != nullptr)
|
||||||
{
|
{
|
||||||
logger_->debug(
|
logger_->error(
|
||||||
"Level 2 product previously loaded, loading from cache");
|
"Level 2 product previously loaded, loading from cache");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1503,15 +1545,56 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
|
||||||
std::vector<float> elevationCuts {};
|
std::vector<float> elevationCuts {};
|
||||||
std::chrono::system_clock::time_point foundTime {};
|
std::chrono::system_clock::time_point foundTime {};
|
||||||
|
|
||||||
auto records = p->GetLevel2ProductRecords(time);
|
const bool isEpox = time == std::chrono::system_clock::time_point {};
|
||||||
|
bool needArchive = true;
|
||||||
|
static const auto maxChunkDelay = std::chrono::minutes(10);
|
||||||
|
const std::chrono::system_clock::time_point firstValidChunkTime =
|
||||||
|
(isEpox ? std::chrono::system_clock::now() : time) - maxChunkDelay;
|
||||||
|
|
||||||
|
// See if we have this one in the chunk provider.
|
||||||
|
auto chunkFile = std::dynamic_pointer_cast<wsr88d::Ar2vFile>(
|
||||||
|
p->level2ChunksProviderManager_->provider_->LoadObjectByTime(time));
|
||||||
|
if (chunkFile != nullptr)
|
||||||
|
{
|
||||||
|
std::tie(radarData, elevationCut, elevationCuts) =
|
||||||
|
chunkFile->GetElevationScan(dataBlockType, elevation, time);
|
||||||
|
|
||||||
|
if (radarData != nullptr)
|
||||||
|
{
|
||||||
|
auto& radarData0 = (*radarData)[0];
|
||||||
|
foundTime = std::chrono::floor<std::chrono::seconds>(
|
||||||
|
scwx::util::TimePoint(radarData0->modified_julian_date(),
|
||||||
|
radarData0->collection_time()));
|
||||||
|
|
||||||
|
const std::optional<float> incomingElevation =
|
||||||
|
std::dynamic_pointer_cast<provider::AwsLevel2ChunksDataProvider>(
|
||||||
|
p->level2ChunksProviderManager_->provider_)
|
||||||
|
->GetCurrentElevation();
|
||||||
|
if (incomingElevation != p->incomingLevel2Elevation_)
|
||||||
|
{
|
||||||
|
p->incomingLevel2Elevation_ = incomingElevation;
|
||||||
|
Q_EMIT IncomingLevel2ElevationChanged(incomingElevation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTime >= firstValidChunkTime)
|
||||||
|
{
|
||||||
|
needArchive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is not in the chunk provider, so get it from the archive
|
||||||
|
if (needArchive)
|
||||||
|
{
|
||||||
|
auto records = p->GetLevel2ProductRecords(time);
|
||||||
for (auto& recordPair : records)
|
for (auto& recordPair : records)
|
||||||
{
|
{
|
||||||
auto& record = recordPair.second;
|
auto& record = recordPair.second;
|
||||||
|
|
||||||
if (record != nullptr)
|
if (record != nullptr)
|
||||||
{
|
{
|
||||||
std::shared_ptr<wsr88d::rda::ElevationScan> recordRadarData = nullptr;
|
std::shared_ptr<wsr88d::rda::ElevationScan> recordRadarData =
|
||||||
|
nullptr;
|
||||||
float recordElevationCut = 0.0f;
|
float recordElevationCut = 0.0f;
|
||||||
std::vector<float> recordElevationCuts;
|
std::vector<float> recordElevationCuts;
|
||||||
|
|
||||||
|
|
@ -1528,12 +1611,21 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
|
||||||
|
|
||||||
// Find the newest radar data, not newer than the selected time
|
// Find the newest radar data, not newer than the selected time
|
||||||
if (radarData == nullptr ||
|
if (radarData == nullptr ||
|
||||||
(collectionTime <= time && foundTime < collectionTime))
|
(collectionTime <= time && foundTime < collectionTime) ||
|
||||||
|
(isEpox && foundTime < collectionTime))
|
||||||
{
|
{
|
||||||
radarData = recordRadarData;
|
radarData = recordRadarData;
|
||||||
elevationCut = recordElevationCut;
|
elevationCut = recordElevationCut;
|
||||||
elevationCuts = std::move(recordElevationCuts);
|
elevationCuts = std::move(recordElevationCuts);
|
||||||
foundTime = collectionTime;
|
foundTime = collectionTime;
|
||||||
|
|
||||||
|
if (!p->incomingLevel2Elevation_.has_value())
|
||||||
|
{
|
||||||
|
p->incomingLevel2Elevation_ = {};
|
||||||
|
Q_EMIT IncomingLevel2ElevationChanged(
|
||||||
|
p->incomingLevel2Elevation_);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,9 @@ public:
|
||||||
[[nodiscard]] const std::vector<float>&
|
[[nodiscard]] const std::vector<float>&
|
||||||
coordinates(common::RadialSize radialSize, bool smoothingEnabled) const;
|
coordinates(common::RadialSize radialSize, bool smoothingEnabled) const;
|
||||||
[[nodiscard]] const scwx::util::time_zone* default_time_zone() const;
|
[[nodiscard]] const scwx::util::time_zone* default_time_zone() const;
|
||||||
[[nodiscard]] bool is_tdwr() const;
|
|
||||||
[[nodiscard]] float gate_size() const;
|
[[nodiscard]] float gate_size() const;
|
||||||
|
[[nodiscard]] std::optional<float> incoming_level_2_elevation() const;
|
||||||
|
[[nodiscard]] bool is_tdwr() const;
|
||||||
[[nodiscard]] std::string radar_id() const;
|
[[nodiscard]] std::string radar_id() const;
|
||||||
[[nodiscard]] std::shared_ptr<config::RadarSite> radar_site() const;
|
[[nodiscard]] std::shared_ptr<config::RadarSite> radar_site() const;
|
||||||
|
|
||||||
|
|
@ -147,7 +148,9 @@ signals:
|
||||||
void Level3ProductsChanged();
|
void Level3ProductsChanged();
|
||||||
void NewDataAvailable(common::RadarProductGroup group,
|
void NewDataAvailable(common::RadarProductGroup group,
|
||||||
const std::string& product,
|
const std::string& product,
|
||||||
|
bool isChunks,
|
||||||
std::chrono::system_clock::time_point latestTime);
|
std::chrono::system_clock::time_point latestTime);
|
||||||
|
void IncomingLevel2ElevationChanged(std::optional<float> incomingElevation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<RadarProductManagerImpl> p;
|
std::unique_ptr<RadarProductManagerImpl> p;
|
||||||
|
|
|
||||||
|
|
@ -656,6 +656,11 @@ std::vector<float> MapWidget::GetElevationCuts() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<float> MapWidget::GetIncomingLevel2Elevation() const
|
||||||
|
{
|
||||||
|
return p->radarProductManager_->incoming_level_2_elevation();
|
||||||
|
}
|
||||||
|
|
||||||
common::Level2Product
|
common::Level2Product
|
||||||
MapWidgetImpl::GetLevel2ProductOrDefault(const std::string& productName) const
|
MapWidgetImpl::GetLevel2ProductOrDefault(const std::string& productName) const
|
||||||
{
|
{
|
||||||
|
|
@ -1796,6 +1801,14 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
{
|
{
|
||||||
if (radarProductManager_ != nullptr)
|
if (radarProductManager_ != nullptr)
|
||||||
{
|
{
|
||||||
|
connect(radarProductManager_.get(),
|
||||||
|
&manager::RadarProductManager::IncomingLevel2ElevationChanged,
|
||||||
|
this,
|
||||||
|
[this](std::optional<float> incomingElevation)
|
||||||
|
{
|
||||||
|
Q_EMIT widget_->IncomingLevel2ElevationChanged(
|
||||||
|
incomingElevation);
|
||||||
|
});
|
||||||
connect(radarProductManager_.get(),
|
connect(radarProductManager_.get(),
|
||||||
&manager::RadarProductManager::Level3ProductsChanged,
|
&manager::RadarProductManager::Level3ProductsChanged,
|
||||||
this,
|
this,
|
||||||
|
|
@ -1830,15 +1843,24 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
this,
|
this,
|
||||||
[this](common::RadarProductGroup group,
|
[this](common::RadarProductGroup group,
|
||||||
const std::string& product,
|
const std::string& product,
|
||||||
|
bool isChunks,
|
||||||
std::chrono::system_clock::time_point latestTime)
|
std::chrono::system_clock::time_point latestTime)
|
||||||
{
|
{
|
||||||
if (autoRefreshEnabled_ &&
|
if (autoRefreshEnabled_ &&
|
||||||
context_->radar_product_group() == group &&
|
context_->radar_product_group() == group &&
|
||||||
(group == common::RadarProductGroup::Level2 ||
|
(group == common::RadarProductGroup::Level2 ||
|
||||||
context_->radar_product() == product))
|
context_->radar_product() == product))
|
||||||
|
{
|
||||||
|
if (isChunks && autoUpdateEnabled_)
|
||||||
|
{
|
||||||
|
// Level 2 products may have multiple time points,
|
||||||
|
// ensure the latest is selected
|
||||||
|
widget_->SelectRadarProduct(group, product);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// Create file request
|
// Create file request
|
||||||
std::shared_ptr<request::NexradFileRequest> request =
|
const std::shared_ptr<request::NexradFileRequest> request =
|
||||||
std::make_shared<request::NexradFileRequest>(
|
std::make_shared<request::NexradFileRequest>(
|
||||||
radarProductManager_->radar_id());
|
radarProductManager_->radar_id());
|
||||||
|
|
||||||
|
|
@ -1849,8 +1871,9 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
request.get(),
|
request.get(),
|
||||||
&request::NexradFileRequest::RequestComplete,
|
&request::NexradFileRequest::RequestComplete,
|
||||||
this,
|
this,
|
||||||
[=,
|
[group, product, this](
|
||||||
this](std::shared_ptr<request::NexradFileRequest> request)
|
const std::shared_ptr<request::NexradFileRequest>&
|
||||||
|
request)
|
||||||
{
|
{
|
||||||
// Select loaded record
|
// Select loaded record
|
||||||
auto record = request->radar_product_record();
|
auto record = request->radar_product_record();
|
||||||
|
|
@ -1867,8 +1890,8 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
{
|
{
|
||||||
if (group == common::RadarProductGroup::Level2)
|
if (group == common::RadarProductGroup::Level2)
|
||||||
{
|
{
|
||||||
// Level 2 products may have multiple time points,
|
// Level 2 products may have multiple time
|
||||||
// ensure the latest is selected
|
// points, ensure the latest is selected
|
||||||
widget_->SelectRadarProduct(group, product);
|
widget_->SelectRadarProduct(group, product);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1882,7 +1905,7 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
// Load file
|
// Load file
|
||||||
boost::asio::post(
|
boost::asio::post(
|
||||||
threadPool_,
|
threadPool_,
|
||||||
[=, this]()
|
[group, latestTime, request, product, this]()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -1903,6 +1926,7 @@ void MapWidgetImpl::RadarProductManagerConnect()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
@ -1916,6 +1940,10 @@ void MapWidgetImpl::RadarProductManagerDisconnect()
|
||||||
&manager::RadarProductManager::NewDataAvailable,
|
&manager::RadarProductManager::NewDataAvailable,
|
||||||
this,
|
this,
|
||||||
nullptr);
|
nullptr);
|
||||||
|
disconnect(radarProductManager_.get(),
|
||||||
|
&manager::RadarProductManager::IncomingLevel2ElevationChanged,
|
||||||
|
this,
|
||||||
|
nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ public:
|
||||||
GetAvailableLevel3Categories();
|
GetAvailableLevel3Categories();
|
||||||
[[nodiscard]] std::optional<float> GetElevation() const;
|
[[nodiscard]] std::optional<float> GetElevation() const;
|
||||||
[[nodiscard]] std::vector<float> GetElevationCuts() const;
|
[[nodiscard]] std::vector<float> GetElevationCuts() const;
|
||||||
|
[[nodiscard]] std::optional<float> GetIncomingLevel2Elevation() const;
|
||||||
[[nodiscard]] std::vector<std::string> GetLevel3Products();
|
[[nodiscard]] std::vector<std::string> GetLevel3Products();
|
||||||
[[nodiscard]] std::string GetMapStyle() const;
|
[[nodiscard]] std::string GetMapStyle() const;
|
||||||
[[nodiscard]] common::RadarProductGroup GetRadarProductGroup() const;
|
[[nodiscard]] common::RadarProductGroup GetRadarProductGroup() const;
|
||||||
|
|
@ -184,6 +185,7 @@ signals:
|
||||||
void RadarSweepUpdated();
|
void RadarSweepUpdated();
|
||||||
void RadarSweepNotUpdated(types::NoUpdateReason reason);
|
void RadarSweepNotUpdated(types::NoUpdateReason reason);
|
||||||
void WidgetPainted();
|
void WidgetPainted();
|
||||||
|
void IncomingLevel2ElevationChanged(std::optional<float> incomingElevation);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace map
|
} // namespace map
|
||||||
|
|
|
||||||
|
|
@ -159,8 +159,6 @@ void RadarProductLayer::Initialize()
|
||||||
|
|
||||||
void RadarProductLayer::UpdateSweep()
|
void RadarProductLayer::UpdateSweep()
|
||||||
{
|
{
|
||||||
logger_->debug("UpdateSweep()");
|
|
||||||
|
|
||||||
gl::OpenGLFunctions& gl = context()->gl();
|
gl::OpenGLFunctions& gl = context()->gl();
|
||||||
|
|
||||||
boost::timer::cpu_timer timer;
|
boost::timer::cpu_timer timer;
|
||||||
|
|
@ -172,9 +170,10 @@ void RadarProductLayer::UpdateSweep()
|
||||||
std::try_to_lock);
|
std::try_to_lock);
|
||||||
if (!sweepLock.owns_lock())
|
if (!sweepLock.owns_lock())
|
||||||
{
|
{
|
||||||
logger_->debug("Sweep locked, deferring update");
|
logger_->trace("Sweep locked, deferring update");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger_->debug("UpdateSweep()");
|
||||||
|
|
||||||
p->sweepNeedsUpdate_ = false;
|
p->sweepNeedsUpdate_ = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <qlabel.h>
|
||||||
#include <scwx/qt/ui/level2_settings_widget.hpp>
|
#include <scwx/qt/ui/level2_settings_widget.hpp>
|
||||||
#include <scwx/qt/ui/flow_layout.hpp>
|
#include <scwx/qt/ui/flow_layout.hpp>
|
||||||
#include <scwx/qt/manager/hotkey_manager.hpp>
|
#include <scwx/qt/manager/hotkey_manager.hpp>
|
||||||
|
|
@ -29,16 +30,15 @@ public:
|
||||||
explicit Level2SettingsWidgetImpl(Level2SettingsWidget* self) :
|
explicit Level2SettingsWidgetImpl(Level2SettingsWidget* self) :
|
||||||
self_ {self},
|
self_ {self},
|
||||||
layout_ {new QVBoxLayout(self)},
|
layout_ {new QVBoxLayout(self)},
|
||||||
elevationGroupBox_ {},
|
|
||||||
elevationButtons_ {},
|
elevationButtons_ {},
|
||||||
elevationCuts_ {},
|
elevationCuts_ {}
|
||||||
elevationButtonsChanged_ {false},
|
|
||||||
resizeElevationButtons_ {false},
|
|
||||||
settingsGroupBox_ {},
|
|
||||||
declutterCheckBox_ {}
|
|
||||||
{
|
{
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-owning-memory) Qt takes care of this
|
||||||
layout_->setContentsMargins(0, 0, 0, 0);
|
layout_->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
incomingElevationLabel_ = new QLabel("", self);
|
||||||
|
layout_->addWidget(incomingElevationLabel_);
|
||||||
|
|
||||||
elevationGroupBox_ = new QGroupBox(tr("Elevation"), self);
|
elevationGroupBox_ = new QGroupBox(tr("Elevation"), self);
|
||||||
new ui::FlowLayout(elevationGroupBox_);
|
new ui::FlowLayout(elevationGroupBox_);
|
||||||
layout_->addWidget(elevationGroupBox_);
|
layout_->addWidget(elevationGroupBox_);
|
||||||
|
|
@ -51,6 +51,7 @@ public:
|
||||||
settingsLayout->addWidget(declutterCheckBox_);
|
settingsLayout->addWidget(declutterCheckBox_);
|
||||||
|
|
||||||
settingsGroupBox_->setVisible(false);
|
settingsGroupBox_->setVisible(false);
|
||||||
|
// NOLINTEND(cppcoreguidelines-owning-memory) Qt takes care of this
|
||||||
|
|
||||||
QObject::connect(hotkeyManager_.get(),
|
QObject::connect(hotkeyManager_.get(),
|
||||||
&manager::HotkeyManager::HotkeyPressed,
|
&manager::HotkeyManager::HotkeyPressed,
|
||||||
|
|
@ -66,14 +67,15 @@ public:
|
||||||
Level2SettingsWidget* self_;
|
Level2SettingsWidget* self_;
|
||||||
QLayout* layout_;
|
QLayout* layout_;
|
||||||
|
|
||||||
QGroupBox* elevationGroupBox_;
|
QGroupBox* elevationGroupBox_ {};
|
||||||
|
QLabel* incomingElevationLabel_ {};
|
||||||
std::list<QToolButton*> elevationButtons_;
|
std::list<QToolButton*> elevationButtons_;
|
||||||
std::vector<float> elevationCuts_;
|
std::vector<float> elevationCuts_;
|
||||||
bool elevationButtonsChanged_;
|
bool elevationButtonsChanged_ {};
|
||||||
bool resizeElevationButtons_;
|
bool resizeElevationButtons_ {};
|
||||||
|
|
||||||
QGroupBox* settingsGroupBox_;
|
QGroupBox* settingsGroupBox_ {};
|
||||||
QCheckBox* declutterCheckBox_;
|
QCheckBox* declutterCheckBox_ {};
|
||||||
|
|
||||||
float currentElevation_ {};
|
float currentElevation_ {};
|
||||||
QToolButton* currentElevationButton_ {nullptr};
|
QToolButton* currentElevationButton_ {nullptr};
|
||||||
|
|
@ -240,12 +242,29 @@ void Level2SettingsWidget::UpdateElevationSelection(float elevation)
|
||||||
p->currentElevationButton_ = newElevationButton;
|
p->currentElevationButton_ = newElevationButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Level2SettingsWidget::UpdateIncomingElevation(
|
||||||
|
std::optional<float> incomingElevation)
|
||||||
|
{
|
||||||
|
if (incomingElevation.has_value())
|
||||||
|
{
|
||||||
|
p->incomingElevationLabel_->setText(
|
||||||
|
"Incoming Elevation: " + QString::number(*incomingElevation, 'f', 1) +
|
||||||
|
common::Characters::DEGREE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->incomingElevationLabel_->setText("Incoming Elevation: None");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Level2SettingsWidget::UpdateSettings(map::MapWidget* activeMap)
|
void Level2SettingsWidget::UpdateSettings(map::MapWidget* activeMap)
|
||||||
{
|
{
|
||||||
std::optional<float> currentElevationOption = activeMap->GetElevation();
|
std::optional<float> currentElevationOption = activeMap->GetElevation();
|
||||||
const float currentElevation =
|
const float currentElevation =
|
||||||
currentElevationOption.has_value() ? *currentElevationOption : 0.0f;
|
currentElevationOption.has_value() ? *currentElevationOption : 0.0f;
|
||||||
std::vector<float> elevationCuts = activeMap->GetElevationCuts();
|
const std::vector<float> elevationCuts = activeMap->GetElevationCuts();
|
||||||
|
const std::optional<float> incomingElevation =
|
||||||
|
activeMap->GetIncomingLevel2Elevation();
|
||||||
|
|
||||||
if (p->elevationCuts_ != elevationCuts)
|
if (p->elevationCuts_ != elevationCuts)
|
||||||
{
|
{
|
||||||
|
|
@ -279,6 +298,7 @@ void Level2SettingsWidget::UpdateSettings(map::MapWidget* activeMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateElevationSelection(currentElevation);
|
UpdateElevationSelection(currentElevation);
|
||||||
|
UpdateIncomingElevation(incomingElevation);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <scwx/qt/map/map_widget.hpp>
|
#include <scwx/qt/map/map_widget.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
namespace qt
|
namespace qt
|
||||||
|
|
@ -23,6 +25,7 @@ public:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
|
|
||||||
void UpdateElevationSelection(float elevation);
|
void UpdateElevationSelection(float elevation);
|
||||||
|
void UpdateIncomingElevation(std::optional<float> incomingElevation);
|
||||||
void UpdateSettings(map::MapWidget* activeMap);
|
void UpdateSettings(map::MapWidget* activeMap);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -561,7 +561,7 @@ void Level2ProductView::ComputeSweep()
|
||||||
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
|
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (radarData == p->elevationScan_ &&
|
if ((radarData == p->elevationScan_) &&
|
||||||
smoothingEnabled == p->lastSmoothingEnabled_ &&
|
smoothingEnabled == p->lastSmoothingEnabled_ &&
|
||||||
(showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
|
(showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
|
||||||
!smoothingEnabled))
|
!smoothingEnabled))
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ void OverlayProductView::Impl::ConnectRadarProductManager()
|
||||||
self_,
|
self_,
|
||||||
[this](common::RadarProductGroup group,
|
[this](common::RadarProductGroup group,
|
||||||
const std::string& product,
|
const std::string& product,
|
||||||
|
bool /*isChunks*/,
|
||||||
std::chrono::system_clock::time_point latestTime)
|
std::chrono::system_clock::time_point latestTime)
|
||||||
{
|
{
|
||||||
if (autoRefreshEnabled_ &&
|
if (autoRefreshEnabled_ &&
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scwx/provider/nexrad_data_provider.hpp>
|
||||||
|
#include <scwx/provider/aws_level2_data_provider.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace Aws::S3
|
||||||
|
{
|
||||||
|
class S3Client;
|
||||||
|
} // namespace Aws::S3
|
||||||
|
|
||||||
|
namespace scwx::provider
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AWS Level 2 Data Provider
|
||||||
|
*/
|
||||||
|
class AwsLevel2ChunksDataProvider : public NexradDataProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AwsLevel2ChunksDataProvider(const std::string& radarSite);
|
||||||
|
explicit AwsLevel2ChunksDataProvider(const std::string& radarSite,
|
||||||
|
const std::string& bucketName,
|
||||||
|
const std::string& region);
|
||||||
|
~AwsLevel2ChunksDataProvider() override;
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider(const AwsLevel2ChunksDataProvider&) = delete;
|
||||||
|
AwsLevel2ChunksDataProvider&
|
||||||
|
operator=(const AwsLevel2ChunksDataProvider&) = delete;
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider(AwsLevel2ChunksDataProvider&&) noexcept;
|
||||||
|
AwsLevel2ChunksDataProvider&
|
||||||
|
operator=(AwsLevel2ChunksDataProvider&&) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] std::chrono::system_clock::time_point
|
||||||
|
GetTimePointByKey(const std::string& key) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] size_t cache_size() const override;
|
||||||
|
|
||||||
|
[[nodiscard]] std::chrono::system_clock::time_point
|
||||||
|
last_modified() const override;
|
||||||
|
[[nodiscard]] std::chrono::seconds update_period() const override;
|
||||||
|
|
||||||
|
std::string FindKey(std::chrono::system_clock::time_point time) override;
|
||||||
|
std::string FindLatestKey() override;
|
||||||
|
std::chrono::system_clock::time_point FindLatestTime() override;
|
||||||
|
std::vector<std::chrono::system_clock::time_point>
|
||||||
|
GetTimePointsByDate(std::chrono::system_clock::time_point date) override;
|
||||||
|
std::tuple<bool, size_t, size_t>
|
||||||
|
ListObjects(std::chrono::system_clock::time_point date) override;
|
||||||
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
LoadObjectByKey(const std::string& key) override;
|
||||||
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
LoadObjectByTime(std::chrono::system_clock::time_point time) override;
|
||||||
|
std::pair<size_t, size_t> Refresh() override;
|
||||||
|
|
||||||
|
void RequestAvailableProducts() override;
|
||||||
|
std::vector<std::string> GetAvailableProducts() override;
|
||||||
|
|
||||||
|
std::optional<float> GetCurrentElevation();
|
||||||
|
|
||||||
|
void SetLevel2DataProvider(
|
||||||
|
const std::shared_ptr<AwsLevel2DataProvider>& provider);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
std::unique_ptr<Impl> p;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace scwx::provider
|
||||||
|
|
@ -39,12 +39,15 @@ public:
|
||||||
|
|
||||||
std::string FindKey(std::chrono::system_clock::time_point time) override;
|
std::string FindKey(std::chrono::system_clock::time_point time) override;
|
||||||
std::string FindLatestKey() override;
|
std::string FindLatestKey() override;
|
||||||
|
std::chrono::system_clock::time_point FindLatestTime() override;
|
||||||
std::vector<std::chrono::system_clock::time_point>
|
std::vector<std::chrono::system_clock::time_point>
|
||||||
GetTimePointsByDate(std::chrono::system_clock::time_point date) override;
|
GetTimePointsByDate(std::chrono::system_clock::time_point date) override;
|
||||||
std::tuple<bool, size_t, size_t>
|
std::tuple<bool, size_t, size_t>
|
||||||
ListObjects(std::chrono::system_clock::time_point date) override;
|
ListObjects(std::chrono::system_clock::time_point date) override;
|
||||||
std::shared_ptr<wsr88d::NexradFile>
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
LoadObjectByKey(const std::string& key) override;
|
LoadObjectByKey(const std::string& key) override;
|
||||||
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
LoadObjectByTime(std::chrono::system_clock::time_point time) override;
|
||||||
std::pair<size_t, size_t> Refresh() override;
|
std::pair<size_t, size_t> Refresh() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,13 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual std::string FindLatestKey() = 0;
|
virtual std::string FindLatestKey() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the most recent time in the cache.
|
||||||
|
*
|
||||||
|
* @return NEXRAD data key
|
||||||
|
*/
|
||||||
|
virtual std::chrono::system_clock::time_point FindLatestTime() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists NEXRAD objects for the date supplied, and adds them to the cache.
|
* Lists NEXRAD objects for the date supplied, and adds them to the cache.
|
||||||
*
|
*
|
||||||
|
|
@ -81,6 +88,16 @@ public:
|
||||||
virtual std::shared_ptr<wsr88d::NexradFile>
|
virtual std::shared_ptr<wsr88d::NexradFile>
|
||||||
LoadObjectByKey(const std::string& key) = 0;
|
LoadObjectByKey(const std::string& key) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a NEXRAD file object at the given time
|
||||||
|
*
|
||||||
|
* @param time NEXRAD time
|
||||||
|
*
|
||||||
|
* @return NEXRAD data
|
||||||
|
*/
|
||||||
|
virtual std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
LoadObjectByTime(std::chrono::system_clock::time_point time) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists NEXRAD objects for the current date, and adds them to the cache. If
|
* Lists NEXRAD objects for the current date, and adds them to the cache. If
|
||||||
* no objects have been added to the cache for the current date, the previous
|
* no objects have been added to the cache for the current date, the previous
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ public:
|
||||||
static std::shared_ptr<NexradDataProvider>
|
static std::shared_ptr<NexradDataProvider>
|
||||||
CreateLevel2DataProvider(const std::string& radarSite);
|
CreateLevel2DataProvider(const std::string& radarSite);
|
||||||
|
|
||||||
|
static std::shared_ptr<NexradDataProvider>
|
||||||
|
CreateLevel2ChunksDataProvider(const std::string& radarSite);
|
||||||
|
|
||||||
static std::shared_ptr<NexradDataProvider>
|
static std::shared_ptr<NexradDataProvider>
|
||||||
CreateLevel3DataProvider(const std::string& radarSite,
|
CreateLevel3DataProvider(const std::string& radarSite,
|
||||||
const std::string& product);
|
const std::string& product);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ public:
|
||||||
Ar2vFile(Ar2vFile&&) noexcept;
|
Ar2vFile(Ar2vFile&&) noexcept;
|
||||||
Ar2vFile& operator=(Ar2vFile&&) noexcept;
|
Ar2vFile& operator=(Ar2vFile&&) noexcept;
|
||||||
|
|
||||||
|
Ar2vFile(const std::shared_ptr<Ar2vFile>& current,
|
||||||
|
const std::shared_ptr<Ar2vFile>& last);
|
||||||
|
|
||||||
std::uint32_t julian_date() const;
|
std::uint32_t julian_date() const;
|
||||||
std::uint32_t milliseconds() const;
|
std::uint32_t milliseconds() const;
|
||||||
std::string icao() const;
|
std::string icao() const;
|
||||||
|
|
@ -53,6 +56,9 @@ public:
|
||||||
bool LoadFile(const std::string& filename);
|
bool LoadFile(const std::string& filename);
|
||||||
bool LoadData(std::istream& is);
|
bool LoadData(std::istream& is);
|
||||||
|
|
||||||
|
bool LoadLDMRecords(std::istream& is);
|
||||||
|
bool IndexFile();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Ar2vFileImpl> p;
|
std::unique_ptr<Ar2vFileImpl> p;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
783
wxdata/source/scwx/provider/aws_level2_chunks_data_provider.cpp
Normal file
783
wxdata/source/scwx/provider/aws_level2_chunks_data_provider.cpp
Normal file
|
|
@ -0,0 +1,783 @@
|
||||||
|
#include "scwx/wsr88d/rda/digital_radar_data.hpp"
|
||||||
|
#include <scwx/provider/aws_level2_chunks_data_provider.hpp>
|
||||||
|
#include <scwx/util/environment.hpp>
|
||||||
|
#include <scwx/util/map.hpp>
|
||||||
|
#include <scwx/util/logger.hpp>
|
||||||
|
#include <scwx/util/time.hpp>
|
||||||
|
#include <scwx/wsr88d/ar2v_file.hpp>
|
||||||
|
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <aws/core/auth/AWSCredentials.h>
|
||||||
|
#include <aws/s3/S3Client.h>
|
||||||
|
#include <aws/s3/model/GetObjectRequest.h>
|
||||||
|
#include <aws/s3/model/ListObjectsV2Request.h>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
// Avoid circular refrence errors in boost
|
||||||
|
// NOLINTBEGIN(misc-header-include-cycle)
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# pragma warning(push, 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <boost/asio/thread_pool.hpp>
|
||||||
|
#include <boost/asio/post.hpp>
|
||||||
|
#include <boost/timer/timer.hpp>
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
// NOLINTEND(misc-header-include-cycle)
|
||||||
|
|
||||||
|
#if (__cpp_lib_chrono < 201907L)
|
||||||
|
# include <date/date.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace scwx::provider
|
||||||
|
{
|
||||||
|
static const std::string logPrefix_ =
|
||||||
|
"scwx::provider::aws_level2_chunks_data_provider";
|
||||||
|
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||||
|
|
||||||
|
static const std::string kDefaultBucketName_ = "unidata-nexrad-level2-chunks";
|
||||||
|
static const std::string kDefaultRegion_ = "us-east-1";
|
||||||
|
|
||||||
|
class AwsLevel2ChunksDataProvider::Impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct ScanRecord
|
||||||
|
{
|
||||||
|
explicit ScanRecord(std::string prefix, bool valid = true) :
|
||||||
|
valid_ {valid},
|
||||||
|
prefix_ {std::move(prefix)},
|
||||||
|
nexradFile_ {},
|
||||||
|
lastModified_ {},
|
||||||
|
lastKey_ {""}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~ScanRecord() = default;
|
||||||
|
ScanRecord(const ScanRecord&) = default;
|
||||||
|
ScanRecord(ScanRecord&&) = default;
|
||||||
|
ScanRecord& operator=(const ScanRecord&) = default;
|
||||||
|
ScanRecord& operator=(ScanRecord&&) = default;
|
||||||
|
|
||||||
|
bool valid_;
|
||||||
|
std::string prefix_;
|
||||||
|
std::shared_ptr<wsr88d::Ar2vFile> nexradFile_;
|
||||||
|
std::chrono::system_clock::time_point time_;
|
||||||
|
std::chrono::system_clock::time_point lastModified_;
|
||||||
|
std::chrono::system_clock::time_point secondLastModified_;
|
||||||
|
std::string lastKey_;
|
||||||
|
int nextFile_ {1};
|
||||||
|
bool hasAllFiles_ {false};
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Impl(AwsLevel2ChunksDataProvider* self,
|
||||||
|
std::string radarSite,
|
||||||
|
std::string bucketName,
|
||||||
|
std::string region) :
|
||||||
|
radarSite_ {std::move(radarSite)},
|
||||||
|
bucketName_ {std::move(bucketName)},
|
||||||
|
region_ {std::move(region)},
|
||||||
|
client_ {nullptr},
|
||||||
|
scanTimes_ {},
|
||||||
|
lastScan_ {"", false},
|
||||||
|
currentScan_ {"", false},
|
||||||
|
scansMutex_ {},
|
||||||
|
lastTimeListed_ {},
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) about average
|
||||||
|
updatePeriod_ {7},
|
||||||
|
level2DataProvider_ {},
|
||||||
|
self_ {self}
|
||||||
|
{
|
||||||
|
// Disable HTTP request for region
|
||||||
|
util::SetEnvironment("AWS_EC2_METADATA_DISABLED", "true");
|
||||||
|
|
||||||
|
// Use anonymous credentials
|
||||||
|
const Aws::Auth::AWSCredentials credentials {};
|
||||||
|
|
||||||
|
Aws::Client::ClientConfiguration config;
|
||||||
|
config.region = region_;
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) arbitrary
|
||||||
|
config.connectTimeoutMs = 10000;
|
||||||
|
|
||||||
|
client_ = std::make_shared<Aws::S3::S3Client>(
|
||||||
|
credentials,
|
||||||
|
Aws::MakeShared<Aws::S3::S3EndpointProvider>(
|
||||||
|
Aws::S3::S3Client::GetAllocationTag()),
|
||||||
|
config);
|
||||||
|
}
|
||||||
|
~Impl() = default;
|
||||||
|
Impl(const Impl&) = delete;
|
||||||
|
Impl(Impl&&) = delete;
|
||||||
|
Impl& operator=(const Impl&) = delete;
|
||||||
|
Impl& operator=(Impl&&) = delete;
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point GetScanTime(const std::string& prefix);
|
||||||
|
int GetScanNumber(const std::string& prefix);
|
||||||
|
|
||||||
|
bool LoadScan(Impl::ScanRecord& scanRecord);
|
||||||
|
std::tuple<bool, size_t, size_t> ListObjects();
|
||||||
|
|
||||||
|
std::string radarSite_;
|
||||||
|
std::string bucketName_;
|
||||||
|
std::string region_;
|
||||||
|
std::shared_ptr<Aws::S3::S3Client> client_;
|
||||||
|
|
||||||
|
std::mutex refreshMutex_;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::chrono::system_clock::time_point>
|
||||||
|
scanTimes_;
|
||||||
|
ScanRecord lastScan_;
|
||||||
|
ScanRecord currentScan_;
|
||||||
|
std::shared_mutex scansMutex_;
|
||||||
|
std::chrono::system_clock::time_point lastTimeListed_;
|
||||||
|
|
||||||
|
std::chrono::seconds updatePeriod_;
|
||||||
|
|
||||||
|
std::weak_ptr<AwsLevel2DataProvider> level2DataProvider_;
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider* self_;
|
||||||
|
};
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider::AwsLevel2ChunksDataProvider(
|
||||||
|
const std::string& radarSite) :
|
||||||
|
AwsLevel2ChunksDataProvider(radarSite, kDefaultBucketName_, kDefaultRegion_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider::AwsLevel2ChunksDataProvider(
|
||||||
|
const std::string& radarSite,
|
||||||
|
const std::string& bucketName,
|
||||||
|
const std::string& region) :
|
||||||
|
p(std::make_unique<Impl>(this, radarSite, bucketName, region))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider::~AwsLevel2ChunksDataProvider() = default;
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point
|
||||||
|
AwsLevel2ChunksDataProvider::GetTimePointByKey(const std::string& key) const
|
||||||
|
{
|
||||||
|
std::chrono::system_clock::time_point time {};
|
||||||
|
|
||||||
|
const size_t lastSeparator = key.rfind('/');
|
||||||
|
const size_t offset =
|
||||||
|
(lastSeparator == std::string::npos) ? 0 : lastSeparator + 1;
|
||||||
|
|
||||||
|
// Filename format is YYYYMMDD-TTTTTT-AAA-B
|
||||||
|
static const size_t formatSize = std::string("YYYYMMDD-TTTTTT").size();
|
||||||
|
|
||||||
|
if (key.size() >= offset + formatSize)
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
#if (__cpp_lib_chrono < 201907L)
|
||||||
|
using namespace date;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const std::string timeFormat {"%Y%m%d-%H%M%S"};
|
||||||
|
|
||||||
|
std::string timeStr {key.substr(offset, formatSize)};
|
||||||
|
std::istringstream in {timeStr};
|
||||||
|
in >> parse(timeFormat, time);
|
||||||
|
|
||||||
|
if (in.fail())
|
||||||
|
{
|
||||||
|
logger_->warn("Invalid time: \"{}\"", timeStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger_->warn("Time not parsable from key: \"{}\"", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AwsLevel2ChunksDataProvider::cache_size() const
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point
|
||||||
|
AwsLevel2ChunksDataProvider::last_modified() const
|
||||||
|
{
|
||||||
|
// There is a slight delay between the "modified time" and when it is
|
||||||
|
// actually available. Radar product manager uses this as available time
|
||||||
|
static const auto extra = std::chrono::seconds(2);
|
||||||
|
|
||||||
|
const std::shared_lock lock(p->scansMutex_);
|
||||||
|
if (p->currentScan_.valid_ && p->currentScan_.lastModified_ !=
|
||||||
|
std::chrono::system_clock::time_point {})
|
||||||
|
{
|
||||||
|
return p->currentScan_.lastModified_ + extra;
|
||||||
|
}
|
||||||
|
else if (p->lastScan_.valid_ && p->lastScan_.lastModified_ !=
|
||||||
|
std::chrono::system_clock::time_point {})
|
||||||
|
{
|
||||||
|
return p->lastScan_.lastModified_ + extra;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::chrono::seconds AwsLevel2ChunksDataProvider::update_period() const
|
||||||
|
{
|
||||||
|
const std::shared_lock lock(p->scansMutex_);
|
||||||
|
// get update period from time between chunks
|
||||||
|
if (p->currentScan_.valid_ && p->currentScan_.nextFile_ > 2)
|
||||||
|
{
|
||||||
|
auto delta =
|
||||||
|
p->currentScan_.lastModified_ - p->currentScan_.secondLastModified_;
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(delta);
|
||||||
|
}
|
||||||
|
else if (p->lastScan_.valid_ && p->lastScan_.nextFile_ > 2)
|
||||||
|
{
|
||||||
|
auto delta =
|
||||||
|
p->lastScan_.lastModified_ - p->lastScan_.secondLastModified_;
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to a set update period
|
||||||
|
return p->updatePeriod_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
AwsLevel2ChunksDataProvider::FindKey(std::chrono::system_clock::time_point time)
|
||||||
|
{
|
||||||
|
logger_->debug("FindKey: {}", util::TimeString(time));
|
||||||
|
|
||||||
|
const std::shared_lock lock(p->scansMutex_);
|
||||||
|
if (p->currentScan_.valid_ && time >= p->currentScan_.time_)
|
||||||
|
{
|
||||||
|
return p->currentScan_.prefix_;
|
||||||
|
}
|
||||||
|
else if (p->lastScan_.valid_ && time >= p->lastScan_.time_)
|
||||||
|
{
|
||||||
|
return p->lastScan_.prefix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AwsLevel2ChunksDataProvider::FindLatestKey()
|
||||||
|
{
|
||||||
|
const std::shared_lock lock(p->scansMutex_);
|
||||||
|
if (!p->currentScan_.valid_)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return p->currentScan_.prefix_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point
|
||||||
|
AwsLevel2ChunksDataProvider::FindLatestTime()
|
||||||
|
{
|
||||||
|
const std::shared_lock lock(p->scansMutex_);
|
||||||
|
if (!p->currentScan_.valid_)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return p->currentScan_.time_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::chrono::system_clock::time_point>
|
||||||
|
AwsLevel2ChunksDataProvider::GetTimePointsByDate(
|
||||||
|
std::chrono::system_clock::time_point /*date*/)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point
|
||||||
|
AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix)
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
const auto& scanTimeIt = scanTimes_.find(prefix); // O(log(n))
|
||||||
|
if (scanTimeIt != scanTimes_.cend())
|
||||||
|
{
|
||||||
|
// If the time is greater than 2 hours ago, it may be a new scan
|
||||||
|
auto replaceBy = system_clock::now() - hours {2};
|
||||||
|
if (scanTimeIt->second > replaceBy)
|
||||||
|
{
|
||||||
|
return scanTimeIt->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Aws::S3::Model::ListObjectsV2Request request;
|
||||||
|
request.SetBucket(bucketName_);
|
||||||
|
request.SetPrefix(prefix);
|
||||||
|
request.SetDelimiter("/");
|
||||||
|
request.SetMaxKeys(1);
|
||||||
|
|
||||||
|
auto outcome = client_->ListObjectsV2(request);
|
||||||
|
if (outcome.IsSuccess())
|
||||||
|
{
|
||||||
|
auto timePoint = self_->GetTimePointByKey(
|
||||||
|
outcome.GetResult().GetContents().at(0).GetKey());
|
||||||
|
scanTimes_.insert_or_assign(prefix, timePoint);
|
||||||
|
return timePoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<bool, size_t, size_t>
|
||||||
|
AwsLevel2ChunksDataProvider::Impl::ListObjects()
|
||||||
|
{
|
||||||
|
size_t newObjects = 0;
|
||||||
|
const size_t totalObjects = 0;
|
||||||
|
|
||||||
|
const std::chrono::system_clock::time_point now =
|
||||||
|
std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
if (currentScan_.valid_ && !currentScan_.hasAllFiles_ &&
|
||||||
|
lastTimeListed_ + std::chrono::minutes(2) > now)
|
||||||
|
{
|
||||||
|
return {true, newObjects, totalObjects};
|
||||||
|
}
|
||||||
|
logger_->trace("ListObjects");
|
||||||
|
lastTimeListed_ = now;
|
||||||
|
|
||||||
|
const std::string prefix = radarSite_ + "/";
|
||||||
|
|
||||||
|
Aws::S3::Model::ListObjectsV2Request request;
|
||||||
|
request.SetBucket(bucketName_);
|
||||||
|
request.SetPrefix(prefix);
|
||||||
|
request.SetDelimiter("/");
|
||||||
|
|
||||||
|
auto outcome = client_->ListObjectsV2(request);
|
||||||
|
|
||||||
|
if (outcome.IsSuccess())
|
||||||
|
{
|
||||||
|
auto& scans = outcome.GetResult().GetCommonPrefixes();
|
||||||
|
logger_->trace("Found {} scans", scans.size());
|
||||||
|
|
||||||
|
if (scans.size() > 0)
|
||||||
|
{
|
||||||
|
// find latest scan
|
||||||
|
auto scanNumberMap = std::map<int, std::string>();
|
||||||
|
|
||||||
|
for (auto& scan : scans) // O(n log(n)) n <= 999
|
||||||
|
{
|
||||||
|
const std::string& scanPrefix = scan.GetPrefix();
|
||||||
|
scanNumberMap.insert_or_assign(GetScanNumber(scanPrefix),
|
||||||
|
scanPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with last scan
|
||||||
|
int previousScanNumber = scanNumberMap.crbegin()->first;
|
||||||
|
const int firstScanNumber = scanNumberMap.cbegin()->first;
|
||||||
|
|
||||||
|
// Look for a gap in scan numbers. This indicates that is the latest
|
||||||
|
// scan.
|
||||||
|
|
||||||
|
auto possibleLastNumbers = std::unordered_set<int>();
|
||||||
|
// This indicates that highest number scan may be the last scan
|
||||||
|
// (including if there is only 1 scan)
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
if (previousScanNumber != 999 || firstScanNumber != 1)
|
||||||
|
{
|
||||||
|
possibleLastNumbers.emplace(previousScanNumber);
|
||||||
|
}
|
||||||
|
// Have already checked scan with highest number, so skip first
|
||||||
|
previousScanNumber = firstScanNumber;
|
||||||
|
bool first = true;
|
||||||
|
for (const auto& scan : scanNumberMap)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (scan.first != previousScanNumber + 1)
|
||||||
|
{
|
||||||
|
possibleLastNumbers.emplace(previousScanNumber);
|
||||||
|
}
|
||||||
|
previousScanNumber = scan.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (possibleLastNumbers.empty())
|
||||||
|
{
|
||||||
|
logger_->warn("Could not find last scan");
|
||||||
|
return {false, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastScanNumber = -1;
|
||||||
|
std::chrono::system_clock::time_point lastScanTime = {};
|
||||||
|
std::string lastScanPrefix;
|
||||||
|
|
||||||
|
for (const int scanNumber : possibleLastNumbers)
|
||||||
|
{
|
||||||
|
const std::string& scanPrefix = scanNumberMap.at(scanNumber);
|
||||||
|
auto scanTime = GetScanTime(scanPrefix);
|
||||||
|
if (scanTime > lastScanTime)
|
||||||
|
{
|
||||||
|
lastScanTime = scanTime;
|
||||||
|
lastScanPrefix = scanPrefix;
|
||||||
|
lastScanNumber = scanNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int secondLastScanNumber =
|
||||||
|
// 999 is the last file possible
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
lastScanNumber == 1 ? 999 : lastScanNumber - 1;
|
||||||
|
|
||||||
|
const auto& secondLastScanPrefix =
|
||||||
|
scanNumberMap.find(secondLastScanNumber);
|
||||||
|
|
||||||
|
if (!currentScan_.valid_ || currentScan_.prefix_ != lastScanPrefix)
|
||||||
|
{
|
||||||
|
if (currentScan_.valid_ &&
|
||||||
|
(secondLastScanPrefix == scanNumberMap.cend() ||
|
||||||
|
currentScan_.prefix_ == secondLastScanPrefix->second))
|
||||||
|
{
|
||||||
|
lastScan_ = currentScan_;
|
||||||
|
}
|
||||||
|
else if (secondLastScanPrefix != scanNumberMap.cend())
|
||||||
|
{
|
||||||
|
lastScan_.valid_ = true;
|
||||||
|
lastScan_.prefix_ = secondLastScanPrefix->second;
|
||||||
|
lastScan_.nexradFile_ = nullptr;
|
||||||
|
lastScan_.time_ = GetScanTime(secondLastScanPrefix->second);
|
||||||
|
lastScan_.lastModified_ = {};
|
||||||
|
lastScan_.secondLastModified_ = {};
|
||||||
|
lastScan_.lastKey_ = "";
|
||||||
|
lastScan_.nextFile_ = 1;
|
||||||
|
lastScan_.hasAllFiles_ = false;
|
||||||
|
newObjects += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentScan_.valid_ = true;
|
||||||
|
currentScan_.prefix_ = lastScanPrefix;
|
||||||
|
currentScan_.nexradFile_ = nullptr;
|
||||||
|
currentScan_.time_ = lastScanTime;
|
||||||
|
currentScan_.lastModified_ = {};
|
||||||
|
currentScan_.secondLastModified_ = {};
|
||||||
|
currentScan_.lastKey_ = "";
|
||||||
|
currentScan_.nextFile_ = 1;
|
||||||
|
currentScan_.hasAllFiles_ = false;
|
||||||
|
newObjects += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {true, newObjects, totalObjects};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<bool, size_t, size_t>
|
||||||
|
AwsLevel2ChunksDataProvider::ListObjects(std::chrono::system_clock::time_point)
|
||||||
|
{
|
||||||
|
return {true, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
AwsLevel2ChunksDataProvider::LoadObjectByKey(const std::string& /*prefix*/)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AwsLevel2ChunksDataProvider::Impl::LoadScan(Impl::ScanRecord& scanRecord)
|
||||||
|
{
|
||||||
|
if (!scanRecord.valid_)
|
||||||
|
{
|
||||||
|
logger_->warn("Tried to load scan which was not listed yet");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (scanRecord.hasAllFiles_)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Aws::S3::Model::ListObjectsV2Request listRequest;
|
||||||
|
listRequest.SetBucket(bucketName_);
|
||||||
|
listRequest.SetPrefix(scanRecord.prefix_);
|
||||||
|
listRequest.SetDelimiter("/");
|
||||||
|
if (!scanRecord.lastKey_.empty())
|
||||||
|
{
|
||||||
|
listRequest.SetStartAfter(scanRecord.lastKey_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto listOutcome = client_->ListObjectsV2(listRequest);
|
||||||
|
if (!listOutcome.IsSuccess())
|
||||||
|
{
|
||||||
|
logger_->warn("Could not find scan at {}", scanRecord.prefix_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasNew = false;
|
||||||
|
auto& chunks = listOutcome.GetResult().GetContents();
|
||||||
|
logger_->trace("Found {} new chunks.", chunks.size());
|
||||||
|
for (const auto& chunk : chunks)
|
||||||
|
{
|
||||||
|
const std::string& key = chunk.GetKey();
|
||||||
|
|
||||||
|
// KIND/585/20250324-134727-001-S
|
||||||
|
// KIND/5/20250324-134727-001-S
|
||||||
|
static const size_t firstSlash = std::string("KIND/").size();
|
||||||
|
const size_t secondSlash = key.find('/', firstSlash);
|
||||||
|
static const size_t startNumberPosOffset =
|
||||||
|
std::string("/20250324-134727-").size();
|
||||||
|
const size_t startNumberPos = secondSlash + startNumberPosOffset;
|
||||||
|
const std::string& keyNumberStr = key.substr(startNumberPos, 3);
|
||||||
|
const int keyNumber = std::stoi(keyNumberStr);
|
||||||
|
// As far as order goes, only the first one matters. This may cause some
|
||||||
|
// issues if keys come in out of order, but usually they just skip chunks
|
||||||
|
if (scanRecord.nextFile_ == 1 && keyNumber != scanRecord.nextFile_)
|
||||||
|
{
|
||||||
|
logger_->warn("Chunk found that was not in order {} {}",
|
||||||
|
scanRecord.lastKey_,
|
||||||
|
key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we want the ending char
|
||||||
|
// KIND/585/20250324-134727-001-S
|
||||||
|
static const size_t charPos = std::string("/20250324-134727-001-").size();
|
||||||
|
if (secondSlash + charPos >= key.size())
|
||||||
|
{
|
||||||
|
logger_->warn("Chunk key was not long enough");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char keyChar = key[secondSlash + charPos];
|
||||||
|
|
||||||
|
Aws::S3::Model::GetObjectRequest objectRequest;
|
||||||
|
objectRequest.SetBucket(bucketName_);
|
||||||
|
objectRequest.SetKey(key);
|
||||||
|
|
||||||
|
auto outcome = client_->GetObject(objectRequest);
|
||||||
|
|
||||||
|
if (!outcome.IsSuccess())
|
||||||
|
{
|
||||||
|
logger_->warn("Could not get object: {}",
|
||||||
|
outcome.GetError().GetMessage());
|
||||||
|
return hasNew;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& body = outcome.GetResultWithOwnership().GetBody();
|
||||||
|
|
||||||
|
switch (keyChar)
|
||||||
|
{
|
||||||
|
case 'S':
|
||||||
|
{ // First chunk
|
||||||
|
scanRecord.nexradFile_ = std::make_shared<wsr88d::Ar2vFile>();
|
||||||
|
if (!scanRecord.nexradFile_->LoadData(body))
|
||||||
|
{
|
||||||
|
logger_->warn("Failed to load first chunk");
|
||||||
|
return hasNew;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'I':
|
||||||
|
{ // Middle chunk
|
||||||
|
if (!scanRecord.nexradFile_->LoadLDMRecords(body))
|
||||||
|
{
|
||||||
|
logger_->warn("Failed to load middle chunk");
|
||||||
|
return hasNew;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'E':
|
||||||
|
{ // Last chunk
|
||||||
|
if (!scanRecord.nexradFile_->LoadLDMRecords(body))
|
||||||
|
{
|
||||||
|
logger_->warn("Failed to load last chunk");
|
||||||
|
return hasNew;
|
||||||
|
}
|
||||||
|
scanRecord.hasAllFiles_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logger_->warn("Could not load chunk with unknown char");
|
||||||
|
return hasNew;
|
||||||
|
}
|
||||||
|
hasNew = true;
|
||||||
|
|
||||||
|
const std::chrono::seconds lastModifiedSeconds {
|
||||||
|
outcome.GetResult().GetLastModified().Seconds()};
|
||||||
|
const std::chrono::system_clock::time_point lastModified {
|
||||||
|
lastModifiedSeconds};
|
||||||
|
|
||||||
|
scanRecord.secondLastModified_ = scanRecord.lastModified_;
|
||||||
|
scanRecord.lastModified_ = lastModified;
|
||||||
|
|
||||||
|
scanRecord.nextFile_ = keyNumber + 1;
|
||||||
|
scanRecord.lastKey_ = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanRecord.nexradFile_ == nullptr)
|
||||||
|
{
|
||||||
|
logger_->warn("Could not load file");
|
||||||
|
}
|
||||||
|
else if (hasNew)
|
||||||
|
{
|
||||||
|
scanRecord.nexradFile_->IndexFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasNew;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<wsr88d::NexradFile>
|
||||||
|
AwsLevel2ChunksDataProvider::LoadObjectByTime(
|
||||||
|
std::chrono::system_clock::time_point time)
|
||||||
|
{
|
||||||
|
const std::unique_lock lock(p->scansMutex_);
|
||||||
|
static const std::chrono::system_clock::time_point epoch {};
|
||||||
|
|
||||||
|
if (p->currentScan_.valid_ &&
|
||||||
|
(time == epoch || time >= p->currentScan_.time_))
|
||||||
|
{
|
||||||
|
return std::make_shared<wsr88d::Ar2vFile>(p->currentScan_.nexradFile_,
|
||||||
|
p->lastScan_.nexradFile_);
|
||||||
|
}
|
||||||
|
else if (p->lastScan_.valid_ && time >= p->lastScan_.time_)
|
||||||
|
{
|
||||||
|
return p->lastScan_.nexradFile_;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int AwsLevel2ChunksDataProvider::Impl::GetScanNumber(const std::string& prefix)
|
||||||
|
{
|
||||||
|
// KIND/585/20250324-134727-001-S
|
||||||
|
static const size_t firstSlash = std::string("KIND/").size();
|
||||||
|
const std::string& prefixNumberStr = prefix.substr(firstSlash, 3);
|
||||||
|
return std::stoi(prefixNumberStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<size_t, size_t> AwsLevel2ChunksDataProvider::Refresh()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
boost::timer::cpu_timer timer {};
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
const std::unique_lock lock(p->refreshMutex_);
|
||||||
|
const std::unique_lock scanLock(p->scansMutex_);
|
||||||
|
|
||||||
|
auto [success, newObjects, totalObjects] = p->ListObjects();
|
||||||
|
|
||||||
|
auto threadPool = boost::asio::thread_pool(3);
|
||||||
|
bool newCurrent = false;
|
||||||
|
bool newLast = false;
|
||||||
|
if (p->currentScan_.valid_)
|
||||||
|
{
|
||||||
|
boost::asio::post(threadPool,
|
||||||
|
[this, &newCurrent]()
|
||||||
|
{ newCurrent = p->LoadScan(p->currentScan_); });
|
||||||
|
totalObjects += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->lastScan_.valid_)
|
||||||
|
{
|
||||||
|
totalObjects += 1;
|
||||||
|
boost::asio::post(
|
||||||
|
threadPool,
|
||||||
|
[this, &newLast]()
|
||||||
|
{
|
||||||
|
if (!p->lastScan_.hasAllFiles_)
|
||||||
|
{
|
||||||
|
// If we have chunks, use chunks
|
||||||
|
if (p->lastScan_.nextFile_ != 1)
|
||||||
|
{
|
||||||
|
newLast = p->LoadScan(p->lastScan_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto level2DataProvider = p->level2DataProvider_.lock();
|
||||||
|
if (level2DataProvider != nullptr)
|
||||||
|
{
|
||||||
|
level2DataProvider->ListObjects(p->lastScan_.time_);
|
||||||
|
p->lastScan_.nexradFile_ =
|
||||||
|
std::dynamic_pointer_cast<wsr88d::Ar2vFile>(
|
||||||
|
level2DataProvider->LoadObjectByTime(
|
||||||
|
p->lastScan_.time_));
|
||||||
|
if (p->lastScan_.nexradFile_ != nullptr)
|
||||||
|
{
|
||||||
|
p->lastScan_.hasAllFiles_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall back to chunks if files did not load
|
||||||
|
newLast = p->lastScan_.nexradFile_ != nullptr ||
|
||||||
|
p->LoadScan(p->lastScan_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
threadPool.join();
|
||||||
|
if (newCurrent)
|
||||||
|
{
|
||||||
|
newObjects += 1;
|
||||||
|
}
|
||||||
|
if (newLast)
|
||||||
|
{
|
||||||
|
newObjects += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.stop();
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) format to 6 digits
|
||||||
|
logger_->debug("Refresh() in {}", timer.format(6, "%ws"));
|
||||||
|
return std::make_pair(newObjects, totalObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AwsLevel2ChunksDataProvider::RequestAvailableProducts() {}
|
||||||
|
std::vector<std::string> AwsLevel2ChunksDataProvider::GetAvailableProducts()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AwsLevel2ChunksDataProvider::AwsLevel2ChunksDataProvider(
|
||||||
|
AwsLevel2ChunksDataProvider&&) noexcept = default;
|
||||||
|
AwsLevel2ChunksDataProvider& AwsLevel2ChunksDataProvider::operator=(
|
||||||
|
AwsLevel2ChunksDataProvider&&) noexcept = default;
|
||||||
|
|
||||||
|
std::optional<float> AwsLevel2ChunksDataProvider::GetCurrentElevation()
|
||||||
|
{
|
||||||
|
if (!p->currentScan_.valid_ || p->currentScan_.nexradFile_ == nullptr)
|
||||||
|
{
|
||||||
|
// Does not have any scan elevation.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vcpData = p->currentScan_.nexradFile_->vcp_data();
|
||||||
|
auto radarData = p->currentScan_.nexradFile_->radar_data();
|
||||||
|
if (radarData.size() == 0)
|
||||||
|
{
|
||||||
|
// Does not have any scan elevation.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& lastElevation = radarData.crbegin();
|
||||||
|
const std::shared_ptr<wsr88d::rda::DigitalRadarData> digitalRadarData0 =
|
||||||
|
std::dynamic_pointer_cast<wsr88d::rda::DigitalRadarData>(
|
||||||
|
lastElevation->second->cbegin()->second);
|
||||||
|
|
||||||
|
if (vcpData != nullptr)
|
||||||
|
{
|
||||||
|
return static_cast<float>(vcpData->elevation_angle(lastElevation->first));
|
||||||
|
}
|
||||||
|
else if (digitalRadarData0 != nullptr)
|
||||||
|
{
|
||||||
|
return digitalRadarData0->elevation_angle().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AwsLevel2ChunksDataProvider::SetLevel2DataProvider(
|
||||||
|
const std::shared_ptr<AwsLevel2DataProvider>& provider)
|
||||||
|
{
|
||||||
|
p->level2DataProvider_ = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace scwx::provider
|
||||||
|
|
@ -170,6 +170,11 @@ std::string AwsNexradDataProvider::FindLatestKey()
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point AwsNexradDataProvider::FindLatestTime()
|
||||||
|
{
|
||||||
|
return GetTimePointByKey(FindLatestKey());
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::chrono::system_clock::time_point>
|
std::vector<std::chrono::system_clock::time_point>
|
||||||
AwsNexradDataProvider::GetTimePointsByDate(
|
AwsNexradDataProvider::GetTimePointsByDate(
|
||||||
std::chrono::system_clock::time_point date)
|
std::chrono::system_clock::time_point date)
|
||||||
|
|
@ -327,6 +332,20 @@ AwsNexradDataProvider::LoadObjectByKey(const std::string& key)
|
||||||
return nexradFile;
|
return nexradFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<wsr88d::NexradFile> AwsNexradDataProvider::LoadObjectByTime(
|
||||||
|
std::chrono::system_clock::time_point time)
|
||||||
|
{
|
||||||
|
const std::string key = FindKey(time);
|
||||||
|
if (key.empty())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return LoadObjectByKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<size_t, size_t> AwsNexradDataProvider::Refresh()
|
std::pair<size_t, size_t> AwsNexradDataProvider::Refresh()
|
||||||
{
|
{
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <scwx/provider/nexrad_data_provider_factory.hpp>
|
#include <scwx/provider/nexrad_data_provider_factory.hpp>
|
||||||
#include <scwx/provider/aws_level2_data_provider.hpp>
|
#include <scwx/provider/aws_level2_data_provider.hpp>
|
||||||
|
#include <scwx/provider/aws_level2_chunks_data_provider.hpp>
|
||||||
#include <scwx/provider/aws_level3_data_provider.hpp>
|
#include <scwx/provider/aws_level3_data_provider.hpp>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
|
|
@ -17,6 +18,13 @@ NexradDataProviderFactory::CreateLevel2DataProvider(
|
||||||
return std::make_unique<AwsLevel2DataProvider>(radarSite);
|
return std::make_unique<AwsLevel2DataProvider>(radarSite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NexradDataProvider>
|
||||||
|
NexradDataProviderFactory::CreateLevel2ChunksDataProvider(
|
||||||
|
const std::string& radarSite)
|
||||||
|
{
|
||||||
|
return std::make_unique<AwsLevel2ChunksDataProvider>(radarSite);
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<NexradDataProvider>
|
std::shared_ptr<NexradDataProvider>
|
||||||
NexradDataProviderFactory::CreateLevel3DataProvider(
|
NexradDataProviderFactory::CreateLevel3DataProvider(
|
||||||
const std::string& radarSite, const std::string& product)
|
const std::string& radarSite, const std::string& product)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/rangebuf.hpp>
|
#include <scwx/util/rangebuf.hpp>
|
||||||
#include <scwx/util/time.hpp>
|
#include <scwx/util/time.hpp>
|
||||||
|
#include <scwx/common/geographic.hpp>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
@ -137,7 +138,7 @@ Ar2vFile::GetElevationScan(rda::DataBlockType dataBlockType,
|
||||||
float elevation,
|
float elevation,
|
||||||
std::chrono::system_clock::time_point time) const
|
std::chrono::system_clock::time_point time) const
|
||||||
{
|
{
|
||||||
logger_->debug("GetElevationScan: {} degrees", elevation);
|
logger_->trace("GetElevationScan: {} degrees", elevation);
|
||||||
|
|
||||||
std::shared_ptr<rda::ElevationScan> elevationScan = nullptr;
|
std::shared_ptr<rda::ElevationScan> elevationScan = nullptr;
|
||||||
float elevationCut = 0.0f;
|
float elevationCut = 0.0f;
|
||||||
|
|
@ -272,7 +273,7 @@ bool Ar2vFile::LoadData(std::istream& is)
|
||||||
|
|
||||||
std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is)
|
std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is)
|
||||||
{
|
{
|
||||||
logger_->debug("Decompressing LDM Records");
|
logger_->trace("Decompressing LDM Records");
|
||||||
|
|
||||||
std::size_t numRecords = 0;
|
std::size_t numRecords = 0;
|
||||||
|
|
||||||
|
|
@ -320,14 +321,14 @@ std::size_t Ar2vFileImpl::DecompressLDMRecords(std::istream& is)
|
||||||
++numRecords;
|
++numRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger_->debug("Decompressed {} LDM Records", numRecords);
|
logger_->trace("Decompressed {} LDM Records", numRecords);
|
||||||
|
|
||||||
return numRecords;
|
return numRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ar2vFileImpl::ParseLDMRecords()
|
void Ar2vFileImpl::ParseLDMRecords()
|
||||||
{
|
{
|
||||||
logger_->debug("Parsing LDM Records");
|
logger_->trace("Parsing LDM Records");
|
||||||
|
|
||||||
std::size_t count = 0;
|
std::size_t count = 0;
|
||||||
|
|
||||||
|
|
@ -444,13 +445,11 @@ void Ar2vFileImpl::ProcessRadarData(
|
||||||
|
|
||||||
void Ar2vFileImpl::IndexFile()
|
void Ar2vFileImpl::IndexFile()
|
||||||
{
|
{
|
||||||
logger_->debug("Indexing file");
|
logger_->trace("Indexing file");
|
||||||
|
|
||||||
constexpr float scaleFactor = 8.0f / 0.043945f;
|
|
||||||
|
|
||||||
for (auto& elevationCut : radarData_)
|
for (auto& elevationCut : radarData_)
|
||||||
{
|
{
|
||||||
std::uint16_t elevationAngle {};
|
float elevationAngle {};
|
||||||
rda::WaveformType waveformType = rda::WaveformType::Unknown;
|
rda::WaveformType waveformType = rda::WaveformType::Unknown;
|
||||||
|
|
||||||
std::shared_ptr<rda::GenericRadarData>& radial0 =
|
std::shared_ptr<rda::GenericRadarData>& radial0 =
|
||||||
|
|
@ -466,14 +465,15 @@ void Ar2vFileImpl::IndexFile()
|
||||||
|
|
||||||
if (vcpData_ != nullptr)
|
if (vcpData_ != nullptr)
|
||||||
{
|
{
|
||||||
elevationAngle = vcpData_->elevation_angle_raw(elevationCut.first);
|
elevationAngle =
|
||||||
|
static_cast<float>(vcpData_->elevation_angle(elevationCut.first));
|
||||||
waveformType = vcpData_->waveform_type(elevationCut.first);
|
waveformType = vcpData_->waveform_type(elevationCut.first);
|
||||||
}
|
}
|
||||||
else if ((digitalRadarData0 =
|
else if ((digitalRadarData0 =
|
||||||
std::dynamic_pointer_cast<rda::DigitalRadarData>(radial0)) !=
|
std::dynamic_pointer_cast<rda::DigitalRadarData>(radial0)) !=
|
||||||
nullptr)
|
nullptr)
|
||||||
{
|
{
|
||||||
elevationAngle = digitalRadarData0->elevation_angle_raw();
|
elevationAngle = digitalRadarData0->elevation_angle().value();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -501,19 +501,236 @@ void Ar2vFileImpl::IndexFile()
|
||||||
auto time = util::TimePoint(radial0->modified_julian_date(),
|
auto time = util::TimePoint(radial0->modified_julian_date(),
|
||||||
radial0->collection_time());
|
radial0->collection_time());
|
||||||
|
|
||||||
// NOLINTNEXTLINE This conversion is accurate
|
index_[dataBlockType][elevationAngle][time] = elevationCut.second;
|
||||||
float elevationAngleConverted = elevationAngle / scaleFactor;
|
}
|
||||||
// Any elevation above 90 degrees should be interpreted as a
|
}
|
||||||
// negative angle
|
}
|
||||||
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers)
|
|
||||||
if (elevationAngleConverted > 90)
|
|
||||||
{
|
|
||||||
elevationAngleConverted -= 360;
|
|
||||||
}
|
}
|
||||||
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
|
|
||||||
|
|
||||||
index_[dataBlockType][elevationAngleConverted][time] =
|
bool Ar2vFile::LoadLDMRecords(std::istream& is)
|
||||||
elevationCut.second;
|
{
|
||||||
|
const size_t decompressedRecords = p->DecompressLDMRecords(is);
|
||||||
|
if (decompressedRecords == 0)
|
||||||
|
{
|
||||||
|
p->ParseLDMRecord(is);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->ParseLDMRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Ar2vFile::IndexFile()
|
||||||
|
{
|
||||||
|
p->IndexFile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
bool IsRadarDataIncomplete(
|
||||||
|
const std::shared_ptr<const rda::ElevationScan>& radarData)
|
||||||
|
{
|
||||||
|
// Assume the data is incomplete when the delta between the first and last
|
||||||
|
// angles is greater than 2.5 degrees.
|
||||||
|
constexpr units::degrees<float> kIncompleteDataAngleThreshold_ {2.5};
|
||||||
|
|
||||||
|
const units::degrees<float> firstAngle =
|
||||||
|
radarData->cbegin()->second->azimuth_angle();
|
||||||
|
const units::degrees<float> lastAngle =
|
||||||
|
radarData->crbegin()->second->azimuth_angle();
|
||||||
|
const units::degrees<float> angleDelta =
|
||||||
|
common::GetAngleDelta(firstAngle, lastAngle);
|
||||||
|
|
||||||
|
return angleDelta > kIncompleteDataAngleThreshold_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ar2vFile::Ar2vFile(const std::shared_ptr<Ar2vFile>& current,
|
||||||
|
const std::shared_ptr<Ar2vFile>& last) :
|
||||||
|
Ar2vFile()
|
||||||
|
{
|
||||||
|
// This is only used to index right now, so not a huge deal
|
||||||
|
p->vcpData_ = nullptr;
|
||||||
|
|
||||||
|
// Reconstruct index from the other's indexes
|
||||||
|
if (current != nullptr)
|
||||||
|
{
|
||||||
|
for (const auto& type : current->p->index_)
|
||||||
|
{
|
||||||
|
for (const auto& elevation : type.second)
|
||||||
|
{
|
||||||
|
// Get the most recent scan
|
||||||
|
const auto& mostRecent = elevation.second.crbegin();
|
||||||
|
if (mostRecent == elevation.second.crend())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add previous scans for stepping back in time
|
||||||
|
for (auto scan = ++(elevation.second.rbegin());
|
||||||
|
scan != elevation.second.rend();
|
||||||
|
++scan)
|
||||||
|
{
|
||||||
|
p->index_[type.first][elevation.first][scan->first] =
|
||||||
|
scan->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge this scan with the last one if it is incomplete
|
||||||
|
if (IsRadarDataIncomplete(mostRecent->second))
|
||||||
|
{
|
||||||
|
std::shared_ptr<rda::ElevationScan> secondMostRecent = nullptr;
|
||||||
|
|
||||||
|
// check if this volume scan has an earlier elevation scan
|
||||||
|
auto possibleSecondMostRecent = elevation.second.rbegin();
|
||||||
|
++possibleSecondMostRecent;
|
||||||
|
|
||||||
|
if (possibleSecondMostRecent == elevation.second.rend())
|
||||||
|
{
|
||||||
|
if (last == nullptr)
|
||||||
|
{
|
||||||
|
// Nothing to merge with
|
||||||
|
p->index_[type.first][elevation.first][mostRecent->first] =
|
||||||
|
mostRecent->second;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the scan from the last scan
|
||||||
|
auto elevationScan =
|
||||||
|
std::get<std::shared_ptr<rda::ElevationScan>>(
|
||||||
|
last->GetElevationScan(
|
||||||
|
type.first, elevation.first, {}));
|
||||||
|
if (elevationScan == nullptr)
|
||||||
|
{
|
||||||
|
// Nothing to merge with
|
||||||
|
p->index_[type.first][elevation.first][mostRecent->first] =
|
||||||
|
mostRecent->second;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
secondMostRecent = elevationScan;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
secondMostRecent = possibleSecondMostRecent->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the new scan
|
||||||
|
auto newScan = std::make_shared<rda::ElevationScan>();
|
||||||
|
|
||||||
|
// Copy over the new radials
|
||||||
|
for (const auto& radial : *(mostRecent->second))
|
||||||
|
{
|
||||||
|
(*newScan)[radial.first] = radial.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Correctly order the old radials. The radials need to be in
|
||||||
|
* order for the rendering to work, and the index needs to start
|
||||||
|
* at 0 and increase by one from there. Since the new radial
|
||||||
|
* should have index 0, the old radial needs to be reshaped to
|
||||||
|
* match the new radials indexing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const double lowestAzm =
|
||||||
|
mostRecent->second->cbegin()->second->azimuth_angle().value();
|
||||||
|
const double heighestAzm = mostRecent->second->crbegin()
|
||||||
|
->second->azimuth_angle()
|
||||||
|
.value();
|
||||||
|
std::uint16_t index = mostRecent->second->crbegin()->first + 1;
|
||||||
|
|
||||||
|
// Sort by the azimuth. Makes the rest of this way easier
|
||||||
|
auto secondMostRecentAzmMap =
|
||||||
|
std::map<float, std::shared_ptr<rda::GenericRadarData>>();
|
||||||
|
for (const auto& radial : *secondMostRecent)
|
||||||
|
{
|
||||||
|
secondMostRecentAzmMap[radial.second->azimuth_angle()
|
||||||
|
.value()] = radial.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowestAzm <= heighestAzm) // New scan does not contain 0/360
|
||||||
|
{
|
||||||
|
// Get the radials following the new radials
|
||||||
|
for (const auto& radial : secondMostRecentAzmMap)
|
||||||
|
{
|
||||||
|
if (radial.first > heighestAzm)
|
||||||
|
{
|
||||||
|
(*newScan)[index] = radial.second;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the radials before the new radials
|
||||||
|
for (const auto& radial : secondMostRecentAzmMap)
|
||||||
|
{
|
||||||
|
if (radial.first < lowestAzm)
|
||||||
|
{
|
||||||
|
(*newScan)[index] = radial.second;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // New scan includes 0/360
|
||||||
|
{
|
||||||
|
// The radials will already be in the right order
|
||||||
|
for (const auto& radial : secondMostRecentAzmMap)
|
||||||
|
{
|
||||||
|
if (radial.first > heighestAzm && radial.first < lowestAzm)
|
||||||
|
{
|
||||||
|
(*newScan)[index] = radial.second;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p->index_[type.first][elevation.first][mostRecent->first] =
|
||||||
|
newScan;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->index_[type.first][elevation.first][mostRecent->first] =
|
||||||
|
mostRecent->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go though last, adding other elevations
|
||||||
|
if (last != nullptr)
|
||||||
|
{
|
||||||
|
for (const auto& type : last->p->index_)
|
||||||
|
{
|
||||||
|
// Find the highest elevation this type has for the current scan
|
||||||
|
// Start below any reasonable elevation
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
float highestCurrentElevation = -90;
|
||||||
|
const auto& elevationScans = p->index_.find(type.first);
|
||||||
|
if (elevationScans != p->index_.cend())
|
||||||
|
{
|
||||||
|
const auto& highestElevation = elevationScans->second.crbegin();
|
||||||
|
if (highestElevation != elevationScans->second.crend())
|
||||||
|
{
|
||||||
|
// Add a slight offset to ensure good floating point compare.
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
highestCurrentElevation = highestElevation->first + 0.01f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& elevation : type.second)
|
||||||
|
{
|
||||||
|
// Only add elevations above the current scan's elevation
|
||||||
|
if (elevation.first > highestCurrentElevation)
|
||||||
|
{
|
||||||
|
const auto& mostRecent = elevation.second.crbegin();
|
||||||
|
if (mostRecent == elevation.second.crend())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
p->index_[type.first][elevation.first][mostRecent->first] =
|
||||||
|
mostRecent->second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,18 @@ std::uint16_t DigitalRadarData::elevation_angle_raw() const
|
||||||
|
|
||||||
units::degrees<float> DigitalRadarData::elevation_angle() const
|
units::degrees<float> DigitalRadarData::elevation_angle() const
|
||||||
{
|
{
|
||||||
return units::degrees<float> {p->elevationAngle_ * kAngleDataScale};
|
// NOLINTNEXTLINE This conversion is accurate
|
||||||
|
float elevationAngleConverted = p->elevationAngle_ * kAngleDataScale;
|
||||||
|
// Any elevation above 90 degrees should be interpreted as a
|
||||||
|
// negative angle
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
if (elevationAngleConverted > 90)
|
||||||
|
{
|
||||||
|
elevationAngleConverted -= 360;
|
||||||
|
}
|
||||||
|
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
|
||||||
|
return units::degrees<float> {elevationAngleConverted};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint16_t DigitalRadarData::elevation_number() const
|
std::uint16_t DigitalRadarData::elevation_number() const
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,19 @@ uint16_t VolumeCoveragePatternData::number_of_base_tilts() const
|
||||||
|
|
||||||
double VolumeCoveragePatternData::elevation_angle(uint16_t e) const
|
double VolumeCoveragePatternData::elevation_angle(uint16_t e) const
|
||||||
{
|
{
|
||||||
return p->elevationCuts_[e].elevationAngle_ * ANGLE_DATA_SCALE;
|
|
||||||
|
double elevationAngleConverted =
|
||||||
|
p->elevationCuts_[e].elevationAngle_ * ANGLE_DATA_SCALE;
|
||||||
|
// Any elevation above 90 degrees should be interpreted as a
|
||||||
|
// negative angle
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
if (elevationAngleConverted > 90)
|
||||||
|
{
|
||||||
|
elevationAngleConverted -= 360;
|
||||||
|
}
|
||||||
|
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
|
||||||
|
|
||||||
|
return elevationAngleConverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t VolumeCoveragePatternData::elevation_angle_raw(uint16_t e) const
|
uint16_t VolumeCoveragePatternData::elevation_angle_raw(uint16_t e) const
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ set(HDR_NETWORK include/scwx/network/cpr.hpp
|
||||||
set(SRC_NETWORK source/scwx/network/cpr.cpp
|
set(SRC_NETWORK source/scwx/network/cpr.cpp
|
||||||
source/scwx/network/dir_list.cpp)
|
source/scwx/network/dir_list.cpp)
|
||||||
set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp
|
set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp
|
||||||
|
include/scwx/provider/aws_level2_chunks_data_provider.hpp
|
||||||
include/scwx/provider/aws_level3_data_provider.hpp
|
include/scwx/provider/aws_level3_data_provider.hpp
|
||||||
include/scwx/provider/aws_nexrad_data_provider.hpp
|
include/scwx/provider/aws_nexrad_data_provider.hpp
|
||||||
include/scwx/provider/iem_api_provider.hpp
|
include/scwx/provider/iem_api_provider.hpp
|
||||||
|
|
@ -68,6 +69,7 @@ set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp
|
||||||
include/scwx/provider/nexrad_data_provider_factory.hpp
|
include/scwx/provider/nexrad_data_provider_factory.hpp
|
||||||
include/scwx/provider/warnings_provider.hpp)
|
include/scwx/provider/warnings_provider.hpp)
|
||||||
set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp
|
set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp
|
||||||
|
source/scwx/provider/aws_level2_chunks_data_provider.cpp
|
||||||
source/scwx/provider/aws_level3_data_provider.cpp
|
source/scwx/provider/aws_level3_data_provider.cpp
|
||||||
source/scwx/provider/aws_nexrad_data_provider.cpp
|
source/scwx/provider/aws_nexrad_data_provider.cpp
|
||||||
source/scwx/provider/iem_api_provider.cpp
|
source/scwx/provider/iem_api_provider.cpp
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue