Skip to main content

thrust/data/eurocontrol/aixm/
route_segment.rs

1use crate::error::ThrustError;
2use quick_xml::name::QName;
3use quick_xml::Reader;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::BufReader;
8use std::path::Path;
9use zip::read::ZipArchive;
10
11use crate::data::eurocontrol::aixm::Node;
12
13use super::{find_node, read_text};
14
15/// A single segment of an ATS route connecting two sequential navigation points.
16///
17/// Route segments form the building blocks of complete airways. Each segment
18/// connects a start point and end point, with optional availability restrictions.
19/// Full routes are constructed by chaining multiple segments together in sequence.
20///
21/// # Fields
22/// - `identifier`: Unique database key
23/// - `route_formed`: The parent route designator this segment belongs to
24/// - `start`: Departure point (navaid, waypoint, or airport)
25/// - `end`: Arrival point (navaid, waypoint, or airport)
26///
27/// # Example
28/// ```ignore
29/// let segment = RouteSegment {
30///     identifier: "SEG001".to_string(),
31///     route_formed: Some("N100".to_string()),
32///     start: PointReference::Navaid("SEA".to_string()),
33///     end: PointReference::DesignatedPoint("APTIN".to_string()),
34///     ..Default::default()
35/// };
36/// ```
37#[derive(Debug, Clone, Serialize, Deserialize, Default)]
38pub struct RouteSegment {
39    #[serde(skip)]
40    pub identifier: String,
41    /// The identifier of the route this segment belongs to
42    pub route_formed: Option<String>,
43    /// Starting point of the segment
44    pub start: PointReference,
45    /// Ending point of the segment
46    pub end: PointReference,
47    // the following fields are related to availabilities, which are not properly modelled yet
48    // pub lower_limit: Option<String>,
49    // pub upper_limit: Option<String>,
50    // pub direction: Option<String>,
51}
52
53/// A reference to a navigation point (designated point, navaid, or airport).
54///
55/// Used in route segments, procedure legs, and airspace definitions to reference
56/// specific navigation points. The enum variants represent different point types
57/// that can appear in aviation procedures and routes.
58///
59/// # Variants
60/// - `DesignatedPoint(String)`: Published waypoint/fix identifier (e.g., "APTIN")
61/// - `Navaid(String)`: Navigation aid identifier (e.g., "SEA" for VOR)
62/// - `AirportHeliport(String)`: Airport or heliport identifier (e.g., "KSEA")
63/// - `None`: Point not resolved or undefined
64///
65/// # Example
66/// ```ignore
67/// let point = PointReference::DesignatedPoint("APTIN".to_string());
68/// match point {
69///     PointReference::DesignatedPoint(name) => println!("Waypoint: {}", name),
70///     _ => println!("Other point type"),
71/// }
72/// ```
73#[derive(Debug, Clone, Serialize, Deserialize, Default)]
74pub enum PointReference {
75    DesignatedPoint(String),
76    Navaid(String),
77    AirportHeliport(String),
78    #[default]
79    None,
80}
81
82impl PointReference {
83    pub fn name(&self) -> String {
84        match self {
85            PointReference::DesignatedPoint(id) => id.to_string(),
86            PointReference::Navaid(id) => id.to_string(),
87            PointReference::AirportHeliport(id) => id.to_string(),
88            PointReference::None => "".to_string(),
89        }
90    }
91
92    pub fn is_airport_heliport(&self) -> bool {
93        matches!(self, PointReference::AirportHeliport(_))
94    }
95}
96
97/**
98 * Parse route segment data from a ZIP file containing AIXM data.
99 */
100pub fn parse_route_segment_zip_file<P: AsRef<Path>>(path: P) -> Result<HashMap<String, RouteSegment>, ThrustError> {
101    let file = File::open(path)?;
102    let mut archive = ZipArchive::new(file)?;
103    let mut route_segments = HashMap::new();
104
105    for i in 0..archive.len() {
106        let file = archive.by_index(i)?;
107        if file.name().ends_with(".BASELINE") {
108            let mut reader = Reader::from_reader(BufReader::new(file));
109
110            while let Ok(_node) = find_node(&mut reader, vec![QName(b"aixm:RouteSegment")], None) {
111                let route_segment = parse_route_segment(&mut reader)?;
112                route_segments.insert(route_segment.identifier.clone(), route_segment);
113            }
114        }
115    }
116
117    Ok(route_segments)
118}
119
120fn parse_route_segment<R: std::io::BufRead>(reader: &mut Reader<R>) -> Result<RouteSegment, ThrustError> {
121    let mut segment = RouteSegment::default();
122
123    while let Ok(node) = find_node(
124        reader,
125        vec![
126            QName(b"gml:identifier"),
127            QName(b"aixm:routeFormed"),
128            QName(b"aixm:start"),
129            QName(b"aixm:end"),
130            //QName(b"aixm:lowerLimit"),
131            //QName(b"aixm:upperLimit"),
132            //QName(b"aixm:direction"),
133            QName(b"aixm:extension"),
134            QName(b"aixm:annotation"),
135            QName(b"aixm:availability"),
136        ],
137        Some(QName(b"aixm:RouteSegment")),
138    ) {
139        let Node { name, attributes } = node;
140        match name {
141            QName(b"gml:identifier") => {
142                segment.identifier = read_text(reader, name)?;
143            }
144            QName(b"aixm:extension") | QName(b"aixm:availability") | QName(b"aixm_annotation") => {
145                // Skip the whole block
146                let _ = find_node(reader, vec![], Some(name));
147            }
148            QName(b"aixm:routeFormed") => {
149                if let Some(id) = attributes
150                    .get("xlink:href")
151                    .map(|s| s.strip_prefix("urn:uuid:").unwrap_or(s))
152                {
153                    segment.route_formed = Some(id.to_string());
154                }
155            }
156            /*QName(b"aixm:lowerLimit") => {
157                segment.lower_limit = Some(read_text(reader, node)?);
158            }
159            QName(b"aixm:upperLimit") => {
160                segment.upper_limit = Some(read_text(reader, node)?);
161            }
162            QName(b"aixm:direction") => {
163                segment.direction = Some(read_text(reader, node)?);
164            }*/
165            QName(b"aixm:start") => {
166                while let Ok(node) = find_node(
167                    reader,
168                    vec![
169                        QName(b"aixm:pointChoice_fixDesignatedPoint"),
170                        QName(b"aixm:pointChoice_navaidSystem"),
171                    ],
172                    Some(name),
173                ) {
174                    let Node { name, attributes } = node;
175                    match name {
176                        QName(b"aixm:pointChoice_fixDesignatedPoint") => {
177                            if let Some(id) = attributes
178                                .get("xlink:href")
179                                .map(|s| s.strip_prefix("urn:uuid:").unwrap_or(s))
180                            {
181                                segment.start = PointReference::DesignatedPoint(id.to_string());
182                            }
183                        }
184                        QName(b"aixm:pointChoice_navaidSystem") => {
185                            if let Some(id) = attributes
186                                .get("xlink:href")
187                                .map(|s| s.strip_prefix("urn:uuid:").unwrap_or(s))
188                            {
189                                segment.start = PointReference::Navaid(id.to_string());
190                            }
191                        }
192                        _ => (),
193                    }
194                }
195            }
196            QName(b"aixm:end") => {
197                while let Ok(node) = find_node(
198                    reader,
199                    vec![
200                        QName(b"aixm:pointChoice_fixDesignatedPoint"),
201                        QName(b"aixm:pointChoice_navaidSystem"),
202                    ],
203                    Some(name),
204                ) {
205                    let Node { name, attributes } = node;
206                    match name {
207                        QName(b"aixm:pointChoice_fixDesignatedPoint") => {
208                            if let Some(id) = attributes
209                                .get("xlink:href")
210                                .map(|s| s.strip_prefix("urn:uuid:").unwrap_or(s))
211                            {
212                                segment.end = PointReference::DesignatedPoint(id.to_string());
213                            }
214                        }
215                        QName(b"aixm:pointChoice_navaidSystem") => {
216                            if let Some(id) = attributes
217                                .get("xlink:href")
218                                .map(|s| s.strip_prefix("urn:uuid:").unwrap_or(s))
219                            {
220                                segment.end = PointReference::Navaid(id.to_string());
221                            }
222                        }
223                        _ => (),
224                    }
225                }
226            }
227            _ => (),
228        }
229    }
230    Ok(segment)
231}