-
Notifications
You must be signed in to change notification settings - Fork 3
/
client.rs
130 lines (119 loc) · 3.83 KB
/
client.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
ephemeris::{
EphemerisOrbitalElementsItem, EphemerisOrbitalElementsParser, EphemerisVectorItem,
EphemerisVectorParser,
},
major_bodies::MajorBody,
};
/// Generic Horizons response. Their API just gives some JSON with two field,
/// some statuses and `result` field which is just human-readable string
/// normally seen in telnet or web API.
#[derive(Deserialize, Debug)]
struct HorizonsResponse {
result: String,
}
#[derive(Error, Debug)]
#[error("error returned from Horizons")]
struct HorizonsQueryError;
/// Query the Horizons API, returning a result in form of lines.
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
}
// TODO: Don't panic.
panic!("max retries exceeded");
}
/// Get names and identifiers of all major bodies in the Solar System.
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()
}
/// Get vector ephemeris (position and velocity) of a major body. Coordinates are
/// relative to the Sun's center.
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()),
// Select Sun as a observer. Note that Solar System Barycenter is in a
// slightly different place.
// https://astronomy.stackexchange.com/questions/44851/
("CENTER", "500@10"),
("EPHEM_TYPE", "VECTORS"),
// https://ssd.jpl.nasa.gov/horizons/manual.html#time
(
"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()
}
/// Get orbital element ephemeris (e.g. eccentricity, semi-major axis, ...) of a
/// major body relative to the Sun's center
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()),
// Select Sun as a observer. Note that Solar System Barycenter is in a
// slightly different place.
// https://astronomy.stackexchange.com/questions/44851/
("CENTER", "500@10"),
("EPHEM_TYPE", "ELEMENTS"),
// https://ssd.jpl.nasa.gov/horizons/manual.html#time
(
"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()
}