thrust/data/eurocontrol/ddr/
procedures.rs1use crate::error::ThrustError;
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5use std::path::Path;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33pub struct DdrProcedureRef {
34 pub airport: String,
35 pub designator: String,
36 pub kind: String,
37 pub raw: String,
38}
39
40pub fn parse_sid_star_dir<P: AsRef<Path>>(dir: P) -> Result<(Vec<DdrProcedureRef>, Vec<DdrProcedureRef>), ThrustError> {
41 let mut sids = Vec::new();
42 let mut stars = Vec::new();
43
44 for entry in std::fs::read_dir(dir)? {
45 let path = entry?.path();
46 let Some(name) = path.file_name().and_then(|x| x.to_str()) else {
47 continue;
48 };
49 if name.ends_with(".sid") {
50 sids.extend(parse_procedure_file(&path, "SID")?);
51 } else if name.ends_with(".star") {
52 stars.extend(parse_procedure_file(&path, "STAR")?);
53 }
54 }
55
56 Ok((sids, stars))
57}
58
59pub fn parse_procedure_file<P: AsRef<Path>>(path: P, kind: &str) -> Result<Vec<DdrProcedureRef>, ThrustError> {
60 let text = std::fs::read_to_string(path)?;
61 let mut rows = Vec::new();
62
63 for line in text.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) {
64 let tokens = line.split_whitespace().collect::<Vec<_>>();
65 if tokens.len() < 2 {
66 continue;
67 }
68 let airport = tokens[0].to_string();
69 let designator = tokens[1].to_string();
70 rows.push(DdrProcedureRef {
71 airport,
72 designator,
73 kind: kind.to_string(),
74 raw: line.to_string(),
75 });
76 }
77
78 Ok(rows)
79}
80
81pub fn procedure_designator_index(procedures: &[DdrProcedureRef]) -> HashSet<String> {
82 let mut out = HashSet::new();
83 for p in procedures {
84 for c in normalize_designator_candidates(&p.designator) {
85 out.insert(c);
86 }
87 }
88 out
89}
90
91fn normalize_designator_candidates(designator: &str) -> Vec<String> {
92 let mut out = vec![designator.to_uppercase()];
93 let upper = designator.to_uppercase();
94
95 if let Some(base) = upper.strip_suffix(".D") {
96 out.push(base.to_string());
97 }
98 if let Some(base) = upper.strip_suffix(".A") {
99 out.push(base.to_string());
100 }
101 if let Some(base) = upper.strip_suffix('.') {
102 out.push(base.to_string());
103 }
104
105 let compact = upper.chars().filter(|c| c.is_ascii_alphanumeric()).collect::<String>();
106 if !compact.is_empty() {
107 out.push(compact);
108 }
109
110 out.sort();
111 out.dedup();
112 out
113}