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
use super::frames::Icrs;
use super::EquatorialCoord;
use hyper::Client;
use measurements::Angle;
use thiserror::Error;
use urlencoding::encode;
const SIMBAD_BASE_URL: &str =
"http://simbad.u-strasbg.fr/simbad/sim-id?output.format=votable&Ident=";
const SIMBAD_OUTPUT_PARAMS: &str = "&output.params=ra(d;ICRS;J2000;2000),dec(d;ICRS;J2000;2000)";
#[derive(Debug, Error)]
pub enum AstroLookupError {
#[error(transparent)]
NetworkError(#[from] hyper::Error),
#[error("{reason}")]
ParseError {
reason: String,
},
#[error("Could not find coordinate data for {name}")]
InvalidName {
name: String,
},
}
pub async fn lookup_by_name(name: &str) -> Result<Icrs, AstroLookupError> {
let client = Client::new();
let uri_string = [SIMBAD_BASE_URL, &encode(name), SIMBAD_OUTPUT_PARAMS].concat();
let uri = uri_string
.parse()
.map_err(|_| AstroLookupError::InvalidName {
name: name.to_owned(),
})?;
let response = client.get(uri).await?;
let text_buf = hyper::body::to_bytes(response).await?;
let xml_string = String::from_utf8(text_buf.as_ref().to_vec()).map_err(|er| {
AstroLookupError::ParseError {
reason: er.to_string(),
}
})?;
if !xml_string.contains("<TD>") {
return Err(AstroLookupError::InvalidName {
name: name.to_owned(),
});
}
let mut xml_parts = xml_string.split("<TD>");
let ra = if let Some(ra_string) = xml_parts.nth(1) {
let ra_trimmed = ra_string.trim_end_matches(|c: char| !c.is_numeric());
ra_trimmed
.parse()
.map_err(|_| AstroLookupError::ParseError {
reason: ["Could not parse ra value: ", ra_trimmed].concat(),
})?
} else {
return Err(AstroLookupError::ParseError {
reason: String::from("Could not find ra value"),
});
};
let dec = if let Some(dec_string) = xml_parts.next() {
let dec_trimmed = dec_string.trim_end_matches(|c: char| !c.is_numeric());
dec_trimmed
.parse()
.map_err(|_| AstroLookupError::ParseError {
reason: ["Could not parse dec value: ", dec_trimmed].concat(),
})?
} else {
return Err(AstroLookupError::ParseError {
reason: String::from("Could not find dec value"),
});
};
Ok(Icrs {
coords: EquatorialCoord {
ra: Angle::from_degrees(ra),
dec: Angle::from_degrees(dec),
},
})
}