sct_reader/package/
map.rs

1use crate::loaders::vnas_crc::CrcVideoMapRef;
2use crate::loaders::{
3    ese::FreeTextGroup,
4    euroscope::{
5        line::{ColouredLine, LineGroup},
6        sector::{LabelGroup, RegionGroup},
7    },
8};
9use anyhow::{anyhow, bail, Context};
10use geojson::{Feature, FeatureCollection, GeoJson, Geometry, Value};
11use serde::{Deserialize, Serialize};
12use serde_json::Map;
13use std::collections::HashMap;
14use std::fs::File;
15use std::io::BufReader;
16use std::path::Path;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum AtcMapData {
20    Embedded {features: FeatureCollection},
21    ExternalFile {filename: String}
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AtcMap {
26    pub name: String,
27    pub data: AtcMapData
28}
29
30impl AtcMap {
31    pub fn try_from_es_line_group(sector_file_id: String, item_type: String, value: LineGroup<ColouredLine>) -> anyhow::Result<Self> {
32        let name = format!("{}_{}_{}", sector_file_id, item_type, value.name);
33        let mut features = Vec::with_capacity(value.lines.len());
34        for line in value.lines {
35            // Properties
36            let mut props_map = Map::new();
37            props_map.insert("itemType".to_string(), serde_json::to_value(&item_type)?);
38            if let Some(line_color) = line.colour {
39                props_map.insert(
40                    "color".to_string(),
41                    serde_json::to_value(format!("#{:02X}{:02X}{:02X}", line_color.r, line_color.g, line_color.b))?,
42                );
43            }
44
45            features.push(Feature {
46                id: None,
47                bbox: None,
48                foreign_members: None,
49                geometry: Some(Geometry::new(Value::LineString(vec![
50                    vec![line.line.start.lon, line.line.start.lat],
51                    vec![line.line.end.lon, line.line.end.lat],
52                ]))),
53                properties: Some(props_map),
54            });
55        }
56
57        Ok(AtcMap {
58            name: name,
59            data: AtcMapData::Embedded { 
60                features: FeatureCollection {
61                    bbox: None,
62                    features: features,
63                    foreign_members: None,
64                }
65            }
66        })
67    }
68
69    pub fn try_from_es_region_group(sector_file_id: String, item_type: String, value: RegionGroup) -> anyhow::Result<Self> {
70        let name = format!("{}_{}_{}", sector_file_id, item_type, value.name);
71        let mut features = Vec::with_capacity(value.regions.capacity());
72        for region in value.regions {
73            // Properties
74            let mut props_map = Map::new();
75            props_map.insert("itemType".to_string(), serde_json::to_value(&item_type)?);
76            props_map.insert(
77                "color".to_string(),
78                serde_json::to_value(format!("#{:02X}{:02X}{:02X}", region.colour.r, region.colour.g, region.colour.b))?,
79            );
80
81            let mut points = region.vertices.iter().map(|vert| vec![vert.lon, vert.lat]).collect::<Vec<Vec<f64>>>();
82            if let Some(start_pt) = points.get(0) {
83                points.push(start_pt.clone());
84            }
85
86            features.push(Feature {
87                id: None,
88                bbox: None,
89                foreign_members: None,
90                geometry: Some(Geometry::new(Value::Polygon(vec![points]))),
91                properties: Some(props_map),
92            });
93        }
94
95        Ok(AtcMap {
96            name: name,
97            data: AtcMapData::Embedded { 
98                features: FeatureCollection {
99                    bbox: None,
100                    features: features,
101                    foreign_members: None,
102                }
103            }
104        })
105    }
106
107    pub fn try_from_es_labels_group(sector_file_id: String, item_type: String, value: LabelGroup) -> anyhow::Result<Self> {
108        let name = format!("{}_{}_{}", sector_file_id, item_type, value.name);
109        let mut features = Vec::with_capacity(value.labels.capacity());
110        for label in value.labels {
111            // Properties
112            let mut props_map = Map::new();
113            props_map.insert("itemType".to_string(), serde_json::to_value(&item_type)?);
114            props_map.insert(
115                "textColor".to_string(),
116                serde_json::to_value(format!("#{:02X}{:02X}{:02X}", label.colour.r, label.colour.g, label.colour.b))?,
117            );
118            props_map.insert("text".to_string(), serde_json::to_value(label.name.to_string())?);
119            props_map.insert("showText".to_string(), serde_json::to_value(true)?);
120
121            features.push(Feature {
122                id: None,
123                bbox: None,
124                foreign_members: None,
125                geometry: Some(Geometry::new(Value::Point(vec![label.position.lon, label.position.lat]))),
126                properties: Some(props_map),
127            });
128        }
129
130        Ok(AtcMap {
131            name: name,
132            data: AtcMapData::Embedded { 
133                features: FeatureCollection {
134                    bbox: None,
135                    features: features,
136                    foreign_members: None,
137                }
138            }
139        })
140    }
141
142    pub fn try_from_es_freetext_group(sector_file_id: String, item_type: String, value: FreeTextGroup) -> anyhow::Result<Self> {
143        let name = format!("{}_{}_{}", sector_file_id, item_type, value.name);
144        let mut features = Vec::with_capacity(value.entries.capacity());
145        for label in value.entries {
146            // Properties
147            let mut props_map = Map::new();
148            props_map.insert("itemType".to_string(), serde_json::to_value(&item_type)?);
149            props_map.insert("text".to_string(), serde_json::to_value(label.text.to_string())?);
150            props_map.insert("showText".to_string(), serde_json::to_value(true)?);
151
152            features.push(Feature {
153                id: None,
154                bbox: None,
155                foreign_members: None,
156                geometry: Some(Geometry::new(Value::Point(vec![label.position.lon, label.position.lat]))),
157                properties: Some(props_map),
158            });
159        }
160
161        Ok(AtcMap {
162            name: name,
163            data: AtcMapData::Embedded { 
164                features: FeatureCollection {
165                    bbox: None,
166                    features: features,
167                    foreign_members: None,
168                }
169            }
170        })
171    }
172
173    pub fn try_from_crc_video_map(map_ref: &CrcVideoMapRef, facility_file_path: impl AsRef<Path>, facility_name: String) -> anyhow::Result<AtcMap> {
174        // Determine path
175        let video_map_path = facility_file_path
176            .as_ref()
177            .join("..")
178            .join("..")
179            .join("VideoMaps")
180            .join(facility_name.to_string())
181            .join(format!("{}.geojson", map_ref.id))
182            .canonicalize()?;
183
184        let geojson = GeoJson::from_reader(BufReader::new(File::open(&video_map_path).context(format!(
185            "Couldn't open video map at path {}",
186            &video_map_path.to_str().unwrap_or_default().to_string()
187        ))?))
188        .context("Couldn't parse GeoJSON")?;
189
190        if let GeoJson::FeatureCollection(mut features) = geojson {
191            let mut new_features = Vec::new();
192            let mut default_line_style = "solid".to_string();
193            let mut default_line_thickness = 1;
194            let mut default_text_opaque = false;
195            let mut default_text_size = 1;
196            let mut default_text_underline = false;
197            let mut default_text_offset = (0, 0);
198            let mut default_symbol_style = "".to_string();
199            let mut default_symbol_size = 1;
200
201            for feature in &features {
202                let properties = feature.properties.clone().unwrap_or_default();
203                // Handle Defaults
204                if properties
205                    .get(&"isLineDefaults".to_string())
206                    .unwrap_or(&serde_json::Value::Bool(false))
207                    .as_bool()
208                    .unwrap_or(false)
209                {
210                    if let Some(value) = properties.get(&"style".to_string()) {
211                        default_line_style = value.as_str().unwrap_or_default().to_lowercase();
212                    }
213                    if let Some(value) = properties.get(&"thickness".to_string()) {
214                        default_line_thickness = value.as_i64().unwrap_or(1) as i32;
215                    }
216                } else if properties
217                    .get(&"isTextDefaults".to_string())
218                    .unwrap_or(&serde_json::Value::Bool(false))
219                    .as_bool()
220                    .unwrap_or(false)
221                {
222                    if let Some(value) = properties.get(&"size".to_string()) {
223                        default_text_size = value.as_i64().unwrap_or(1) as i32;
224                    }
225                    if let Some(value) = properties.get(&"xOffset".to_string()) {
226                        default_text_offset.0 = value.as_i64().unwrap_or(0) as i32;
227                    }
228                    if let Some(value) = properties.get(&"yOffset".to_string()) {
229                        default_text_offset.1 = value.as_i64().unwrap_or(0) as i32;
230                    }
231                    if let Some(value) = properties.get(&"opaque".to_string()) {
232                        default_text_opaque = value.as_bool().unwrap_or(false);
233                    }
234                    if let Some(value) = properties.get(&"underline".to_string()) {
235                        default_text_underline = value.as_bool().unwrap_or(false);
236                    }
237                } else if properties
238                    .get(&"isSymbolDefaults".to_string())
239                    .unwrap_or(&serde_json::Value::Bool(false))
240                    .as_bool()
241                    .unwrap_or(false)
242                {
243                    if let Some(value) = properties.get(&"style".to_string()) {
244                        default_symbol_style = value.as_str().unwrap_or_default().to_lowercase();
245                    }
246                    if let Some(value) = properties.get(&"size".to_string()) {
247                        default_symbol_size = value.as_i64().unwrap_or(1) as i32;
248                    }
249                } else if let Some(geometry) = &feature.geometry {
250                    let mut new_props = serde_json::Map::new();
251
252                    // Set color
253                    if let Some(color) = properties.get(&"color".to_string()) {
254                        new_props.insert("color".to_string(), color.clone());
255                    }
256
257                    if let Some(z_index) = properties.get(&"zIndex".to_string()) {
258                        new_props.insert("zIndex".to_string(), z_index.clone());
259                    }
260
261                    match geometry.value.type_name() {
262                        "Point" => {
263                            // Check for text
264                            if let Some(text) = properties.get(&"text".to_string()) {
265                                new_props.insert(
266                                    "size".to_string(),
267                                    properties
268                                        .get(&"size".to_string())
269                                        .cloned()
270                                        .unwrap_or(serde_json::to_value(default_text_size)?),
271                                );
272                                new_props.insert(
273                                    "opaque".to_string(),
274                                    properties
275                                        .get(&"opaque".to_string())
276                                        .cloned()
277                                        .unwrap_or(serde_json::to_value(default_text_opaque)?),
278                                );
279                                new_props.insert(
280                                    "underline".to_string(),
281                                    properties
282                                        .get(&"underline".to_string())
283                                        .cloned()
284                                        .unwrap_or(serde_json::to_value(default_text_underline)?),
285                                );
286                                new_props.insert(
287                                    "size".to_string(),
288                                    properties
289                                        .get(&"size".to_string())
290                                        .cloned()
291                                        .unwrap_or(serde_json::to_value(default_text_size)?),
292                                );
293                                let mut text_str = "".to_string();
294                                for line in (&text).as_array().unwrap_or(&Vec::new()) {
295                                    if text_str == "" {
296                                        text_str = line.as_str().unwrap_or_default().to_string();
297                                    } else {
298                                        text_str = format!("{}\n{}", &text_str, line.as_str().unwrap_or_default());
299                                    }
300                                }
301                                new_props.insert("text".to_string(), serde_json::to_value(&text_str)?);
302                                new_props.insert("showText".to_string(), serde_json::to_value(true)?);
303                            } else {
304                                new_props.insert(
305                                    "style".to_string(),
306                                    properties
307                                        .get(&"style".to_string())
308                                        .cloned()
309                                        .unwrap_or(serde_json::to_value(default_symbol_style.to_string())?),
310                                );
311                                new_props.insert(
312                                    "size".to_string(),
313                                    properties
314                                        .get(&"size".to_string())
315                                        .cloned()
316                                        .unwrap_or(serde_json::to_value(default_symbol_size)?),
317                                );
318                                new_props.insert("showSymbol".to_string(), serde_json::to_value(true)?);
319                            }
320                        }
321                        "LineString" => {
322                            new_props.insert(
323                                "style".to_string(),
324                                properties
325                                    .get(&"style".to_string())
326                                    .cloned()
327                                    .unwrap_or(serde_json::to_value(default_line_style.to_string())?),
328                            );
329
330                            new_props.insert(
331                                "thickness".to_string(),
332                                properties
333                                    .get(&"thickness".to_string())
334                                    .cloned()
335                                    .unwrap_or(serde_json::to_value(&default_line_thickness)?),
336                            );
337                        }
338                        &_ => {}
339                    };
340
341                    if let Some(asdex_item_type) = properties.get(&"asdex".to_string()) {
342                        new_props.insert("itemType".to_string(), asdex_item_type.clone());
343                        new_props.remove(&"color".to_string());
344                    } else {
345                        new_props.insert(
346                            "itemType".to_string(),
347                            serde_json::to_value(format!("stars-{}", &map_ref.stars_brightness_category))?,
348                        );
349                    }
350
351                    new_features.push(Feature {
352                        bbox: feature.bbox.clone(),
353                        geometry: feature.geometry.clone(),
354                        id: feature.id.clone(),
355                        properties: Some(new_props),
356                        foreign_members: feature.foreign_members.clone(),
357                    });
358                }
359            }
360
361            return Ok(AtcMap {
362                name: map_ref.name.to_string(),
363                data: AtcMapData::Embedded { 
364                    features: FeatureCollection {
365                        bbox: None,
366                        features: new_features,
367                        foreign_members: None,
368                    }
369                }
370            });
371        }
372
373        Err(anyhow!("No Features found in GeoJSON!"))
374    }
375}