use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
ephemeris::{
EphemerisOrbitalElementsItem, EphemerisOrbitalElementsParser, EphemerisVectorItem,
EphemerisVectorParser,
},
major_bodies::MajorBody,
};
#[derive(Deserialize, Debug)]
struct HorizonsResponse {
result: String,
}
#[derive(Error, Debug)]
#[error("error returned from Horizons")]
struct HorizonsQueryError;
async fn query<T>(parameters: &T) -> Result<Vec<String>, HorizonsQueryError>
where
T: Serialize,
{
let result = reqwest::Client::new()
.get("https://ssd.jpl.nasa.gov/api/horizons.api")
.query(parameters)
.send()
.await
.map_err(|_| HorizonsQueryError)?
.json::<HorizonsResponse>()
.await
.map_err(|_| HorizonsQueryError)?
.result
.split('\n')
.map(str::to_owned)
.collect::<Vec<String>>();
for line in &result {
log::trace!("{}", line);
}
Ok(result)
}
async fn query_with_retries<T>(parameters: &T) -> Vec<String>
where
T: Serialize,
{
for n in 1..10 {
log::trace!("try {}", n);
if let Ok(result) = query(parameters).await {
return result;
}
tokio::time::sleep(std::time::Duration::from_secs(1)).await
}
panic!("max retries exceeded");
}
pub async fn major_bodies() -> Vec<MajorBody> {
query_with_retries(&[("COMMAND", "MB")])
.await
.iter()
.filter_map(|s| MajorBody::try_from(s.as_str()).ok())
.collect()
}
pub async fn ephemeris_vector(
id: i32,
start_time: DateTime<Utc>,
stop_time: DateTime<Utc>,
) -> Vec<EphemerisVectorItem> {
let result = query_with_retries(&[
("COMMAND", id.to_string().as_str()),
("CENTER", "500@10"),
("EPHEM_TYPE", "VECTORS"),
(
"START_TIME",
start_time.format("%Y-%b-%d-%T").to_string().as_str(),
),
(
"STOP_TIME",
stop_time.format("%Y-%b-%d-%T").to_string().as_str(),
),
])
.await;
EphemerisVectorParser::parse(result.iter().map(String::as_str)).collect()
}
pub async fn ephemeris_orbital_elements(
id: i32,
start_time: DateTime<Utc>,
stop_time: DateTime<Utc>,
) -> Vec<EphemerisOrbitalElementsItem> {
let result = query_with_retries(&[
("COMMAND", id.to_string().as_str()),
("CENTER", "500@10"),
("EPHEM_TYPE", "ELEMENTS"),
(
"START_TIME",
start_time.format("%Y-%b-%d-%T").to_string().as_str(),
),
(
"STOP_TIME",
stop_time.format("%Y-%b-%d-%T").to_string().as_str(),
),
])
.await;
EphemerisOrbitalElementsParser::parse(result.iter().map(String::as_str)).collect()
}