sct_reader/package/
display.rs

1use std::collections::HashMap;
2use std::ops::Deref;
3use aviation_calc_util::{geo::{Bearing, GeoPoint}, units::{Angle, Length}};
4use geojson::{Feature, FeatureCollection, Geometry, Value};
5use serde::{Deserialize, Serialize};
6use serde_json::Map;
7
8use crate::loaders::euroscope::{colour::Colour, line::{ColouredLine, LineGroup}, sector::RegionGroup, symbology::{self, SymbologyInfo, SymbologyItemType}, EsAsr};
9use crate::loaders::euroscope::partial::SidStarType::Star;
10use crate::loaders::vnas_crc::CrcVideoMapRef;
11use crate::loaders::vnas_crc::eram::EramConfig;
12use crate::loaders::vnas_crc::stars::{StarsArea, StarsConfiguration};
13use crate::loaders::vnas_crc::tower::TowerCabConfig;
14use super::symbol::SymbolIcon;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub enum AtcDisplayItem {
18    Map{id: String, visible: bool},
19    Symbol{id: String, show_symbol: bool, show_label: bool},
20    NavdataItem{symbol_type: String, ident: String, show_symbol: bool, show_label: bool},
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, Default)]
24#[repr(u8)]
25pub enum TextAlign {
26    #[default]
27    TopLeft = 0,
28    CenterLeft = 1,
29    BottomLeft = 2,
30    TopCenter = 3,
31    CenterCenter = 4,
32    BottomCenter = 5,
33    TopRight = 6,
34    CenterRight = 7,
35    BottomRight = 8
36}
37
38impl From<u8> for TextAlign {
39    fn from(value: u8) -> Self {
40        match value {
41            1 => Self::CenterRight,
42            2 => Self::BottomLeft,
43            3 => Self::TopCenter,
44            4 => Self::CenterCenter,
45            5 => Self::BottomCenter,
46            6 => Self::TopRight,
47            7 => Self::CenterRight,
48            8 => Self::BottomRight,
49            _ => Self::TopLeft
50        }
51    }
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
55pub struct DisplayDefaultConfig {
56    pub color: Colour,
57    pub size: f32,
58    pub line_weight: u8,
59    pub line_style: String,
60    pub text_align: TextAlign,
61}
62
63#[derive(Debug, Clone, Default, Serialize, Deserialize)]
64pub enum AtcDisplayBackground {
65    #[default]
66    Blank,
67    Satellite,
68    Color(String),
69}
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72pub struct AtcDisplayType {
73    pub id: String,
74    pub map_defaults: HashMap<String, DisplayDefaultConfig>,
75    pub symbol_defaults:  HashMap<String, (DisplayDefaultConfig, DisplayDefaultConfig)>,
76    pub symbol_icons: HashMap<String, SymbolIcon>,
77    pub line_types: HashMap<String, Vec<u8>>,
78    pub background: AtcDisplayBackground
79}
80
81impl AtcDisplayType {
82    pub fn try_from_es_symbology(id: String, symbology: SymbologyInfo) -> anyhow::Result<Self> {
83        let mut map_defaults = HashMap::new();
84        let mut symbol_defaults = HashMap::new();
85        let mut symbol_icons = HashMap::new();
86        let mut background = AtcDisplayBackground::Blank;
87
88        for symbol in symbology.symbols {
89            if matches!(symbol.item_type, SymbologyItemType::Airports | SymbologyItemType::Fixes | SymbologyItemType::Vors | SymbologyItemType::Ndbs) {
90                let mut symb_cfg = DisplayDefaultConfig::default();
91                let mut name_cfg = DisplayDefaultConfig::default();
92                for attr in symbol.defs {
93                    if attr.attribute == "name" {
94                        name_cfg.color = attr.color;
95                        name_cfg.line_style = Self::es_line_type_to_string(attr.line_style);
96                        name_cfg.line_weight = attr.line_weight;
97                        name_cfg.size = attr.size;
98                        name_cfg.text_align = attr.text_align.into();
99                    } else {
100                        symb_cfg.color = attr.color;
101                        symb_cfg.line_style = Self::es_line_type_to_string(attr.line_style);
102                        symb_cfg.line_weight = attr.line_weight;
103                        symb_cfg.size = attr.size;
104                        symb_cfg.text_align = attr.text_align.into();
105                    }
106                }
107                symbol_defaults.insert(symbol.item_type.to_key_string(), (symb_cfg, name_cfg));
108            } else if matches!(symbol.item_type, SymbologyItemType::ArtccBoundary | SymbologyItemType::ArtccHighBoundary | SymbologyItemType::ArtccLowBoundary | SymbologyItemType::Geo | SymbologyItemType::LowAirways | SymbologyItemType::HighAirways | SymbologyItemType::Region | SymbologyItemType::Sids | SymbologyItemType::Stars) {
109                let mut cfg = DisplayDefaultConfig::default();
110
111                for attr in symbol.defs {
112                    if attr.attribute == "line" {
113                        cfg.color = attr.color;
114                        cfg.line_style = Self::es_line_type_to_string(attr.line_style);
115                        cfg.line_weight = attr.line_weight;
116                        cfg.size = attr.size;
117                        cfg.text_align = attr.text_align.into();
118                    }
119                }
120
121                map_defaults.insert(symbol.item_type.to_key_string(), cfg);
122            } else if symbol.item_type == SymbologyItemType::Sector {
123                for attr in symbol.defs {
124                    if attr.attribute == "active sector background" {
125                        background = AtcDisplayBackground::Color(format!("#{:02X}{:02X}{:02X}", attr.color.r, attr.color.g, attr.color.b));
126                    }
127                }
128            }
129        }
130
131        for icon in symbology.symbol_icons {
132            let ret_icon = SymbolIcon::try_from_es_symbol_icon(icon.0, icon.1)?;
133
134            symbol_icons.insert(ret_icon.symbol_type.to_string(), ret_icon);
135        }
136
137        Ok(AtcDisplayType {
138            id: id.to_string(),
139            map_defaults,
140            symbol_defaults,
141            symbol_icons,
142            line_types: Self::get_es_line_types(),
143            background: background
144        })
145    }
146
147    fn get_es_line_types() -> HashMap<String, Vec<u8>> {
148        HashMap::from([
149            ("solid".to_string(), vec![1]),
150            ("dash".to_string(), vec![18, 6]),
151            ("dot".to_string(), vec![3, 3]),
152            ("dash-dot".to_string(), vec![9, 6, 3, 6]),
153            ("dash-dot-dot".to_string(), vec![9, 3, 3, 3, 3, 3])
154        ])
155    }
156
157    fn es_line_type_to_string(input: u8) -> String {
158        match input {
159            1 => "dash".to_string(),
160            2 => "dot".to_string(),
161            3 => "dash-dot".to_string(),
162            4 => "dash-dot-dot".to_string(),
163            _ => "solid".to_string()
164        }
165    }
166}
167
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
169pub struct AtcDisplay {
170    pub name: String,
171    pub center: GeoPoint,
172    pub screen_height: Length,
173    pub rotation: Angle,
174    pub display_items: Vec<AtcDisplayItem>,
175    pub display_type: String    
176}
177
178impl AtcDisplay {
179    pub fn from_es_asr(default_sector_id: String, display_type: String, value: EsAsr) -> Self {
180        let mut ret_val = AtcDisplay::default();
181        ret_val.name = value.name;
182        ret_val.display_type = display_type;
183
184        // Center
185        let dist = (value.window_area.1 - value.window_area.0) / 2;
186        let bearing = GeoPoint::initial_bearing(&value.window_area.0, &value.window_area.1);
187        let mut center = value.window_area.0.clone();
188        center.move_by(bearing, dist);
189        ret_val.center = center;
190
191        // Screen Height
192        let theta = (Bearing::from_radians(0_f64) - bearing) + value.display_rotation;
193        ret_val.screen_height = dist * 2 * theta.as_radians().cos().abs();
194        ret_val.rotation = value.display_rotation;
195
196        let mut items = Vec::new();
197        let mut symbols_map = HashMap::<String, usize>::new();
198
199        let sector_id = value.sector_file_id.clone().unwrap_or(default_sector_id.to_string());
200
201        let mut loaded_freetexts= HashMap::new();
202
203        for item in value.display_items {
204            if matches!(item.item_type, SymbologyItemType::Airports | SymbologyItemType::Fixes | SymbologyItemType::Ndbs | SymbologyItemType::Vors) {
205                let ident = format!("{}_{}_{}", sector_id.to_string(), item.item_type.to_key_string(), item.name);
206
207                let symbol_opt = match symbols_map.get_mut(&ident) {
208                    Some(symb_index) => items.get_mut(*symb_index),
209                    None => {
210                        let symb_index = items.len();
211                        symbols_map.insert(ident.to_string(), symb_index);
212                        items.push(AtcDisplayItem::Symbol { id: ident.to_string(), show_label: false, show_symbol: false});
213                        items.get_mut(symb_index)
214                    }
215                };
216                if let Some(symbol) = symbol_opt {
217                    if let AtcDisplayItem::Symbol { id, show_symbol, show_label } = symbol {
218                        if item.attribute == "symbol" {
219                            *show_symbol = true;
220                        } else if item.attribute == "name" {
221                            *show_label = true;
222                        }
223                    }                    
224                }
225            } else if matches!(item.item_type, SymbologyItemType::ArtccBoundary | SymbologyItemType::ArtccHighBoundary | SymbologyItemType::ArtccLowBoundary | SymbologyItemType::Geo | SymbologyItemType::HighAirways | SymbologyItemType::LowAirways | SymbologyItemType::Region | SymbologyItemType::Sids | SymbologyItemType::Stars) {
226                items.push(AtcDisplayItem::Map { id: format!("{}_{}_{}", sector_id.to_string(), item.item_type.to_key_string(), item.name), visible: true })
227            } else if matches!(item.item_type, SymbologyItemType::Label) {
228                if item.attribute == "freetext" {
229                    let name_split = item.name.split("\\").collect::<Vec<&str>>();
230                    if name_split.len() >= 1 {
231                        if !loaded_freetexts.contains_key(&name_split[0].to_string()) {
232                            items.push(AtcDisplayItem::Map {id: format!("{}_{}_{}", sector_id.to_string(), item.item_type.to_key_string(), name_split[0].to_string()), visible: true});
233                            loaded_freetexts.insert(name_split[0].to_string(), ());
234                        }
235                    }
236                }
237            }
238        }
239
240        ret_val.display_items = items;
241
242        ret_val
243    }
244
245    pub fn from_crc_twr_asdex(display_type: String, display_name: String, twr_cfg: &TowerCabConfig) -> AtcDisplay {
246        AtcDisplay {
247            name: display_name.to_string(),
248            display_type: display_type.to_string(),
249            center: twr_cfg.tower_location.unwrap_or_default(),
250            screen_height: Length::from_feet(f64::from(twr_cfg.default_zoom_range) * 200_f64),
251            rotation: Angle::from_degrees(twr_cfg.default_rotation.into()),
252            display_items: vec![AtcDisplayItem::Map {id: twr_cfg.video_map_id.to_string(), visible: true}]
253        }
254    }
255
256    fn get_tdm_maps_from_crc(video_map_ids: &Vec<String>, map_refs: &HashMap<String, CrcVideoMapRef>) -> (Vec<AtcDisplayItem>, Vec<AtcDisplayItem>) {
257        let mut display_items = Vec::new();
258        let mut display_items_tdm = Vec::new();
259
260        for video_map_id in video_map_ids {
261            // Check for TDM
262            if let Some(map_ref) = map_refs.get(video_map_id) {                
263                display_items_tdm.push(AtcDisplayItem::Map {id: video_map_id.to_string(), visible: map_ref.stars_always_visible});
264                if !map_ref.tdm_only {
265                    display_items.push(AtcDisplayItem::Map {id: video_map_id.to_string(), visible: map_ref.stars_always_visible});
266                }
267            }
268        }
269
270        (display_items, display_items_tdm)
271    }
272
273    pub fn from_crc_stars(stars_cfg: &StarsConfiguration, map_refs: &HashMap<String, CrcVideoMapRef>) -> Vec<AtcDisplay> {
274        let default_area = StarsArea::default();
275        let area = stars_cfg.areas.get(0).unwrap_or(&default_area);
276        let display_items = Self::get_tdm_maps_from_crc(&stars_cfg.video_map_ids, map_refs);
277
278        vec![
279            AtcDisplay {
280                name: "STARS".to_string(),
281                display_type: "stars".to_string(),
282                center: area.visibility_center,
283                screen_height: Length::from_nautical_miles(f64::from(area.surveillance_range) * 2_f64),
284                rotation: Angle::from_radians(0_f64),
285                display_items: display_items.0
286            },
287            AtcDisplay {
288                name: "STARS (Top Down Mode)".to_string(),
289                display_type: "stars".to_string(),
290                center: area.visibility_center,
291                screen_height: Length::from_nautical_miles(f64::from(area.surveillance_range) * 2_f64),
292                rotation: Angle::from_radians(0_f64),
293                display_items: display_items.1
294            }
295        ]
296    }
297
298    pub fn from_crc_eram(eram_cfg: &EramConfig, map_refs: &HashMap<String, CrcVideoMapRef>) -> Vec<AtcDisplay> {
299        let mut displays = Vec::new();
300        for geo_map in &eram_cfg.geo_maps {
301            let display_items = Self::get_tdm_maps_from_crc(&geo_map.video_map_ids, map_refs);
302
303            displays.push(AtcDisplay {
304                name: format!("ERAM {}", geo_map.name),
305                display_type: "eram".to_string(),
306                center: GeoPoint::default(),
307                screen_height: Length::from_nautical_miles(600_f64),
308                rotation: Angle::from_radians(0_f64),
309                display_items: display_items.0
310            });
311            displays.push(AtcDisplay {
312                name: format!("ERAM {} (Top Down Mode)", geo_map.name),
313                display_type: "eram".to_string(),
314                center: GeoPoint::default(),
315                screen_height: Length::from_nautical_miles(600_f64),
316                rotation: Angle::from_radians(0_f64),
317                display_items: display_items.1
318            });
319        }
320
321        displays
322    }
323}