kml_to_fgfp/route/
handlers.rs

1use std::{error::Error, io::Write};
2
3use xml::writer::EventWriter;
4
5use super::Airport;
6
7/// Used to write a waypoint to the .fgfp file.
8pub struct Waypoint {
9    pub number: usize,
10    pub ident: String,
11    pub lon: f64,
12    pub lat: f64,
13    pub altitude: usize,
14}
15
16/// An example of the sequence to look for in the .kml's xml is:
17///
18/// ```text
19/// <Placemark>
20///    <name>EZE11</name>
21///    <styleUrl>#FixMark</styleUrl>
22///    <coordinates>-58.594239,-34.811897,823</coordinates>
23/// </Placemark>
24/// ```
25pub enum LookingFor {
26    OpeningPlacemark,
27    OpeningName,
28    ContentName,
29    ClosingName,
30    OpeningStyleUrl,
31    ContentStyleUrl,
32    ClosingStyleUrl,
33    OpeningCoordinates,
34    ContentCoordinates,
35    ClosingCoordinates,
36    ClosingPlacemark,
37}
38
39/// Internal function to handle start events from the `transform_route` function.
40pub fn handle_start_event(
41    mut waypoint: Waypoint,
42    mut current_search: LookingFor,
43    mut drop: bool,
44    wp: usize,
45    name: &str,
46) -> (Waypoint, LookingFor, bool) {
47    // 1. Find opening of `Placemark`
48    if matches!(current_search, LookingFor::OpeningPlacemark) && name == "Placemark" {
49        waypoint.number = wp;
50        current_search = LookingFor::OpeningName;
51        drop = false;
52    }
53
54    // 2. Find opening of `name`
55    if matches!(current_search, LookingFor::OpeningName) && name == "name" {
56        current_search = LookingFor::ContentName;
57    }
58
59    // 5. Find opening of `styleUrl`
60    if matches!(current_search, LookingFor::OpeningStyleUrl) && name == "styleUrl" {
61        current_search = LookingFor::ContentStyleUrl;
62    }
63
64    // 8. Find opening of `coordinates`
65    if matches!(current_search, LookingFor::OpeningCoordinates) && name == "coordinates" {
66        current_search = LookingFor::ContentCoordinates;
67    }
68
69    (waypoint, current_search, drop)
70}
71
72/// Internal function to handle characters events from the `transform_route` function.
73pub fn handle_characters_event(
74    mut waypoint: Waypoint,
75    mut current_search: LookingFor,
76    mut drop: bool,
77    line: String,
78    departure_airport: &Option<Airport>,
79    destination_airport: &Option<Airport>,
80) -> (Waypoint, LookingFor, bool) {
81    // 3. Find contents of `name`
82    if matches!(current_search, LookingFor::ContentName) {
83        waypoint.ident = String::from(&line);
84        current_search = LookingFor::ClosingName;
85
86        // Handle the waypoints that reference airports by dropping them.
87        if let Some(airport) = departure_airport {
88            if line == airport.ident {
89                current_search = LookingFor::ClosingPlacemark;
90                drop = true;
91            }
92        }
93
94        if let Some(airport) = destination_airport {
95            if line == airport.ident {
96                current_search = LookingFor::ClosingPlacemark;
97                drop = true;
98            }
99        }
100    }
101
102    // 6. Find contents of `styleUrl`
103    if matches!(current_search, LookingFor::ContentStyleUrl) {
104        if line != "#FixMark" {
105            drop = true;
106
107            // We found that this Placemark is not part of the route, so we avoid
108            // further processing of the waypoint.
109            current_search = LookingFor::ClosingPlacemark;
110
111            return (waypoint, current_search, drop);
112        }
113        current_search = LookingFor::ClosingStyleUrl;
114    }
115
116    // 9. Find contents of `coordinates`
117    if matches!(current_search, LookingFor::ContentCoordinates) {
118        let data: Vec<&str> = line.split(',').map(|l| l.trim()).collect();
119
120        let mut message = String::new();
121
122        waypoint.lon = match data[0].parse() {
123            Ok(d) => d,
124            Err(e) => {
125                message = e.to_string();
126                0f64
127            }
128        };
129        waypoint.lat = match data[1].parse() {
130            Ok(d) => d,
131            Err(e) => {
132                message = e.to_string();
133                0f64
134            }
135        };
136        waypoint.altitude = {
137            let meters: f64 = match data[2].parse() {
138                Ok(d) => d,
139                Err(e) => {
140                    message = e.to_string();
141                    0f64
142                }
143            };
144
145            // We don't want exact precision, we need to be precise up to one hundred feet. Example:
146            // If the real altitude is 12478.64 feet, we interpret that as 12500 feet. We divide by
147            // one hundred and multiply by one hundred to let the round function do this for us.
148            let feet = (meters * 3.280839895 / 100.0).round() * 100.0;
149            feet as usize
150        };
151
152        if !message.is_empty() {
153            eprintln!(
154                "\x1B[01;33mDropping\x1B[00;01m {}\x1B[00m waypoint: {}",
155                waypoint.ident, message
156            );
157            drop = true;
158        }
159
160        current_search = LookingFor::ClosingCoordinates;
161    }
162
163    (waypoint, current_search, drop)
164}
165
166/// Internal function to handle end events from the `transform_route` function.
167pub fn handle_end_event<W: Write>(
168    writer: &mut EventWriter<W>,
169    waypoint: Waypoint,
170    mut current_search: LookingFor,
171    drop: bool,
172    mut wp: usize,
173    name: &str,
174) -> Result<(Waypoint, LookingFor, usize), Box<dyn Error>> {
175    // 4. Find closing of `name`
176    if matches!(current_search, LookingFor::ClosingName) && name == "name" {
177        current_search = LookingFor::OpeningStyleUrl;
178    }
179
180    // 7. Find closing of `styleUrl`
181    if matches!(current_search, LookingFor::ClosingStyleUrl) && name == "styleUrl" {
182        current_search = LookingFor::OpeningCoordinates;
183    }
184
185    // 10. Find closing of `coordinates`
186    if matches!(current_search, LookingFor::ClosingCoordinates) && name == "coordinates" {
187        current_search = LookingFor::ClosingPlacemark;
188    }
189
190    // 11. Find closing of `Placemark`
191    if matches!(current_search, LookingFor::ClosingPlacemark) && name == "Placemark" {
192        if !drop {
193            super::write_waypoint(writer, &waypoint)?;
194            wp += 1;
195        }
196        current_search = LookingFor::OpeningPlacemark;
197    }
198
199    Ok((waypoint, current_search, wp))
200}