astro_rs/coordinates/
lookup.rs1use super::frames::Icrs;
2use super::lookup_config::SesameConfig;
3use super::EquatorialCoord;
4
5use hyper::client::HttpConnector;
6use hyper::Client;
7use once_cell::sync::OnceCell;
8use regex::Regex;
9use thiserror::Error;
10use uom::si::angle::{degree, Angle};
11use urlencoding::encode;
12
13static SESAME_CONFIG: OnceCell<SesameConfig> = OnceCell::new();
14static SESAME_PARSER: OnceCell<Regex> = OnceCell::new();
15
16fn init_sesame_parser() -> Regex {
17 Regex::new(r"%J\s*([0-9\.]+)\s*([\+\-\.0-9]+)").unwrap()
18}
19
20#[derive(Debug, Error)]
22pub enum AstroLookupError {
23 #[error("Invalid configuration: {reason}")]
25 InvalidConfiguration {
26 reason: String,
28 },
29 #[error(transparent)]
31 NetworkError(#[from] hyper::Error),
32 #[error("{reason}")]
34 ParseError {
35 reason: String,
37 },
38 #[error("Could not find coordinate data for {name}")]
40 InvalidName {
41 name: String,
43 },
44}
45
46pub async fn lookup_by_name(name: &str) -> Result<Icrs, AstroLookupError> {
70 let sesame_config = SESAME_CONFIG.get_or_init(SesameConfig::init);
71 let sesame_parser = SESAME_PARSER.get_or_init(init_sesame_parser);
72 let client = Client::new();
73
74 let mut err_result = Err(AstroLookupError::InvalidConfiguration {
75 reason: String::from("No configured SESAME URLs"),
76 });
77
78 for url in &sesame_config.urls {
79 let uri_string = [
80 url.as_str(),
81 if url.ends_with('/') { "" } else { "/" },
82 "~",
83 sesame_config.database.to_str(),
84 "?",
85 &encode(name),
86 ]
87 .concat();
88
89 let result = lookup_by_uri(name, sesame_parser, &client, uri_string).await;
90
91 if result.is_ok() {
92 return result;
93 } else {
94 err_result = result;
95 }
96 }
97
98 err_result
99}
100
101async fn lookup_by_uri(
102 name: &str,
103 sesame_parser: &Regex,
104 client: &Client<HttpConnector>,
105 uri_string: String,
106) -> Result<Icrs, AstroLookupError> {
107 let uri = uri_string
108 .parse()
109 .map_err(|_| AstroLookupError::InvalidName {
110 name: name.to_owned(),
111 })?;
112
113 let response = client.get(uri).await?;
114 let body_bytes = hyper::body::to_bytes(response).await?;
115 let body_string = String::from_utf8(body_bytes.as_ref().to_vec()).map_err(|er| {
116 AstroLookupError::ParseError {
117 reason: er.to_string(),
118 }
119 })?;
120
121 if let Some(cap) = sesame_parser.captures(&body_string) {
122 let ra_string = &cap[1];
123 let dec_string = &cap[2];
124
125 let ra: f64 = ra_string
126 .parse()
127 .map_err(|_| AstroLookupError::ParseError {
128 reason: ["Could not parse ra value: ", ra_string].concat(),
129 })?;
130 let dec: f64 = dec_string
131 .parse()
132 .map_err(|_| AstroLookupError::ParseError {
133 reason: ["Could not parse dec value: ", dec_string].concat(),
134 })?;
135
136 let coords = EquatorialCoord {
137 ra: Angle::new::<degree>(ra),
138 dec: Angle::new::<degree>(dec),
139 };
140 return Ok(Icrs { coords });
141 }
142
143 Err(AstroLookupError::InvalidName {
144 name: name.to_owned(),
145 })
146}