opensky_network/
flights.rs

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//! Module for retrieving flight data for a certain time interval.
use std::sync::Arc;

use crate::errors::Error;
use log::{debug, warn};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
/// Represents a flight object returned by the OpenSky API
pub struct Flight {
    /// Unique ICAO 24-bit address of the transponder in hex string
    /// representation. All letters are lower case.
    pub icao24: String,
    #[serde(alias = "firstSeen")]
    /// Estimated time of departure for the flight as Unix time (seconds since
    /// epoch).
    pub first_seen: u64,
    #[serde(alias = "estDepartureAirport")]
    /// ICAO code of the estimated departure airport. Can be None if the airport
    /// could not be identified.
    pub est_departure_airport: Option<String>,
    #[serde(alias = "lastSeen")]
    /// Estimated time of arrival for the flight as Unix time (seconds since
    /// epoch).
    pub last_seen: u64,
    #[serde(alias = "estArrivalAirport")]
    ///  ICAO code of the estimated arrival airport. Can be None if the airport
    /// could not be identified.
    pub est_arrival_airport: Option<String>,
    /// Callsign of the vehicle (8 chars). Can be None if no callsign has been
    /// received. If the vehicle transmits multiple callsigns during the flight,
    /// we take the one seen most frequently.
    pub callsign: Option<String>,
    #[serde(alias = "estDepartureAirportHorizDistance")]
    /// Horizontal distance of the last received airborne position to the
    /// estimated departure airport in meters.
    pub est_departure_airport_horiz_distance: Option<u32>,
    #[serde(alias = "estDepartureAirportVertDistance")]
    /// Vertical distance of the last received airborne position to the
    /// estimated departure airport in meters.
    pub est_departure_airport_vert_distance: Option<u32>,
    #[serde(alias = "estArrivalAirportHorizDistance")]
    /// Horizontal distance of the last received airborne position to the
    /// estimated arrival airport in meters.
    pub est_arrival_airport_horiz_distance: Option<u32>,
    #[serde(alias = "estArrivalAirportVertDistance")]
    /// Vertical distance of the last received airborne position to the
    /// estimated arrival airport in meters.
    pub est_arrival_airport_vert_distance: Option<u32>,
    #[serde(alias = "departureAirportCandidatesCount")]
    /// Number of other possible departure airports. These are airports in short
    /// distance to estDepartureAirport.
    pub departure_airport_candidates_count: u16,
    #[serde(alias = "arrivalAirportCandidatesCount")]
    /// Number of other possible departure airports.
    pub arrival_airport_candidates_count: u16,
}

#[derive(Debug, Clone)]
pub enum FlightsRequestType {
    All,
    Aircraft(String),
    Arrival(String),
    Departure(String),
}

impl FlightsRequestType {
    pub fn max_interval(&self) -> u64 {
        match self {
            // 2 hours
            FlightsRequestType::All => 2 * 60 * 60,
            // 30 days
            FlightsRequestType::Aircraft(_) => 30 * 24 * 60 * 60,
            // 7 days
            FlightsRequestType::Arrival(_) => 7 * 24 * 60 * 60,
            // 7 days
            FlightsRequestType::Departure(_) => 7 * 24 * 60 * 60,
        }
    }

    fn endpoint(&self) -> &'static str {
        match self {
            FlightsRequestType::All => "all",
            FlightsRequestType::Aircraft(_) => "aircraft",
            FlightsRequestType::Arrival(_) => "arrival",
            FlightsRequestType::Departure(_) => "departure",
        }
    }
}

#[derive(Debug, Clone)]
pub struct FlightsRequest {
    login: Option<Arc<(String, String)>>,
    begin: u64,
    end: u64,
    request_type: FlightsRequestType,
}

