Compare commits

..

43 commits

Author SHA1 Message Date
Dan Paulat
d218d05184
Merge pull request #508 from dpaulat/hotfix/level2-elevation-race-condition
Some checks failed
CI / macos_clang18_arm64 (push) Has been cancelled
CI / macos_clang18_x64 (push) Has been cancelled
CI / windows_msvc2022_x64 (push) Has been cancelled
CI / linux_gcc_arm64 (push) Has been cancelled
CI / linux_clang_x64 (push) Has been cancelled
CI / linux_gcc_x64 (push) Has been cancelled
Elevation cuts needs protected by mutex
2025-09-20 09:27:50 -05:00
Dan Paulat
2cc5e48102 Elevation cuts needs protected by mutex
- If elevation cuts is read while GetLevel2Data is being called from ComputeSweep, the application will crash.
- Additionally, the elevationCut_ variable should probably be made atomic.
2025-09-20 08:25:25 -05:00
Dan Paulat
4502b59830
Merge pull request #507 from dpaulat/hotfix/validate-level3-product
Ensure level 3 product is validated in settings to prevent crash
2025-09-14 18:19:25 -05:00
Dan Paulat
eed2736dde Update level 3 defaults in test data 2025-09-14 14:00:27 -05:00
Dan Paulat
d687d04d76 Make sure level3Product is const 2025-09-14 13:59:24 -05:00
Dan Paulat
54fd86de5c Ensure level 3 product is validated in settings to prevent crash on invalid value 2025-09-14 11:48:25 -05:00
Dan Paulat
b8e9cb659e
Merge pull request #489 from AdenKoperczak/alerts_on_mouse_up
Switch alert clicks to mouse button release
2025-09-07 08:58:56 -05:00
Dan Paulat
94dc5b547d Bump version to v0.5.2 2025-09-07 08:49:13 -05:00
Dan Paulat
2dc69544ec Fix Mineral Mapbox style drawBelow value to tunnel 2025-09-07 08:48:44 -05:00
Dan Paulat
f8ebd265e0
Merge pull request #504 from dpaulat/hotfix/level2-fixes
Level 2 AWS Bucket Name Update
2025-09-07 08:44:57 -05:00
Dan Paulat
a43c2df13f Add an additional provider thread pool 2025-09-06 23:09:15 -05:00
Dan Paulat
a952d890e6 Ensure animation thread pools stop instead of taking on new work on destruction 2025-09-06 23:08:56 -05:00
Dan Paulat
9416ff1546 Level 2 bucket name changed
The NEXRAD Level II archive data is moving to a new bucket: unidata-nexrad-level2 and SNS topic: arn:aws:sns:us-east-1:684042711724:NewNEXRADLevel2Archive. The old bucket and SNS topic are now deprecated and will no longer be available starting September 1, 2025.
2025-09-06 20:40:58 -05:00
Dan Paulat
cb749e7b9e Re-add chunks provider to populate product times 2025-09-06 08:54:17 -05:00
Dan Paulat
4fdf52b9b4
Merge pull request #503 from dpaulat/feature/product-load-status
Background Network Requests and No Data Available
2025-09-05 21:34:45 -05:00
Dan Paulat
76a74922c5 AlertLayer threading fixes
(cherry picked from commit e6c395a657bbb6d3532eb0cf883c154a3b508deb)
2025-09-04 21:39:42 -05:00
Dan Paulat
f34d11a7ea RefreshData clang-tidy fixes
(cherry picked from commit c630c2969929ce46d96fd4f941f054dcc70d1d8b)
2025-09-03 22:39:20 -05:00
Dan Paulat
3fe7dd9eed RefreshData should be run entirely through ProviderManager
- Impl may be destroyed before ProviderManager, leading to a use-after-free condition (lifetime race condition)
- Moving to ProviderManager entirely should let the thread pool join prevent use-after-free

(cherry picked from commit 212cca973f6f5d1462e19a8a3e1cc535e691e552)
2025-09-03 22:17:32 -05:00
Dan Paulat
985473a0a4 Release all mutexes before joining threads in TimelineManager 2025-09-01 10:00:48 -05:00
Dan Paulat
7fbd9e45a9 Release all mutexes before joining threads in RadarProductManager 2025-09-01 09:44:37 -05:00
Dan Paulat
889b6e81be Taking load data mutexes on destruction can cause a deadlock 2025-09-01 01:30:47 -05:00
Dan Paulat
acfb515e10 Filter PopulateProductTimes log entries 2025-08-31 10:32:04 -05:00
Dan Paulat
22a6ed33c1 Product load status clang-tidy fixes 2025-08-31 00:44:26 -05:00
Dan Paulat
95b9a03437 Add required parameter for GetTimePointsByDate 2025-08-31 00:02:35 -05:00
Dan Paulat
77ae293e87 Remove unused newLoadStatus 2025-08-31 00:02:05 -05:00
Dan Paulat
d6834127db NotAvailable should not hang animation 2025-08-30 23:10:23 -05:00
Dan Paulat
1f8cd8ee39 Overlay layer should not need to latch load status
- Causes false NO DATA AVAILABLE indications when changing products
2025-08-30 23:01:21 -05:00
Dan Paulat
b0c7554f47 Applying product load status to level 2 2025-08-30 22:46:40 -05:00
Dan Paulat
403a556b30 Add tooltip to NO DATA AVAILABLE indication 2025-08-30 22:45:37 -05:00
Dan Paulat
2beac20ee3 Asset suffix should be const, not constexpr 2025-08-30 22:38:31 -05:00
Dan Paulat
07adbb382d Prevent flickering of radar data between two "no data available" products 2025-08-30 21:19:33 -05:00
Dan Paulat
41bf7f731f RadarProductLayer clang-tidy fixes 2025-08-30 20:34:32 -05:00
Dan Paulat
931dd2d0a7 Store last load status 2025-08-30 20:30:32 -05:00
Dan Paulat
341096af1d Render data not available status in the upper right (overlay layer) 2025-08-30 18:28:46 -05:00
Dan Paulat
f679f37fc1 Hide the radar sweep if no data is available 2025-08-30 18:26:10 -05:00
Dan Paulat
a306fb4363 Add NoUpdateReason of NotAvailable, driven by ProductNotAvailable 2025-08-30 18:24:50 -05:00
Dan Paulat
449d8cb796 A query for the epoch should be treated as time now 2025-08-30 18:21:16 -05:00
Dan Paulat
f4226b487d Initial moving of product listing to the background for level 3 2025-08-30 16:28:19 -05:00
Dan Paulat
68f66c0c2f Add Qt version to log 2025-08-30 16:28:19 -05:00
Dan Paulat
df0d698837
Merge pull request #502 from dpaulat/hotfix/restore-from-minimize
Only restore geometry on first show, not restore from minimize
2025-08-30 15:00:00 -05:00
Dan Paulat
b6797eee7e Adding const to uiGeometry and uiState 2025-08-28 19:08:50 -05:00
Dan Paulat
3c5c9ea27e Only restore geometry on first show, not restore from minimize 2025-08-28 18:30:23 -05:00
AdenKoperczak
0e21884322
Switch alert clicks to mouse button release 2025-07-19 09:20:00 -04:00
32 changed files with 868 additions and 355 deletions

View file

@ -127,7 +127,7 @@ jobs:
env: env:
CC: ${{ matrix.env_cc }} CC: ${{ matrix.env_cc }}
CXX: ${{ matrix.env_cxx }} CXX: ${{ matrix.env_cxx }}
SCWX_VERSION: v0.5.1 SCWX_VERSION: v0.5.2
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:

View file

@ -8,7 +8,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 12.0)
scwx_python_setup() scwx_python_setup()
project(${PROJECT_NAME} project(${PROJECT_NAME}
VERSION 0.5.1 VERSION 0.5.2
DESCRIPTION "Supercell Wx is a free, open source advanced weather radar viewer." DESCRIPTION "Supercell Wx is a free, open source advanced weather radar viewer."
HOMEPAGE_URL "https://github.com/dpaulat/supercell-wx" HOMEPAGE_URL "https://github.com/dpaulat/supercell-wx"
LANGUAGES C CXX) LANGUAGES C CXX)
@ -32,7 +32,7 @@ set_property(DIRECTORY
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_ALL_NO_LIB") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_ALL_NO_LIB")
set(SCWX_DIR ${PROJECT_SOURCE_DIR}) set(SCWX_DIR ${PROJECT_SOURCE_DIR})
set(SCWX_VERSION "0.5.1") set(SCWX_VERSION "0.5.2")
option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF) option(SCWX_ADDRESS_SANITIZER "Build with Address Sanitizer" OFF)

View file

@ -234,6 +234,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp
source/scwx/qt/types/media_types.hpp source/scwx/qt/types/media_types.hpp
source/scwx/qt/types/qt_types.hpp source/scwx/qt/types/qt_types.hpp
source/scwx/qt/types/radar_product_record.hpp source/scwx/qt/types/radar_product_record.hpp
source/scwx/qt/types/radar_product_types.hpp
source/scwx/qt/types/text_event_key.hpp source/scwx/qt/types/text_event_key.hpp
source/scwx/qt/types/text_types.hpp source/scwx/qt/types/text_types.hpp
source/scwx/qt/types/texture_types.hpp source/scwx/qt/types/texture_types.hpp

View file

@ -27,6 +27,7 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <fmt/format.h> #include <fmt/format.h>
#include <QApplication> #include <QApplication>
#include <QLibraryInfo>
#include <QStandardPaths> #include <QStandardPaths>
#include <QStyleHints> #include <QStyleHints>
#include <QSurfaceFormat> #include <QSurfaceFormat>
@ -71,6 +72,8 @@ int main(int argc, char* argv[])
scwx::qt::main::kVersionString_, scwx::qt::main::kVersionString_,
scwx::qt::main::kBuildNumber_, scwx::qt::main::kBuildNumber_,
scwx::qt::main::kCommitString_); scwx::qt::main::kCommitString_);
logger_->info("Qt version {}",
QLibraryInfo::version().toString().toStdString());
InitializeOpenGL(); InitializeOpenGL();

View file

