Skip to content

Commit

Permalink
bug fixes for live tracking and prediction from ns118 (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
zacharyburnett authored May 28, 2023
1 parent b015139 commit 49d8973
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 108 deletions.
1 change: 1 addition & 0 deletions src/configuration/prediction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl Prediction {
None,
None,
false,
None,
)
}
}
Expand Down
1 change: 0 additions & 1 deletion src/connection/aprs_fi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ struct AprsFiWeather {
#[derive(serde::Deserialize)]
struct AprsFiMessage {
messageid: String,
#[serde(with = "crate::utilities::utc_datetime_string")]
time: chrono::DateTime<chrono::Utc>,
srccall: String,
dst: String,
Expand Down
2 changes: 0 additions & 2 deletions src/connection/sondehub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,8 @@ struct SondeHubLocation {
software_name: String,
software_version: String,
uploader_callsign: String,
#[serde(with = "crate::utilities::utc_datetime_string")]
time_received: chrono::DateTime<chrono::Utc>,
payload_callsign: String,
#[serde(with = "crate::utilities::utc_datetime_string")]
datetime: chrono::DateTime<chrono::Utc>,
lat: f64,
lon: f64,
Expand Down
57 changes: 31 additions & 26 deletions src/connection/text/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,34 +173,39 @@ impl GeoJsonFile {
for feature in &collection.features {
if let Some(ref geometry) = feature.geometry {
if let geojson::Value::Point(point) = &geometry.value {
let properties = feature.properties.as_ref().unwrap();

let time = match properties.get("time").unwrap() {
serde_json::Value::String(time) => {
match chrono::NaiveDateTime::parse_from_str(
time.as_str(),
"%Y%m%d%H%M%S",
) {
Ok(datetime) => {
datetime.and_local_timezone(chrono::Local).unwrap()
}
Err(error) => {
return Err(
crate::connection::ConnectionError::ReadFailure {
connection: self.path.to_owned(),
message: format!("{:} - {:}", time, error),
},
)
}
}
}
serde_json::Value::Number(time) => chrono::Local
.timestamp_opt(time.as_i64().unwrap(), 0)
.unwrap()
.with_timezone(&chrono::Local),
_ => continue,
let properties = match &feature.properties {
Some(properties) => properties,
None => continue,
};

let time =
match properties.get("time") {
Some(value) => match value {
serde_json::Value::String(time) => {
match chrono::NaiveDateTime::parse_from_str(
time.as_str(),
"%Y%m%d%H%M%S",
) {
Ok(datetime) => {
datetime.and_local_timezone(chrono::Local).unwrap()
}
Err(error) => return Err(
crate::connection::ConnectionError::ReadFailure {
connection: self.path.to_owned(),
message: format!("{:} - {:}", time, error),
},
),
}
}
serde_json::Value::Number(time) => chrono::Local
.timestamp_opt(time.as_i64().unwrap(), 0)
.unwrap()
.with_timezone(&chrono::Local),
_ => continue,
},
None => continue,
};

let altitude = if point.len() > 2 {
Some(point[2])
} else {
Expand Down
5 changes: 1 addition & 4 deletions src/location/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ impl PartialEq for Location {
}
None => false,
},
None => match other.altitude {
None => true,
_ => false,
},
None => other.altitude.is_none(),
}
}
}
Expand Down
40 changes: 33 additions & 7 deletions src/location/track/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,24 @@ pub fn with_altitude(locations: &[super::BalloonLocation]) -> Vec<super::Balloon
pub fn intervals(locations: &[super::BalloonLocation]) -> Vec<chrono::Duration> {
let mut values = vec![];

for index in 0..(locations.len() - 1) {
let current = locations.get(index).unwrap();
let next = locations.get(index + 1).unwrap();
let mut index = 0;
let mut current = match locations.first() {
Some(first) => first,
None => return values,
};
let mut next;
loop {
next = match locations.get(index + 1) {
Some(next) => next,
None => {
break;
}
};

values.push(next.location.time - current.location.time);

current = next;
index += 1;
}

values
Expand All @@ -130,7 +144,10 @@ pub fn ascents(locations: &[super::BalloonLocation]) -> Vec<f64> {
let mut values = vec![];

let mut index = 0;
let mut current = locations.first().unwrap();
let mut current = match locations.first() {
Some(first) => first,
None => return values,
};
let mut next;
loop {
next = match locations.get(index + 1) {
Expand Down Expand Up @@ -158,14 +175,20 @@ pub fn ascent_rates(locations: &[super::BalloonLocation]) -> Vec<f64> {
values.push(ascent / intervals.get(index).unwrap().num_seconds() as f64);
}

values.into_iter().filter(|value| !value.is_nan()).collect()
values
.into_iter()
.filter(|value| value.is_finite())
.collect()
}

pub fn overground_distances(locations: &[super::BalloonLocation]) -> Vec<f64> {
let mut values = vec![];

let mut index = 0;
let mut current = locations.first().unwrap();
let mut current = match locations.first() {
Some(first) => first,
None => return values,
};
let mut next;
loop {
next = match locations.get(index + 1) {
Expand Down Expand Up @@ -195,5 +218,8 @@ pub fn ground_speeds(locations: &[super::BalloonLocation]) -> Vec<f64> {
values.push(distance / intervals.get(index).unwrap().num_seconds() as f64);
}

values.into_iter().filter(|value| !value.is_nan()).collect()
values
.into_iter()
.filter(|value| value.is_finite())
.collect()
}
3 changes: 3 additions & 0 deletions src/prediction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub struct BalloonPredictionQuery {
pub profile: FlightProfile,
pub name: Option<String>,
pub descent_only: bool,
pub float_start: Option<chrono::DateTime<chrono::Local>>,
}

impl BalloonPredictionQuery {
Expand All @@ -82,13 +83,15 @@ impl BalloonPredictionQuery {
profile: &FlightProfile,
name: Option<String>,
descent_only: bool,
float_start: Option<chrono::DateTime<chrono::Local>>,
) -> Self {
Self {
api_url,
start: start.to_owned(),
profile: profile.to_owned(),
name,
descent_only,
float_start,
}
}
}
74 changes: 56 additions & 18 deletions src/prediction/tawhiri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ impl TawhiriQuery {
version: Option<f64>,
name: Option<String>,
descent_only: bool,
float_start: Option<chrono::DateTime<chrono::Local>>,
) -> TawhiriQuery {
TawhiriQuery {
query: crate::prediction::BalloonPredictionQuery::new(
Expand All @@ -20,6 +21,7 @@ impl TawhiriQuery {
profile,
name,
descent_only,
float_start,
),
dataset_time,
version,
Expand Down Expand Up @@ -47,7 +49,14 @@ impl TawhiriQuery {
let mut parameters = vec![
("launch_longitude", format!("{:.2}", start_location.x)),
("launch_latitude", format!("{:.2}", start_location.y)),
("launch_datetime", self.query.start.time.to_rfc3339()),
(
"launch_datetime",
self.query
.start
.time
.with_timezone(&chrono::Utc)
.to_rfc3339(),
),
(
"ascent_rate",
format!("{:.2}", self.query.profile.ascent_rate),
Expand All @@ -59,11 +68,12 @@ impl TawhiriQuery {
),
];

if let Some(altitude) = self.query.start.altitude {
let launch_altitude = self.query.start.altitude;
if let Some(altitude) = launch_altitude {
parameters.push(("launch_altitude", format!("{:.2}", altitude)));
}
if let Some(dataset_time) = self.dataset_time {
parameters.push(("dataset", format!("{:}", dataset_time.to_rfc3339())));
parameters.push(("dataset", dataset_time.to_rfc3339()));
}
if let Some(version) = self.version {
parameters.push(("version", format!("{:}", version)));
Expand All @@ -72,20 +82,31 @@ impl TawhiriQuery {
if let Some(float_duration) = self.query.profile.float_duration {
if !self.query.descent_only {
parameters.push(("profile", "float_profile".to_string()));
let float_altitude = self
let mut float_altitude = self
.query
.profile
.float_altitude
.unwrap_or(self.query.profile.burst_altitude);
if let Some(launch_altitude) = launch_altitude {
if float_altitude <= launch_altitude {
float_altitude = launch_altitude + 1.0;
}
}

parameters.push(("float_altitude", format!("{:.2}", float_altitude)));
let start_altitude = self.query.start.altitude.unwrap_or(0.0);
let float_start_time = self.query.start.time
+ chrono::Duration::seconds(
(float_altitude - start_altitude / self.query.profile.ascent_rate) as i64,
);

let float_start_time = self.query.float_start.unwrap_or({
self.query.start.time
+ chrono::Duration::seconds(
(float_altitude
- self.query.start.altitude.unwrap_or(0.0)
/ self.query.profile.ascent_rate)
as i64,
)
});
parameters.push((
"stop_datetime",
format!("{:}", (float_start_time + float_duration).to_rfc3339()),
(float_start_time + float_duration).to_rfc3339(),
));
}
} else {
Expand Down Expand Up @@ -145,6 +166,7 @@ impl TawhiriQuery {
self.version,
None,
true,
None,
);
let descent: TawhiriResponse = descent_query.get().unwrap();
for stage in descent.prediction {
Expand Down Expand Up @@ -220,13 +242,31 @@ impl crate::location::track::BalloonTrack {
&self,
profile: &super::FlightProfile,
) -> Result<crate::location::track::LocationTrack, TawhiriError> {
let float_start = if let Some(float_altitude) = profile.float_altitude {
let mut locations_at_float_altitude: Vec<&crate::location::BalloonLocation> = self
.locations
.iter()
.filter(|location| match location.location.altitude {
Some(altitude) => {
(altitude - float_altitude).abs() <= profile.float_uncertainty
}
None => false,
})
.collect();
locations_at_float_altitude.sort_by_key(|location| location.location.time);
Some(locations_at_float_altitude.first().unwrap().location.time)
} else {
None
};

let query = crate::prediction::tawhiri::TawhiriQuery::new(
&self.locations.last().unwrap().location,
profile,
None,
None,
None,
self.descending() || self.falling().is_some(),
float_start,
);

query.retrieve_prediction()
Expand Down Expand Up @@ -305,7 +345,7 @@ struct TawhiriPrediction {
#[derive(serde::Deserialize, Clone)]
struct TawhiriLocation {
altitude: f64,
datetime: String,
datetime: chrono::DateTime<chrono::Utc>,
latitude: f64,
longitude: f64,
}
Expand All @@ -320,9 +360,7 @@ impl TawhiriLocation {

crate::location::BalloonLocation {
location: crate::location::Location {
time: chrono::DateTime::parse_from_rfc3339(&self.datetime)
.unwrap()
.with_timezone(&chrono::Local),
time: self.datetime.with_timezone(&chrono::Local),
coord: geo::coord! { x: longitude, y: self.latitude },
altitude: Some(self.altitude),
},
Expand Down Expand Up @@ -351,7 +389,7 @@ mod tests {
};
let profile = crate::prediction::FlightProfile::new_standard(5.5, 28000.0, 9.0);

let query = TawhiriQuery::new(&start, &profile, None, None, None, false);
let query = TawhiriQuery::new(&start, &profile, None, None, None, false, None);

let response = query.get().unwrap();
let prediction = query.retrieve_prediction();
Expand All @@ -378,7 +416,7 @@ mod tests {
};
let profile = crate::prediction::FlightProfile::new_standard(5.5, 28000.0, 9.0);

let query = TawhiriQuery::new(&start, &profile, None, None, None, false);
let query = TawhiriQuery::new(&start, &profile, None, None, None, false, None);

let response = query.get().unwrap();
let prediction = query.retrieve_prediction();
Expand Down Expand Up @@ -406,7 +444,7 @@ mod tests {
let profile =
crate::prediction::FlightProfile::new_standard(5.5, start.altitude.unwrap(), 9.0);

let query = TawhiriQuery::new(&start, &profile, None, None, None, true);
let query = TawhiriQuery::new(&start, &profile, None, None, None, true, None);

let response = query.get().unwrap();
let prediction = query.retrieve_prediction();
Expand Down Expand Up @@ -438,7 +476,7 @@ mod tests {
9.0,
);

let query = TawhiriQuery::new(&start, &profile, None, None, None, false);
let query = TawhiriQuery::new(&start, &profile, None, None, None, false, None);

let response = query.get().unwrap();
let prediction = query.retrieve_prediction();
Expand Down
Loading

0 comments on commit 49d8973

Please sign in to comment.