Skip to main content

thrust_wasm/
nasr.rs

1use std::collections::HashMap;
2
3use wasm_bindgen::prelude::*;
4
5use thrust::data::faa::nasr::{parse_airspaces_from_nasr_bytes, parse_field15_data_from_nasr_bytes};
6
7use crate::models::{
8    normalize_airway_name, normalize_point_code, point_kind, AirportRecord, AirspaceRecord, AirwayPointRecord,
9    AirwayRecord, NavpointRecord,
10};
11
12#[wasm_bindgen]
13pub struct NasrResolver {
14    airports: Vec<AirportRecord>,
15    navaids: Vec<NavpointRecord>,
16    fixes: Vec<NavpointRecord>,
17    airways: Vec<AirwayRecord>,
18    airspaces: Vec<AirspaceRecord>,
19    airport_index: HashMap<String, Vec<usize>>,
20    navaid_index: HashMap<String, Vec<usize>>,
21    fix_index: HashMap<String, Vec<usize>>,
22    airway_index: HashMap<String, Vec<usize>>,
23    airspace_index: HashMap<String, Vec<usize>>,
24}
25
26#[wasm_bindgen]
27impl NasrResolver {
28    #[wasm_bindgen(constructor)]
29    pub fn new(zip_bytes: &[u8]) -> Result<NasrResolver, JsValue> {
30        let data = parse_field15_data_from_nasr_bytes(zip_bytes).map_err(|e| JsValue::from_str(&e.to_string()))?;
31        let nasr_airspaces =
32            parse_airspaces_from_nasr_bytes(zip_bytes).map_err(|e| JsValue::from_str(&e.to_string()))?;
33
34        let points = data.points;
35        let airway_segments = data.airways;
36
37        let airports: Vec<AirportRecord> = points
38            .iter()
39            .filter(|p| p.kind == "AIRPORT")
40            .map(|p| {
41                let code = p.identifier.to_uppercase();
42                let iata = if code.len() == 3 { Some(code.clone()) } else { None };
43                let icao = if code.len() == 4 { Some(code.clone()) } else { None };
44
45                AirportRecord {
46                    code,
47                    iata,
48                    icao,
49                    name: p.name.clone(),
50                    latitude: p.latitude,
51                    longitude: p.longitude,
52                    region: p.region.clone(),
53                    source: "faa_nasr".to_string(),
54                }
55            })
56            .collect();
57
58        let fixes: Vec<NavpointRecord> = points
59            .iter()
60            .filter(|p| p.kind == "FIX")
61            .map(|p| NavpointRecord {
62                code: normalize_point_code(&p.identifier),
63                identifier: p.identifier.to_uppercase(),
64                kind: "fix".to_string(),
65                name: p.name.clone(),
66                latitude: p.latitude,
67                longitude: p.longitude,
68                description: p.description.clone(),
69                frequency: p.frequency,
70                point_type: p.point_type.clone(),
71                region: p.region.clone(),
72                source: "faa_nasr".to_string(),
73            })
74            .collect();
75
76        let navaids: Vec<NavpointRecord> = points
77            .iter()
78            .filter(|p| p.kind == "NAVAID")
79            .map(|p| NavpointRecord {
80                code: normalize_point_code(&p.identifier),
81                identifier: p.identifier.to_uppercase(),
82                kind: "navaid".to_string(),
83                name: p.name.clone(),
84                latitude: p.latitude,
85                longitude: p.longitude,
86                description: p.description.clone(),
87                frequency: p.frequency,
88                point_type: p.point_type.clone(),
89                region: p.region.clone(),
90                source: "faa_nasr".to_string(),
91            })
92            .collect();
93
94        let mut point_index: HashMap<String, AirwayPointRecord> = HashMap::new();
95        for p in &points {
96            let normalized = normalize_point_code(&p.identifier);
97            let record = AirwayPointRecord {
98                code: normalized.clone(),
99                raw_code: p.identifier.to_uppercase(),
100                kind: point_kind(&p.kind),
101                latitude: p.latitude,
102                longitude: p.longitude,
103            };
104            point_index.entry(p.identifier.to_uppercase()).or_insert(record.clone());
105            point_index.entry(normalized).or_insert(record);
106        }
107
108        let mut grouped: HashMap<String, Vec<AirwayPointRecord>> = HashMap::new();
109        for seg in airway_segments {
110            let route_name = if seg.airway_id.trim().is_empty() {
111                seg.airway_name.clone()
112            } else {
113                seg.airway_id.clone()
114            };
115            let entry = grouped.entry(route_name).or_default();
116
117            let from_key = seg.from_point.to_uppercase();
118            let to_key = seg.to_point.to_uppercase();
119            let from = point_index.get(&from_key).cloned().unwrap_or(AirwayPointRecord {
120                code: normalize_point_code(&from_key),
121                raw_code: from_key.clone(),
122                kind: "point".to_string(),
123                latitude: 0.0,
124                longitude: 0.0,
125            });
126            let to = point_index.get(&to_key).cloned().unwrap_or(AirwayPointRecord {
127                code: normalize_point_code(&to_key),
128                raw_code: to_key.clone(),
129                kind: "point".to_string(),
130                latitude: 0.0,
131                longitude: 0.0,
132            });
133
134            if entry.last().map(|x| &x.code) != Some(&from.code) {
135                entry.push(from);
136            }
137            if entry.last().map(|x| &x.code) != Some(&to.code) {
138                entry.push(to);
139            }
140        }
141
142        let airways: Vec<AirwayRecord> = grouped
143            .into_iter()
144            .map(|(name, points)| AirwayRecord {
145                name,
146                source: "faa_nasr".to_string(),
147                points,
148            })
149            .collect();
150
151        let airspaces: Vec<AirspaceRecord> = nasr_airspaces
152            .into_iter()
153            .map(|a| AirspaceRecord {
154                designator: a.designator,
155                name: a.name,
156                type_: a.type_,
157                lower: a.lower,
158                upper: a.upper,
159                coordinates: a.coordinates,
160                source: "faa_nasr".to_string(),
161            })
162            .collect();
163
164        let mut airport_index: HashMap<String, Vec<usize>> = HashMap::new();
165        for (i, a) in airports.iter().enumerate() {
166            airport_index.entry(a.code.clone()).or_default().push(i);
167            if let Some(v) = &a.iata {
168                airport_index.entry(v.clone()).or_default().push(i);
169            }
170            if let Some(v) = &a.icao {
171                airport_index.entry(v.clone()).or_default().push(i);
172            }
173        }
174
175        let mut navaid_index: HashMap<String, Vec<usize>> = HashMap::new();
176        for (i, n) in navaids.iter().enumerate() {
177            navaid_index.entry(n.code.clone()).or_default().push(i);
178            navaid_index.entry(n.identifier.clone()).or_default().push(i);
179        }
180
181        let mut fix_index: HashMap<String, Vec<usize>> = HashMap::new();
182        for (i, n) in fixes.iter().enumerate() {
183            fix_index.entry(n.code.clone()).or_default().push(i);
184            fix_index.entry(n.identifier.clone()).or_default().push(i);
185        }
186
187        let mut airway_index: HashMap<String, Vec<usize>> = HashMap::new();
188        for (i, a) in airways.iter().enumerate() {
189            airway_index.entry(normalize_airway_name(&a.name)).or_default().push(i);
190            airway_index.entry(a.name.to_uppercase()).or_default().push(i);
191        }
192
193        let mut airspace_index: HashMap<String, Vec<usize>> = HashMap::new();
194        for (i, a) in airspaces.iter().enumerate() {
195            airspace_index.entry(a.designator.to_uppercase()).or_default().push(i);
196        }
197
198        Ok(Self {
199            airports,
200            navaids,
201            fixes,
202            airways,
203            airspaces,
204            airport_index,
205            navaid_index,
206            fix_index,
207            airway_index,
208            airspace_index,
209        })
210    }
211
212    pub fn airports(&self) -> Result<JsValue, JsValue> {
213        serde_wasm_bindgen::to_value(&self.airports).map_err(|e| JsValue::from_str(&e.to_string()))
214    }
215
216    pub fn resolve_airport(&self, code: String) -> Result<JsValue, JsValue> {
217        let key = code.to_uppercase();
218        let item = self
219            .airport_index
220            .get(&key)
221            .and_then(|idx| idx.first().copied())
222            .and_then(|i| self.airports.get(i))
223            .cloned();
224
225        serde_wasm_bindgen::to_value(&item).map_err(|e| JsValue::from_str(&e.to_string()))
226    }
227
228    pub fn navaids(&self) -> Result<JsValue, JsValue> {
229        serde_wasm_bindgen::to_value(&self.navaids).map_err(|e| JsValue::from_str(&e.to_string()))
230    }
231
232    pub fn fixes(&self) -> Result<JsValue, JsValue> {
233        serde_wasm_bindgen::to_value(&self.fixes).map_err(|e| JsValue::from_str(&e.to_string()))
234    }
235
236    pub fn airways(&self) -> Result<JsValue, JsValue> {
237        serde_wasm_bindgen::to_value(&self.airways).map_err(|e| JsValue::from_str(&e.to_string()))
238    }
239
240    pub fn airspaces(&self) -> Result<JsValue, JsValue> {
241        serde_wasm_bindgen::to_value(&self.airspaces).map_err(|e| JsValue::from_str(&e.to_string()))
242    }
243
244    pub fn resolve_navaid(&self, code: String) -> Result<JsValue, JsValue> {
245        let key = code.to_uppercase();
246        let item = self
247            .navaid_index
248            .get(&key)
249            .and_then(|idx| idx.first().copied())
250            .and_then(|i| self.navaids.get(i))
251            .cloned();
252
253        serde_wasm_bindgen::to_value(&item).map_err(|e| JsValue::from_str(&e.to_string()))
254    }
255
256    pub fn resolve_fix(&self, code: String) -> Result<JsValue, JsValue> {
257        let key = code.to_uppercase();
258        let item = self
259            .fix_index
260            .get(&key)
261            .and_then(|idx| idx.first().copied())
262            .and_then(|i| self.fixes.get(i))
263            .cloned();
264
265        serde_wasm_bindgen::to_value(&item).map_err(|e| JsValue::from_str(&e.to_string()))
266    }
267
268    pub fn resolve_airway(&self, name: String) -> Result<JsValue, JsValue> {
269        let key = normalize_airway_name(&name);
270        let item = self
271            .airway_index
272            .get(&key)
273            .and_then(|idx| idx.first().copied())
274            .and_then(|i| self.airways.get(i))
275            .cloned();
276
277        serde_wasm_bindgen::to_value(&item).map_err(|e| JsValue::from_str(&e.to_string()))
278    }
279
280    pub fn resolve_airspace(&self, designator: String) -> Result<JsValue, JsValue> {
281        let key = designator.to_uppercase();
282        let item = self
283            .airspace_index
284            .get(&key)
285            .and_then(|idx| idx.first().copied())
286            .and_then(|i| self.airspaces.get(i))
287            .cloned();
288
289        serde_wasm_bindgen::to_value(&item).map_err(|e| JsValue::from_str(&e.to_string()))
290    }
291}