@ -463,18 +463,28 @@ void MainWindow::keyReleaseEvent(QKeyEvent* ev)
void MainWindow::showEvent(QShowEvent* event) void MainWindow::showEvent(QShowEvent* event)
{ {
QMainWindow::showEvent(event); QMainWindow::showEvent(event);
auto& uiSettings = settings::UiSettings::Instance();
// restore the geometry state static bool firstShowEvent = true;
std::string uiGeometry = uiSettings.main_ui_geometry().GetValue(); bool restored = false;
restoreGeometry(
QByteArray::fromBase64(QByteArray::fromStdString(uiGeometry)));
// restore the UI state if (firstShowEvent)
std::string uiState = uiSettings.main_ui_state().GetValue(); {
auto& uiSettings = settings::UiSettings::Instance();
// restore the geometry state
const std::string uiGeometry = uiSettings.main_ui_geometry().GetValue();
restoreGeometry(
QByteArray::fromBase64(QByteArray::fromStdString(uiGeometry)));
// restore the UI state
const std::string uiState = uiSettings.main_ui_state().GetValue();
restored = restoreState(
QByteArray::fromBase64(QByteArray::fromStdString(uiState)));
firstShowEvent = false;
}
bool restored =
restoreState(QByteArray::fromBase64(QByteArray::fromStdString(uiState)));
if (!restored) if (!restored)
{ {
resizeDocks({ui->radarToolboxDock}, {194}, Qt::Horizontal); resizeDocks({ui->radarToolboxDock}, {194}, Qt::Horizontal);

View file

@ -15,6 +15,7 @@
#include <execution> #include <execution>
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
@ -36,11 +37,7 @@
# pragma warning(pop) # pragma warning(pop)
#endif #endif
namespace scwx namespace scwx::qt::manager
{
namespace qt
{
namespace manager
{ {
static const std::string logPrefix_ = static const std::string logPrefix_ =
@ -109,21 +106,27 @@ public:
group, product, isChunks_, latestTime); group, product, isChunks_, latestTime);
}); });
} }
~ProviderManager() { threadPool_.join(); }; ~ProviderManager() override
{
providerThreadPool_.stop();
providerThreadPool_.join();
};
std::string name() const; std::string name() const;
void Disable(); void Disable();
void RefreshData();
void RefreshDataSync();
boost::asio::thread_pool threadPool_ {1u}; boost::asio::thread_pool providerThreadPool_ {2u};
const std::string radarId_; const std::string radarId_;
const common::RadarProductGroup group_; const common::RadarProductGroup group_;
const std::string product_; const std::string product_;
const bool isChunks_; const bool isChunks_;
bool refreshEnabled_ {false}; bool refreshEnabled_ {false};
boost::asio::steady_timer refreshTimer_ {threadPool_}; boost::asio::steady_timer refreshTimer_ {providerThreadPool_};
std::mutex refreshTimerMutex_ {}; std::mutex refreshTimerMutex_ {};
std::shared_ptr<provider::NexradDataProvider> provider_ {nullptr}; std::shared_ptr<provider::NexradDataProvider> provider_ {nullptr};
size_t refreshCount_ {0}; size_t refreshCount_ {0};
@ -184,11 +187,9 @@ public:
auto& [key, providerManager] = p; auto& [key, providerManager] = p;
providerManager->Disable(); providerManager->Disable();
}); });
lock.unlock();
// Lock other mutexes before destroying, ensure loading is complete threadPool_.stop();
std::unique_lock loadLevel2DataLock {loadLevel2DataMutex_};
std::unique_lock loadLevel3DataLock {loadLevel3DataMutex_};
threadPool_.join(); threadPool_.join();
} }
@ -203,14 +204,14 @@ public:
boost::uuids::uuid uuid, boost::uuids::uuid uuid,
const std::set<std::shared_ptr<ProviderManager>>& providerManagers, const std::set<std::shared_ptr<ProviderManager>>& providerManagers,
bool enabled); bool enabled);
void RefreshData(std::shared_ptr<ProviderManager> providerManager);
void RefreshDataSync(std::shared_ptr<ProviderManager> providerManager);
std::map<std::chrono::system_clock::time_point, std::tuple<std::map<std::chrono::system_clock::time_point,
std::shared_ptr<types::RadarProductRecord>> std::shared_ptr<types::RadarProductRecord>>,
types::RadarProductLoadStatus>
GetLevel2ProductRecords(std::chrono::system_clock::time_point time); GetLevel2ProductRecords(std::chrono::system_clock::time_point time);
std::tuple<std::shared_ptr<types::RadarProductRecord>, std::tuple<std::shared_ptr<types::RadarProductRecord>,
std::chrono::system_clock::time_point> std::chrono::system_clock::time_point,
types::RadarProductLoadStatus>
GetLevel3ProductRecord(const std::string& product, GetLevel3ProductRecord(const std::string& product,
std::chrono::system_clock::time_point time); std::chrono::system_clock::time_point time);
std::shared_ptr<types::RadarProductRecord> std::shared_ptr<types::RadarProductRecord>
@ -224,15 +225,24 @@ public:
std::mutex& mutex, std::mutex& mutex,
std::chrono::system_clock::time_point time); std::chrono::system_clock::time_point time);
void void
LoadProviderData(std::chrono::system_clock::time_point time, LoadProviderData(std::chrono::system_clock::time_point time,
std::shared_ptr<ProviderManager> providerManager, std::shared_ptr<ProviderManager> providerManager,
RadarProductRecordMap& recordMap, RadarProductRecordMap& recordMap,
std::shared_mutex& recordMutex, std::shared_mutex& recordMutex,
std::mutex& loadDataMutex, std::mutex& loadDataMutex,
const std::shared_ptr<request::NexradFileRequest>& request); const std::shared_ptr<request::NexradFileRequest>& request);
void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time);
bool AreLevel2ProductTimesPopulated(
std::chrono::system_clock::time_point time) const;
bool
AreLevel3ProductTimesPopulated(const std::string& product,
std::chrono::system_clock::time_point time);
void PopulateLevel2ProductTimes(std::chrono::system_clock::time_point time,
bool update = true);
void PopulateLevel3ProductTimes(const std::string& product, void PopulateLevel3ProductTimes(const std::string& product,
std::chrono::system_clock::time_point time); std::chrono::system_clock::time_point time,
bool update = true);
void UpdateAvailableProductsSync(); void UpdateAvailableProductsSync();
@ -243,11 +253,16 @@ public:
const float gateRangeOffset, const float gateRangeOffset,
std::vector<float>& outputCoordinates); std::vector<float>& outputCoordinates);
static bool AreProductTimesPopulated(
const std::shared_ptr<ProviderManager>& providerManager,
std::chrono::system_clock::time_point time);
static void static void
PopulateProductTimes(std::shared_ptr<ProviderManager> providerManager, PopulateProductTimes(std::shared_ptr<ProviderManager> providerManager,
RadarProductRecordMap& productRecordMap, RadarProductRecordMap& productRecordMap,
std::shared_mutex& productRecordMutex, std::shared_mutex& productRecordMutex,
std::chrono::system_clock::time_point time); std::chrono::system_clock::time_point time,
bool update);
static void static void
LoadNexradFile(CreateNexradFileFunction load, LoadNexradFile(CreateNexradFileFunction load,
@ -771,28 +786,27 @@ void RadarProductManagerImpl::EnableRefresh(
if (providerManager->refreshEnabled_ != enabled) if (providerManager->refreshEnabled_ != enabled)
{ {
providerManager->refreshEnabled_ = enabled; providerManager->refreshEnabled_ = enabled;
RefreshData(providerManager); providerManager->RefreshData();
} }
} }
} }
} }
void RadarProductManagerImpl::RefreshData( void ProviderManager::RefreshData()
std::shared_ptr<ProviderManager> providerManager)
{ {
logger_->trace("RefreshData: {}", providerManager->name()); logger_->trace("RefreshData: {}", name());
{ {
std::unique_lock lock(providerManager->refreshTimerMutex_); const std::unique_lock lock(refreshTimerMutex_);
providerManager->refreshTimer_.cancel(); refreshTimer_.cancel();
} }
boost::asio::post(threadPool_, boost::asio::post(providerThreadPool_,
[=, this]() [this]()
{ {
try try
{ {
RefreshDataSync(providerManager); RefreshDataSync();
} }
catch (const std::exception& ex) catch (const std::exception& ex)
{ {
@ -801,27 +815,24 @@ void RadarProductManagerImpl::RefreshData(
}); });
} }
void RadarProductManagerImpl::RefreshDataSync( void ProviderManager::RefreshDataSync()
std::shared_ptr<ProviderManager> providerManager)
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
auto [newObjects, totalObjects] = providerManager->provider_->Refresh(); auto [newObjects, totalObjects] = provider_->Refresh();
// Level2 chunked data is updated quickly and uses a faster interval // Level2 chunked data is updated quickly and uses a faster interval
const std::chrono::milliseconds fastRetryInterval = const std::chrono::milliseconds fastRetryInterval =
providerManager->isChunks_ ? kFastRetryIntervalChunks_ : isChunks_ ? kFastRetryIntervalChunks_ : kFastRetryInterval_;
kFastRetryInterval_;
const std::chrono::milliseconds slowRetryInterval = const std::chrono::milliseconds slowRetryInterval =
providerManager->isChunks_ ? kSlowRetryIntervalChunks_ : isChunks_ ? kSlowRetryIntervalChunks_ : kSlowRetryInterval_;
kSlowRetryInterval_;
std::chrono::milliseconds interval = fastRetryInterval; std::chrono::milliseconds interval = fastRetryInterval;
if (totalObjects > 0) if (totalObjects > 0)
{ {
auto latestTime = providerManager->provider_->FindLatestTime(); auto latestTime = provider_->FindLatestTime();
auto updatePeriod = providerManager->provider_->update_period(); auto updatePeriod = provider_->update_period();
auto lastModified = providerManager->provider_->last_modified(); auto lastModified = provider_->last_modified();
auto sinceLastModified = scwx::util::time::now() - lastModified; auto sinceLastModified = scwx::util::time::now() - lastModified;
// For the default interval, assume products are updated at a // For the default interval, assume products are updated at a
@ -830,7 +841,11 @@ void RadarProductManagerImpl::RefreshDataSync(
interval = std::chrono::duration_cast<std::chrono::milliseconds>( interval = std::chrono::duration_cast<std::chrono::milliseconds>(
updatePeriod - sinceLastModified); updatePeriod - sinceLastModified);
if (updatePeriod > 0s && sinceLastModified > updatePeriod * 5) // Allow 5 update periods before considering the data stale
constexpr std::size_t kUpdatePeriodStaleCount = 5;
if (updatePeriod > 0s &&
sinceLastModified > updatePeriod * kUpdatePeriodStaleCount)
{ {
// If it has been at least 5 update periods since the file has // If it has been at least 5 update periods since the file has
// been last modified, slow the retry period // been last modified, slow the retry period
@ -844,46 +859,43 @@ void RadarProductManagerImpl::RefreshDataSync(
if (newObjects > 0) if (newObjects > 0)
{ {
Q_EMIT providerManager->NewDataAvailable( Q_EMIT NewDataAvailable(group_, product_, latestTime);
providerManager->group_, providerManager->product_, latestTime);
} }
} }
else if (providerManager->refreshEnabled_) else if (refreshEnabled_)
{ {
logger_->info("[{}] No data found", providerManager->name()); logger_->info("[{}] No data found", name());
// If no data is found, retry at the slow retry interval // If no data is found, retry at the slow retry interval
interval = slowRetryInterval; interval = slowRetryInterval;
} }
std::unique_lock const lock(providerManager->refreshTimerMutex_); std::unique_lock const lock(refreshTimerMutex_);
if (providerManager->refreshEnabled_) if (refreshEnabled_)
{ {
logger_->trace( logger_->trace(
"[{}] Scheduled refresh in {:%M:%S}", "[{}] Scheduled refresh in {:%M:%S}",
providerManager->name(), name(),
std::chrono::duration_cast<std::chrono::seconds>(interval)); std::chrono::duration_cast<std::chrono::seconds>(interval));
{ {
providerManager->refreshTimer_.expires_after(interval); refreshTimer_.expires_after(interval);
providerManager->refreshTimer_.async_wait( refreshTimer_.async_wait(
[=, this](const boost::system::error_code& e) [this](const boost::system::error_code& e)
{ {
if (e == boost::system::errc::success) if (e == boost::system::errc::success)
{ {
RefreshData(providerManager); RefreshData();
} }
else if (e == boost::asio::error::operation_aborted) else if (e == boost::asio::error::operation_aborted)
{ {
logger_->debug("[{}] Data refresh timer cancelled", logger_->debug("[{}] Data refresh timer cancelled", name());
providerManager->name());
} }
else else
{ {
logger_->warn("[{}] Data refresh timer error: {}", logger_->warn(
providerManager->name(), "[{}] Data refresh timer error: {}", name(), e.message());
e.message());
} }
}); });
} }
@ -934,32 +946,32 @@ RadarProductManager::GetActiveVolumeTimes(
[&](const std::shared_ptr<provider::NexradDataProvider>& provider) [&](const std::shared_ptr<provider::NexradDataProvider>& provider)
{ {
// For yesterday, today and tomorrow (in parallel) // For yesterday, today and tomorrow (in parallel)
std::for_each(std::execution::par, std::for_each(
dates.begin(), std::execution::par,
dates.end(), dates.begin(),
[&](const auto& date) dates.end(),
{ [&](const auto& date)
// Don't query for a time point in the future {
if (date > scwx::util::time::now()) // Don't query for a time point in the future
{ if (date > scwx::util::time::now())
return; {
} return;
}
// Query the provider for volume time points // Query the provider for volume time points
auto timePoints = provider->GetTimePointsByDate(date); auto timePoints = provider->GetTimePointsByDate(date, true);
// TODO: Note, this will miss volume times present in // TODO: Note, this will miss volume times present in Level 2
// Level 2 products with a second scan // products with a second scan
// Lock the merged volume time list // Lock the merged volume time list
std::unique_lock volumeTimesLock {volumeTimesMutex}; const std::unique_lock volumeTimesLock {volumeTimesMutex};
// Copy time points to the merged list // Copy time points to the merged list
std::copy( std::copy(timePoints.begin(),
timePoints.begin(), timePoints.end(),
timePoints.end(), std::inserter(volumeTimes, volumeTimes.end()));
std::inserter(volumeTimes, volumeTimes.end())); });
});
}); });
// Return merged volume times list // Return merged volume times list
@ -1202,21 +1214,75 @@ void RadarProductManagerImpl::LoadNexradFile(
} }
} }
bool RadarProductManagerImpl::AreLevel2ProductTimesPopulated(
std::chrono::system_clock::time_point time) const
{
return AreProductTimesPopulated(level2ProviderManager_, time);
}
bool RadarProductManagerImpl::AreLevel3ProductTimesPopulated(
const std::string& product, std::chrono::system_clock::time_point time)
{
// Get provider manager
const auto level3ProviderManager = GetLevel3ProviderManager(product);
return AreProductTimesPopulated(level3ProviderManager, time);
}
bool RadarProductManagerImpl::AreProductTimesPopulated(
const std::shared_ptr<ProviderManager>& providerManager,
std::chrono::system_clock::time_point time)
{
auto today = std::chrono::floor<std::chrono::days>(time);
bool productTimesPopulated = true;
// Assume a query for the epoch is a query for now
if (today == std::chrono::system_clock::time_point {})
{
today = std::chrono::floor<std::chrono::days>(scwx::util::time::now());
}
const auto yesterday = today - std::chrono::days {1};
const auto tomorrow = today + std::chrono::days {1};
const auto dates = {yesterday, today, tomorrow};
for (auto& date : dates)
{
// Don't query for a time point in the future
if (date > scwx::util::time::now())
{
continue;
}
if (!providerManager->provider_->IsDateCached(date))
{
productTimesPopulated = false;
}
}
return productTimesPopulated;
}
void RadarProductManagerImpl::PopulateLevel2ProductTimes( void RadarProductManagerImpl::PopulateLevel2ProductTimes(
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time, bool update)
{ {
PopulateProductTimes(level2ProviderManager_, PopulateProductTimes(level2ProviderManager_,
level2ProductRecords_, level2ProductRecords_,
level2ProductRecordMutex_, level2ProductRecordMutex_,
time); time,
update);
PopulateProductTimes(level2ChunksProviderManager_, PopulateProductTimes(level2ChunksProviderManager_,
level2ProductRecords_, level2ProductRecords_,
level2ProductRecordMutex_, level2ProductRecordMutex_,
time); time,
update);
} }
void RadarProductManagerImpl::PopulateLevel3ProductTimes( void RadarProductManagerImpl::PopulateLevel3ProductTimes(
const std::string& product, std::chrono::system_clock::time_point time) const std::string& product,
std::chrono::system_clock::time_point time,
bool update)
{ {
// Get provider manager // Get provider manager
auto level3ProviderManager = GetLevel3ProviderManager(product); auto level3ProviderManager = GetLevel3ProviderManager(product);
@ -1229,21 +1295,38 @@ void RadarProductManagerImpl::PopulateLevel3ProductTimes(
PopulateProductTimes(level3ProviderManager, PopulateProductTimes(level3ProviderManager,
level3ProductRecords, level3ProductRecords,
level3ProductRecordMutex_, level3ProductRecordMutex_,
time); time,
update);
} }
void RadarProductManagerImpl::PopulateProductTimes( void RadarProductManagerImpl::PopulateProductTimes(
std::shared_ptr<ProviderManager> providerManager, std::shared_ptr<ProviderManager> providerManager,
RadarProductRecordMap& productRecordMap, RadarProductRecordMap& productRecordMap,
std::shared_mutex& productRecordMutex, std::shared_mutex& productRecordMutex,
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time,
bool update)
{ {
const auto today = std::chrono::floor<std::chrono::days>(time); if (update)
{
logger_->debug("Populating product times: {}, {}, {}",
common::GetRadarProductGroupName(providerManager->group_),
providerManager->product_,
scwx::util::time::TimeString(time));
}
else
{
logger_->trace("Populating cached product times: {}, {}, {}",
common::GetRadarProductGroupName(providerManager->group_),
providerManager->product_,
scwx::util::time::TimeString(time));
}
// Don't query for the epoch auto today = std::chrono::floor<std::chrono::days>(time);
// Assume a query for the epoch is a query for now
if (today == std::chrono::system_clock::time_point {}) if (today == std::chrono::system_clock::time_point {})
{ {
return; today = std::chrono::floor<std::chrono::days>(scwx::util::time::now());
} }
const auto yesterday = today - std::chrono::days {1}; const auto yesterday = today - std::chrono::days {1};
@ -1267,7 +1350,8 @@ void RadarProductManagerImpl::PopulateProductTimes(
// Query the provider for volume time points // Query the provider for volume time points
auto timePoints = auto timePoints =
providerManager->provider_->GetTimePointsByDate(date); providerManager->provider_->GetTimePointsByDate(date,
update);
// Lock the merged volume time list // Lock the merged volume time list
std::unique_lock volumeTimesLock {volumeTimesMutex}; std::unique_lock volumeTimesLock {volumeTimesMutex};
@ -1293,8 +1377,9 @@ void RadarProductManagerImpl::PopulateProductTimes(
}); });
} }
std::map<std::chrono::system_clock::time_point, std::tuple<std::map<std::chrono::system_clock::time_point,
std::shared_ptr<types::RadarProductRecord>> std::shared_ptr<types::RadarProductRecord>>,
types::RadarProductLoadStatus>
RadarProductManagerImpl::GetLevel2ProductRecords( RadarProductManagerImpl::GetLevel2ProductRecords(
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time)
{ {
@ -1302,9 +1387,40 @@ RadarProductManagerImpl::GetLevel2ProductRecords(
std::shared_ptr<types::RadarProductRecord>> std::shared_ptr<types::RadarProductRecord>>
records {}; records {};
std::vector<RadarProductRecordMap::const_pointer> recordPtrs {}; std::vector<RadarProductRecordMap::const_pointer> recordPtrs {};
types::RadarProductLoadStatus status {
types::RadarProductLoadStatus::ListingProducts};
std::size_t recordPtrCount = 0u;
std::size_t recordCount = 0u;
// Ensure Level 2 product records are updated // Ensure Level 2 product records are updated
PopulateLevel2ProductTimes(time); if (!AreLevel2ProductTimesPopulated(time))
{
logger_->debug("Level 2 product times need populated: {}",
scwx::util::time::TimeString(time));
// Populate level 2 product times asynchronously
boost::asio::post(threadPool_,
[time, this]()
{
// Populate product times
PopulateLevel2ProductTimes(time);
// Signal finished
Q_EMIT self_->ProductTimesPopulated(
common::RadarProductGroup::Level2, "", time);
});
// Return listing products status
return {records, status};
}
else
{
PopulateLevel2ProductTimes(time, false);
}
// Advance to loading product
status = types::RadarProductLoadStatus::LoadingProduct;
{ {
std::shared_lock lock {level2ProductRecordMutex_}; std::shared_lock lock {level2ProductRecordMutex_};
@ -1343,9 +1459,29 @@ RadarProductManagerImpl::GetLevel2ProductRecords(
if (recordPtr != nullptr) if (recordPtr != nullptr)
{ {
using namespace std::chrono_literals;
// Don't check for an exact time match for level 2 products // Don't check for an exact time match for level 2 products
recordTime = recordPtr->first; recordTime = recordPtr->first;
record = recordPtr->second.lock();
if (
// For latest data, ensure it is from the last 24 hours
(time == std::chrono::system_clock::time_point {} &&
(recordTime > scwx::util::time::now() - 24h ||
recordTime == time)) ||
// For time queries, ensure data is within 24 hours of the request
(time != std::chrono::system_clock::time_point {} &&
std::chrono::abs(recordTime - time) < 24h))
{
record = recordPtr->second.lock();
++recordPtrCount;
}
else
{
// Reset the record
recordPtr = nullptr;
recordTime = time;
}
} }
if (recordPtr != nullptr && record == nullptr && if (recordPtr != nullptr && record == nullptr &&
@ -1368,29 +1504,73 @@ RadarProductManagerImpl::GetLevel2ProductRecords(
}); });
self_->LoadLevel2Data(recordTime, request); self_->LoadLevel2Data(recordTime, request);
// Status is already set to LoadingProduct
} }
if (record != nullptr) if (record != nullptr)
{ {
// Return valid records // Return valid records
records.insert_or_assign(recordTime, record); records.insert_or_assign(recordTime, record);
++recordCount;
} }
} }
return records; if (recordPtrCount == 0)
{
// If all records are empty, the product is not available
status = types::RadarProductLoadStatus::ProductNotAvailable;
}
else if (recordCount == recordPtrCount)
{
// If all records were populated, the product has been loaded
status = types::RadarProductLoadStatus::ProductLoaded;
}
return {records, status};
} }
std::tuple<std::shared_ptr<types::RadarProductRecord>, std::tuple<std::shared_ptr<types::RadarProductRecord>,
std::chrono::system_clock::time_point> std::chrono::system_clock::time_point,
types::RadarProductLoadStatus>
RadarProductManagerImpl::GetLevel3ProductRecord( RadarProductManagerImpl::GetLevel3ProductRecord(
const std::string& product, std::chrono::system_clock::time_point time) const std::string& product, std::chrono::system_clock::time_point time)
{ {
std::shared_ptr<types::RadarProductRecord> record {nullptr}; std::shared_ptr<types::RadarProductRecord> record {nullptr};
RadarProductRecordMap::const_pointer recordPtr {nullptr}; RadarProductRecordMap::const_pointer recordPtr {nullptr};
std::chrono::system_clock::time_point recordTime {time}; std::chrono::system_clock::time_point recordTime {time};
types::RadarProductLoadStatus status {
types::RadarProductLoadStatus::ListingProducts};
// Ensure Level 3 product records are updated // Ensure Level 3 product records are updated
PopulateLevel3ProductTimes(product, time); if (!AreLevel3ProductTimesPopulated(product, time))
{
logger_->debug("Level 3 product times need populated: {}, {}",
product,
scwx::util::time::TimeString(time));
// Populate level 3 product times asynchronously
boost::asio::post(threadPool_,
[product, time, this]()
{
// Populate product times
PopulateLevel3ProductTimes(product, time);
// Signal finished
Q_EMIT self_->ProductTimesPopulated(
common::RadarProductGroup::Level3, product, time);
});
// Return listing products status
return {record, recordTime, status};
}
else
{
PopulateLevel3ProductTimes(product, time, false);
}
// Advance to loading product
status = types::RadarProductLoadStatus::LoadingProduct;
std::unique_lock lock {level3ProductRecordMutex_}; std::unique_lock lock {level3ProductRecordMutex_};
@ -1415,9 +1595,27 @@ RadarProductManagerImpl::GetLevel3ProductRecord(
if (recordPtr != nullptr) if (recordPtr != nullptr)
{ {
using namespace std::chrono_literals;
// Don't check for an exact time match for level 3 products // Don't check for an exact time match for level 3 products
recordTime = recordPtr->first; recordTime = recordPtr->first;
record = recordPtr->second.lock();
if (
// For latest data, ensure it is from the last 24 hours
(time == std::chrono::system_clock::time_point {} &&
(recordTime > scwx::util::time::now() - 24h || recordTime == time)) ||
// For time queries, ensure data is within 24 hours of the request
(time != std::chrono::system_clock::time_point {} &&
std::chrono::abs(recordTime - time) < 24h))
{
record = recordPtr->second.lock();
}
else
{
// Reset the record
recordPtr = nullptr;
recordTime = time;
}
} }
if (recordPtr != nullptr && record == nullptr && if (recordPtr != nullptr && record == nullptr &&
@ -1440,9 +1638,22 @@ RadarProductManagerImpl::GetLevel3ProductRecord(
}); });
self_->LoadLevel3Data(product, recordTime, request); self_->LoadLevel3Data(product, recordTime, request);
// Status is already set to LoadingProduct
} }
return {record, recordTime}; if (recordPtr == nullptr)
{
// If the record is empty, the product is not available
status = types::RadarProductLoadStatus::ProductNotAvailable;
}
else if (record != nullptr)
{
// If the record was populated, the product has been loaded
status = types::RadarProductLoadStatus::ProductLoaded;
}
return {record, recordTime, status};
} }
std::shared_ptr<types::RadarProductRecord> std::shared_ptr<types::RadarProductRecord>
@ -1543,7 +1754,8 @@ void RadarProductManagerImpl::UpdateRecentRecords(
std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>,
float, float,
std::vector<float>, std::vector<float>,
std::chrono::system_clock::time_point> std::chrono::system_clock::time_point,
types::RadarProductLoadStatus>
RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
float elevation, float elevation,
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time)
@ -1552,6 +1764,8 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
float elevationCut = 0.0f; float elevationCut = 0.0f;
std::vector<float> elevationCuts {}; std::vector<float> elevationCuts {};
std::chrono::system_clock::time_point foundTime {}; std::chrono::system_clock::time_point foundTime {};
types::RadarProductLoadStatus loadStatus {
types::RadarProductLoadStatus::ProductNotLoaded};
const bool isEpox = time == std::chrono::system_clock::time_point {}; const bool isEpox = time == std::chrono::system_clock::time_point {};
bool needArchive = true; bool needArchive = true;
@ -1587,6 +1801,7 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
if (foundTime >= firstValidChunkTime) if (foundTime >= firstValidChunkTime)
{ {
needArchive = false; needArchive = false;
loadStatus = types::RadarProductLoadStatus::ProductLoaded;
} }
} }
} }
@ -1594,7 +1809,11 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
// It is not in the chunk provider, so get it from the archive // It is not in the chunk provider, so get it from the archive
if (needArchive) if (needArchive)
{ {
auto records = p->GetLevel2ProductRecords(time); std::map<std::chrono::system_clock::time_point,
std::shared_ptr<types::RadarProductRecord>>
records;
std::tie(records, loadStatus) = p->GetLevel2ProductRecords(time);
for (auto& recordPair : records) for (auto& recordPair : records)
{ {
auto& record = recordPair.second; auto& record = recordPair.second;
@ -1639,25 +1858,35 @@ RadarProductManager::GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
} }
} }
return {radarData, elevationCut, elevationCuts, foundTime}; if (loadStatus == types::RadarProductLoadStatus::ProductLoaded &&
radarData == nullptr)
{
// If all data was available for the time point, but there is no matching
// radar data, consider this as no product available
loadStatus = types::RadarProductLoadStatus::ProductNotAvailable;
}
return {radarData, elevationCut, elevationCuts, foundTime, loadStatus};
} }
std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>,
std::chrono::system_clock::time_point> std::chrono::system_clock::time_point,
types::RadarProductLoadStatus>
RadarProductManager::GetLevel3Data(const std::string& product, RadarProductManager::GetLevel3Data(const std::string& product,
std::chrono::system_clock::time_point time) std::chrono::system_clock::time_point time)
{ {
std::shared_ptr<wsr88d::rpg::Level3Message> message = nullptr; std::shared_ptr<wsr88d::rpg::Level3Message> message = nullptr;
types::RadarProductLoadStatus status {};
std::shared_ptr<types::RadarProductRecord> record; std::shared_ptr<types::RadarProductRecord> record;
std::tie(record, time) = p->GetLevel3ProductRecord(product, time); std::tie(record, time, status) = p->GetLevel3ProductRecord(product, time);
if (record != nullptr) if (record != nullptr)
{ {
message = record->level3_file()->message(); message = record->level3_file()->message();
} }
return {message, time}; return {message, time, status};
} }
common::Level3ProductCategoryMap common::Level3ProductCategoryMap
@ -1809,6 +2038,4 @@ RadarProductManager::Instance(const std::string& radarSite)
#include "radar_product_manager.moc" #include "radar_product_manager.moc"
} // namespace manager } // namespace scwx::qt::manager
} // namespace qt
} // namespace scwx

