aerocontext_core/
navdata.rs1use chrono::{Days, NaiveDate};
4use serde::{Deserialize, Serialize};
5
6use crate::model::GeoPoint;
7
8pub const FAA_NASR_CYCLE_DAYS: u64 = 28;
10
11pub const FAA_NASR_SUBSCRIPTION_URL: &str =
14 "https://www.faa.gov/air_traffic/flight_info/aeronav/aero_data/NASR_Subscription/";
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[non_exhaustive]
19pub enum NavDataAuthority {
20 FaaNasr,
22}
23
24impl NavDataAuthority {
25 pub fn slug(self) -> &'static str {
28 match self {
29 Self::FaaNasr => "faa-nasr",
30 }
31 }
32}
33
34#[derive(Debug, thiserror::Error)]
36#[non_exhaustive]
37pub enum NavDataError {
38 #[error("could not compute {cycle_days}-day navigation-data cycle after {effective_on}")]
40 CycleEndOutOfRange {
41 effective_on: NaiveDate,
43 cycle_days: u64,
45 },
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[non_exhaustive]
51pub struct NavDataCycle {
52 pub authority: NavDataAuthority,
54 pub effective_on: NaiveDate,
56 pub next_effective_on: NaiveDate,
58 pub source: String,
60}
61
62impl NavDataCycle {
63 pub fn faa_nasr(effective_on: NaiveDate) -> Result<Self, NavDataError> {
65 Self::new(
66 NavDataAuthority::FaaNasr,
67 effective_on,
68 FAA_NASR_CYCLE_DAYS,
69 FAA_NASR_SUBSCRIPTION_URL,
70 )
71 }
72
73 pub fn new(
76 authority: NavDataAuthority,
77 effective_on: NaiveDate,
78 cycle_days: u64,
79 source: impl Into<String>,
80 ) -> Result<Self, NavDataError> {
81 let next_effective_on = effective_on.checked_add_days(Days::new(cycle_days)).ok_or(
82 NavDataError::CycleEndOutOfRange {
83 effective_on,
84 cycle_days,
85 },
86 )?;
87 Ok(Self {
88 authority,
89 effective_on,
90 next_effective_on,
91 source: source.into(),
92 })
93 }
94
95 pub fn contains(&self, date: NaiveDate) -> bool {
97 self.effective_on <= date && date < self.next_effective_on
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103#[non_exhaustive]
104pub enum NavPointKind {
105 Airport,
107 Waypoint,
109 Navaid,
111 Other(String),
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117#[non_exhaustive]
118pub struct NavPoint {
119 pub ident: String,
121 pub kind: NavPointKind,
123 pub position: GeoPoint,
125 pub name: Option<String>,
127}
128
129impl NavPoint {
130 pub fn new(ident: impl Into<String>, kind: NavPointKind, position: GeoPoint) -> Self {
132 Self {
133 ident: ident.into(),
134 kind,
135 position,
136 name: None,
137 }
138 }
139
140 #[must_use]
142 pub fn with_name(mut self, name: Option<String>) -> Self {
143 self.name = name;
144 self
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150#[non_exhaustive]
151pub struct NavDataSnapshot {
152 pub cycle: NavDataCycle,
154 pub points: Vec<NavPoint>,
156}
157
158impl NavDataSnapshot {
159 pub fn new(cycle: NavDataCycle, points: Vec<NavPoint>) -> Self {
161 Self { cycle, points }
162 }
163
164 pub fn resolve(&self, ident: &str) -> Option<&NavPoint> {
166 let wanted = ident.trim();
167 self.points
168 .iter()
169 .find(|point| point.ident.eq_ignore_ascii_case(wanted))
170 }
171
172 pub fn contains(&self, date: NaiveDate) -> bool {
174 self.cycle.contains(date)
175 }
176}
177
178#[cfg(test)]
179mod tests;