use std::sync::Arc;
use log::debug;
use serde::{Deserialize, Serialize};
use serde_json::{from_value, Map, Value};
use crate::{bounding_box::BoundingBox, errors::Error};
#[derive(Debug, Serialize)]
pub struct States {
pub time: u64,
pub states: Vec<StateVector>,
}
impl<'de> Deserialize<'de> for States {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let obj: Value = Deserialize::deserialize(deserializer)?;
let time: u64 = obj.get("time").unwrap().as_u64().unwrap();
let states = obj.get("states").unwrap();
let states: Vec<StateVector> = match states {
Value::Null => Vec::new(),
Value::Array(_) => {
Deserialize::deserialize(states).map_err(serde::de::Error::custom)?
}
_ => return Err(serde::de::Error::custom("expected an array")),
};
Ok(States { time, states })
}
}
#[derive(Debug, Serialize)]
pub struct StateVector {
pub icao24: String,
pub callsign: Option<String>,
pub origin_country: String,
pub time_position: Option<u64>,
pub last_contact: u64,
pub longitude: Option<f32>,
pub latitude: Option<f32>,
pub baro_altitude: Option<f32>,
pub on_ground: bool,
pub velocity: Option<f32>,
pub true_track: Option<f32>,
pub vertical_rate: Option<f32>,
pub sensors: Option<Vec<u64>>,
pub geo_altitude: Option<f32>,
pub squawk: Option<String>,
pub spi: bool,
pub position_source: PositionSource,
pub category: Option<AirCraftCategory>,
}
impl<'de> Deserialize<'de> for StateVector {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let all: Value = Deserialize::deserialize(deserializer)?;
match all {
Value::Array(arr) => Ok(StateVector::from(arr)),
Value::Object(obj) => Ok(StateVector::from(obj)),
_ => Err(serde::de::Error::custom("expected an array")),
}
}
}
impl From<Vec<Value>> for StateVector {
fn from(value: Vec<Value>) -> Self {
StateVector {
icao24: from_value(value[0].clone()).unwrap(),
callsign: from_value(value[1].clone()).unwrap(),
origin_country: from_value(value[2].clone()).unwrap(),
time_position: from_value(value[3].clone()).unwrap(),
last_contact: from_value(value[4].clone()).unwrap(),
longitude: from_value(value[5].clone()).unwrap(),
latitude: from_value(value[6].clone()).unwrap(),
baro_altitude: from_value(value[7].clone()).unwrap(),
on_ground: from_value(value[8].clone()).unwrap(),
velocity: from_value(value[9].clone()).unwrap(),
true_track: from_value(value[10].clone()).unwrap(),
vertical_rate: from_value(value[11].clone()).unwrap(),
sensors: from_value(value[12].clone()).unwrap(),
geo_altitude: from_value(value[13].clone()).unwrap(),
squawk: from_value(value[14].clone()).unwrap(),
spi: from_value(value[15].clone()).unwrap(),
position_source: from_value(value[16].clone()).unwrap(),
category: if value.len() == 18 {
from_value(value[17].clone()).unwrap()
} else {
None
},
}
}
}
impl From<Map<String, Value>> for StateVector {
fn from(map: Map<String, Value>) -> Self {
StateVector {
icao24: from_value(map.get("icao24").unwrap().clone()).unwrap(),
callsign: from_value(map.get("callsign").unwrap().clone()).unwrap(),
origin_country: from_value(map.get("origin_country").unwrap().clone()).unwrap(),
time_position: from_value(map.get("time_position").unwrap().clone()).unwrap(),
last_contact: from_value(map.get("last_contact").unwrap().clone()).unwrap(),
longitude: from_value(map.get("longitude").unwrap().clone()).unwrap(),
latitude: from_value(map.get("latitude").unwrap().clone()).unwrap(),
baro_altitude: from_value(map.get("baro_altitude").unwrap().clone()).unwrap(),
on_ground: from_value(map.get("on_ground").unwrap().clone()).unwrap(),
velocity: from_value(map.get("velocity").unwrap().clone()).unwrap(),
true_track: from_value(map.get("true_track").unwrap().clone()).unwrap(),
vertical_rate: from_value(map.get("vertical_rate").unwrap().clone()).unwrap(),
sensors: from_value(map.get("sensors").unwrap().clone()).unwrap(),
geo_altitude: from_value(map.get("geo_altitude").unwrap().clone()).unwrap(),
squawk: from_value(map.get("squawk").unwrap().clone()).unwrap(),
spi: from_value(map.get("spi").unwrap().clone()).unwrap(),
position_source: from_value(map.get("position_source").unwrap().clone()).unwrap(),
category: from_value(map.get("category").unwrap().clone()).unwrap(),
}
}
}
#[derive(Debug, Serialize)]
pub enum PositionSource {
ADSB,
ASTERIX,
MLAT,
FLARM,
}
impl<'de> Deserialize<'de> for PositionSource {
fn deserialize<D>(deserializer: D) -> Result<PositionSource, D::Error>
where
D: serde::Deserializer<'de>,
{
match Deserialize::deserialize(deserializer)? {
Value::Number(num) => Ok(PositionSource::from(num.as_u64().unwrap() as u8)),
Value::String(s) => Ok(PositionSource::from(s.as_str())),
_ => Err(serde::de::Error::custom("expected a number")),
}
}
}
impl From<u8> for PositionSource {
fn from(value: u8) -> Self {
match value {
0 => PositionSource::ADSB,
1 => PositionSource::ASTERIX,
2 => PositionSource::MLAT,
3 => PositionSource::FLARM,
_ => {
eprintln!("unknown position source: {}", value);
PositionSource::ADSB
}
}
}
}
impl From<&str> for PositionSource {
fn from(value: &str) -> Self {
match value {
"ADSB" => PositionSource::ADSB,
"ASTERIX" => PositionSource::ASTERIX,
"MLAT" => PositionSource::MLAT,
"FLARM" => PositionSource::FLARM,
_ => {
eprintln!("unknown position source: {}", value);
PositionSource::ADSB
}
}
}
}
#[derive(Debug, Serialize)]
pub enum AirCraftCategory {
NoInformation,
NoADSB,
Light,
Small,
Large,
HighVortexLarge,
Heavy,
HighPerformance,
Rotorcraft,
Glider,
LighterThanAir,
Parachutist,
Ultralight,
Reserved,
UAV,
Space,
SurfaceEmergency,
SurfaceService,
PointObstacle,
ClusterObstacle,
LineObstacle,
}
impl<'de> Deserialize<'de> for AirCraftCategory {
fn deserialize<D>(deserializer: D) -> Result<AirCraftCategory, D::Error>
where
D: serde::Deserializer<'de>,
{
match Deserialize::deserialize(deserializer)? {
Value::Number(num) => Ok(AirCraftCategory::from(num.as_u64().unwrap() as u8)),
Value::String(s) => Ok(AirCraftCategory::from(s.as_str())),
_ => Err(serde::de::Error::custom("expected a number")),
}
}
}
impl From<u8> for AirCraftCategory {
fn from(value: u8) -> Self {
match value {
0 => AirCraftCategory::NoInformation,
1 => AirCraftCategory::NoADSB,
2 => AirCraftCategory::Light,
3 => AirCraftCategory::Small,
4 => AirCraftCategory::Large,
5 => AirCraftCategory::HighVortexLarge,
6 => AirCraftCategory::Heavy,
7 => AirCraftCategory::HighPerformance,
8 => AirCraftCategory::Rotorcraft,
9 => AirCraftCategory::Glider,
10 => AirCraftCategory::LighterThanAir,
11 => AirCraftCategory::Parachutist,
12 => AirCraftCategory::Ultralight,
13 => AirCraftCategory::Reserved,
14 => AirCraftCategory::UAV,
15 => AirCraftCategory::Space,
16 => AirCraftCategory::SurfaceEmergency,
17 => AirCraftCategory::SurfaceService,
18 => AirCraftCategory::PointObstacle,
19 => AirCraftCategory::ClusterObstacle,
20 => AirCraftCategory::LineObstacle,
_ => AirCraftCategory::NoInformation,
}
}
}
impl From<&str> for AirCraftCategory {
fn from(value: &str) -> Self {
match value {
"NoInformation" => AirCraftCategory::NoInformation,
"NoADSB" => AirCraftCategory::NoADSB,
"Light" => AirCraftCategory::Light,
"Small" => AirCraftCategory::Small,
"Large" => AirCraftCategory::Large,
"HighVortexLarge" => AirCraftCategory::HighVortexLarge,
"Heavy" => AirCraftCategory::Heavy,
"HighPerformance" => AirCraftCategory::HighPerformance,
"Rotorcraft" => AirCraftCategory::Rotorcraft,
"Glider" => AirCraftCategory::Glider,
"LighterThanAir" => AirCraftCategory::LighterThanAir,
"Parachutist" => AirCraftCategory::Parachutist,
"Ultralight" => AirCraftCategory::Ultralight,
"Reserved" => AirCraftCategory::Reserved,
"UAV" => AirCraftCategory::UAV,
"Space" => AirCraftCategory::Space,
"SurfaceEmergency" => AirCraftCategory::SurfaceEmergency,
"SurfaceService" => AirCraftCategory::SurfaceService,
"PointObstacle" => AirCraftCategory::PointObstacle,
"ClusterObstacle" => AirCraftCategory::ClusterObstacle,
"LineObstacle" => AirCraftCategory::LineObstacle,
_ => {
eprintln!("unknown aircraft category: {}", value);
AirCraftCategory::NoInformation
}
}
}
}
#[derive(Debug, Clone)]
pub struct StateRequest {
login: Option<Arc<(String, String)>>,
bbox: Option<BoundingBox>,
time: Option<u64>,
icao24_addresses: Vec<String>,
serials: Vec<u64>,
}
impl StateRequest {
pub async fn send(&self) -> Result<States, Error> {
let login_part = if let Some(login) = &self.login {
format!("{}:{}@", login.0, login.1)
} else {
String::new()
};
let mut args = String::new();
if let Some(time) = self.time {
if args.is_empty() {
args.push('?');
}
args.push_str(&format!("time={}", time));
}
if let Some(bbox) = self.bbox {
if args.is_empty() {
args.push('?');
} else {
args.push('&');
}
args.push_str(&format!(
"lamin={}&lomin={}&lamax={}&lomax={}",
bbox.lat_min, bbox.long_min, bbox.lat_max, bbox.long_max
));
}
if !self.icao24_addresses.is_empty() {
if args.is_empty() {
args.push('?');
} else {
args.push('&');
}
if let Some(first) = self.icao24_addresses.first() {
args.push_str(&format!("icao24={}", first));
}
for icao24 in self.icao24_addresses.iter().skip(1) {
args.push_str(&format!("&icao24={}", icao24));
}
}
let endpoint = if !self.serials.is_empty() {
if args.is_empty() {
args.push('?');
} else {
args.push('&');
}
if let Some(first) = self.serials.first() {
args.push_str(&format!("serials={}", first));
}
for serial in self.serials.iter().skip(1) {
args.push_str(&format!("&serials={}", serial));
}
"own"
} else {
"all"
};
let url = format!(
"https://{}opensky-network.org/api/states/{}{}",
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(err) => Err(Error::InvalidJson(err)),
}
}
status => Err(Error::Http(status)),
}
}
}
pub struct StateRequestBuilder {
inner: StateRequest,
}
impl StateRequestBuilder {
pub fn new(login: Option<Arc<(String, String)>>) -> Self {
Self {
inner: StateRequest {
login,
bbox: None,
time: None,
icao24_addresses: Vec::new(),
serials: Vec::new(),
},
}
}
pub fn with_bbox(mut self, bbox: BoundingBox) -> Self {
self.inner.bbox = Some(bbox);
self
}
pub fn at_time(mut self, timestamp: u64) -> Self {
self.inner.time = Some(timestamp);
self
}
pub fn with_icao24(mut self, address: String) -> Self {
self.inner.icao24_addresses.push(address);
self
}
pub fn with_serial(mut self, serial: u64) -> Self {
self.inner.serials.push(serial);
self
}
pub fn consume(self) -> StateRequest {
self.inner
}
pub fn finish(&self) -> StateRequest {
self.inner.clone()
}
pub async fn send(self) -> Result<States, Error> {
self.inner.send().await
}
}
impl From<StateRequestBuilder> for StateRequest {
fn from(srb: StateRequestBuilder) -> Self {
srb.consume()
}
}