View file

@ -5,23 +5,19 @@
#include <scwx/qt/config/radar_site.hpp> #include <scwx/qt/config/radar_site.hpp>
#include <scwx/qt/request/nexrad_file_request.hpp> #include <scwx/qt/request/nexrad_file_request.hpp>
#include <scwx/qt/types/radar_product_record.hpp> #include <scwx/qt/types/radar_product_record.hpp>
#include <scwx/qt/types/radar_product_types.hpp>
#include <scwx/util/time.hpp> #include <scwx/util/time.hpp>
#include <scwx/wsr88d/ar2v_file.hpp> #include <scwx/wsr88d/ar2v_file.hpp>
#include <scwx/wsr88d/level3_file.hpp> #include <scwx/wsr88d/level3_file.hpp>
#include <memory> #include <memory>
#include <set> #include <set>
#include <unordered_map>
#include <vector> #include <vector>
#include <boost/uuid/nil_generator.hpp> #include <boost/uuid/nil_generator.hpp>
#include <QObject> #include <QObject>
namespace scwx namespace scwx::qt::manager
{
namespace qt
{
namespace manager
{ {
class RadarProductManagerImpl; class RadarProductManagerImpl;
@ -89,12 +85,13 @@ public:
* @param [in] time Radar product time * @param [in] time Radar product time
* *
* @return Level 2 radar data, selected elevation cut, available elevation * @return Level 2 radar data, selected elevation cut, available elevation
* cuts and selected time * cuts, selected time and product load status
*/ */
std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>, std::tuple<std::shared_ptr<wsr88d::rda::ElevationScan>,
float, float,
std::vector<float>, std::vector<float>,
std::chrono::system_clock::time_point> std::chrono::system_clock::time_point,
types::RadarProductLoadStatus>
GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType, GetLevel2Data(wsr88d::rda::DataBlockType dataBlockType,
float elevation, float elevation,
std::chrono::system_clock::time_point time = {}); std::chrono::system_clock::time_point time = {});
@ -105,10 +102,11 @@ public:
* @param [in] product Radar product name * @param [in] product Radar product name
* @param [in] time Radar product time * @param [in] time Radar product time
* *
* @return Level 3 message data and selected time * @return Level 3 message data, selected time and product load status
*/ */
std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>, std::tuple<std::shared_ptr<wsr88d::rpg::Level3Message>,
std::chrono::system_clock::time_point> std::chrono::system_clock::time_point,
types::RadarProductLoadStatus>
GetLevel3Data(const std::string& product, GetLevel3Data(const std::string& product,
std::chrono::system_clock::time_point time = {}); std::chrono::system_clock::time_point time = {});
@ -151,6 +149,9 @@ signals:
bool isChunks, bool isChunks,
std::chrono::system_clock::time_point latestTime); std::chrono::system_clock::time_point latestTime);
void IncomingLevel2ElevationChanged(std::optional<float> incomingElevation); void IncomingLevel2ElevationChanged(std::optional<float> incomingElevation);
void ProductTimesPopulated(common::RadarProductGroup group,
const std::string& product,
std::chrono::system_clock::time_point queryTime);
private: private:
std::unique_ptr<RadarProductManagerImpl> p; std::unique_ptr<RadarProductManagerImpl> p;
@ -158,6 +159,4 @@ private:
friend class RadarProductManagerImpl; friend class RadarProductManagerImpl;
}; };
} // namespace manager } // namespace scwx::qt::manager
} // namespace qt
} // namespace scwx