impl FlightsRequest {
    pub async fn send(&self) -> Result<Vec<Flight>, Error> {
        let login_part = if let Some(login) = &self.login {
            format!("{}:{}@", login.0, login.1)
        } else {
            String::new()
        };

        let endpoint = self.request_type.endpoint();
        let interval = self.end - self.begin;
        if interval > self.request_type.max_interval() {
            warn!(
                "Interval ({} secs) is larger than limits ({} secs)",
                interval,
                self.request_type.max_interval()
            );
        }

        let mut args = format!("?begin={}&end={}", self.begin, self.end);
        let additional_filters = match &self.request_type {
            FlightsRequestType::All => String::new(),
            FlightsRequestType::Aircraft(address) => format!("&icao24={}", address),
            FlightsRequestType::Arrival(airport_icao) => format!("&airport={}", airport_icao),
            FlightsRequestType::Departure(airport_icao) => format!("&airport={}", airport_icao),
        };
        args.push_str(&additional_filters);

        let url = format!(
            "https://{}opensky-network.org/api/flights/{}{}",
            login_part, endpoint, args
        );

        debug!("Request url = {}", url);

        let res = reqwest::get(url).await?;

        match res.status() {
            reqwest::StatusCode::OK => {
                let bytes = res.bytes().await?.to_vec();

                match serde_json::from_slice(&bytes) {
                    Ok(result) => Ok(result),
                    Err(e) => Err(Error::InvalidJson(e)),
                }
            }
            reqwest::StatusCode::NOT_FOUND => Ok(Vec::new()),
            status => Err(Error::Http(status)),
        }
    }
}

#[derive(Debug, Clone)]
pub struct FlightsRequestBuilder {
    inner: FlightsRequest,
}

impl FlightsRequestBuilder {
    pub fn new(login: Option<Arc<(String, String)>>, begin: u64, end: u64) -> Self {
        //assert!(end - begin <= 7200, "Interval must not span greater than 2 hours");
        //assert!(end > begin, "End time must be greater than begin time");
        Self {
            inner: FlightsRequest {
                login,
                begin,
                end,
                request_type: FlightsRequestType::All,
            },
        }
    }

    /// This method is redundant, but can be used to reuse the same
    /// FlightsRequestBuilder multiple times to create different requests.
    /// This sets the beginning and end of the flight request interval. The
    /// beginning and ending times are numbers that represent times in seconds
    /// since the Unix Epoch.
    ///
    /// The interval must not span greater than 2 hours, otherwise the request
    /// will fail.
    pub fn in_interval(&mut self, begin: u64, end: u64) -> &mut Self {
        assert!(
            end - begin <= 7200,
            "Interval must not span greater than 2 hours"
        );
        assert!(end > begin, "End time must be greater than begin time");
        self.inner.begin = begin;
        self.inner.end = end;

        self
    }

    /// This method can be used to filter the flight data by a specific
    /// aircraft. The aircraft ICAO24 address is in hex string
    /// representation.
    pub fn by_aircraft(&mut self, address: String) -> &mut Self {
        self.inner.request_type = FlightsRequestType::Aircraft(address);

        self
    }

    /// This method can be used to filter the flight data by a arrival airport.
    /// The airport ICAO code is a 4-letter string.
    pub fn by_arrival(&mut self, airport_icao: String) -> &mut Self {
        self.inner.request_type = FlightsRequestType::Arrival(airport_icao);

        self
    }

    /// This method can be used to filter the flight data by departure airport.
    pub fn by_departure(&mut self, airport_icao: String) -> &mut Self {
        self.inner.request_type = FlightsRequestType::Departure(airport_icao);

        self
    }

    /// Consumes this FlightsRequestBuilder and returns a new FlightsRequest. If
    /// this FlightsRequestBuilder could be used again effectively, then the
    /// finish() method should be called instead because that will allow
    /// this to be reused.
    pub fn consume(self) -> FlightsRequest {
        self.inner
    }

    /// Returns the FlightsRequest that this FlightsRequestBuilder has created.
    /// This clones the inner FlightsRequest. If this FlightsRequestBuilder
    /// will be only used once, the consume() method should be used instead
    /// which will only move the inner value instead of calling clone()
    pub fn finish(&self) -> FlightsRequest {
        self.inner.clone()
    }

    /// Consumes this FlightsRequestBuilder and sends the request to the API.
    pub async fn send(self) -> Result<Vec<Flight>, Error> {
        self.inner.send().await
    }
}

impl From<FlightsRequestBuilder> for FlightsRequest {
    fn from(frb: FlightsRequestBuilder) -> Self {
        frb.consume()
    }
}