1use std::sync::Arc;
3
4use log::debug;
5use serde::{Deserialize, Serialize};
6use serde_json::{from_value, Map, Value};
7
8use crate::{bounding_box::BoundingBox, errors::Error};
9
10#[derive(Debug, Serialize)]
11pub struct States {
13 pub time: u64,
14 pub states: Vec<StateVector>,
15}
16
17impl<'de> Deserialize<'de> for States {
18 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
19 where
20 D: serde::Deserializer<'de>,
21 {
22 let obj: Value = Deserialize::deserialize(deserializer)?;
23 let time: u64 = obj.get("time").unwrap().as_u64().unwrap();
24 let states = obj.get("states").unwrap();
25 let states: Vec<StateVector> = match states {
26 Value::Null => Vec::new(),
27 Value::Array(_) => {
28 Deserialize::deserialize(states).map_err(serde::de::Error::custom)?
29 }
30 _ => return Err(serde::de::Error::custom("expected an array")),
31 };
32
33 Ok(States { time, states })
34 }
35}
36
37#[derive(Debug, Serialize)]
38pub struct StateVector {
40 pub icao24: String,
43 pub callsign: Option<String>,
46 pub origin_country: String,
48 pub time_position: Option<u64>,
51 pub last_contact: u64,
54 pub longitude: Option<f32>,
56 pub latitude: Option<f32>,
58 pub baro_altitude: Option<f32>,
60 pub on_ground: bool,
63 pub velocity: Option<f32>,
65 pub true_track: Option<f32>,
68 pub vertical_rate: Option<f32>,
71 pub sensors: Option<Vec<u64>>,
74 pub geo_altitude: Option<f32>,
76 pub squawk: Option<String>,
78 pub spi: bool,
80 pub position_source: PositionSource,
82 pub category: Option<AirCraftCategory>,
84}
85
86impl<'de> Deserialize<'de> for StateVector {
87 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88 where
89 D: serde::Deserializer<'de>,
90 {
91 let all: Value = Deserialize::deserialize(deserializer)?;
92 match all {
93 Value::Array(arr) => Ok(StateVector::from(arr)),
94 Value::Object(obj) => Ok(StateVector::from(obj)),
95 _ => Err(serde::de::Error::custom("expected an array")),
96 }
97 }
98}
99
100impl From<Vec<Value>> for StateVector {
101 fn from(value: Vec<Value>) -> Self {
102 StateVector {
103 icao24: from_value(value[0].clone()).unwrap(),
104 callsign: from_value(value[1].clone()).unwrap(),
105 origin_country: from_value(value[2].clone()).unwrap(),
106 time_position: from_value(value[3].clone()).unwrap(),
107 last_contact: from_value(value[4].clone()).unwrap(),
108 longitude: from_value(value[5].clone()).unwrap(),
109 latitude: from_value(value[6].clone()).unwrap(),
110 baro_altitude: from_value(value[7].clone()).unwrap(),
111 on_ground: from_value(value[8].clone()).unwrap(),
112 velocity: from_value(value[9].clone()).unwrap(),
113 true_track: from_value(value[10].clone()).unwrap(),
114 vertical_rate: from_value(value[11].clone()).unwrap(),
115 sensors: from_value(value[12].clone()).unwrap(),
116 geo_altitude: from_value(value[13].clone()).unwrap(),
117 squawk: from_value(value[14].clone()).unwrap(),
118 spi: from_value(value[15].clone()).unwrap(),
119 position_source: from_value(value[16].clone()).unwrap(),
120 category: if value.len() == 18 {
121 from_value(value[17].clone()).unwrap()
122 } else {
123 None
124 },
125 }
126 }
127}
128
129impl From<Map<String, Value>> for StateVector {
130 fn from(map: Map<String, Value>) -> Self {
131 StateVector {
132 icao24: from_value(map.get("icao24").unwrap().clone()).unwrap(),
133 callsign: from_value(map.get("callsign").unwrap().clone()).unwrap(),
134 origin_country: from_value(map.get("origin_country").unwrap().clone()).unwrap(),
135 time_position: from_value(map.get("time_position").unwrap().clone()).unwrap(),
136 last_contact: from_value(map.get("last_contact").unwrap().clone()).unwrap(),
137 longitude: from_value(map.get("longitude").unwrap().clone()).unwrap(),
138 latitude: from_value(map.get("latitude").unwrap().clone()).unwrap(),
139 baro_altitude: from_value(map.get("baro_altitude").unwrap().clone()).unwrap(),
140 on_ground: from_value(map.get("on_ground").unwrap().clone()).unwrap(),
141 velocity: from_value(map.get("velocity").unwrap().clone()).unwrap(),
142 true_track: from_value(map.get("true_track").unwrap().clone()).unwrap(),
143 vertical_rate: from_value(map.get("vertical_rate").unwrap().clone()).unwrap(),
144 sensors: from_value(map.get("sensors").unwrap().clone()).unwrap(),
145 geo_altitude: from_value(map.get("geo_altitude").unwrap().clone()).unwrap(),
146 squawk: from_value(map.get("squawk").unwrap().clone()).unwrap(),
147 spi: from_value(map.get("spi").unwrap().clone()).unwrap(),
148 position_source: from_value(map.get("position_source").unwrap().clone()).unwrap(),
149 category: from_value(map.get("category").unwrap().clone()).unwrap(),
150 }
151 }
152}
153
154#[derive(Debug, Serialize)]
155pub enum PositionSource {
156 ADSB,
157 ASTERIX,
158 MLAT,
159 FLARM,
160}
161
162impl<'de> Deserialize<'de> for PositionSource {
163 fn deserialize<D>(deserializer: D) -> Result<PositionSource, D::Error>
164 where
165 D: serde::Deserializer<'de>,
166 {
167 match Deserialize::deserialize(deserializer)? {
168 Value::Number(num) => Ok(PositionSource::from(num.as_u64().unwrap() as u8)),
169 Value::String(s) => Ok(PositionSource::from(s.as_str())),
170 _ => Err(serde::de::Error::custom("expected a number")),
171 }
172 }
173}
174
175impl From<u8> for PositionSource {
176 fn from(value: u8) -> Self {
177 match value {
178 0 => PositionSource::ADSB,
179 1 => PositionSource::ASTERIX,
180 2 => PositionSource::MLAT,
181 3 => PositionSource::FLARM,
182 _ => {
183 eprintln!("unknown position source: {}", value);
184 PositionSource::ADSB
185 }
186 }
187 }
188}
189
190impl From<&str> for PositionSource {
191 fn from(value: &str) -> Self {
192 match value {
193 "ADSB" => PositionSource::ADSB,
194 "ASTERIX" => PositionSource::ASTERIX,
195 "MLAT" => PositionSource::MLAT,
196 "FLARM" => PositionSource::FLARM,
197 _ => {
198 eprintln!("unknown position source: {}", value);
199 PositionSource::ADSB
200 }
201 }
202 }
203}
204
205#[derive(Debug, Serialize)]
206pub enum AirCraftCategory {
207 NoInformation,
209 NoADSB,
211 Light,
213 Small,
215 Large,
217 HighVortexLarge,
219 Heavy,
221 HighPerformance,
223 Rotorcraft,
225 Glider,
227 LighterThanAir,
229 Parachutist,
231 Ultralight,
233 Reserved,
235 UAV,
237 Space,
239 SurfaceEmergency,
241 SurfaceService,
243 PointObstacle,
245 ClusterObstacle,
247 LineObstacle,
249}
250
251impl<'de> Deserialize<'de> for AirCraftCategory {
252 fn deserialize<D>(deserializer: D) -> Result<AirCraftCategory, D::Error>
253 where
254 D: serde::Deserializer<'de>,
255 {
256 match Deserialize::deserialize(deserializer)? {
257 Value::Number(num) => Ok(AirCraftCategory::from(num.as_u64().unwrap() as u8)),
258 Value::String(s) => Ok(AirCraftCategory::from(s.as_str())),
259 _ => Err(serde::de::Error::custom("expected a number")),
260 }
261 }
262}
263
264impl From<u8> for AirCraftCategory {
265 fn from(value: u8) -> Self {
266 match value {
267 0 => AirCraftCategory::NoInformation,
268 1 => AirCraftCategory::NoADSB,
269 2 => AirCraftCategory::Light,
270 3 => AirCraftCategory::Small,
271 4 => AirCraftCategory::Large,
272 5 => AirCraftCategory::HighVortexLarge,
273 6 => AirCraftCategory::Heavy,
274 7 => AirCraftCategory::HighPerformance,
275 8 => AirCraftCategory::Rotorcraft,
276 9 => AirCraftCategory::Glider,
277 10 => AirCraftCategory::LighterThanAir,
278 11 => AirCraftCategory::Parachutist,
279 12 => AirCraftCategory::Ultralight,
280 13 => AirCraftCategory::Reserved,
281 14 => AirCraftCategory::UAV,
282 15 => AirCraftCategory::Space,
283 16 => AirCraftCategory::SurfaceEmergency,
284 17 => AirCraftCategory::SurfaceService,
285 18 => AirCraftCategory::PointObstacle,
286 19 => AirCraftCategory::ClusterObstacle,
287 20 => AirCraftCategory::LineObstacle,
288 _ => AirCraftCategory::NoInformation,
289 }
290 }
291}
292
293impl From<&str> for AirCraftCategory {
294 fn from(value: &str) -> Self {
295 match value {
296 "NoInformation" => AirCraftCategory::NoInformation,
297 "NoADSB" => AirCraftCategory::NoADSB,
298 "Light" => AirCraftCategory::Light,
299 "Small" => AirCraftCategory::Small,
300 "Large" => AirCraftCategory::Large,
301 "HighVortexLarge" => AirCraftCategory::HighVortexLarge,
302 "Heavy" => AirCraftCategory::Heavy,
303 "HighPerformance" => AirCraftCategory::HighPerformance,
304 "Rotorcraft" => AirCraftCategory::Rotorcraft,
305 "Glider" => AirCraftCategory::Glider,
306 "LighterThanAir" => AirCraftCategory::LighterThanAir,
307 "Parachutist" => AirCraftCategory::Parachutist,
308 "Ultralight" => AirCraftCategory::Ultralight,
309 "Reserved" => AirCraftCategory::Reserved,
310 "UAV" => AirCraftCategory::UAV,
311 "Space" => AirCraftCategory::Space,
312 "SurfaceEmergency" => AirCraftCategory::SurfaceEmergency,
313 "SurfaceService" => AirCraftCategory::SurfaceService,
314 "PointObstacle" => AirCraftCategory::PointObstacle,
315 "ClusterObstacle" => AirCraftCategory::ClusterObstacle,
316 "LineObstacle" => AirCraftCategory::LineObstacle,
317 _ => {
318 eprintln!("unknown aircraft category: {}", value);
319 AirCraftCategory::NoInformation
320 }
321 }
322 }
323}
324
325#[derive(Debug, Clone)]
326pub struct StateRequest {
327 login: Option<Arc<(String, String)>>,
328 bbox: Option<BoundingBox>,
329 time: Option<u64>,
330 icao24_addresses: Vec<String>,
331 serials: Vec<u64>,
332}
333
334impl StateRequest {
335 pub async fn send(&self) -> Result<States, Error> {
336 let login_part = if let Some(login) = &self.login {
337 format!("{}:{}@", login.0, login.1)
338 } else {
339 String::new()
340 };
341
342 let mut args = String::new();
343
344 if let Some(time) = self.time {
345 if args.is_empty() {
346 args.push('?');
347 }
348
349 args.push_str(&format!("time={}", time));
350 }
351
352 if let Some(bbox) = self.bbox {
353 if args.is_empty() {
354 args.push('?');
355 } else {
356 args.push('&');
357 }
358
359 args.push_str(&format!(
360 "lamin={}&lomin={}&lamax={}&lomax={}",
361 bbox.lat_min, bbox.long_min, bbox.lat_max, bbox.long_max
362 ));
363 }
364
365 if !self.icao24_addresses.is_empty() {
366 if args.is_empty() {
367 args.push('?');
368 } else {
369 args.push('&');
370 }
371
372 if let Some(first) = self.icao24_addresses.first() {
373 args.push_str(&format!("icao24={}", first));
374 }
375
376 for icao24 in self.icao24_addresses.iter().skip(1) {
377 args.push_str(&format!("&icao24={}", icao24));
378 }
379 }
380
381 let endpoint = if !self.serials.is_empty() {
383 if args.is_empty() {
384 args.push('?');
385 } else {
386 args.push('&');
387 }
388
389 if let Some(first) = self.serials.first() {
390 args.push_str(&format!("serials={}", first));
391 }
392
393 for serial in self.serials.iter().skip(1) {
394 args.push_str(&format!("&serials={}", serial));
395 }
396
397 "own"
398 } else {
399 "all"
400 };
401
402 let url = format!(
403 "https://{}opensky-network.org/api/states/{}{}",
404 login_part, endpoint, args
405 );
406 debug!("Request url = {}", url);
407
408 let res = reqwest::get(url).await?;
409
410 match res.status() {
411 reqwest::StatusCode::OK => {
412 let bytes = res.bytes().await?.to_vec();
413
414 match serde_json::from_slice(&bytes) {
415 Ok(result) => Ok(result),
416 Err(err) => Err(Error::InvalidJson(err)),
417 }
418 }
419 status => Err(Error::Http(status)),
420 }
421 }
422}
423
424pub struct StateRequestBuilder {
425 inner: StateRequest,
426}
427
428impl StateRequestBuilder {
429 pub fn new(login: Option<Arc<(String, String)>>) -> Self {
430 Self {
431 inner: StateRequest {
432 login,
433 bbox: None,
434 time: None,
435 icao24_addresses: Vec::new(),
436 serials: Vec::new(),
437 },
438 }
439 }
440
441 pub fn with_bbox(mut self, bbox: BoundingBox) -> Self {
445 self.inner.bbox = Some(bbox);
446
447 self
448 }
449
450 pub fn at_time(mut self, timestamp: u64) -> Self {
456 self.inner.time = Some(timestamp);
457
458 self
459 }
460
461 pub fn with_icao24(mut self, address: String) -> Self {
468 self.inner.icao24_addresses.push(address);
469
470 self
471 }
472
473 pub fn with_serial(mut self, serial: u64) -> Self {
480 self.inner.serials.push(serial);
481
482 self
483 }
484
485 pub fn consume(self) -> StateRequest {
490 self.inner
491 }
492
493 pub fn finish(&self) -> StateRequest {
498 self.inner.clone()
499 }
500
501 pub async fn send(self) -> Result<States, Error> {
503 self.inner.send().await
504 }
505}
506
507impl From<StateRequestBuilder> for StateRequest {
508 fn from(srb: StateRequestBuilder) -> Self {
509 srb.consume()
510 }
511}