View file

@ -53,8 +53,13 @@ public:
// Lock mutexes before destroying // Lock mutexes before destroying
std::unique_lock animationTimerLock {animationTimerMutex_}; std::unique_lock animationTimerLock {animationTimerMutex_};
animationTimer_.cancel(); animationTimer_.cancel();
animationTimerLock.unlock();
std::unique_lock selectTimeLock {selectTimeMutex_}; selectThreadPool_.stop();
playThreadPool_.stop();
selectThreadPool_.join();
playThreadPool_.join();
} }
TimelineManager* self_; TimelineManager* self_;

View file

@ -6,8 +6,8 @@
#include <scwx/qt/util/color.hpp> #include <scwx/qt/util/color.hpp>
#include <scwx/qt/util/tooltip.hpp> #include <scwx/qt/util/tooltip.hpp>
#include <scwx/util/logger.hpp> #include <scwx/util/logger.hpp>
#include <scwx/util/time.hpp>
#include <atomic>
#include <chrono> #include <chrono>
#include <mutex> #include <mutex>
#include <ranges> #include <ranges>
@ -15,6 +15,7 @@
#include <shared_mutex> #include <shared_mutex>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/asio/system_timer.hpp> #include <boost/asio/system_timer.hpp>
@ -22,7 +23,6 @@
#include <boost/container/stable_vector.hpp> #include <boost/container/stable_vector.hpp>
#include <boost/container_hash/hash.hpp> #include <boost/container_hash/hash.hpp>
#include <QEvent> #include <QEvent>
#include <utility>
namespace scwx::qt::map namespace scwx::qt::map
{ {
@ -153,9 +153,11 @@ public:
~Impl() ~Impl()
{ {
std::unique_lock refreshLock(refreshMutex_); std::unique_lock refreshLock(refreshMutex_);
refreshEnabled_ = false;
refreshTimer_.cancel(); refreshTimer_.cancel();
refreshLock.unlock(); refreshLock.unlock();
threadPool_.stop();
threadPool_.join(); threadPool_.join();
receiver_ = nullptr; receiver_ = nullptr;
@ -213,6 +215,7 @@ public:
AlertLayer* self_; AlertLayer* self_;
std::atomic<bool> refreshEnabled_ {true};
boost::asio::system_timer refreshTimer_ {threadPool_}; boost::asio::system_timer refreshTimer_ {threadPool_};
std::mutex refreshMutex_; std::mutex refreshMutex_;
@ -582,7 +585,8 @@ void AlertLayer::Impl::ScheduleRefresh()
// Expires at the top of the next minute // Expires at the top of the next minute
std::chrono::system_clock::time_point now = std::chrono::system_clock::time_point now =
std::chrono::floor<std::chrono::minutes>(scwx::util::time::now()); std::chrono::floor<std::chrono::minutes>(
std::chrono::system_clock::now());
refreshTimer_.expires_at(now + 1min); refreshTimer_.expires_at(now + 1min);
refreshTimer_.async_wait( refreshTimer_.async_wait(
@ -599,7 +603,11 @@ void AlertLayer::Impl::ScheduleRefresh()
else else
{ {
Q_EMIT self_->NeedsRendering(); Q_EMIT self_->NeedsRendering();
ScheduleRefresh();
if (refreshEnabled_)
{
ScheduleRefresh();
}
} }
}); });
} }
@ -883,7 +891,7 @@ void AlertLayer::Impl::HandleGeoLinesEvent(
switch (ev->type()) switch (ev->type())
{ {
case QEvent::Type::MouseButtonPress: case QEvent::Type::MouseButtonRelease:
{ {
auto it = segmentsByLine_.find(di); auto it = segmentsByLine_.find(di);
if (it != segmentsByLine_.cend()) if (it != segmentsByLine_.cend())

View file

@ -73,7 +73,7 @@ static const std::unordered_map<MapProvider, MapProviderInfo> mapProviderInfo_ {
.drawBelow_ {mapboxDrawBelow_}}, .drawBelow_ {mapboxDrawBelow_}},
{.name_ {"Mineral"}, {.name_ {"Mineral"},
.url_ {"mapbox://styles/mapbox/cjtep62gq54l21frr1whf27ak"}, .url_ {"mapbox://styles/mapbox/cjtep62gq54l21frr1whf27ak"},
.drawBelow_ {mapboxDrawBelow_}}, .drawBelow_ {"tunnel"}},
{.name_ {"Minimo"}, {.name_ {"Minimo"},
.url_ { .url_ {
"mapbox://styles/mapbox-map-design/cksjc2nsq1bg117pnekb655h1"}, "mapbox://styles/mapbox-map-design/cksjc2nsq1bg117pnekb655h1"},

View file

@ -92,8 +92,15 @@ public:
Impl(const Impl&&) = delete; Impl(const Impl&&) = delete;
Impl& operator=(const Impl&&) = delete; Impl& operator=(const Impl&&) = delete;
void RenderProductName(const std::shared_ptr<MapContext>& mapContext);
void
RenderProductDetails(const std::shared_ptr<MapContext>& mapContext,
const QMapLibre::CustomLayerRenderParameters& params);
void RenderAttribution(const std::shared_ptr<MapContext>& mapContext,
const QMapLibre::CustomLayerRenderParameters& params);
void SetupGeoIcons(); void SetupGeoIcons();
void SetCusorLocation(common::Coordinate coordinate); void SetCursorLocation(common::Coordinate coordinate);
OverlayLayer* self_; OverlayLayer* self_;
@ -173,7 +180,7 @@ OverlayLayer::~OverlayLayer()
p->cursorScaleConnection_.disconnect(); p->cursorScaleConnection_.disconnect();
} }
void OverlayLayer::Impl::SetCusorLocation(common::Coordinate coordinate) void OverlayLayer::Impl::SetCursorLocation(common::Coordinate coordinate)
{ {
geoIcons_->SetIconLocation( geoIcons_->SetIconLocation(
cursorIcon_, coordinate.latitude_, coordinate.longitude_); cursorIcon_, coordinate.latitude_, coordinate.longitude_);
@ -388,7 +395,7 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
p->geoIcons_->SetIconVisible(p->cursorIcon_, cursorIconVisible); p->geoIcons_->SetIconVisible(p->cursorIcon_, cursorIconVisible);
if (cursorIconVisible) if (cursorIconVisible)
{ {
p->SetCusorLocation(mapContext->mouse_coordinate()); p->SetCursorLocation(mapContext->mouse_coordinate());
} }
// Location Icon // Location Icon
@ -425,6 +432,58 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
} }
} }
p->RenderProductName(mapContext);
p->RenderProductDetails(mapContext, params);
// Map Center Icon
if (params.width != p->lastWidth_ || params.height != p->lastHeight_)
{
static constexpr double xPosition = 0.5;
static constexpr double yPosition = 0.5;
// Draw the icon in the center of the widget
p->icons_->SetIconLocation(p->mapCenterIcon_,
params.width * xPosition,
params.height * yPosition);
}
p->icons_->SetIconVisible(p->mapCenterIcon_,
generalSettings.show_map_center().GetValue());
const QMargins colorTableMargins = mapContext->color_table_margins();
if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_)
{
static constexpr int xOffset = 10;
static constexpr int yOffset = 10;
// Draw map logo with a 10x10 indent from the bottom left
p->icons_->SetIconLocation(p->mapLogoIcon_,
colorTableMargins.left() + xOffset,
colorTableMargins.bottom() + yOffset);
}
p->icons_->SetIconVisible(p->mapLogoIcon_,
generalSettings.show_map_logo().GetValue());
DrawLayer::RenderWithoutImGui(params);
p->RenderAttribution(mapContext, params);
p->firstRender_ = false;
p->lastWidth_ = params.width;
p->lastHeight_ = params.height;
p->lastBearing_ = params.bearing;
p->lastFontSize_ = ImGui::GetFontSize();
p->lastColorTableMargins_ = colorTableMargins;
ImGuiFrameEnd();
SCWX_GL_CHECK_ERROR();
}
void OverlayLayer::Impl::RenderProductName(
const std::shared_ptr<MapContext>& mapContext)
{
auto radarProductView = mapContext->radar_product_view();
if (radarProductView != nullptr) if (radarProductView != nullptr)
{ {
// Render product name // Render product name
@ -456,14 +515,68 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
ImGui::End(); ImGui::End();
} }
} }
}
if (p->sweepTimeString_.length() > 0) void OverlayLayer::Impl::RenderProductDetails(
const std::shared_ptr<MapContext>& mapContext,
const QMapLibre::CustomLayerRenderParameters& params)
{
auto radarProductView = mapContext->radar_product_view();
ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), 0.0f},
ImGuiCond_Always,
ImVec2 {1.0f, 0.0f});
bool productNotAvailable = false;
types::RadarProductLoadStatus newLoadStatus =
types::RadarProductLoadStatus::ProductNotLoaded;
if (radarProductView != nullptr)
{
newLoadStatus = radarProductView->load_status();
switch (newLoadStatus)
{
case types::RadarProductLoadStatus::ProductNotAvailable:
productNotAvailable = true;
break;
default:
productNotAvailable = false;
}
}
if (productNotAvailable)
{
ImGui::Begin("Product Not Available",
nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize);
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255));
ImGui::TextUnformatted("NO DATA AVAILABLE");
ImGui::PopStyleColor();
if (ImGui::BeginItemTooltip())
{
static constexpr float kFontSizeFactor_ = 20.0f;
static constexpr double kMaxWidthPercent_ = 0.8;
ImGui::PushTextWrapPos(
std::min(ImGui::GetFontSize() * kFontSizeFactor_,
static_cast<float>(params.width * kMaxWidthPercent_)));
ImGui::TextUnformatted(
"No data found for the selected product and time. Please select a "
"different product, or update your time selection.");
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::End();
}
else if (sweepTimeString_.length() > 0)
{ {
// Render time // Render time
ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), 0.0f}, ImGui::Begin("Product Details",
ImGuiCond_Always,
ImVec2 {1.0f, 0.0f});
ImGui::Begin("Sweep Time",
nullptr, nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize); ImGuiWindowFlags_AlwaysAutoResize);
@ -471,12 +584,12 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
if (radarProductView != nullptr && ImGui::IsWindowHovered()) if (radarProductView != nullptr && ImGui::IsWindowHovered())
{ {
// Show a detailed product description when the sweep time is hovered // Show a detailed product description when the sweep time is hovered
p->sweepTimePicked_ = true; sweepTimePicked_ = true;
auto fields = radarProductView->GetDescriptionFields(); auto fields = radarProductView->GetDescriptionFields();
if (fields.empty()) if (fields.empty())
{ {
ImGui::TextUnformatted(p->sweepTimeString_.c_str()); ImGui::TextUnformatted(sweepTimeString_.c_str());
} }
else else
{ {
@ -496,34 +609,19 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
} }
else else
{ {
ImGui::TextUnformatted(p->sweepTimeString_.c_str()); ImGui::TextUnformatted(sweepTimeString_.c_str());
} }
ImGui::End(); ImGui::End();
} }
}
// Map Center Icon void OverlayLayer::Impl::RenderAttribution(
if (params.width != p->lastWidth_ || params.height != p->lastHeight_) const std::shared_ptr<MapContext>& mapContext,
{ const QMapLibre::CustomLayerRenderParameters& params)
// Draw the icon in the center of the widget {
p->icons_->SetIconLocation(
p->mapCenterIcon_, params.width / 2.0, params.height / 2.0);
}
p->icons_->SetIconVisible(p->mapCenterIcon_,
generalSettings.show_map_center().GetValue());
const QMargins colorTableMargins = mapContext->color_table_margins(); const QMargins colorTableMargins = mapContext->color_table_margins();
if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_) auto& generalSettings = settings::GeneralSettings::Instance();
{
// Draw map logo with a 10x10 indent from the bottom left
p->icons_->SetIconLocation(p->mapLogoIcon_,
10 + colorTableMargins.left(),
10 + colorTableMargins.bottom());
}
p->icons_->SetIconVisible(p->mapLogoIcon_,
generalSettings.show_map_logo().GetValue());
DrawLayer::RenderWithoutImGui(params);
auto mapCopyrights = mapContext->map_copyrights(); auto mapCopyrights = mapContext->map_copyrights();
if (mapCopyrights.length() > 0 && if (mapCopyrights.length() > 0 &&
@ -532,13 +630,19 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
auto attributionFont = manager::FontManager::Instance().GetImGuiFont( auto attributionFont = manager::FontManager::Instance().GetImGuiFont(
types::FontCategory::Attribution); types::FontCategory::Attribution);
ImGui::SetNextWindowPos(ImVec2 {static_cast<float>(params.width), static constexpr float kWindowBgAlpha_ = 0.5f;
static_cast<float>(params.height) - static constexpr float kWindowPaddingX_ = 3.0f;
colorTableMargins.bottom()}, static constexpr float kWindowPaddingY_ = 2.0f;
ImGuiCond_Always,
ImVec2 {1.0f, 1.0f}); ImGui::SetNextWindowPos(
ImGui::SetNextWindowBgAlpha(0.5f); ImVec2 {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {3.0f, 2.0f}); static_cast<float>(params.width),
static_cast<float>(params.height - colorTableMargins.bottom())},
ImGuiCond_Always,
ImVec2 {1.0f, 1.0f});
ImGui::SetNextWindowBgAlpha(kWindowBgAlpha_);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
ImVec2 {kWindowPaddingX_, kWindowPaddingY_});
ImGui::PushFont(attributionFont.first->font(), ImGui::PushFont(attributionFont.first->font(),
attributionFont.second.value()); attributionFont.second.value());
ImGui::Begin("Attribution", ImGui::Begin("Attribution",
@ -550,17 +654,6 @@ void OverlayLayer::Render(const std::shared_ptr<MapContext>& mapContext,
ImGui::PopFont(); ImGui::PopFont();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
} }
p->firstRender_ = false;
p->lastWidth_ = params.width;
p->lastHeight_ = params.height;
p->lastBearing_ = params.bearing;
p->lastFontSize_ = ImGui::GetFontSize();
p->lastColorTableMargins_ = colorTableMargins;
ImGuiFrameEnd();
SCWX_GL_CHECK_ERROR();
} }
void OverlayLayer::Deinitialize() void OverlayLayer::Deinitialize()

