Skip to main content

thrust/data/eurocontrol/ddr/
procedures.rs

1use crate::error::ThrustError;
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5use std::path::Path;
6
7/// A reference to an instrument procedure (SID or STAR) from DDR data.
8///
9/// Represents a Standard Instrument Departure (SID) or Standard Arrival Route (STAR)
10/// procedure with minimal data: airport, procedure name, and type.
11///
12/// # Fields
13/// - `airport`: Departure/arrival airport ICAO code (e.g., "KSEA")
14/// - `designator`: Published procedure name (e.g., "KSEA01", "ORCAS3")
15/// - `kind`: Procedure type ("SID" or "STAR")
16/// - `raw`: Raw procedure definition string (format varies by source)
17///
18/// # Example
19/// ```ignore
20/// let proc = DdrProcedureRef {
21///     airport: "KSEA".to_string(),
22///     designator: "KSEA01".to_string(),
23///     kind: "SID".to_string(),
24///     raw: "KSEA KSEA01 SID ...".to_string(),
25/// };
26/// ```
27///
28/// # Note
29/// For detailed procedure information (legs, waypoints, restrictions),
30/// use [`StandardInstrumentDeparture`](crate::data::eurocontrol::aixm::standard_instrument_departure::StandardInstrumentDeparture)
31/// or [`StandardInstrumentArrival`](crate::data::eurocontrol::aixm::standard_instrument_arrival::StandardInstrumentArrival) from AIXM data.
32#[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}