use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::rc::Rc;
use log::{debug, trace, warn};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use geo::{Contains, Point};
use rstar::AABB;
use crate::error::Error;
use crate::measurements::Length;
use crate::MagneticVariation;
mod airac_cycle;
mod airport;
mod airspace;
mod builder;
mod convert;
mod fix;
mod index;
mod location;
mod navaid;
mod runway;
mod waypoint;
#[cfg(feature = "sqlite")]
pub mod db;
pub use airac_cycle::{AiracCycle, CycleValidity};
pub use airport::Airport;
pub use airspace::{Airspace, AirspaceClassification, AirspaceType};
pub use fix::Fix;
pub use location::LocationIndicator;
pub use navaid::NavAid;
pub use runway::*;
pub use waypoint::*;
pub(crate) use builder::NavigationDataBuilder;
pub(crate) use index::{AirspaceIndex, NavAidIndex};
#[repr(C)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SourceFormat {
A424,
OpenAir,
}
type TerminalWaypoints = HashMap<String, Vec<Rc<Waypoint>>>;
#[derive(Clone, Debug, Default)]
pub struct Nearby {
pub airspaces: Vec<Rc<Airspace>>,
pub navaids: Vec<NavAid>,
}
impl Nearby {
pub fn is_empty(&self) -> bool {
self.airspaces.is_empty() && self.navaids.is_empty()
}
pub fn len(&self) -> usize {
self.airspaces.len() + self.navaids.len()
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NavigationData {
airports: Vec<Rc<Airport>>,
airspaces: Vec<Rc<Airspace>>,
#[cfg_attr(feature = "serde", serde(skip))]
airspace_index: AirspaceIndex,
#[cfg_attr(feature = "serde", serde(skip))]
navaid_index: NavAidIndex,
waypoints: Vec<Rc<Waypoint>>,
terminal_waypoints: TerminalWaypoints,
locations: Vec<LocationIndicator>,
cycle: Option<AiracCycle>,
partition_id: u64,
source_format: Option<SourceFormat>,
partitions: HashMap<u64, NavigationData>,
errors: Vec<Error>,
}
impl NavigationData {
pub fn new() -> Self {
Self::default()
}
pub(super) fn builder() -> NavigationDataBuilder {
NavigationDataBuilder::new()
}
pub fn locations(&self) -> &[LocationIndicator] {
self.locations.as_slice()
}
pub fn cycle(&self) -> Option<&AiracCycle> {
self.cycle.as_ref()
}
pub fn source_format(&self) -> Option<SourceFormat> {
let mut formats = std::iter::once(self.source_format)
.chain(self.partitions.values().map(|p| p.source_format))
.flatten();
let first = formats.next()?;
if formats.all(|f| f == first) {
Some(first)
} else {
None
}
}
pub fn partition_id(&self) -> u64 {
self.partition_id
}
pub fn at(&self, point: &Point<f64>, radius: Length) -> Nearby {
let airspaces: Vec<_> = self
.airspace_index
.candidates_at(point.x(), point.y())
.filter(|airspace| airspace.polygon.contains(point))
.cloned()
.collect();
let navaids: Vec<_> = self
.navaid_index
.within_radius(point, radius)
.cloned()
.collect();
Nearby { airspaces, navaids }
}
pub(crate) fn candidate_airspaces_for_envelope(
&self,
envelope: &AABB<Point<f64>>,
) -> Vec<Rc<Airspace>> {
self.airspace_index
.candidates_intersecting(envelope)
.cloned()
.collect()
}
pub fn find(&self, ident: &str) -> Option<NavAid> {
let result = self
.waypoints()
.find(|&wp| wp.ident() == ident)
.map(|wp| NavAid::Waypoint(Rc::clone(wp)))
.or(self
.airports()
.find(|&arpt| arpt.ident() == ident)
.map(|arpt| NavAid::Airport(Rc::clone(arpt))));
match &result {
Some(navaid) => trace!("found navaid for ident {:?}: {}", ident, navaid.ident()),
None => trace!("no navaid found for ident {:?}", ident),
}
result
}
pub fn find_terminal_waypoint(&self, airport_ident: &str, fix_ident: &str) -> Option<NavAid> {
let result = self
.terminal_waypoints(airport_ident)
.find(|&wp| wp.fix_ident == fix_ident)
.map(|wp| NavAid::Waypoint(Rc::clone(wp)));
match &result {
Some(_) => trace!("found terminal waypoint {} at {}", fix_ident, airport_ident),
None => trace!(
"no terminal waypoint {} found at {}",
fix_ident,
airport_ident
),
}
result
}
pub fn append(&mut self, other: NavigationData) {
let id = other.partition_id();
debug!("appending navigation data partition {}", id);
self.partitions.insert(id, other);
debug!(
"navigation data now has {} partition(s)",
self.partitions.len()
);
self.reindex();
}
pub fn concat(&mut self, other: Vec<NavigationData>) {
for nd in other {
let id = nd.partition_id();
debug!("appending navigation data partition {}", id);
self.partitions.insert(id, nd);
}
debug!(
"navigation data now has {} partition(s)",
self.partitions.len()
);
self.reindex();
}
pub fn remove(&mut self, partition_id: &u64) {
if self.partitions.remove(partition_id).is_some() {
debug!("removed navigation data partition {}", partition_id);
self.reindex();
} else {
warn!("attempted to remove unknown partition {}", partition_id);
}
}
fn reindex(&mut self) {
self.airspace_index = AirspaceIndex::new(self.airspaces());
self.navaid_index = NavAidIndex::new(self.airports(), self.waypoints());
}
pub fn expired_partitions(&self) -> Vec<u64> {
let expired: Vec<u64> = self
.partitions
.iter()
.filter_map(|(&id, nd)| {
nd.cycle
.and_then(|cycle| cycle.now_valid())
.filter(|&validity| validity == CycleValidity::Expired)
.map(|_| id)
})
.collect();
if !expired.is_empty() {
warn!(
"{} navigation data partition(s) expired: {:?}",
expired.len(),
expired
);
}
expired
}
pub fn errors(&self) -> &[Error] {
&self.errors
}
pub(crate) fn airports(&self) -> impl Iterator<Item = &Rc<Airport>> {
self.airports.iter().chain(
self.partitions
.values()
.flat_map(|partition| partition.airports.iter()),
)
}
pub(crate) fn airspaces(&self) -> impl Iterator<Item = &Rc<Airspace>> {
self.airspaces.iter().chain(
self.partitions
.values()
.flat_map(|partition| partition.airspaces.iter()),
)
}
pub(crate) fn waypoints(&self) -> impl Iterator<Item = &Rc<Waypoint>> {
self.waypoints.iter().chain(
self.partitions
.values()
.flat_map(|partition| partition.waypoints.iter()),
)
}
pub(crate) fn terminal_waypoints<'a>(
&'a self,
ident: &'a str,
) -> impl Iterator<Item = &'a Rc<Waypoint>> + 'a {
self.terminal_waypoints
.get(ident)
.into_iter()
.flatten()
.chain(
self.partitions
.values()
.filter_map(move |partition| partition.terminal_waypoints.get(ident))
.flatten(),
)
}
}
#[cfg(test)]
mod tests {
use crate::VerticalDistance;
use super::*;
#[test]
fn airspace_at_point() {
let mut builder = NavigationData::builder();
let inside = coord!(53.03759, 9.00533);
let outside = coord!(53.04892, 8.90907);
builder.add_airspace(Airspace {
name: String::from("TMA BREMEN A"),
airspace_type: AirspaceType::CTA,
classification: Some(AirspaceClassification::D),
ceiling: VerticalDistance::Fl(65),
floor: VerticalDistance::Msl(1500),
polygon: polygon![
(53.10111, 8.974999),
(53.102776, 9.079166),
(52.97028, 9.084444),
(52.96889, 8.982222),
(53.10111, 8.974999)
],
});
let nd = builder.build();
let nearby_inside = nd.at(&inside, Length::nm(1.0));
let nearby_outside = nd.at(&outside, Length::nm(1.0));
assert_eq!(nearby_inside.airspaces, vec![Rc::clone(&nd.airspaces[0])]);
assert!(nearby_outside.airspaces.is_empty());
}
#[test]
fn navaids_within_radius() {
let mut builder = NavigationData::builder();
builder.add_airport(Airport {
icao_ident: "EDDH".to_string(),
iata_designator: "HAM".to_string(),
name: "Hamburg".to_string(),
coordinate: Point::new(9.99, 53.63), mag_var: None,
elevation: VerticalDistance::Gnd,
runways: vec![],
location: None,
cycle: None,
});
builder.add_waypoint(Waypoint {
fix_ident: "DHN1".to_string(),
desc: "Delta November 1".to_string(),
usage: WaypointUsage::VFROnly,
coordinate: Point::new(9.95, 53.60), mag_var: None,
region: Region::Enroute,
location: None,
cycle: None,
});
builder.add_waypoint(Waypoint {
fix_ident: "FAR1".to_string(),
desc: "Far Away".to_string(),
usage: WaypointUsage::Unknown,
coordinate: Point::new(10.5, 54.5), mag_var: None,
region: Region::Enroute,
location: None,
cycle: None,
});
let nd = builder.build();
let center = Point::new(9.97, 53.62);
let nearby = nd.at(¢er, Length::nm(5.0));
assert_eq!(nearby.navaids.len(), 2);
let nearby = nd.at(¢er, Length::nm(100.0));
assert_eq!(nearby.navaids.len(), 3);
}
}