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}