use crate::aws::realtime::{
ChunkCharacteristics, ChunkIdentifier, ChunkTimingModel, ChunkTimingStats, ElevationChunkMapper,
};
use chrono::{DateTime, Duration, Utc};
use nexrad_decode::messages::volume_coverage_pattern;
#[derive(Debug, Clone)]
pub struct ScanTimingProjection {
anchor_sequence: usize,
anchor_time: DateTime<Utc>,
chunks: Vec<ChunkProjection>,
volume_end_time: DateTime<Utc>,
remaining_duration: Duration,
}
impl ScanTimingProjection {
pub fn anchor_sequence(&self) -> usize {
self.anchor_sequence
}
pub fn anchor_time(&self) -> DateTime<Utc> {
self.anchor_time
}
pub fn chunks(&self) -> &[ChunkProjection] {
&self.chunks
}
pub fn volume_end_time(&self) -> DateTime<Utc> {
self.volume_end_time
}
pub fn remaining_duration(&self) -> Duration {
self.remaining_duration
}
}
#[derive(Debug, Clone)]
pub struct ChunkProjection {
sequence: usize,
elevation_number: Option<usize>,
elevation_angle_deg: f64,
projected_time: DateTime<Utc>,
offset_from_anchor: Duration,
interval_from_previous: Duration,
starts_new_sweep: bool,
}
impl ChunkProjection {
pub fn sequence(&self) -> usize {
self.sequence
}
pub fn elevation_number(&self) -> Option<usize> {
self.elevation_number
}
pub fn elevation_angle_deg(&self) -> f64 {
self.elevation_angle_deg
}
pub fn projected_time(&self) -> DateTime<Utc> {
self.projected_time
}
pub fn offset_from_anchor(&self) -> Duration {
self.offset_from_anchor
}
pub fn interval_from_previous(&self) -> Duration {
self.interval_from_previous
}
pub fn starts_new_sweep(&self) -> bool {
self.starts_new_sweep
}
}
pub fn project_scan_timing(
anchor_chunk: &ChunkIdentifier,
_vcp: &volume_coverage_pattern::Message,
mapper: &ElevationChunkMapper,
timing_stats: Option<&ChunkTimingStats>,
) -> Option<ScanTimingProjection> {
let anchor_sequence = anchor_chunk.sequence();
let anchor_time = anchor_chunk.upload_date_time().unwrap_or_else(Utc::now);
let final_sequence = mapper.final_sequence();
if anchor_sequence >= final_sequence {
return None;
}
let anchor_metadata = mapper.get_chunk_metadata(anchor_sequence)?;
let mut projections = Vec::new();
let mut cumulative_offset_ms: i64 = 0;
let mut prev_metadata = anchor_metadata;
for seq in (anchor_sequence + 1)..=final_sequence {
let next_metadata = mapper.get_chunk_metadata(seq)?;
let mut interval_secs =
ChunkTimingModel::estimate_chunk_interval_secs(prev_metadata, next_metadata);
if let Some(stats) = timing_stats {
if let Some(elev_num) = next_metadata.elevation_number() {
if let Some(elev_data) = _vcp.elevations().get(elev_num - 1) {
let characteristics = ChunkCharacteristics {
chunk_type: crate::aws::realtime::ChunkType::Intermediate,
waveform_type: elev_data.waveform_type(),
channel_configuration: elev_data.channel_configuration(),
};
if let Some(avg_timing) = stats.get_average_timing(&characteristics) {
let historical_secs = avg_timing.num_milliseconds() as f64 / 1000.0;
interval_secs = interval_secs * 0.7 + historical_secs * 0.3;
}
}
}
}
let interval_ms = (interval_secs * 1000.0) as i64;
cumulative_offset_ms += interval_ms;
let interval_duration = Duration::milliseconds(interval_ms);
let offset_duration = Duration::milliseconds(cumulative_offset_ms);
let projected_time = anchor_time + offset_duration;
projections.push(ChunkProjection {
sequence: seq,
elevation_number: next_metadata.elevation_number(),
elevation_angle_deg: next_metadata.elevation_angle_deg(),
projected_time,
offset_from_anchor: offset_duration,
interval_from_previous: interval_duration,
starts_new_sweep: next_metadata.is_first_in_sweep(),
});
prev_metadata = next_metadata;
}
let volume_end_time = projections
.last()
.map(|p| p.projected_time)
.unwrap_or(anchor_time);
let remaining_duration = Duration::milliseconds(cumulative_offset_ms);
Some(ScanTimingProjection {
anchor_sequence,
anchor_time,
chunks: projections,
volume_end_time,
remaining_duration,
})
}
pub fn project_full_scan_timing(
site: &str,
volume: crate::aws::realtime::VolumeIndex,
start_time: DateTime<Utc>,
vcp: &volume_coverage_pattern::Message,
mapper: &ElevationChunkMapper,
timing_stats: Option<&ChunkTimingStats>,
) -> Option<ScanTimingProjection> {
let start_chunk = ChunkIdentifier::new(
site.to_string(),
volume,
start_time.naive_utc(),
1,
crate::aws::realtime::ChunkType::Start,
Some(start_time),
);
project_scan_timing(&start_chunk, vcp, mapper, timing_stats)
}