View file

@ -63,6 +63,9 @@ public:
bool colorTableNeedsUpdate_ {false}; bool colorTableNeedsUpdate_ {false};
bool sweepNeedsUpdate_ {false}; bool sweepNeedsUpdate_ {false};
types::RadarProductLoadStatus latchedLoadStatus_ {
types::RadarProductLoadStatus::ProductNotAvailable};
}; };
RadarProductLayer::RadarProductLayer(std::shared_ptr<gl::GlContext> glContext) : RadarProductLayer::RadarProductLayer(std::shared_ptr<gl::GlContext> glContext) :
@ -146,6 +149,26 @@ void RadarProductLayer::Initialize(
&view::RadarProductView::SweepComputed, &view::RadarProductView::SweepComputed,
this, this,
[this]() { p->sweepNeedsUpdate_ = true; }); [this]() { p->sweepNeedsUpdate_ = true; });
connect(radarProductView.get(),
&view::RadarProductView::SweepNotComputed,
this,
[this](types::NoUpdateReason reason)
{
if (reason == types::NoUpdateReason::NotAvailable)
{
// Ensure the radar product is hidden by re-rendering
Q_EMIT NeedsRendering();
}
if (reason == types::NoUpdateReason::NoChange)
{
if (p->latchedLoadStatus_ ==
types::RadarProductLoadStatus::ProductNotAvailable)
{
// Ensure the radar product is shown by re-rendering
Q_EMIT NeedsRendering();
}
}
});
} }
void RadarProductLayer::UpdateSweep( void RadarProductLayer::UpdateSweep(
@ -189,10 +212,10 @@ void RadarProductLayer::UpdateSweep(
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
// Buffer data moments // Buffer data moments
const GLvoid* data; const GLvoid* data {};
GLsizeiptr dataSize; GLsizeiptr dataSize {};
size_t componentSize; size_t componentSize {};
GLenum type; GLenum type {};
std::tie(data, dataSize, componentSize) = radarProductView->GetMomentData(); std::tie(data, dataSize, componentSize) = radarProductView->GetMomentData();
@ -215,10 +238,10 @@ void RadarProductLayer::UpdateSweep(
glEnableVertexAttribArray(1); glEnableVertexAttribArray(1);
// Buffer CFP data // Buffer CFP data
const GLvoid* cfpData; const GLvoid* cfpData {};
GLsizeiptr cfpDataSize; GLsizeiptr cfpDataSize {};
size_t cfpComponentSize; size_t cfpComponentSize {};
GLenum cfpType; GLenum cfpType {};
std::tie(cfpData, cfpDataSize, cfpComponentSize) = std::tie(cfpData, cfpDataSize, cfpComponentSize) =
radarProductView->GetCfpMomentData(); radarProductView->GetCfpMomentData();
@ -258,7 +281,6 @@ void RadarProductLayer::Render(
const std::shared_ptr<MapContext>& mapContext, const std::shared_ptr<MapContext>& mapContext,
const QMapLibre::CustomLayerRenderParameters& params) const QMapLibre::CustomLayerRenderParameters& params)
{ {
p->shaderProgram_->Use(); p->shaderProgram_->Use();
// Set OpenGL blend mode for transparency // Set OpenGL blend mode for transparency
@ -281,34 +303,65 @@ void RadarProductLayer::Render(
UpdateSweep(mapContext); UpdateSweep(mapContext);
} }
const float scale = std::pow(2.0, params.zoom) * 2.0f * const std::shared_ptr<view::RadarProductView> radarProductView =
mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; mapContext->radar_product_view();
const float xScale = scale / params.width;
const float yScale = scale / params.height;
glm::mat4 uMVPMatrix(1.0f); bool sweepVisible = false;
uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); types::RadarProductLoadStatus newLoadStatus =
uMVPMatrix = glm::rotate(uMVPMatrix, types::RadarProductLoadStatus::ProductNotLoaded;
glm::radians<float>(params.bearing),
glm::vec3(0.0f, 0.0f, 1.0f));
glUniform2fv(p->uMapScreenCoordLocation_, if (radarProductView != nullptr)
1, {
glm::value_ptr(util::maplibre::LatLongToScreenCoordinate( newLoadStatus = radarProductView->load_status();
{params.latitude, params.longitude})));
glUniformMatrix4fv( switch (newLoadStatus)
p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); {
case types::RadarProductLoadStatus::ProductNotAvailable:
sweepVisible = false;
break;
glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0); case types::RadarProductLoadStatus::ListingProducts:
sweepVisible = p->latchedLoadStatus_ !=
types::RadarProductLoadStatus::ProductNotAvailable;
break;
glUniform1ui(p->uDataMomentOffsetLocation_, p->rangeMin_); default:
glUniform1f(p->uDataMomentScaleLocation_, p->scale_); sweepVisible = true;
}
}
glActiveTexture(GL_TEXTURE0); if (sweepVisible)
glBindTexture(GL_TEXTURE_1D, p->texture_); {
glBindVertexArray(p->vao_); const double scale = std::pow(2.0, params.zoom) * 2.0 *
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(p->numVertices_)); mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX;
const auto xScale = static_cast<float>(scale / params.width);
const auto yScale = static_cast<float>(scale / params.height);
glm::mat4 uMVPMatrix(1.0f);
uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f));
uMVPMatrix = glm::rotate(uMVPMatrix,
glm::radians(static_cast<float>(params.bearing)),
glm::vec3(0.0f, 0.0f, 1.0f));
glUniform2fv(p->uMapScreenCoordLocation_,
1,
glm::value_ptr(util::maplibre::LatLongToScreenCoordinate(
{params.latitude, params.longitude})));
glUniformMatrix4fv(
p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix));
glUniform1i(p->uCFPEnabledLocation_, p->cfpEnabled_ ? 1 : 0);
glUniform1ui(p->uDataMomentOffsetLocation_, p->rangeMin_);
glUniform1f(p->uDataMomentScaleLocation_, p->scale_);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_1D, p->texture_);
glBindVertexArray(p->vao_);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(p->numVertices_));
}
if (wireframeEnabled) if (wireframeEnabled)
{ {
@ -316,6 +369,16 @@ void RadarProductLayer::Render(
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
} }
if (radarProductView != nullptr &&
// Don't latch a transition from Not Available to Listing Products
!(p->latchedLoadStatus_ ==
types::RadarProductLoadStatus::ProductNotAvailable &&
newLoadStatus == types::RadarProductLoadStatus::ListingProducts))
{
// Latch last load status
p->latchedLoadStatus_ = newLoadStatus;
}
SCWX_GL_CHECK_ERROR(); SCWX_GL_CHECK_ERROR();
} }
@ -543,7 +606,7 @@ void RadarProductLayer::UpdateColorTable(
const uint16_t rangeMin = radarProductView->color_table_min(); const uint16_t rangeMin = radarProductView->color_table_min();
const uint16_t rangeMax = radarProductView->color_table_max(); const uint16_t rangeMax = radarProductView->color_table_max();
const float scale = rangeMax - rangeMin; const auto scale = static_cast<float>(rangeMax - rangeMin);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_1D, p->texture_); glBindTexture(GL_TEXTURE_1D, p->texture_);

