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}