opensky_network/
flights.rs

1//! Module for retrieving flight data for a certain time interval.
2use std::sync::Arc;
3
4use crate::errors::Error;
5use log::{debug, warn};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Serialize, Deserialize)]
9/// Represents a flight object returned by the OpenSky API
10pub struct Flight {
11    /// Unique ICAO 24-bit address of the transponder in hex string
12    /// representation. All letters are lower case.
13    pub icao24: String,
14    #[serde(alias = "firstSeen")]
15    /// Estimated time of departure for the flight as Unix time (seconds since
16    /// epoch).
17    pub first_seen: u64,
18    #[serde(alias = "estDepartureAirport")]
19    /// ICAO code of the estimated departure airport. Can be None if the airport
20    /// could not be identified.
21    pub est_departure_airport: Option<String>,
22    #[serde(alias = "lastSeen")]
23    /// Estimated time of arrival for the flight as Unix time (seconds since
24    /// epoch).
25    pub last_seen: u64,
26    #[serde(alias = "estArrivalAirport")]
27    ///  ICAO code of the estimated arrival airport. Can be None if the airport
28    /// could not be identified.
29    pub est_arrival_airport: Option<String>,
30    /// Callsign of the vehicle (8 chars). Can be None if no callsign has been
31    /// received. If the vehicle transmits multiple callsigns during the flight,
32    /// we take the one seen most frequently.
33    pub callsign: Option<String>,
34    #[serde(alias = "estDepartureAirportHorizDistance")]
35    /// Horizontal distance of the last received airborne position to the
36    /// estimated departure airport in meters.
37    pub est_departure_airport_horiz_distance: Option<u32>,
38    #[serde(alias = "estDepartureAirportVertDistance")]
39    /// Vertical distance of the last received airborne position to the
40    /// estimated departure airport in meters.
41    pub est_departure_airport_vert_distance: Option<u32>,
42    #[serde(alias = "estArrivalAirportHorizDistance")]
43    /// Horizontal distance of the last received airborne position to the
44    /// estimated arrival airport in meters.
45    pub est_arrival_airport_horiz_distance: Option<u32>,
46    #[serde(alias = "estArrivalAirportVertDistance")]
47    /// Vertical distance of the last received airborne position to the
48    /// estimated arrival airport in meters.
49    pub est_arrival_airport_vert_distance: Option<u32>,
50    #[serde(alias = "departureAirportCandidatesCount")]
51    /// Number of other possible departure airports. These are airports in short
52    /// distance to estDepartureAirport.
53    pub departure_airport_candidates_count: u16,
54    #[serde(alias = "arrivalAirportCandidatesCount")]
55    /// Number of other possible departure airports.
56    pub arrival_airport_candidates_count: u16,
57}
58
59#[derive(Debug, Clone)]
60pub enum FlightsRequestType {
61    All,
62    Aircraft(String),
63    Arrival(String),
64    Departure(String),
65}
66
67impl FlightsRequestType {
68    pub fn max_interval(&self) -> u64 {
69        match self {
70            // 2 hours
71            FlightsRequestType::All => 2 * 60 * 60,
72            // 30 days
73            FlightsRequestType::Aircraft(_) => 30 * 24 * 60 * 60,
74            // 7 days
75            FlightsRequestType::Arrival(_) => 7 * 24 * 60 * 60,
76            // 7 days
77            FlightsRequestType::Departure(_) => 7 * 24 * 60 * 60,
78        }
79    }
80
81    fn endpoint(&self) -> &'static str {
82        match self {
83            FlightsRequestType::All => "all",
84            FlightsRequestType::Aircraft(_) => "aircraft",
85            FlightsRequestType::Arrival(_) => "arrival",
86            FlightsRequestType::Departure(_) => "departure",
87        }
88    }
89}
90
91#[derive(Debug, Clone)]
92pub struct FlightsRequest {
93    login: Option<Arc<(String, String)>>,
94    begin: u64,
95    end: u64,
96    request_type: FlightsRequestType,
97}
98
99impl FlightsRequest {
100    pub async fn send(&self) -> Result<Vec<Flight>, Error> {
101        let login_part = if let Some(login) = &self.login {
102            format!("{}:{}@", login.0, login.1)
103        } else {
104            String::new()
105        };
106
107        let endpoint = self.request_type.endpoint();
108        let interval = self.end - self.begin;
109        if interval > self.request_type.max_interval() {
110            warn!(
111                "Interval ({} secs) is larger than limits ({} secs)",
112                interval,
113                self.request_type.max_interval()
114            );
115        }
116
117        let mut args = format!("?begin={}&end={}", self.begin, self.end);
118        let additional_filters = match &self.request_type {
119            FlightsRequestType::All => String::new(),
120            FlightsRequestType::Aircraft(address) => format!("&icao24={}", address),
121            FlightsRequestType::Arrival(airport_icao) => format!("&airport={}", airport_icao),
122            FlightsRequestType::Departure(airport_icao) => format!("&airport={}", airport_icao),
123        };
124        args.push_str(&additional_filters);
125
126        let url = format!(
127            "https://{}opensky-network.org/api/flights/{}{}",
128            login_part, endpoint, args
129        );
130
131        debug!("Request url = {}", url);
132
133        let res = reqwest::get(url).await?;
134
135        match res.status() {
136            reqwest::StatusCode::OK => {
137                let bytes = res.bytes().await?.to_vec();
138
139                match serde_json::from_slice(&bytes) {
140                    Ok(result) => Ok(result),
141                    Err(e) => Err(Error::InvalidJson(e)),
142                }
143            }
144            reqwest::StatusCode::NOT_FOUND => Ok(Vec::new()),
145            status => Err(Error::Http(status)),
146        }
147    }
148}
149
150#[derive(Debug, Clone)]
151pub struct FlightsRequestBuilder {
152    inner: FlightsRequest,
153}
154
155impl FlightsRequestBuilder {
156    pub fn new(login: Option<Arc<(String, String)>>, begin: u64, end: u64) -> Self {
157        //assert!(end - begin <= 7200, "Interval must not span greater than 2 hours");
158        //assert!(end > begin, "End time must be greater than begin time");
159        Self {
160            inner: FlightsRequest {
161                login,
162                begin,
163                end,
164                request_type: FlightsRequestType::All,
165            },
166        }
167    }
168
169    /// This method is redundant, but can be used to reuse the same
170    /// FlightsRequestBuilder multiple times to create different requests.
171    /// This sets the beginning and end of the flight request interval. The
172    /// beginning and ending times are numbers that represent times in seconds
173    /// since the Unix Epoch.
174    ///
175    /// The interval must not span greater than 2 hours, otherwise the request
176    /// will fail.
177    pub fn in_interval(&mut self, begin: u64, end: u64) -> &mut Self {
178        assert!(
179            end - begin <= 7200,
180            "Interval must not span greater than 2 hours"
181        );
182        assert!(end > begin, "End time must be greater than begin time");
183        self.inner.begin = begin;
184        self.inner.end = end;
185
186        self
187    }
188
189    /// This method can be used to filter the flight data by a specific
190    /// aircraft. The aircraft ICAO24 address is in hex string
191    /// representation.
192    pub fn by_aircraft(&mut self, address: String) -> &mut Self {
193        self.inner.request_type = FlightsRequestType::Aircraft(address);
194
195        self
196    }
197
198    /// This method can be used to filter the flight data by a arrival airport.
199    /// The airport ICAO code is a 4-letter string.
200    pub fn by_arrival(&mut self, airport_icao: String) -> &mut Self {
201        self.inner.request_type = FlightsRequestType::Arrival(airport_icao);
202
203        self
204    }
205
206    /// This method can be used to filter the flight data by departure airport.
207    pub fn by_departure(&mut self, airport_icao: String) -> &mut Self {
208        self.inner.request_type = FlightsRequestType::Departure(airport_icao);
209
210        self
211    }
212
213    /// Consumes this FlightsRequestBuilder and returns a new FlightsRequest. If
214    /// this FlightsRequestBuilder could be used again effectively, then the
215    /// finish() method should be called instead because that will allow
216    /// this to be reused.
217    pub fn consume(self) -> FlightsRequest {
218        self.inner
219    }
220
221    /// Returns the FlightsRequest that this FlightsRequestBuilder has created.
222    /// This clones the inner FlightsRequest. If this FlightsRequestBuilder
223    /// will be only used once, the consume() method should be used instead
224    /// which will only move the inner value instead of calling clone()
225    pub fn finish(&self) -> FlightsRequest {
226        self.inner.clone()
227    }
228
229    /// Consumes this FlightsRequestBuilder and sends the request to the API.
230    pub async fn send(self) -> Result<Vec<Flight>, Error> {
231        self.inner.send().await
232    }
233}
234
235impl From<FlightsRequestBuilder> for FlightsRequest {
236    fn from(frb: FlightsRequestBuilder) -> Self {
237        frb.consume()
238    }
239}