View file

@ -86,8 +86,10 @@ public:
} }
else else
{ {
// TODO: Validate level 3 product // Validate level 3 product
return true; const auto level3Product =
common::GetLevel3ProductByAwipsId(value);
return !level3Product.empty() && level3Product != "?";
} }
}); });

View file

@ -25,6 +25,7 @@ enum class NoUpdateReason
{ {
NoChange, NoChange,
NotLoaded, NotLoaded,
NotAvailable,
InvalidProduct, InvalidProduct,
InvalidData InvalidData
}; };

View file

@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
namespace scwx::qt::types
{
enum class RadarProductLoadStatus : std::uint8_t
{
ProductNotLoaded,
ProductLoaded,
ListingProducts,
LoadingProduct,
ProductNotAvailable
};
}

View file

@ -93,9 +93,9 @@ void UpdateDialog::Impl::HandleAsset(const types::gh::ReleaseAsset& asset)
#if defined(_WIN32) #if defined(_WIN32)
# if defined(_M_AMD64) # if defined(_M_AMD64)
static constexpr std::string assetSuffix = "-x64.msi"; static const std::string assetSuffix = "-x64.msi";
# else # else
static constexpr std::string assetSuffix = "-arm64.msi"; static const std::string assetSuffix = "-arm64.msi";
# endif # endif
if (asset.name_.ends_with(assetSuffix)) if (asset.name_.ends_with(assetSuffix))

View file

@ -8,14 +8,13 @@
#include <scwx/util/threads.hpp> #include <scwx/util/threads.hpp>
#include <scwx/util/time.hpp> #include <scwx/util/time.hpp>
#include <atomic>
#include <mutex>
#include <boost/range/irange.hpp> #include <boost/range/irange.hpp>
#include <boost/timer/timer.hpp> #include <boost/timer/timer.hpp>
namespace scwx namespace scwx::qt::view
{
namespace qt
{
namespace view
{ {
static const std::string logPrefix_ = "scwx::qt::view::level2_product_view"; static const std::string logPrefix_ = "scwx::qt::view::level2_product_view";
@ -164,11 +163,13 @@ public:
float latitude_; float latitude_;
float longitude_; float longitude_;
float elevationCut_; std::atomic<float> elevationCut_;
std::vector<float> elevationCuts_; std::vector<float> elevationCuts_;
units::kilometers<float> range_; units::kilometers<float> range_;
uint16_t vcp_; uint16_t vcp_;
std::mutex elevationCutsMutex_ {};
std::chrono::system_clock::time_point sweepTime_; std::chrono::system_clock::time_point sweepTime_;
std::shared_ptr<common::ColorTable> colorTable_; std::shared_ptr<common::ColorTable> colorTable_;
@ -214,6 +215,22 @@ void Level2ProductView::ConnectRadarProductManager()
Update(); Update();
} }
}); });
connect(radar_product_manager().get(),
&manager::RadarProductManager::ProductTimesPopulated,
this,
[this](common::RadarProductGroup group,
const std::string& /* product */,
std::chrono::system_clock::time_point queryTime)
{
if (group == common::RadarProductGroup::Level2 &&
queryTime == selected_time())
{
// If the data associated with the currently selected time is
// reloaded, update the view
Update();
}
});
} }
void Level2ProductView::DisconnectRadarProductManager() void Level2ProductView::DisconnectRadarProductManager()
@ -356,6 +373,7 @@ std::string Level2ProductView::GetRadarProductName() const
std::vector<float> Level2ProductView::GetElevationCuts() const std::vector<float> Level2ProductView::GetElevationCuts() const
{ {
const std::unique_lock lock {p->elevationCutsMutex_};
return p->elevationCuts_; return p->elevationCuts_;
} }
@ -552,13 +570,26 @@ void Level2ProductView::ComputeSweep()
std::shared_ptr<wsr88d::rda::ElevationScan> radarData; std::shared_ptr<wsr88d::rda::ElevationScan> radarData;
std::chrono::system_clock::time_point requestedTime {selected_time()}; std::chrono::system_clock::time_point requestedTime {selected_time()};
std::tie(radarData, p->elevationCut_, p->elevationCuts_, std::ignore) = types::RadarProductLoadStatus loadStatus {};
std::vector<float> newElevationCuts {};
std::tie(
radarData, p->elevationCut_, newElevationCuts, std::ignore, loadStatus) =
radarProductManager->GetLevel2Data( radarProductManager->GetLevel2Data(
p->dataBlockType_, p->selectedElevation_, requestedTime); p->dataBlockType_, p->selectedElevation_, requestedTime);
std::unique_lock elevationCutsLock {p->elevationCutsMutex_};
p->elevationCuts_ = newElevationCuts;
elevationCutsLock.unlock();
set_load_status(loadStatus);
if (radarData == nullptr) if (radarData == nullptr)
{ {
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); Q_EMIT SweepNotComputed(
loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ?
types::NoUpdateReason::NotAvailable :
types::NoUpdateReason::NotLoaded);
return; return;
} }
if ((radarData == p->elevationScan_) && if ((radarData == p->elevationScan_) &&
@ -1369,7 +1400,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
auto nextRadial = radarData->find((i + 1) % numRadials); auto nextRadial = radarData->find((i + 1) % numRadials);
if (nextRadial != radarData->cend()) if (nextRadial != radarData->cend())
{ {
nextAngle = nextRadial->second->azimuth_angle(); nextAngle = nextRadial->second->azimuth_angle();
// Level 2 angles are the center of the bins. // Level 2 angles are the center of the bins.
const units::degrees<float> deltaAngle = const units::degrees<float> deltaAngle =
@ -1564,6 +1595,4 @@ std::shared_ptr<Level2ProductView> Level2ProductView::Create(
return std::make_shared<Level2ProductView>(product, radarProductManager); return std::make_shared<Level2ProductView>(product, radarProductManager);
} }
} // namespace view } // namespace scwx::qt::view
} // namespace qt
} // namespace scwx

View file

@ -11,17 +11,12 @@
#include <scwx/wsr88d/rpg/radial_data_packet.hpp> #include <scwx/wsr88d/rpg/radial_data_packet.hpp>
#include <limits> #include <limits>
#include <unordered_set>
#include <boost/range/irange.hpp> #include <boost/range/irange.hpp>
#include <boost/timer/timer.hpp> #include <boost/timer/timer.hpp>
#include <fmt/format.h> #include <fmt/format.h>
namespace scwx namespace scwx::qt::view
{
namespace qt
{
namespace view
{ {
static const std::string logPrefix_ = "scwx::qt::view::level3_product_view"; static const std::string logPrefix_ = "scwx::qt::view::level3_product_view";
@ -151,6 +146,22 @@ void Level3ProductView::ConnectRadarProductManager()
Update(); Update();
} }
}); });
connect(radar_product_manager().get(),
&manager::RadarProductManager::ProductTimesPopulated,
this,
[this](common::RadarProductGroup group,
const std::string& product,
std::chrono::system_clock::time_point queryTime)
{
if (group == common::RadarProductGroup::Level3 &&
product == p->product_ && queryTime == selected_time())
{
// If the data associated with the currently selected time is
// reloaded, update the view
Update();
}
});
} }
void Level3ProductView::DisconnectRadarProductManager() void Level3ProductView::DisconnectRadarProductManager()
@ -596,6 +607,4 @@ bool Level3ProductView::IgnoreUnits() const
return false; return false;
} }
} // namespace view } // namespace scwx::qt::view
} // namespace qt
} // namespace scwx

