opensky_network/
tracks.rs

1//! Module for handling flight tracks by aircraft.
2use std::{
3    sync::Arc,
4    time::{SystemTime, UNIX_EPOCH},
5};
6
7use log::{debug, warn};
8use serde::{Deserialize, Deserializer, Serialize};
9use serde_json::{Map, Value};
10
11use crate::errors::Error;
12
13#[derive(Debug, Serialize, Deserialize)]
14/// Represents the trajectory for a certain aircraft at a given time.
15pub struct FlightTrack {
16    /// Unique ICAO 24-bit address of the transponder in lower case hex string
17    /// representation.
18    pub icao24: String,
19    #[serde(alias = "startTime")]
20    /// Time of the first waypoint in seconds since epoch (Unix time).
21    pub start_time: f64,
22    #[serde(alias = "endTime")]
23    /// Time of the last waypoint in seconds since epoch (Unix time).
24    pub end_time: f64,
25    /// Callsign (8 characters) that holds for the whole track.
26    pub callsign: Option<String>,
27    /// Waypoints of the trajectory
28    pub path: Vec<Waypoint>,
29}
30
31#[derive(Debug, Serialize)]
32/// Represents the single waypoint that is a basic part of flight trajectory.
33pub struct Waypoint {
34    /// Time which the given waypoint is associated with in seconds since epoch
35    /// (Unix time).
36    pub time: u64,
37    /// WGS-84 latitude in decimal degrees.
38    pub latitude: Option<f64>,
39    /// WGS-84 longitude in decimal degrees.
40    pub longitude: Option<f64>,
41    /// Barometric altitude in meters.
42    pub baro_altitude: Option<f64>,
43    /// True track in decimal degrees clockwise from north (north=0°). Can be
44    /// None.
45    pub true_track: Option<f64>,
46    /// Boolean value which indicates if the position was retrieved from a
47    /// surface position report.
48    pub on_ground: bool,
49}
50
51impl<'de> Deserialize<'de> for Waypoint {
52    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53    where
54        D: Deserializer<'de>,
55    {
56        let values = Deserialize::deserialize(deserializer)?;
57        match values {
58            Value::Array(arr) => Ok(Waypoint::from(arr)),
59            Value::Object(obj) => Ok(Waypoint::from(obj)),
60            _ => Err(serde::de::Error::custom("expected array")),
61        }
62    }
63}
64
65impl From<Vec<Value>> for Waypoint {
66    fn from(value: Vec<Value>) -> Self {
67        Waypoint {
68            time: value[0].as_u64().unwrap(),
69            latitude: value[1].as_f64(),
70            longitude: value[2].as_f64(),
71            baro_altitude: value[3].as_f64(),
72            true_track: value[4].as_f64(),
73            on_ground: value[5].as_bool().unwrap(),
74        }
75    }
76}
77
78impl From<Map<String, Value>> for Waypoint {
79    fn from(value: Map<String, Value>) -> Self {
80        Waypoint {
81            time: value["time"].as_u64().unwrap(),
82            latitude: value["latitude"].as_f64(),
83            longitude: value["longitude"].as_f64(),
84            baro_altitude: value["baro_altitude"].as_f64(),
85            true_track: value["true_track"].as_f64(),
86            on_ground: value["on_ground"].as_bool().unwrap(),
87        }
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct TrackRequest {
93    login: Option<Arc<(String, String)>>,
94    icao24: String,
95    time: u64,
96}
97
98impl TrackRequest {
99    pub async fn send(&self) -> Result<FlightTrack, Error> {
100        let login_part = if let Some(login) = &self.login {
101            format!("{}:{}@", login.0, login.1)
102        } else {
103            String::new()
104        };
105
106        let now = SystemTime::now()
107            .duration_since(UNIX_EPOCH)
108            .unwrap()
109            .as_secs();
110        if self.time != 0 && now - self.time > 30 * 24 * 60 * 60 {
111            warn!(
112                "Interval ({} secs) is larger than limits ({} secs)",
113                now - self.time,
114                30 * 24 * 60 * 60
115            );
116        }
117
118        let url = format!(
119            "https://{}opensky-network.org/api/tracks/all?icao24={}&time={}",
120            login_part, self.icao24, self.time
121        );
122
123        debug!("url = {}", url);
124
125        let res = reqwest::get(url).await?;
126
127        match res.status() {
128            reqwest::StatusCode::OK => {
129                let bytes = res.bytes().await?.to_vec();
130
131                let result: FlightTrack = match serde_json::from_slice(&bytes) {
132                    Ok(result) => result,
133                    Err(e) => {
134                        return Err(Error::InvalidJson(e));
135                    }
136                };
137
138                Ok(result)
139            }
140            status => Err(Error::Http(status)),
141        }
142    }
143}
144
145pub struct TrackRequestBuilder {
146    inner: TrackRequest,
147}
148
149impl TrackRequestBuilder {
150    pub fn new(login: Option<Arc<(String, String)>>, icao24: String) -> Self {
151        Self {
152            inner: TrackRequest {
153                login,
154                icao24,
155                time: 0,
156            },
157        }
158    }
159
160    pub fn at_time(&mut self, time: u64) -> &mut Self {
161        self.inner.time = time;
162
163        self
164    }
165
166    pub async fn send(self) -> Result<FlightTrack, Error> {
167        self.inner.send().await
168    }
169}