#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(html_logo_url = "https://raw.githubusercontent.com/rsadsb/adsb_deku/master/media/logo.png")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{collections::BTreeMap, fmt, string::String, vec, vec::Vec};
#[cfg(feature = "alloc")]
use core::{
clone::Clone, default::Default, fmt::Debug, marker::Copy, prelude::rust_2021::derive,
result::Result::Ok, writeln,
};
#[cfg(feature = "std")]
use std::time::SystemTime;
use adsb_deku::adsb::{AirborneVelocity, Identification, ME};
use adsb_deku::{cpr, Altitude, CPRFormat, Frame, DF, ICAO};
use tracing::{debug, info, warn};
const MAX_AIRCRAFT_DISTANCE: f64 = 100.0;
#[derive(Debug, PartialEq, Eq)]
pub enum Added {
No,
Yes,
}
impl From<bool> for Added {
fn from(other: bool) -> Self {
match other {
true => Self::Yes,
false => Self::No,
}
}
}
#[cfg_attr(feature = "serde", serde_with::serde_as)]
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Airplanes(
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<Vec<(serde_with::DisplayFromStr, serde_with::Same)>>")
)]
BTreeMap<ICAO, AirplaneState>,
);
impl fmt::Display for Airplanes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for key in self.0.keys() {
let value = self.aircraft_details(*key);
if let Some(value) = value {
writeln!(f, "{key}: {value:?}")?;
}
}
Ok(())
}
}
impl Airplanes {
#[must_use]
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn iter(&self) -> alloc::collections::btree_map::Iter<'_, ICAO, AirplaneState> {
self.0.iter()
}
pub fn keys(&self) -> alloc::collections::btree_map::Keys<'_, ICAO, AirplaneState> {
self.0.keys()
}
#[must_use]
pub fn get(&self, key: ICAO) -> Option<&AirplaneState> {
self.0.get(&key)
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn action(&mut self, frame: Frame, lat_long: (f64, f64), max_rang: f64) -> Added {
let mut airplane_added = Added::No;
match frame.df {
DF::ADSB(ref adsb) => {
airplane_added = match &adsb.me {
ME::AircraftIdentification(identification) => {
self.add_identification(adsb.icao, identification)
}
ME::AirborneVelocity(vel) => self.add_airborne_velocity(adsb.icao, vel),
ME::AirbornePositionGNSSAltitude(altitude)
| ME::AirbornePositionBaroAltitude(altitude) => {
self.update_position(adsb.icao, altitude, lat_long, max_rang)
}
_ => Added::No,
};
let incr_airplane_added = self.incr_messages(adsb.icao);
airplane_added =
if incr_airplane_added == Added::Yes || airplane_added == Added::Yes {
Added::Yes
} else {
Added::No
};
}
DF::TisB { cf, pi } => {
info!("TISB: {cf:?}, {pi:?}");
airplane_added = match cf.me {
ME::AircraftIdentification(identification) => {
self.add_identification(pi, &identification)
}
ME::AirborneVelocity(vel) => self.add_airborne_velocity(pi, &vel),
ME::AirbornePositionGNSSAltitude(altitude)
| ME::AirbornePositionBaroAltitude(altitude) => {
self.update_position(pi, &altitude, lat_long, max_rang)
}
_ => Added::No,
};
let incr_airplane_added = self.incr_messages(pi);
airplane_added =
if incr_airplane_added == Added::Yes || airplane_added == Added::Yes {
Added::Yes
} else {
Added::No
};
}
_ => (),
}
airplane_added
}
#[must_use]
pub fn aircraft_details(&self, icao: ICAO) -> Option<AirplaneDetails> {
match self.get(icao) {
Some(airplane_state) => {
let track = &airplane_state.track;
let coor = &airplane_state.coords;
if let (Some(position), Some(altitude), Some(kilo_distance)) =
(&coor.position, coor.altitude(), coor.kilo_distance)
{
Some(AirplaneDetails {
position: *position,
altitude,
kilo_distance,
heading: airplane_state.heading,
track: track.clone(),
})
} else {
None
}
}
None => None,
}
}
#[must_use]
pub fn all_position(&self) -> Vec<(ICAO, cpr::Position)> {
let mut all_lat_long = vec![];
for (key, airplane_state) in self.iter() {
let coor = &airplane_state.coords;
if let Some(position) = &coor.position {
all_lat_long.push((*key, *position));
}
}
all_lat_long
}
#[cfg(feature = "std")]
pub fn prune(&mut self, filter_time: u64) {
self.0.retain(|k, v| {
if let Ok(time) = v.last_time.elapsed() {
if time < std::time::Duration::from_secs(filter_time) {
true
} else {
info!("[{k}] non-active, removing");
false
}
} else {
info!("[{k}] non-active(time error), removing");
false
}
});
}
}
impl Airplanes {
fn entry_or_insert(&mut self, icao: ICAO) -> (&mut AirplaneState, Added) {
let entry = self.0.entry(icao);
let airplane_added =
Added::from(matches!(entry, alloc::collections::btree_map::Entry::Vacant(_)));
if Added::Yes == airplane_added {
info!("[{icao}] now tracking");
}
(entry.or_default(), airplane_added)
}
pub fn incr_messages(&mut self, icao: ICAO) -> Added {
let (state, airplane_added) = self.entry_or_insert(icao);
state.num_messages += 1;
#[cfg(feature = "std")]
{
state.last_time = std::time::SystemTime::now();
}
airplane_added
}
fn add_identification(&mut self, icao: ICAO, identification: &Identification) -> Added {
let (state, airplane_added) = self.entry_or_insert(icao);
state.callsign = Some(identification.cn.clone());
info!("[{icao}] with identification: {}", identification.cn);
airplane_added
}
fn add_airborne_velocity(&mut self, icao: ICAO, vel: &AirborneVelocity) -> Added {
let (state, airplane_added) = self.entry_or_insert(icao);
if let Some((heading, ground_speed, vert_speed)) = vel.calculate() {
info!("[{icao}] with airborne velocity: heading: {heading}, speed: {ground_speed}, vertical speed: {vert_speed}");
state.heading = Some(heading);
state.speed = Some(ground_speed as f32);
state.vert_speed = Some(vert_speed);
}
airplane_added
}
fn update_position(
&mut self,
icao: ICAO,
altitude: &Altitude,
lat_long: (f64, f64),
max_range: f64,
) -> Added {
let (state, airplane_added) = self.entry_or_insert(icao);
info!(
"[{icao}] with: {:?}, cpr lat: {}, cpr long: {}",
altitude.alt, altitude.lat_cpr, altitude.lon_cpr
);
let mut temp_coords = match altitude.odd_flag {
CPRFormat::Odd => AirplaneCoor {
altitudes: [state.coords.altitudes[0], Some(*altitude)],
..state.coords
},
CPRFormat::Even => AirplaneCoor {
altitudes: [Some(*altitude), state.coords.altitudes[1]],
..state.coords
},
};
if temp_coords.update_position(lat_long, max_range) {
if state.coords != temp_coords {
if let Some(track) = &mut state.track {
track.push(state.coords);
} else {
state.track = Some(vec![state.coords]);
}
state.coords = temp_coords;
}
} else {
state.coords = AirplaneCoor::default();
}
airplane_added
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AirplaneDetails {
pub position: cpr::Position,
pub altitude: u16,
pub kilo_distance: f64,
pub heading: Option<f32>,
pub track: Option<Vec<AirplaneCoor>>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AirplaneState {
pub coords: AirplaneCoor,
pub squawk: Option<u32>,
pub callsign: Option<String>,
pub heading: Option<f32>,
pub speed: Option<f32>,
pub vert_speed: Option<i16>,
pub on_ground: Option<bool>,
pub num_messages: u32,
#[cfg(feature = "std")]
pub last_time: SystemTime,
pub track: Option<Vec<AirplaneCoor>>,
}
impl Default for AirplaneState {
fn default() -> Self {
Self {
coords: AirplaneCoor::default(),
squawk: None,
callsign: None,
heading: None,
speed: None,
vert_speed: None,
on_ground: None,
num_messages: 0,
#[cfg(feature = "std")]
last_time: SystemTime::now(),
track: None,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AirplaneCoor {
pub altitudes: [Option<Altitude>; 2],
pub position: Option<cpr::Position>,
#[cfg(feature = "std")]
pub last_time: Option<SystemTime>,
pub kilo_distance: Option<f64>,
}
impl AirplaneCoor {
fn update_position(&mut self, lat_long: (f64, f64), max_range: f64) -> bool {
if let [Some(odd), Some(even)] = self.altitudes {
let test_position = cpr::get_position((&odd, &even));
if let Some(test_position) = test_position {
let kilo_distance = Self::haversine_distance(
lat_long,
(test_position.latitude, test_position.longitude),
);
if kilo_distance > max_range {
warn!("range: {kilo_distance} - old: {lat_long:?} new: {test_position:?}");
return false;
}
self.kilo_distance = Some(kilo_distance);
debug!("range: {kilo_distance}");
}
if let (Some(current_position), Some(test_position)) = (self.position, test_position) {
let distance = Self::haversine_distance_position(current_position, test_position);
if distance > MAX_AIRCRAFT_DISTANCE {
warn!("distance: {distance} old: {current_position:?}, invalid: {test_position:?}");
return false;
}
debug!("distance: {distance}");
}
self.position = test_position;
debug!("update_position: odd: (lat: {}, long: {}), even: (lat: {}, long: {}), position: {:?}",
odd.lat_cpr,
odd.lon_cpr,
even.lat_cpr,
even.lat_cpr,
self.position);
#[cfg(feature = "std")]
{
self.last_time = Some(SystemTime::now());
}
}
true
}
fn altitude(&self) -> Option<u16> {
if let Some(odd) = self.altitudes[0] {
if let Some(alt) = odd.alt {
return Some(alt);
}
}
None
}
fn haversine_distance_position(position: cpr::Position, other: cpr::Position) -> f64 {
let lat1 = position.latitude;
let lat2 = other.latitude;
let long1 = position.longitude;
let long2 = other.longitude;
Self::haversine_distance((lat1, long1), (lat2, long2))
}
fn haversine_distance(s: (f64, f64), other: (f64, f64)) -> f64 {
let lat1_rad = s.0.to_radians();
let lat2_rad = other.0.to_radians();
let long1_rad = s.1.to_radians();
let long2_rad = other.1.to_radians();
let x_lat = libm::sin((lat2_rad - lat1_rad) / 2.00);
let x_long = libm::sin((long2_rad - long1_rad) / 2.00);
#[allow(clippy::suboptimal_flops)]
let a = x_lat * x_lat
+ libm::cos(lat1_rad)
* libm::cos(lat2_rad)
* f64::from(libm::powf(libm::sin(x_long) as f32, 2.0));
let c = 2.0 * libm::atan2(libm::sqrt(a), libm::sqrt(1.0 - a));
let r = 6371.00;
r * c
}
}