View file

@ -10,11 +10,7 @@
#include <boost/range/irange.hpp> #include <boost/range/irange.hpp>
#include <boost/timer/timer.hpp> #include <boost/timer/timer.hpp>
namespace scwx namespace scwx::qt::view
{
namespace qt
{
namespace view
{ {
static const std::string logPrefix_ = "scwx::qt::view::level3_radial_view"; static const std::string logPrefix_ = "scwx::qt::view::level3_radial_view";
@ -31,15 +27,7 @@ static constexpr std::uint32_t VALUES_PER_VERTEX = 2u;
class Level3RadialView::Impl class Level3RadialView::Impl
{ {
public: public:
explicit Impl(Level3RadialView* self) : explicit Impl(Level3RadialView* self) : self_ {self} {}
self_ {self},
latitude_ {},
longitude_ {},
range_ {},
vcp_ {},
sweepTime_ {}
{
}
~Impl() { threadPool_.join(); }; ~Impl() { threadPool_.join(); };
void ComputeCoordinates( void ComputeCoordinates(
@ -65,13 +53,13 @@ public:
bool lastShowSmoothedRangeFolding_ {false}; bool lastShowSmoothedRangeFolding_ {false};
bool lastSmoothingEnabled_ {false}; bool lastSmoothingEnabled_ {false};
float latitude_; float latitude_ {};
float longitude_; float longitude_ {};
std::optional<float> elevation_ {}; std::optional<float> elevation_ {};
float range_; float range_ {};
std::uint16_t vcp_; std::uint16_t vcp_ {};
std::chrono::system_clock::time_point sweepTime_; std::chrono::system_clock::time_point sweepTime_ {};
}; };
Level3RadialView::Level3RadialView( Level3RadialView::Level3RadialView(
@ -148,9 +136,12 @@ void Level3RadialView::ComputeSweep()
std::shared_ptr<wsr88d::rpg::Level3Message> message; std::shared_ptr<wsr88d::rpg::Level3Message> message;
std::chrono::system_clock::time_point requestedTime {selected_time()}; std::chrono::system_clock::time_point requestedTime {selected_time()};
std::chrono::system_clock::time_point foundTime; std::chrono::system_clock::time_point foundTime;
std::tie(message, foundTime) = types::RadarProductLoadStatus loadStatus {};
std::tie(message, foundTime, loadStatus) =
radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime);
set_load_status(loadStatus);
// If a different time was found than what was requested, update it // If a different time was found than what was requested, update it
if (requestedTime != foundTime) if (requestedTime != foundTime)
{ {
@ -160,7 +151,10 @@ void Level3RadialView::ComputeSweep()
if (message == nullptr) if (message == nullptr)
{ {
logger_->debug("Level 3 data not found"); logger_->debug("Level 3 data not found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); Q_EMIT SweepNotComputed(
loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ?
types::NoUpdateReason::NotAvailable :
types::NoUpdateReason::NotLoaded);
return; return;
} }
@ -752,6 +746,4 @@ std::shared_ptr<Level3RadialView> Level3RadialView::Create(
return std::make_shared<Level3RadialView>(product, radarProductManager); return std::make_shared<Level3RadialView>(product, radarProductManager);
} }
} // namespace view } // namespace scwx::qt::view
} // namespace qt
} // namespace scwx

View file

@ -10,11 +10,7 @@
#include <boost/timer/timer.hpp> #include <boost/timer/timer.hpp>
#include <units/angle.h> #include <units/angle.h>
namespace scwx namespace scwx::qt::view
{
namespace qt
{
namespace view
{ {
static const std::string logPrefix_ = "scwx::qt::view::level3_raster_view"; static const std::string logPrefix_ = "scwx::qt::view::level3_raster_view";
@ -125,9 +121,12 @@ void Level3RasterView::ComputeSweep()
std::shared_ptr<wsr88d::rpg::Level3Message> message; std::shared_ptr<wsr88d::rpg::Level3Message> message;
std::chrono::system_clock::time_point requestedTime {selected_time()}; std::chrono::system_clock::time_point requestedTime {selected_time()};
std::chrono::system_clock::time_point foundTime; std::chrono::system_clock::time_point foundTime;
std::tie(message, foundTime) = types::RadarProductLoadStatus loadStatus {};
std::tie(message, foundTime, loadStatus) =
radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime); radarProductManager->GetLevel3Data(GetRadarProductName(), requestedTime);
set_load_status(loadStatus);
// If a different time was found than what was requested, update it // If a different time was found than what was requested, update it
if (requestedTime != foundTime) if (requestedTime != foundTime)
{ {
@ -137,7 +136,10 @@ void Level3RasterView::ComputeSweep()
if (message == nullptr) if (message == nullptr)
{ {
logger_->debug("Level 3 data not found"); logger_->debug("Level 3 data not found");
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); Q_EMIT SweepNotComputed(
loadStatus == types::RadarProductLoadStatus::ProductNotAvailable ?
types::NoUpdateReason::NotAvailable :
types::NoUpdateReason::NotLoaded);
return; return;
} }
@ -538,6 +540,4 @@ std::shared_ptr<Level3RasterView> Level3RasterView::Create(
return std::make_shared<Level3RasterView>(product, radarProductManager); return std::make_shared<Level3RasterView>(product, radarProductManager);
} }
} // namespace view } // namespace scwx::qt::view
} // namespace qt
} // namespace scwx

View file

@ -8,11 +8,7 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/uuid/random_generator.hpp> #include <boost/uuid/random_generator.hpp>
namespace scwx namespace scwx::qt::view
{
namespace qt
{
namespace view
{ {
static const std::string logPrefix_ = "scwx::qt::view::overlay_product_view"; static const std::string logPrefix_ = "scwx::qt::view::overlay_product_view";
@ -128,6 +124,22 @@ void OverlayProductView::Impl::ConnectRadarProductManager()
} }
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
connect(radarProductManager_.get(),
&manager::RadarProductManager::ProductTimesPopulated,
self_,
[this](common::RadarProductGroup group,
const std::string& product,
std::chrono::system_clock::time_point queryTime)
{
if (group == common::RadarProductGroup::Level3 &&
product == kNst_ && queryTime == selectedTime_)
{
// If the data associated with the currently selected time is
// reloaded, update the view
Update(product);
}
});
} }
void OverlayProductView::Impl::DisconnectRadarProductManager() void OverlayProductView::Impl::DisconnectRadarProductManager()
@ -286,7 +298,7 @@ void OverlayProductView::Impl::Update(const std::string& product)
std::shared_ptr<wsr88d::rpg::Level3Message> message; std::shared_ptr<wsr88d::rpg::Level3Message> message;
std::chrono::system_clock::time_point requestedTime {selectedTime_}; std::chrono::system_clock::time_point requestedTime {selectedTime_};
std::chrono::system_clock::time_point foundTime; std::chrono::system_clock::time_point foundTime;
std::tie(message, foundTime) = std::tie(message, foundTime, std::ignore) =
radarProductManager_->GetLevel3Data(product, requestedTime); radarProductManager_->GetLevel3Data(product, requestedTime);
// If a different time was found than what was requested, update it // If a different time was found than what was requested, update it
@ -329,6 +341,4 @@ void OverlayProductView::SetAutoUpdate(bool enabled)
p->autoUpdateEnabled_ = enabled; p->autoUpdateEnabled_ = enabled;
} }
} // namespace view } // namespace scwx::qt::view
} // namespace qt
} // namespace scwx

View file

