Merge pull request #61 from dpaulat/feature/timeline-part-2

Timeline Animation Continued
This commit is contained in:
Dan Paulat 2023-06-13 22:03:37 -05:00 committed by GitHub
commit 31319076c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 206 additions and 126 deletions

View file

@ -672,6 +672,10 @@ void MainWindowImpl::ConnectAnimationSignals()
&ui::AnimationDockWidget::LoopSpeedChanged, &ui::AnimationDockWidget::LoopSpeedChanged,
timelineManager_.get(), timelineManager_.get(),
&manager::TimelineManager::SetLoopSpeed); &manager::TimelineManager::SetLoopSpeed);
connect(animationDockWidget_,
&ui::AnimationDockWidget::LoopDelayChanged,
timelineManager_.get(),
&manager::TimelineManager::SetLoopDelay);
connect(animationDockWidget_, connect(animationDockWidget_,
&ui::AnimationDockWidget::AnimationStepBeginSelected, &ui::AnimationDockWidget::AnimationStepBeginSelected,
timelineManager_.get(), timelineManager_.get(),

View file

@ -210,7 +210,8 @@ public:
static void static void
LoadNexradFile(CreateNexradFileFunction load, LoadNexradFile(CreateNexradFileFunction load,
std::shared_ptr<request::NexradFileRequest> request, std::shared_ptr<request::NexradFileRequest> request,
std::mutex& mutex); std::mutex& mutex,
std::chrono::system_clock::time_point time = {});
const std::string radarId_; const std::string radarId_;
bool initialized_; bool initialized_;
@ -801,7 +802,8 @@ void RadarProductManagerImpl::LoadProviderData(
return nexradFile; return nexradFile;
}, },
request, request,
loadDataMutex); loadDataMutex,
time);
} }
void RadarProductManager::LoadLevel2Data( void RadarProductManager::LoadLevel2Data(
@ -912,7 +914,8 @@ void RadarProductManager::LoadFile(
void RadarProductManagerImpl::LoadNexradFile( void RadarProductManagerImpl::LoadNexradFile(
CreateNexradFileFunction load, CreateNexradFileFunction load,
std::shared_ptr<request::NexradFileRequest> request, std::shared_ptr<request::NexradFileRequest> request,
std::mutex& mutex) std::mutex& mutex,
std::chrono::system_clock::time_point time)
{ {
scwx::util::async( scwx::util::async(
[=, &mutex]() [=, &mutex]()
@ -929,6 +932,15 @@ void RadarProductManagerImpl::LoadNexradFile(
{ {
record = types::RadarProductRecord::Create(nexradFile); record = types::RadarProductRecord::Create(nexradFile);
// If the time is already determined, override the time in the file.
// Sometimes, level 2 data has been seen to be a few seconds off
// between filename and file data. Overriding this can help prevent
// issues with locating and storing the correct records.
if (time != std::chrono::system_clock::time_point {})
{
record->set_time(time);
}
std::shared_ptr<RadarProductManager> manager = std::shared_ptr<RadarProductManager> manager =
RadarProductManager::Instance(record->radar_id()); RadarProductManager::Instance(record->radar_id());
@ -1192,15 +1204,17 @@ void RadarProductManagerImpl::UpdateRecentRecords(
std::shared_ptr<types::RadarProductRecord> record) std::shared_ptr<types::RadarProductRecord> record)
{ {
const std::size_t recentListMaxSize {cacheLimit_}; const std::size_t recentListMaxSize {cacheLimit_};
bool iteratorErased = false;
auto it = std::find(recentList.cbegin(), recentList.cend(), record); auto it = std::find(recentList.cbegin(), recentList.cend(), record);
if (it != recentList.cbegin() && it != recentList.cend()) if (it != recentList.cbegin() && it != recentList.cend())
{ {
// If the record exists beyond the front of the list, remove it // If the record exists beyond the front of the list, remove it
recentList.erase(it); recentList.erase(it);
iteratorErased = true;
} }
if (recentList.size() == 0 || it != recentList.cbegin()) if (iteratorErased || recentList.size() == 0 || it != recentList.cbegin())
{ {
// Add the record to the front of the list, unless it's already there // Add the record to the front of the list, unless it's already there
recentList.push_front(record); recentList.push_front(record);

View file

@ -59,8 +59,11 @@ public:
void Pause(); void Pause();
void Play(); void Play();
void SelectTime(std::chrono::system_clock::time_point selectedTime = {}); void
void Step(Direction direction); SelectTimeAsync(std::chrono::system_clock::time_point selectedTime = {});
std::pair<bool, bool>
SelectTime(std::chrono::system_clock::time_point selectedTime = {});
void StepAsync(Direction direction);
std::size_t mapCount_ {0}; std::size_t mapCount_ {0};
std::string radarSite_ {"?"}; std::string radarSite_ {"?"};
@ -71,6 +74,7 @@ public:
types::MapTime viewType_ {types::MapTime::Live}; types::MapTime viewType_ {types::MapTime::Live};
std::chrono::minutes loopTime_ {30}; std::chrono::minutes loopTime_ {30};
double loopSpeed_ {5.0}; double loopSpeed_ {5.0};
std::chrono::milliseconds loopDelay_ {2500};
bool radarSweepMonitorActive_ {false}; bool radarSweepMonitorActive_ {false};
std::mutex radarSweepMonitorMutex_ {}; std::mutex radarSweepMonitorMutex_ {};
@ -113,7 +117,7 @@ void TimelineManager::SetRadarSite(const std::string& radarSite)
else else
{ {
// If the selected view type is archive, select using the selected time // If the selected view type is archive, select using the selected time
p->SelectTime(p->selectedTime_); p->SelectTimeAsync(p->selectedTime_);
} }
} }
@ -127,7 +131,7 @@ void TimelineManager::SetDateTime(
if (p->viewType_ == types::MapTime::Archive) if (p->viewType_ == types::MapTime::Archive)
{ {
// Only select if the view type is archive // Only select if the view type is archive
p->SelectTime(dateTime); p->SelectTimeAsync(dateTime);
} }
// Ignore a date/time selection if the view type is live // Ignore a date/time selection if the view type is live
@ -147,7 +151,7 @@ void TimelineManager::SetViewType(types::MapTime viewType)
else else
{ {
// If the selected view type is archive, select using the pinned time // If the selected view type is archive, select using the pinned time
p->SelectTime(p->pinnedTime_); p->SelectTimeAsync(p->pinnedTime_);
} }
} }
@ -170,6 +174,13 @@ void TimelineManager::SetLoopSpeed(double loopSpeed)
p->loopSpeed_ = loopSpeed; p->loopSpeed_ = loopSpeed;
} }
void TimelineManager::SetLoopDelay(std::chrono::milliseconds loopDelay)
{
logger_->debug("SetLoopDelay: {}", loopDelay);
p->loopDelay_ = loopDelay;
}
void TimelineManager::AnimationStepBegin() void TimelineManager::AnimationStepBegin()
{ {
logger_->debug("AnimationStepBegin"); logger_->debug("AnimationStepBegin");
@ -180,12 +191,12 @@ void TimelineManager::AnimationStepBegin()
p->pinnedTime_ == std::chrono::system_clock::time_point {}) p->pinnedTime_ == std::chrono::system_clock::time_point {})
{ {
// If the selected view type is live, select the current products // If the selected view type is live, select the current products
p->SelectTime(std::chrono::system_clock::now() - p->loopTime_); p->SelectTimeAsync(std::chrono::system_clock::now() - p->loopTime_);
} }
else else
{ {
// If the selected view type is archive, select using the pinned time // If the selected view type is archive, select using the pinned time
p->SelectTime(p->pinnedTime_ - p->loopTime_); p->SelectTimeAsync(p->pinnedTime_ - p->loopTime_);
} }
} }
@ -194,7 +205,7 @@ void TimelineManager::AnimationStepBack()
logger_->debug("AnimationStepBack"); logger_->debug("AnimationStepBack");
p->Pause(); p->Pause();
p->Step(Direction::Back); p->StepAsync(Direction::Back);
} }
void TimelineManager::AnimationPlayPause() void TimelineManager::AnimationPlayPause()
@ -216,7 +227,7 @@ void TimelineManager::AnimationStepNext()
logger_->debug("AnimationStepNext"); logger_->debug("AnimationStepNext");
p->Pause(); p->Pause();
p->Step(Direction::Next); p->StepAsync(Direction::Next);
} }
void TimelineManager::AnimationStepEnd() void TimelineManager::AnimationStepEnd()
@ -233,7 +244,7 @@ void TimelineManager::AnimationStepEnd()
else else
{ {
// If the selected view type is archive, select using the pinned time // If the selected view type is archive, select using the pinned time
p->SelectTime(p->pinnedTime_); p->SelectTimeAsync(p->pinnedTime_);
} }
} }
@ -407,30 +418,50 @@ void TimelineManager::Impl::Play()
newTime = currentTime + 1min; newTime = currentTime + 1min;
} }
// Unlock prior to selecting time
lock.unlock();
// Lock radar sweep monitor
std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_};
// Reset radar sweep monitor in preparation for update
RadarSweepMonitorReset();
// Select the time
auto selectTimeStart = std::chrono::steady_clock::now();
auto [volumeTimeUpdated, selectedTimeUpdated] = SelectTime(newTime);
auto selectTimeEnd = std::chrono::steady_clock::now();
auto elapsedTime = selectTimeEnd - selectTimeStart;
if (volumeTimeUpdated)
{
// Wait for radar sweeps to update
RadarSweepMonitorWait(radarSweepMonitorLock);
}
else
{
// Disable radar sweep monitor
RadarSweepMonitorDisable();
}
// Calculate the interval until the next update, prior to selecting // Calculate the interval until the next update, prior to selecting
std::chrono::milliseconds interval; std::chrono::milliseconds interval;
if (newTime != endTime) if (newTime != endTime)
{ {
// Determine repeat interval (speed of 1.0 is 1 minute per second) // Determine repeat interval (speed of 1.0 is 1 minute per second)
interval = interval = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::milliseconds(std::lroundl(1000.0 / loopSpeed_)); std::chrono::milliseconds(std::lroundl(1000.0 / loopSpeed_)) -
elapsedTime);
} }
else else
{ {
// Pause for 2.5 seconds at the end of the loop // Pause at the end of the loop
interval = std::chrono::milliseconds(2500); interval = std::chrono::duration_cast<std::chrono::milliseconds>(
loopDelay_ - elapsedTime);
} }
animationTimer_.expires_after(interval);
// Unlock prior to selecting time
lock.unlock();
// Select the time
SelectTime(newTime);
std::unique_lock animationTimerLock {animationTimerMutex_}; std::unique_lock animationTimerLock {animationTimerMutex_};
animationTimer_.expires_after(interval);
animationTimer_.async_wait( animationTimer_.async_wait(
[this](const boost::system::error_code& e) [this](const boost::system::error_code& e)
{ {
@ -453,79 +484,63 @@ void TimelineManager::Impl::Play()
}); });
} }
void TimelineManager::Impl::SelectTime( void TimelineManager::Impl::SelectTimeAsync(
std::chrono::system_clock::time_point selectedTime) std::chrono::system_clock::time_point selectedTime)
{ {
scwx::util::async([=, this]() { SelectTime(selectedTime); });
}
std::pair<bool, bool> TimelineManager::Impl::SelectTime(
std::chrono::system_clock::time_point selectedTime)
{
bool volumeTimeUpdated = false;
bool selectedTimeUpdated = false;
if (selectedTime_ == selectedTime && radarSite_ == previousRadarSite_) if (selectedTime_ == selectedTime && radarSite_ == previousRadarSite_)
{ {
// Nothing to do // Nothing to do
return; return {volumeTimeUpdated, selectedTimeUpdated};
} }
else if (selectedTime == std::chrono::system_clock::time_point {}) else if (selectedTime == std::chrono::system_clock::time_point {})
{ {
scwx::util::async(
[=, this]()
{
// Take a lock for time selection
std::unique_lock lock {selectTimeMutex_};
// If a default time point is given, reset to a live view // If a default time point is given, reset to a live view
selectedTime_ = selectedTime; selectedTime_ = selectedTime;
adjustedTime_ = selectedTime; adjustedTime_ = selectedTime;
previousRadarSite_ = radarSite_;
logger_->debug("Time updated: Live"); logger_->debug("Time updated: Live");
std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_};
// Reset radar sweep monitor in preparation for update
RadarSweepMonitorReset();
Q_EMIT self_->LiveStateUpdated(true); Q_EMIT self_->LiveStateUpdated(true);
Q_EMIT self_->VolumeTimeUpdated(selectedTime); Q_EMIT self_->VolumeTimeUpdated(selectedTime);
Q_EMIT self_->SelectedTimeUpdated(selectedTime); Q_EMIT self_->SelectedTimeUpdated(selectedTime);
// Wait for radar sweeps to update volumeTimeUpdated = true;
RadarSweepMonitorWait(radarSweepMonitorLock); selectedTimeUpdated = true;
});
return; return {volumeTimeUpdated, selectedTimeUpdated};
} }
scwx::util::async(
[=, this]()
{
// Take a lock for time selection // Take a lock for time selection
std::unique_lock lock {selectTimeMutex_}; std::unique_lock lock {selectTimeMutex_};
// Request active volume times // Request active volume times
auto radarProductManager = auto radarProductManager =
manager::RadarProductManager::Instance(radarSite_); manager::RadarProductManager::Instance(radarSite_);
auto volumeTimes = auto volumeTimes = radarProductManager->GetActiveVolumeTimes(selectedTime);
radarProductManager->GetActiveVolumeTimes(selectedTime);
// Dynamically update maximum cached volume scans // Dynamically update maximum cached volume scans
UpdateCacheLimit(radarProductManager, volumeTimes); UpdateCacheLimit(radarProductManager, volumeTimes);
// Find the best match bounded time // Find the best match bounded time
auto elementPtr = auto elementPtr = util::GetBoundedElementPointer(volumeTimes, selectedTime);
util::GetBoundedElementPointer(volumeTimes, selectedTime);
// The timeline is no longer live // The timeline is no longer live
Q_EMIT self_->LiveStateUpdated(false); Q_EMIT self_->LiveStateUpdated(false);
bool volumeTimeUpdated = false;
std::unique_lock radarSweepMonitorLock {radarSweepMonitorMutex_};
// Reset radar sweep monitor in preparation for update
RadarSweepMonitorReset();
if (elementPtr != nullptr) if (elementPtr != nullptr)
{ {
// If the adjusted time changed, or if a new radar site has been // If the adjusted time changed, or if a new radar site has been selected
// selected if (adjustedTime_ != *elementPtr || radarSite_ != previousRadarSite_)
if (adjustedTime_ != *elementPtr ||
radarSite_ != previousRadarSite_)
{ {
// If the time was found, select it // If the time was found, select it
adjustedTime_ = *elementPtr; adjustedTime_ = *elementPtr;
@ -548,23 +563,15 @@ void TimelineManager::Impl::SelectTime(
scwx::util::TimeString(selectedTime)); scwx::util::TimeString(selectedTime));
selectedTime_ = selectedTime; selectedTime_ = selectedTime;
selectedTimeUpdated = true;
Q_EMIT self_->SelectedTimeUpdated(selectedTime); Q_EMIT self_->SelectedTimeUpdated(selectedTime);
previousRadarSite_ = radarSite_; previousRadarSite_ = radarSite_;
if (volumeTimeUpdated) return {volumeTimeUpdated, selectedTimeUpdated};
{
// Wait for radar sweeps to update
RadarSweepMonitorWait(radarSweepMonitorLock);
}
else
{
RadarSweepMonitorDisable();
}
});
} }
void TimelineManager::Impl::Step(Direction direction) void TimelineManager::Impl::StepAsync(Direction direction)
{ {
scwx::util::async( scwx::util::async(
[=, this]() [=, this]()

View file

@ -34,6 +34,7 @@ public slots:
void SetLoopTime(std::chrono::minutes loopTime); void SetLoopTime(std::chrono::minutes loopTime);
void SetLoopSpeed(double loopSpeed); void SetLoopSpeed(double loopSpeed);
void SetLoopDelay(std::chrono::milliseconds loopDelay);
void AnimationStepBegin(); void AnimationStepBegin();
void AnimationStepBack(); void AnimationStepBack();

View file

@ -1053,6 +1053,9 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite)
// Set new RadarProductManager // Set new RadarProductManager
radarProductManager_ = manager::RadarProductManager::Instance(radarSite); radarProductManager_ = manager::RadarProductManager::Instance(radarSite);
// Re-enable auto-update
autoUpdateEnabled_ = true;
// Connect signals to new RadarProductManager // Connect signals to new RadarProductManager
RadarProductManagerConnect(); RadarProductManagerConnect();

View file

@ -133,6 +133,11 @@ std::chrono::system_clock::time_point RadarProductRecord::time() const
return p->time_; return p->time_;
} }
void RadarProductRecord::set_time(std::chrono::system_clock::time_point time)
{
p->time_ = time;
}
std::shared_ptr<RadarProductRecord> std::shared_ptr<RadarProductRecord>
RadarProductRecord::Create(std::shared_ptr<wsr88d::NexradFile> nexradFile) RadarProductRecord::Create(std::shared_ptr<wsr88d::NexradFile> nexradFile)
{ {

View file

@ -38,6 +38,8 @@ public:
std::string site_id() const; std::string site_id() const;
std::chrono::system_clock::time_point time() const; std::chrono::system_clock::time_point time() const;
void set_time(std::chrono::system_clock::time_point time);
static std::shared_ptr<RadarProductRecord> static std::shared_ptr<RadarProductRecord>
Create(std::shared_ptr<wsr88d::NexradFile> nexradFile); Create(std::shared_ptr<wsr88d::NexradFile> nexradFile);

View file

@ -88,6 +88,7 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) :
// Set loop defaults // Set loop defaults
ui->loopTimeSpinBox->setValue(30); ui->loopTimeSpinBox->setValue(30);
ui->loopSpeedSpinBox->setValue(5.0); ui->loopSpeedSpinBox->setValue(5.0);
ui->loopDelaySpinBox->setValue(2.5);
// Connect widget signals // Connect widget signals
p->ConnectSignals(); p->ConnectSignals();
@ -161,6 +162,15 @@ void AnimationDockWidgetImpl::ConnectSignals()
&QDoubleSpinBox::valueChanged, &QDoubleSpinBox::valueChanged,
self_, self_,
[this](double d) { Q_EMIT self_->LoopSpeedChanged(d); }); [this](double d) { Q_EMIT self_->LoopSpeedChanged(d); });
QObject::connect(
self_->ui->loopDelaySpinBox,
&QDoubleSpinBox::valueChanged,
self_,
[this](double d)
{
Q_EMIT self_->LoopDelayChanged(std::chrono::milliseconds(
static_cast<typename std::chrono::milliseconds::rep>(d * 1000.0)));
});
// Animation controls // Animation controls
QObject::connect(self_->ui->beginButton, QObject::connect(self_->ui->beginButton,

View file

@ -38,6 +38,7 @@ signals:
void LoopTimeChanged(std::chrono::minutes loopTime); void LoopTimeChanged(std::chrono::minutes loopTime);
void LoopSpeedChanged(double loopSpeed); void LoopSpeedChanged(double loopSpeed);
void LoopDelayChanged(std::chrono::milliseconds loopDelay);
void AnimationStepBeginSelected(); void AnimationStepBeginSelected();
void AnimationStepBackSelected(); void AnimationStepBackSelected();

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>200</width> <width>200</width>
<height>337</height> <height>348</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -130,13 +130,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0">
<widget class="QLabel" name="loopTimeLabel">
<property name="text">
<string>Loop Time</string>
</property>
</widget>
</item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QSpinBox" name="loopTimeSpinBox"> <widget class="QSpinBox" name="loopTimeSpinBox">
<property name="correctionMode"> <property name="correctionMode">
@ -156,6 +149,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QLabel" name="loopTimeLabel">
<property name="text">
<string>Loop Time</string>
</property>
</widget>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="loopSpeedLabel"> <widget class="QLabel" name="loopSpeedLabel">
<property name="text"> <property name="text">
@ -179,6 +179,32 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QLabel" name="loopDelayLabel">
<property name="text">
<string>Loop Delay</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="loopDelaySpinBox">
<property name="suffix">
<string> sec</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>15.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>2.500000000000000</double>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -725,7 +725,8 @@ void Level2ProductViewImpl::ComputeCoordinates(
const std::uint16_t numRadials = const std::uint16_t numRadials =
static_cast<std::uint16_t>(radarData->size()); static_cast<std::uint16_t>(radarData->size());
const std::uint16_t numRangeBins = const std::uint16_t numRangeBins =
momentData0->number_of_data_moment_gates(); std::max(momentData0->number_of_data_moment_gates() + 1u,
common::MAX_DATA_MOMENT_GATES);
auto radials = boost::irange<std::uint32_t>(0u, numRadials); auto radials = boost::irange<std::uint32_t>(0u, numRadials);
auto gates = boost::irange<std::uint32_t>(0u, numRangeBins); auto gates = boost::irange<std::uint32_t>(0u, numRangeBins);

View file

@ -422,6 +422,12 @@ void Ar2vFileImpl::IndexFile()
std::shared_ptr<rda::DigitalRadarData> radial0 = std::shared_ptr<rda::DigitalRadarData> radial0 =
(*elevationCut.second)[0]; (*elevationCut.second)[0];
if (radial0 == nullptr)
{
logger_->warn("Empty radial data");
continue;
}
for (rda::DataBlockType dataBlockType : for (rda::DataBlockType dataBlockType :
rda::MomentDataBlockTypeIterator()) rda::MomentDataBlockTypeIterator())
{ {