Skip to main content

thrust/data/eurocontrol/aixm/
route.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/// An Airway Traffic Service (ATS) route connecting navigation points.
16///
17/// ATS routes are established airways defined by a sequence of navigation points.
18/// The route name follows ICAO convention: `[prefix]second_letter number[multiple_identifier]`
19///
20/// # Examples of Route Designators
21/// - `"N100"` (North Atlantic tracks): prefix=None, second_letter="N", number="100", multiple_id=None
22/// - `"UN123"` (Upper routes): prefix="U", second_letter="N", number="123", multiple_id=None
23/// - `"N456B"` (Alternative routing): prefix=None, second_letter="N", number="456", multiple_id="B"
24/// - `"B216A"` (Jet route with alternative): prefix="B", second_letter="2", number="16", multiple_id="A"
25///
26/// # Fields
27/// - `identifier`: Unique database identifier
28/// - `prefix`: First letter (e.g., "U" for Upper, "L" for Lower), or None
29/// - `second_letter`: Route category designator (e.g., "N" for North Atlantic)
30/// - `number`: Numeric designator (1-999)
31/// - `multiple_identifier`: Optional letter for alternative routes
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33pub struct Route {
34    #[serde(skip)]
35    pub identifier: String,
36    /// The prefix of the route, if any (must be "U" if any)
37    pub prefix: Option<String>,
38    /// The second letter of the route
39    pub second_letter: Option<String>,
40    /// The number of the route
41    pub number: Option<String>,
42    /// The multiple identifier of the route, if any
43    pub multiple_identifier: Option<String>,
44}
45
46/**
47 * Parse route data from a ZIP file containing AIXM data.
48 */
49pub fn parse_route_zip_file<P: AsRef<Path>>(path: P) -> Result<HashMap<String, Route>, ThrustError> {
50    let file = File::open(path)?;
51    let mut archive = ZipArchive::new(file)?;
52    let mut routes = HashMap::new();
53
54    for i in 0..archive.len() {
55        let file = archive.by_index(i)?;
56        if file.name().ends_with(".BASELINE") {
57            let mut reader = Reader::from_reader(BufReader::new(file));
58
59            while let Ok(_node) = find_node(&mut reader, vec![QName(b"aixm:Route")], None) {
60                let route = parse_route(&mut reader)?;
61                routes.insert(route.identifier.clone(), route);
62            }
63        }
64    }
65
66    Ok(routes)
67}
68
69fn parse_route<R: std::io::BufRead>(reader: &mut Reader<R>) -> Result<Route, ThrustError> {
70    let mut route = Route::default();
71
72    while let Ok(node) = find_node(
73        reader,
74        vec![
75            QName(b"gml:identifier"),
76            QName(b"aixm:designatorPrefix"),
77            QName(b"aixm:designatorSecondLetter"),
78            QName(b"aixm:designatorNumber"),
79            QName(b"aixm:multipleIdentifier"),
80        ],
81        Some(QName(b"aixm:Route")),
82    ) {
83        let Node { name, .. } = node;
84        match name {
85            QName(b"gml:identifier") => {
86                route.identifier = read_text(reader, name)?;
87            }
88            QName(b"aixm:designatorPrefix") => {
89                route.prefix = Some(read_text(reader, name)?);
90            }
91            QName(b"aixm:designatorSecondLetter") => {
92                route.second_letter = Some(read_text(reader, name)?);
93            }
94            QName(b"aixm:designatorNumber") => {
95                route.number = Some(read_text(reader, name)?);
96            }
97            QName(b"aixm:multipleIdentifier") => {
98                route.multiple_identifier = Some(read_text(reader, name)?);
99            }
100            _ => (),
101        }
102    }
103    Ok(route)
104}