@ -58,6 +58,8 @@ public:
std::chrono::system_clock::time_point selectedTime_; std::chrono::system_clock::time_point selectedTime_;
bool showSmoothedRangeFolding_ {false}; bool showSmoothedRangeFolding_ {false};
bool smoothingEnabled_ {false}; bool smoothingEnabled_ {false};
types::RadarProductLoadStatus loadStatus_ {
types::RadarProductLoadStatus::ProductNotLoaded};
std::shared_ptr<manager::RadarProductManager> radarProductManager_; std::shared_ptr<manager::RadarProductManager> radarProductManager_;
@ -90,6 +92,11 @@ std::optional<float> RadarProductView::elevation() const
return {}; return {};
} }
types::RadarProductLoadStatus RadarProductView::load_status() const
{
return p->loadStatus_;
}
std::shared_ptr<manager::RadarProductManager> std::shared_ptr<manager::RadarProductManager>
RadarProductView::radar_product_manager() const RadarProductView::radar_product_manager() const
{ {
@ -126,6 +133,11 @@ std::mutex& RadarProductView::sweep_mutex()
return p->sweepMutex_; return p->sweepMutex_;
} }
void RadarProductView::set_load_status(types::RadarProductLoadStatus loadStatus)
{
p->loadStatus_ = loadStatus;
}
void RadarProductView::set_radar_product_manager( void RadarProductView::set_radar_product_manager(
std::shared_ptr<manager::RadarProductManager> radarProductManager) std::shared_ptr<manager::RadarProductManager> radarProductManager)
{ {

View file

@ -38,11 +38,12 @@ public:
[[nodiscard]] virtual std::shared_ptr<common::ColorTable> [[nodiscard]] virtual std::shared_ptr<common::ColorTable>
color_table() const = 0; color_table() const = 0;
[[nodiscard]] virtual const std::vector<boost::gil::rgba8_pixel_t>& [[nodiscard]] virtual const std::vector<boost::gil::rgba8_pixel_t>&
color_table_lut() const; color_table_lut() const;
[[nodiscard]] virtual std::uint16_t color_table_min() const; [[nodiscard]] virtual std::uint16_t color_table_min() const;
[[nodiscard]] virtual std::uint16_t color_table_max() const; [[nodiscard]] virtual std::uint16_t color_table_max() const;
[[nodiscard]] virtual std::optional<float> elevation() const; [[nodiscard]] virtual std::optional<float> elevation() const;
[[nodiscard]] virtual float range() const; [[nodiscard]] types::RadarProductLoadStatus load_status() const;
[[nodiscard]] virtual float range() const;
[[nodiscard]] virtual std::chrono::system_clock::time_point [[nodiscard]] virtual std::chrono::system_clock::time_point
sweep_time() const; sweep_time() const;
[[nodiscard]] virtual float unit_scale() const = 0; [[nodiscard]] virtual float unit_scale() const = 0;
@ -98,6 +99,8 @@ protected:
virtual void DisconnectRadarProductManager() = 0; virtual void DisconnectRadarProductManager() = 0;
virtual void UpdateColorTableLut() = 0; virtual void UpdateColorTableLut() = 0;
void set_load_status(types::RadarProductLoadStatus loadStatus);
protected slots: protected slots:
virtual void ComputeSweep(); virtual void ComputeSweep();

@ -1 +1 @@
Subproject commit c68bee74549963e9a02e0fa998efad0f10f8256b Subproject commit fd8bc8bf1d07474886ce6773abffab4d315d0cd0

View file

@ -76,7 +76,7 @@ TEST(AwsLevel3DataProvider, GetTimePointsByDate)
AwsLevel3DataProvider provider("KLSX", "N0Q"); AwsLevel3DataProvider provider("KLSX", "N0Q");
auto timePoints = provider.GetTimePointsByDate(date); auto timePoints = provider.GetTimePointsByDate(date, true);
EXPECT_GT(timePoints.size(), 0); EXPECT_GT(timePoints.size(), 0);
for (auto timePoint : timePoints) for (auto timePoint : timePoints)

View file

@ -1,5 +1,5 @@
id: net.supercellwx.app id: net.supercellwx.app
version: '0.5.1' version: '0.5.2'
runtime: "org.freedesktop.Platform" runtime: "org.freedesktop.Platform"
runtime-version: "23.08" runtime-version: "23.08"
sdk: "org.freedesktop.Sdk" sdk: "org.freedesktop.Sdk"

View file

@ -46,7 +46,9 @@ public:
std::string FindLatestKey() override; std::string FindLatestKey() override;
std::chrono::system_clock::time_point FindLatestTime() override; std::chrono::system_clock::time_point FindLatestTime() override;
std::vector<std::chrono::system_clock::time_point> std::vector<std::chrono::system_clock::time_point>
GetTimePointsByDate(std::chrono::system_clock::time_point date) override; GetTimePointsByDate(std::chrono::system_clock::time_point date,
bool update) override;
bool IsDateCached(std::chrono::system_clock::time_point date) override;
std::tuple<bool, size_t, size_t> std::tuple<bool, size_t, size_t>
ListObjects(std::chrono::system_clock::time_point date) override; ListObjects(std::chrono::system_clock::time_point date) override;
std::shared_ptr<wsr88d::NexradFile> std::shared_ptr<wsr88d::NexradFile>

View file

@ -2,17 +2,12 @@
#include <scwx/provider/nexrad_data_provider.hpp> #include <scwx/provider/nexrad_data_provider.hpp>
namespace Aws namespace Aws::S3
{
namespace S3
{ {
class S3Client; class S3Client;
} // namespace S3 } // namespace Aws::S3
} // namespace Aws
namespace scwx namespace scwx::provider
{
namespace provider
{ {
/** /**
@ -32,20 +27,23 @@ public:
AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept; AwsNexradDataProvider(AwsNexradDataProvider&&) noexcept;
AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept; AwsNexradDataProvider& operator=(AwsNexradDataProvider&&) noexcept;
size_t cache_size() const override; [[nodiscard]] std::size_t cache_size() const override;
std::chrono::system_clock::time_point last_modified() const override; [[nodiscard]] std::chrono::system_clock::time_point
std::chrono::seconds update_period() const override; last_modified() const override;
[[nodiscard]] std::chrono::seconds update_period() const override;
std::string FindKey(std::chrono::system_clock::time_point time) override; std::string FindKey(std::chrono::system_clock::time_point time) override;
std::string FindLatestKey() override; std::string FindLatestKey() override;
std::chrono::system_clock::time_point FindLatestTime() override; std::chrono::system_clock::time_point FindLatestTime() override;
std::vector<std::chrono::system_clock::time_point> std::vector<std::chrono::system_clock::time_point>
GetTimePointsByDate(std::chrono::system_clock::time_point date) override; GetTimePointsByDate(std::chrono::system_clock::time_point date,
bool update) override;
bool IsDateCached(std::chrono::system_clock::time_point date) override;
std::tuple<bool, size_t, size_t> std::tuple<bool, size_t, size_t>
ListObjects(std::chrono::system_clock::time_point date) override; ListObjects(std::chrono::system_clock::time_point date) override;
std::shared_ptr<wsr88d::NexradFile> std::shared_ptr<wsr88d::NexradFile>
LoadObjectByKey(const std::string& key) override; LoadObjectByKey(const std::string& key) override;
std::shared_ptr<wsr88d::NexradFile> std::shared_ptr<wsr88d::NexradFile>
LoadObjectByTime(std::chrono::system_clock::time_point time) override; LoadObjectByTime(std::chrono::system_clock::time_point time) override;
std::pair<size_t, size_t> Refresh() override; std::pair<size_t, size_t> Refresh() override;
@ -61,5 +59,4 @@ private:
std::unique_ptr<Impl> p; std::unique_ptr<Impl> p;
}; };
} // namespace provider } // namespace scwx::provider
} // namespace scwx

View file

@ -7,9 +7,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace scwx namespace scwx::provider
{
namespace provider
{ {
class NexradDataProvider class NexradDataProvider
@ -24,7 +22,7 @@ public:
NexradDataProvider(NexradDataProvider&&) noexcept; NexradDataProvider(NexradDataProvider&&) noexcept;
NexradDataProvider& operator=(NexradDataProvider&&) noexcept; NexradDataProvider& operator=(NexradDataProvider&&) noexcept;
virtual size_t cache_size() const = 0; [[nodiscard]] virtual size_t cache_size() const = 0;
/** /**
* Gets the last modified time. This is equal to the most recent object's * Gets the last modified time. This is equal to the most recent object's
@ -32,7 +30,8 @@ public:
* *
* @return Last modified time * @return Last modified time
*/ */
virtual std::chrono::system_clock::time_point last_modified() const = 0; [[nodiscard]] virtual std::chrono::system_clock::time_point
last_modified() const = 0;
/** /**
* Gets the current update period. This is equal to the difference between * Gets the current update period. This is equal to the difference between
@ -41,7 +40,7 @@ public:
* *
* @return Update period * @return Update period
*/ */
virtual std::chrono::seconds update_period() const = 0; [[nodiscard]] virtual std::chrono::seconds update_period() const = 0;
/** /**
* Finds the most recent key in the cache, no later than the time provided. * Finds the most recent key in the cache, no later than the time provided.
@ -116,7 +115,7 @@ public:
* *
* @return NEXRAD data time point * @return NEXRAD data time point
*/ */
virtual std::chrono::system_clock::time_point [[nodiscard]] virtual std::chrono::system_clock::time_point
GetTimePointByKey(const std::string& key) const = 0; GetTimePointByKey(const std::string& key) const = 0;
/** /**
@ -124,11 +123,22 @@ public:
* to the cache if required. * to the cache if required.
* *
* @param date Date for which to get NEXRAD data time points * @param date Date for which to get NEXRAD data time points
* @param update Whether or not to list and add data not present in the cache
* *
* @return NEXRAD data time points * @return NEXRAD data time points
*/ */
virtual std::vector<std::chrono::system_clock::time_point> virtual std::vector<std::chrono::system_clock::time_point>
GetTimePointsByDate(std::chrono::system_clock::time_point date) = 0; GetTimePointsByDate(std::chrono::system_clock::time_point date,
bool update) = 0;
/**
* Determines if time points for the requested date are cached.
*
* @param date Date for which to query the cache
*
* @return Whether or not the requested date is cached
*/
virtual bool IsDateCached(std::chrono::system_clock::time_point date) = 0;
/** /**
* Requests available NEXRAD products for the current radar site, and adds * Requests available NEXRAD products for the current radar site, and adds
@ -148,5 +158,4 @@ private:
std::unique_ptr<Impl> p; std::unique_ptr<Impl> p;
}; };
} // namespace provider } // namespace scwx::provider
} // namespace scwx

View file

@ -289,11 +289,18 @@ AwsLevel2ChunksDataProvider::FindLatestTime()
std::vector<std::chrono::system_clock::time_point> std::vector<std::chrono::system_clock::time_point>
AwsLevel2ChunksDataProvider::GetTimePointsByDate( AwsLevel2ChunksDataProvider::GetTimePointsByDate(
std::chrono::system_clock::time_point /*date*/) std::chrono::system_clock::time_point /* date */, bool /* update */)
{ {
return {}; return {};
} }
bool AwsLevel2ChunksDataProvider::IsDateCached(
std::chrono::system_clock::time_point /* date */)
{
// No cache, default to true
return true;
}
std::chrono::system_clock::time_point std::chrono::system_clock::time_point
AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix) AwsLevel2ChunksDataProvider::Impl::GetScanTime(const std::string& prefix)
{ {

View file

@ -18,7 +18,7 @@ static const std::string logPrefix_ =
"scwx::provider::aws_level2_data_provider"; "scwx::provider::aws_level2_data_provider";
static const auto logger_ = util::Logger::Create(logPrefix_); static const auto logger_ = util::Logger::Create(logPrefix_);
static const std::string kDefaultBucketName_ = "noaa-nexrad-level2"; static const std::string kDefaultBucketName_ = "unidata-nexrad-level2";
static const std::string kDefaultRegion_ = "us-east-1"; static const std::string kDefaultRegion_ = "us-east-1";
class AwsLevel2DataProvider::Impl class AwsLevel2DataProvider::Impl

View file

@ -15,9 +15,7 @@
#include <aws/s3/model/ListObjectsV2Request.h> #include <aws/s3/model/ListObjectsV2Request.h>
#include <fmt/chrono.h> #include <fmt/chrono.h>
namespace scwx namespace scwx::provider
{
namespace provider
{ {
static const std::string logPrefix_ = static const std::string logPrefix_ =
@ -177,7 +175,7 @@ std::chrono::system_clock::time_point AwsNexradDataProvider::FindLatestTime()
std::vector<std::chrono::system_clock::time_point> std::vector<std::chrono::system_clock::time_point>
AwsNexradDataProvider::GetTimePointsByDate( AwsNexradDataProvider::GetTimePointsByDate(
std::chrono::system_clock::time_point date) std::chrono::system_clock::time_point date, bool update)
{ {
const auto day = std::chrono::floor<std::chrono::days>(date); const auto day = std::chrono::floor<std::chrono::days>(date);
@ -188,23 +186,26 @@ AwsNexradDataProvider::GetTimePointsByDate(
std::shared_lock lock(p->objectsMutex_); std::shared_lock lock(p->objectsMutex_);
// Is the date present in the date list? // Is the date present in the date list?
bool currentDatePresent; bool currentDatePresent = false;
auto currentDateIterator = auto currentDateIterator =
std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day); std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day);
if (currentDateIterator == p->objectDates_.cend()) if (currentDateIterator == p->objectDates_.cend())
{ {
// Temporarily unlock mutex if (update)
lock.unlock();
// List objects, since the date is not present in the date list
auto [success, newObjects, totalObjects] = ListObjects(date);
if (success)
{ {
p->UpdateObjectDates(date); // Temporarily unlock mutex
} lock.unlock();
// Re-lock mutex // List objects, since the date is not present in the date list
lock.lock(); const auto [success, newObjects, totalObjects] = ListObjects(date);
if (success)
{
p->UpdateObjectDates(date);
}
// Re-lock mutex
lock.lock();
}
currentDatePresent = false; currentDatePresent = false;
} }
@ -214,8 +215,8 @@ AwsNexradDataProvider::GetTimePointsByDate(
} }
// Determine objects to retrieve // Determine objects to retrieve
auto objectsBegin = p->objects_.lower_bound(day); const auto objectsBegin = p->objects_.lower_bound(day);
auto objectsEnd = p->objects_.lower_bound(day + std::chrono::days {1}); const auto objectsEnd = p->objects_.lower_bound(day + std::chrono::days {1});
// Copy time points to destination vector // Copy time points to destination vector
std::transform(objectsBegin, std::transform(objectsBegin,
@ -236,6 +237,20 @@ AwsNexradDataProvider::GetTimePointsByDate(
return timePoints; return timePoints;
} }
bool AwsNexradDataProvider::IsDateCached(
std::chrono::system_clock::time_point date)
{
const auto day = std::chrono::floor<std::chrono::days>(date);
const std::shared_lock lock(p->objectsMutex_);
// Is the date present in the date list?
const auto currentDateIterator =
std::find(p->objectDates_.cbegin(), p->objectDates_.cend(), day);
return currentDateIterator != p->objectDates_.cend();
}
std::tuple<bool, size_t, size_t> std::tuple<bool, size_t, size_t>
AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date) AwsNexradDataProvider::ListObjects(std::chrono::system_clock::time_point date)
{ {
@ -446,5 +461,4 @@ void AwsNexradDataProvider::Impl::UpdateObjectDates(
objectDates_.push_back(day); objectDates_.push_back(day);
} }
} // namespace provider } // namespace scwx::provider
} // namespace scwx