opensky_network/
states.rs

1//! Module for searching state vectors from the OpenSky Network API.
2use 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)]
11/// Represents a collection of state vectors returned by the OpenSky API.
12pub 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)]
38/// Represents a state vector of an aircraft.
39pub struct StateVector {
40    /// Unique ICAO 24-bit address of the transponder in hex string
41    /// representation.
42    pub icao24: String,
43    /// Callsign of the vehicle (8 chars). Can be None if no callsign has been
44    /// received.
45    pub callsign: Option<String>,
46    /// Country name inferred from the ICAO 24-bit address.
47    pub origin_country: String,
48    /// Unix timestamp (seconds) for the last position update. Can be None if no
49    /// position report was received by OpenSky within the past 15s.
50    pub time_position: Option<u64>,
51    /// Unix timestamp (seconds) for the last update in general. This field is
52    /// updated for any new, valid message received from the transponder.
53    pub last_contact: u64,
54    /// WGS-84 longitude in decimal degrees.
55    pub longitude: Option<f32>,
56    /// WGS-84 latitude in decimal degrees.
57    pub latitude: Option<f32>,
58    /// Barometric altitude in meters.
59    pub baro_altitude: Option<f32>,
60    /// Boolean value which indicates if the position was retrieved from a
61    /// surface position report.
62    pub on_ground: bool,
63    /// Velocity over ground in m/s.
64    pub velocity: Option<f32>,
65    /// True track in decimal degrees clockwise from north (north=0°). Can be
66    /// None.
67    pub true_track: Option<f32>,
68    /// Vertical rate in m/s. A positive value indicates that the airplane is
69    /// climbing, a negative value indicates that it descends.
70    pub vertical_rate: Option<f32>,
71    /// IDs of the receivers which contributed to this state vector. Is None if
72    /// no filtering for sensor was used in the request.
73    pub sensors: Option<Vec<u64>>,
74    /// Geometric altitude in meters.
75    pub geo_altitude: Option<f32>,
76    /// The transponder code aka Squawk.
77    pub squawk: Option<String>,
78    /// Whether flight status indicates special purpose indicator.
79    pub spi: bool,
80    /// Origin of this state’s position.
81    pub position_source: PositionSource,
82    /// Aircraft category.
83    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    /// No information at all
208    NoInformation,
209    /// No ADS-B Emitter Category Information
210    NoADSB,
211    /// Light (< 15500 lbs)
212    Light,
213    /// Small (15500 to 75000 lbs)
214    Small,
215    /// Large (75000 to 300000 lbs)
216    Large,
217    /// High Vortex Large (aircraft such as B-757)
218    HighVortexLarge,
219    /// Heavy (> 300000 lbs)
220    Heavy,
221    /// High Performance (> 5g acceleration and 400 kts)
222    HighPerformance,
223    /// Rotorcraft
224    Rotorcraft,
225    /// Glider / sailplane
226    Glider,
227    /// Lighter-than-air
228    LighterThanAir,
229    /// Parachutist / Skydiver
230    Parachutist,
231    /// Ultralight / hang-glider / paraglider
232    Ultralight,
233    /// Reserved
234    Reserved,
235    /// Unmanned Aerial Vehicle
236    UAV,
237    /// Space / Trans-atmospheric vehicle
238    Space,
239    /// Surface Vehicle – Emergency Vehicle
240    SurfaceEmergency,
241    /// Surface Vehicle – Service Vehicle
242    SurfaceService,
243    /// Point Obstacle (includes tethered balloons)
244    PointObstacle,
245    /// Cluster Obstacle
246    ClusterObstacle,
247    /// Line Obstacle
248    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        // If serial numbers are provided determines which endpoint we use
382        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    /// Adds the provided bounding box to the request. This will only get states
442    /// that are within that bounding box. This will overwrite any
443    /// previously specified bounding box.
444    pub fn with_bbox(mut self, bbox: BoundingBox) -> Self {
445        self.inner.bbox = Some(bbox);
446
447        self
448    }
449
450    /// Specifies the time at which to get the data. The validity of this
451    /// timestamp depends on how much access the user has to historical
452    /// data.
453    ///
454    /// This time is specified as the time in seconds since the Unix Epoch.
455    pub fn at_time(mut self, timestamp: u64) -> Self {
456        self.inner.time = Some(timestamp);
457
458        self
459    }
460
461    /// Adds an ICAO24 transponder address represented by a hex string (e.g.
462    /// abc9f3) to filter the request by. Calling this function multiple
463    /// times will append more addresses which will be included in the
464    /// returned data.
465    ///
466    /// If this function is never called, it will provide data for all aircraft.
467    pub fn with_icao24(mut self, address: String) -> Self {
468        self.inner.icao24_addresses.push(address);
469
470        self
471    }
472
473    /// Adds a serial number of a sensor that you own. This must be owned by you
474    /// and registered in order to not return an HTTP error 403 (Forbidden).
475    /// Requests from your own sensors are not ratelimited.
476    ///
477    /// Calling this function multiple times will append more serial numbers of
478    /// receiviers which provide the returned data.
479    pub fn with_serial(mut self, serial: u64) -> Self {
480        self.inner.serials.push(serial);
481
482        self
483    }
484
485    /// Consumes this StateRequestBuilder and returns a new StateRequest. If
486    /// this StateRequestBuilder could be used again effectively, then the
487    /// finish() method should be called instead because that will allow
488    /// this to be reused.
489    pub fn consume(self) -> StateRequest {
490        self.inner
491    }
492
493    /// Returns the StateRequest that this StateRequestBuilder has created. This
494    /// clones the inner StateRequest. If this StateRequestBuilder will be
495    /// only used once, the consume() method should be used instead which
496    /// will only move the inner value instead of calling clone()
497    pub fn finish(&self) -> StateRequest {
498        self.inner.clone()
499    }
500
501    /// Consumes this StateRequestBuilder and sends the request to the API.
502    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}