sct_reader/package/
mod.rs

1use anyhow::Context;
2use aviation_calc_util::{
3    geo::{Bearing, GeoPoint},
4    units::{Angle, Length},
5};
6use display::{AtcDisplay, AtcDisplayBackground, AtcDisplayType, DisplayDefaultConfig};
7use flate2::{read::GzDecoder, write::GzEncoder, Compression, GzBuilder};
8use geojson::{Feature, FeatureCollection, GeoJson, Geometry, Value};
9use map::{AtcMap, AtcMapData};
10use serde::{Deserialize, Serialize};
11use serde_json::Map;
12use tar::{Archive, Header};
13use uuid::Uuid;
14use std::{collections::HashMap, env, fmt::format, fs::{self, File}, hash::Hash, io::{BufReader, BufWriter, Read, Write}, path::{Path, PathBuf}};
15use symbol::{AtcMapSymbol, SymbolDrawItem, SymbolIcon};
16
17use crate::loaders::euroscope::{
18    colour::Colour,
19    line::{ColouredLine, LineGroup},
20    loader::EuroScopeResult,
21    sector::{LabelGroup, RegionGroup},
22    symbology::SymbologyItemType,
23    EsAsr,
24};
25
26mod facility;
27use crate::loaders::vnas_crc::{CrcPackage, CrcVideoMapRef};
28use crate::package::display::AtcDisplayItem;
29pub use facility::AtcFacility;
30
31pub mod display;
32pub mod map;
33pub mod position;
34pub mod symbol;
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37pub struct AtcScopePackage {
38    pub facilities: Vec<AtcFacility>,
39    pub maps: HashMap<String, AtcMap>,
40    pub symbols: HashMap<String, AtcMapSymbol>,
41    pub display_types: HashMap<String, AtcDisplayType>,
42}
43
44impl TryFrom<EuroScopeResult> for AtcScopePackage {
45    type Error = anyhow::Error;
46
47    fn try_from(value: EuroScopeResult) -> anyhow::Result<Self> {
48        let mut maps = HashMap::new();
49        let mut symbols = HashMap::new();
50        let mut display_types = HashMap::new();
51        let mut facilities = Vec::new();
52
53        // Parse "maps"
54        for sector in value.sectors {
55            // Geo
56            for geo in sector.1 .0.geo_entries {
57                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::Geo.to_key_string(), geo)?;
58
59                maps.insert(val.name.to_string(), val);
60            }
61
62            // ARTCC
63            for entry in sector.1 .0.artcc_entries {
64                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::ArtccBoundary.to_key_string(), entry)?;
65
66                maps.insert(val.name.to_string(), val);
67            }
68
69            // ARTCC Low
70            for entry in sector.1 .0.artcc_low_entries {
71                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::ArtccLowBoundary.to_key_string(), entry)?;
72
73                maps.insert(val.name.to_string(), val);
74            }
75
76            // ARTCC High
77            for entry in sector.1 .0.artcc_high_entries {
78                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::ArtccHighBoundary.to_key_string(), entry)?;
79
80                maps.insert(val.name.to_string(), val);
81            }
82
83            // Low Airways
84            for entry in sector.1 .0.low_airways {
85                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::LowAirways.to_key_string(), entry)?;
86
87                maps.insert(val.name.to_string(), val);
88            }
89
90            // High Airways
91            for entry in sector.1 .0.high_airways {
92                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::HighAirways.to_key_string(), entry)?;
93
94                maps.insert(val.name.to_string(), val);
95            }
96
97            // SIDs
98            for entry in sector.1 .0.sid_entries {
99                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::Sids.to_key_string(), entry)?;
100
101                maps.insert(val.name.to_string(), val);
102            }
103
104            // STARs
105            for entry in sector.1 .0.star_entries {
106                let val = AtcMap::try_from_es_line_group(sector.0.to_string(), SymbologyItemType::Stars.to_key_string(), entry)?;
107
108                maps.insert(val.name.to_string(), val);
109            }
110
111            // Regions
112            for entry in sector.1 .0.regions {
113                let val = AtcMap::try_from_es_region_group(sector.0.to_string(), SymbologyItemType::Region.to_key_string(), entry)?;
114
115                maps.insert(val.name.to_string(), val);
116            }
117
118            // Labels
119            for entry in sector.1 .0.labels {
120                let val = AtcMap::try_from_es_labels_group(sector.0.to_string(), SymbologyItemType::Label.to_key_string(), entry)?;
121
122                maps.insert(val.name.to_string(), val);
123            }
124
125            // ESE
126            if let Some(ese_file) = sector.1 .1 {
127                for entry in ese_file.free_text {
128                    let val = AtcMap::try_from_es_freetext_group(sector.0.to_string(), SymbologyItemType::Label.to_key_string(), entry)?;
129
130                    maps.insert(val.name.to_string(), val);
131                }
132            }
133
134            // Airports
135            for entry in sector.1 .0.airports {
136                let val = AtcMapSymbol::try_from_es_position(
137                    sector.0.to_string(),
138                    SymbologyItemType::Airports.to_key_string(),
139                    entry.identifier,
140                    entry.position,
141                )?;
142                symbols.insert(val.name.to_string(), val);
143            }
144
145            // Fixes
146            for entry in sector.1 .0.fixes {
147                let val = AtcMapSymbol::try_from_es_position(
148                    sector.0.to_string(),
149                    SymbologyItemType::Fixes.to_key_string(),
150                    entry.identifier,
151                    entry.position,
152                )?;
153                symbols.insert(val.name.to_string(), val);
154            }
155
156            // VORs
157            for entry in sector.1 .0.vors {
158                let val = AtcMapSymbol::try_from_es_position(
159                    sector.0.to_string(),
160                    SymbologyItemType::Vors.to_key_string(),
161                    entry.identifier,
162                    entry.position,
163                )?;
164                symbols.insert(val.name.to_string(), val);
165            }
166
167            // NDBs
168            for entry in sector.1 .0.ndbs {
169                let val = AtcMapSymbol::try_from_es_position(
170                    sector.0.to_string(),
171                    SymbologyItemType::Ndbs.to_key_string(),
172                    entry.identifier,
173                    entry.position,
174                )?;
175                symbols.insert(val.name.to_string(), val);
176            }
177        }
178
179        for prf in value.profiles {
180            let mut facility = AtcFacility::default();
181            facility.name = prf.prf_name;
182
183            // Parse symbology
184            display_types.insert(
185                prf.prf_file.to_string(),
186                AtcDisplayType::try_from_es_symbology(prf.prf_file.to_string(), prf.symbology)?,
187            );
188
189            // Parse ASRs
190            for asr in prf.asrs {
191                let mut disp = AtcDisplay::from_es_asr(prf.default_sector_id.to_string(), prf.prf_file.to_string(), asr.1);
192                facility.displays.push(disp);
193            }
194
195            facilities.push(facility);
196        }
197
198        Ok(AtcScopePackage {
199            facilities: facilities,
200            symbols: symbols,
201            maps: maps,
202            display_types,
203        })
204    }
205}
206
207impl TryFrom<&CrcPackage> for AtcScopePackage {
208    type Error = anyhow::Error;
209
210    fn try_from(value: &CrcPackage) -> Result<Self, Self::Error> {
211        let mut package = AtcScopePackage::default();
212        let mut maps_map: HashMap<String, CrcVideoMapRef> = HashMap::new();
213
214        // Process maps
215        for map_ref in &value.video_maps {
216            package.maps.insert(
217                map_ref.id.to_string(),
218                AtcMap::try_from_crc_video_map(map_ref, &value.file_path, value.id.to_string())?,
219            );
220            maps_map.insert(map_ref.id.to_string(), map_ref.clone());
221        }
222
223        // Process facility
224        package.facilities.push(AtcFacility::try_from_crc(&value.facility, &maps_map)?);
225
226        // ERAM Symbols
227        package.display_types.insert(
228            "eram".to_string(),
229            AtcDisplayType {
230                id: "eram".to_string(),
231                map_defaults: Default::default(),
232                symbol_defaults: Default::default(),
233                symbol_icons: Self::get_eram_symbols(),
234                line_types: Self::get_eram_lines(),
235                background: AtcDisplayBackground::Color("#000000".to_string()),
236            },
237        );
238
239        package.display_types.insert(
240            "stars".to_string(),
241            AtcDisplayType {
242                id: "stars".to_string(),
243                map_defaults: Default::default(),
244                symbol_defaults: Default::default(),
245                symbol_icons: Default::default(),
246                line_types: Default::default(),
247                background: AtcDisplayBackground::Color("#000000".to_string()),
248            },
249        );
250
251        package.display_types.insert(
252            "asdex-day".to_string(),
253            AtcDisplayType {
254                id: "asdex-day".to_string(),
255                map_defaults: HashMap::from([
256                    (
257                        "taxiway".to_string(),
258                        DisplayDefaultConfig {
259                            color: Colour { r: 45, g: 45, b: 45 },
260                            ..Default::default()
261                        },
262                    ),
263                    (
264                        "apron".to_string(),
265                        DisplayDefaultConfig {
266                            color: Colour { r: 70, g: 70, b: 70 },
267                            ..Default::default()
268                        },
269                    ),
270                    (
271                        "structure".to_string(),
272                        DisplayDefaultConfig {
273                            color: Colour { r: 96, g: 96, b: 96 },
274                            ..Default::default()
275                        },
276                    ),
277                    (
278                        "runway".to_string(),
279                        DisplayDefaultConfig {
280                            color: Colour { r: 0, g: 0, b: 0 },
281                            ..Default::default()
282                        },
283                    ),
284                ]),
285                symbol_defaults: Default::default(),
286                symbol_icons: Default::default(),
287                line_types: Default::default(),
288                background: AtcDisplayBackground::Color("#005C73".to_string()),
289            },
290        );
291
292        package.display_types.insert(
293            "asdex-night".to_string(),
294            AtcDisplayType {
295                id: "asdex-night".to_string(),
296                map_defaults: HashMap::from([
297                    (
298                        "taxiway".to_string(),
299                        DisplayDefaultConfig {
300                            color: Colour { r: 16, g: 37, b: 76 },
301                            ..Default::default()
302                        },
303                    ),
304                    (
305                        "apron".to_string(),
306                        DisplayDefaultConfig {
307                            color: Colour { r: 17, g: 52, b: 93 },
308                            ..Default::default()
309                        },
310                    ),
311                    (
312                        "structure".to_string(),
313                        DisplayDefaultConfig {
314                            color: Colour { r: 32, g: 60, b: 98 },
315                            ..Default::default()
316                        },
317                    ),
318                    (
319                        "runway".to_string(),
320                        DisplayDefaultConfig {
321                            color: Colour { r: 0, g: 0, b: 0 },
322                            ..Default::default()
323                        },
324                    ),
325                ]),
326                symbol_defaults: Default::default(),
327                symbol_icons: Default::default(),
328                line_types: Default::default(),
329                background: AtcDisplayBackground::Color("#393939".to_string()),
330            },
331        );
332
333        package.display_types.insert(
334            "twrcab".to_string(),
335            AtcDisplayType {
336                id: "twrcab".to_string(),
337                map_defaults: Default::default(),
338                symbol_defaults: Default::default(),
339                symbol_icons: Default::default(),
340                line_types: Default::default(),
341                background: display::AtcDisplayBackground::Satellite,
342            },
343        );
344
345        Ok(package)
346    }
347}
348
349impl AtcScopePackage {
350    fn get_eram_symbols() -> HashMap<String, SymbolIcon> {
351        HashMap::from([
352            (
353                "vor".to_string(),
354                SymbolIcon {
355                    symbol_type: "vor".to_string(),
356                    draw_items: vec![SymbolDrawItem::Ellipse {
357                        center: (0, 0),
358                        radius: (3, 5),
359                        inner_radius: (0, 0),
360                        rotation: 0,
361                        start_angle: 0,
362                        end_angle: 360,
363                        fill: false,
364                    }],
365                },
366            ),
367            (
368                "ndb".to_string(),
369                SymbolIcon {
370                    symbol_type: "ndb".to_string(),
371                    draw_items: vec![
372                        SymbolDrawItem::Ellipse {
373                            center: (0, 0),
374                            radius: (3, 5),
375                            inner_radius: (0, 0),
376                            rotation: 0,
377                            start_angle: 0,
378                            end_angle: 360,
379                            fill: false,
380                        },
381                        SymbolDrawItem::Line {
382                            start: (-4, -7),
383                            end: (4, 7),
384                        },
385                    ],
386                },
387            ),
388            (
389                "obstruction1".to_string(),
390                SymbolIcon {
391                    symbol_type: "obstruction1".to_string(),
392                    draw_items: vec![
393                        SymbolDrawItem::Line {
394                            start: (-3, -6),
395                            end: (3, 6),
396                        },
397                        SymbolDrawItem::Line {
398                            start: (-3, 6),
399                            end: (3, -6),
400                        },
401                        SymbolDrawItem::Line { start: (-3, 7), end: (3, 7) },
402                    ],
403                },
404            ),
405            (
406                "obstruction2".to_string(),
407                SymbolIcon {
408                    symbol_type: "obstruction2".to_string(),
409                    draw_items: vec![
410                        SymbolDrawItem::Line {
411                            start: (-5, -6),
412                            end: (0, 6),
413                        },
414                        SymbolDrawItem::Line { start: (0, 6), end: (5, -6) },
415                        SymbolDrawItem::Arc {
416                            center: (0, -4),
417                            radius: 2,
418                            inner_radius: 0,
419                            start_angle: 0,
420                            end_angle: 360,
421                            fill: true,
422                        },
423                    ],
424                },
425            ),
426            (
427                "heliport".to_string(),
428                SymbolIcon {
429                    symbol_type: "heliport".to_string(),
430                    draw_items: vec![
431                        SymbolDrawItem::Arc {
432                            center: (0, 0),
433                            radius: 6,
434                            inner_radius: 0,
435                            start_angle: 0,
436                            end_angle: 360,
437                            fill: false,
438                        },
439                        SymbolDrawItem::Line { start: (-2, 0), end: (2, 0) },
440                        SymbolDrawItem::Line {
441                            start: (-2, 2),
442                            end: (-2, -2),
443                        },
444                        SymbolDrawItem::Line { start: (2, 2), end: (2, -2) },
445                    ],
446                },
447            ),
448            (
449                "nuclear".to_string(),
450                SymbolIcon {
451                    symbol_type: "nuclear".to_string(),
452                    draw_items: vec![
453                        SymbolDrawItem::Line { start: (-1, 0), end: (1, 0) },
454                        SymbolDrawItem::Line { start: (0, 1), end: (0, -1) },
455                        SymbolDrawItem::Arc {
456                            center: (0, 0),
457                            radius: 6,
458                            inner_radius: 3,
459                            start_angle: 180,
460                            end_angle: 240,
461                            fill: true,
462                        },
463                        SymbolDrawItem::Arc {
464                            center: (0, 0),
465                            radius: 6,
466                            inner_radius: 3,
467                            start_angle: 300,
468                            end_angle: 360,
469                            fill: true,
470                        },
471                        SymbolDrawItem::Arc {
472                            center: (0, 0),
473                            radius: 6,
474                            inner_radius: 3,
475                            start_angle: 60,
476                            end_angle: 120,
477                            fill: true,
478                        },
479                    ],
480                },
481            ),
482            (
483                "emergencyairport".to_string(),
484                SymbolIcon {
485                    symbol_type: "emergencyairport".to_string(),
486                    draw_items: vec![
487                        SymbolDrawItem::Line { start: (-2, 2), end: (2, 2) },
488                        SymbolDrawItem::Line { start: (2, 2), end: (2, -2) },
489                        SymbolDrawItem::Line {
490                            start: (2, -2),
491                            end: (-2, -2),
492                        },
493                        SymbolDrawItem::Line {
494                            start: (-2, -2),
495                            end: (-2, 2),
496                        },
497                        SymbolDrawItem::Line {
498                            start: (-5, 5),
499                            end: (-2, 2),
500                        },
501                        SymbolDrawItem::Line {
502                            start: (2, -2),
503                            end: (5, -5),
504                        },
505                    ],
506                },
507            ),
508            (
509                "radar".to_string(),
510                SymbolIcon {
511                    symbol_type: "radar".to_string(),
512                    draw_items: vec![
513                        SymbolDrawItem::Line { start: (0, 3), end: (0, -3) },
514                        SymbolDrawItem::Line {
515                            start: (0, 3),
516                            end: (-4, -2),
517                        },
518                        SymbolDrawItem::Line { start: (0, -3), end: (4, 2) },
519                        SymbolDrawItem::Arc {
520                            center: (1, 1),
521                            radius: 7,
522                            inner_radius: 0,
523                            start_angle: 90,
524                            end_angle: 180,
525                            fill: false,
526                        },
527                    ],
528                },
529            ),
530            (
531                "iaf".to_string(),
532                SymbolIcon {
533                    symbol_type: "iaf".to_string(),
534                    draw_items: vec![
535                        SymbolDrawItem::Line { start: (-4, 0), end: (4, 0) },
536                        SymbolDrawItem::Line { start: (0, 4), end: (0, -4) },
537                        SymbolDrawItem::Arc {
538                            center: (0, 0),
539                            radius: 4,
540                            inner_radius: 0,
541                            start_angle: 0,
542                            end_angle: 360,
543                            fill: false,
544                        },
545                    ],
546                },
547            ),
548            (
549                "rnavonlywaypoint".to_string(),
550                SymbolIcon {
551                    symbol_type: "rnavonlywaypoint".to_string(),
552                    draw_items: vec![
553                        SymbolDrawItem::Arc {
554                            center: (0, 0),
555                            radius: 3,
556                            inner_radius: 0,
557                            start_angle: 0,
558                            end_angle: 360,
559                            fill: false,
560                        },
561                        SymbolDrawItem::Arc {
562                            center: (0, 0),
563                            radius: 5,
564                            inner_radius: 0,
565                            start_angle: 0,
566                            end_angle: 360,
567                            fill: false,
568                        },
569                    ],
570                },
571            ),
572            (
573                "rnav".to_string(),
574                SymbolIcon {
575                    symbol_type: "rnav".to_string(),
576                    draw_items: vec![
577                        SymbolDrawItem::Line { start: (-2, 0), end: (2, 0) },
578                        SymbolDrawItem::Line { start: (0, 2), end: (0, -2) },
579                        SymbolDrawItem::Line { start: (-2, 2), end: (2, 2) },
580                        SymbolDrawItem::Line { start: (2, 2), end: (2, -2) },
581                        SymbolDrawItem::Line {
582                            start: (2, -2),
583                            end: (-2, -2),
584                        },
585                        SymbolDrawItem::Line {
586                            start: (-2, -2),
587                            end: (-2, 2),
588                        },
589                    ],
590                },
591            ),
592            (
593                "airwayintersections".to_string(),
594                SymbolIcon {
595                    symbol_type: "airwayintersections".to_string(),
596                    draw_items: vec![
597                        SymbolDrawItem::Line {
598                            start: (-3, -3),
599                            end: (3, -3),
600                        },
601                        SymbolDrawItem::Line { start: (3, -3), end: (0, 3) },
602                        SymbolDrawItem::Line {
603                            start: (0, 3),
604                            end: (-3, -3),
605                        },
606                    ],
607                },
608            ),
609            (
610                "otherwaypoints".to_string(),
611                SymbolIcon {
612                    symbol_type: "otherwaypoints".to_string(),
613                    draw_items: vec![
614                        SymbolDrawItem::Line { start: (-5, 0), end: (5, 0) },
615                        SymbolDrawItem::Line { start: (0, 5), end: (0, -5) },
616                    ],
617                },
618            ),
619            (
620                "airport".to_string(),
621                SymbolIcon {
622                    symbol_type: "airport".to_string(),
623                    draw_items: vec![
624                        SymbolDrawItem::Line { start: (-2, 2), end: (2, 2) },
625                        SymbolDrawItem::Line { start: (2, 2), end: (2, -2) },
626                        SymbolDrawItem::Line {
627                            start: (2, -2),
628                            end: (-2, -2),
629                        },
630                        SymbolDrawItem::Line {
631                            start: (-2, -2),
632                            end: (-2, 2),
633                        },
634                    ],
635                },
636            ),
637            (
638                "satelliteairport".to_string(),
639                SymbolIcon {
640                    symbol_type: "satelliteairport".to_string(),
641                    draw_items: vec![
642                        SymbolDrawItem::Line {
643                            start: (-6, -4),
644                            end: (-6, 3),
645                        },
646                        SymbolDrawItem::Line { start: (-6, 3), end: (5, 3) },
647                    ],
648                },
649            ),
650            (
651                "tacan".to_string(),
652                SymbolIcon {
653                    symbol_type: "tacan".to_string(),
654                    draw_items: vec![
655                        SymbolDrawItem::Ellipse {
656                            center: (0, 0),
657                            radius: (3, 5),
658                            inner_radius: (0, 0),
659                            rotation: 0,
660                            start_angle: 0,
661                            end_angle: 360,
662                            fill: false,
663                        },
664                        SymbolDrawItem::Line { start: (-2, 0), end: (2, 0) },
665                        SymbolDrawItem::Line { start: (0, 2), end: (0, -2) },
666                    ],
667                },
668            ),
669        ])
670    }
671
672    fn get_eram_lines() -> HashMap<String, Vec<u8>> {
673        HashMap::from([
674            ("solid".to_string(), vec![1]),
675            ("shortdashed".to_string(), vec![8, 8]),
676            ("longdashed".to_string(), vec![16, 16]),
677            ("longdashshortdash".to_string(), vec![14, 6, 6, 6]),
678        ])
679    }
680
681    fn write_json_to_targz<W, T>(tar_builder: &mut tar::Builder<W>, tar_path: impl AsRef<Path>, temp_dir: impl AsRef<Path>, file_name: &str, value: &T) -> anyhow::Result<()>
682        where W: Write, T: ?Sized + Serialize {
683        // Write file
684        let map_file_path = &temp_dir.as_ref().join(&file_name);
685        let data = serde_json::to_vec(&value).context("Writing json file.")?;
686
687        // Add to TAR.GZ
688        let mut header = Header::new_gnu();
689        header.set_mode(0o755);
690        header.set_size(data.len() as u64);
691        &tar_builder.append_data(
692            &mut header,
693            &tar_path.as_ref().join(&file_name),
694            &*data)
695            .context("Creating map tar entry.")?;
696
697        Ok(())
698    }
699
700    /// Exports the entire ATC Scope Package to a file.
701    /// 
702    /// File is tarred and gzipped.
703    ///
704    /// All video maps are externalized from the main JSON file to allow lazy loading.
705    /// These video maps are GeoJSON FeatureCollections
706    ///
707    /// Format is as follows:
708    /// - `<name>.atcpkg`
709    ///   - `ScopePackage.json`
710    ///   - `maps`
711    ///     - `<videomap>.geojson`
712    ///     - ...
713    pub fn export_to_gzip(&self, file_name: impl AsRef<Path>, maps_dir: impl AsRef<Path>) -> anyhow::Result<()> {
714        // Create temp working dir
715        let temp_dir = env::temp_dir().join("sct_reader");
716        fs::create_dir_all(&temp_dir).context("Creating temp dir.")?;
717        
718        // Create tar and gz
719        let gz_file = File::create(file_name)?;
720        let gz_builder = GzBuilder::new().write(BufWriter::new(gz_file), Compression::default());
721        let mut tar_builder = tar::Builder::new(gz_builder);
722
723        // Create video maps
724        let new_maps = self.maps.iter()
725            .map(|(id, map)| {
726                match &map.data {
727                    AtcMapData::Embedded { features } => {
728                        // Generate random uuid for filename
729                        let map_uuid = Uuid::new_v4().simple().to_string();
730
731                        // Write file
732                        let map_file_name = format!("{}.geojson", &map_uuid);
733                        Self::write_json_to_targz(&mut tar_builder, "maps", &temp_dir, &map_file_name, &features)?;
734
735                        // Update Map
736                        Ok((
737                            id.clone(), 
738                            AtcMap {
739                                name: map.name.clone(),
740                                data: AtcMapData::ExternalFile {
741                                    filename: format!("{}.geojson", &map_uuid)
742                                }
743                            }
744                        ))
745                    },
746                    AtcMapData::ExternalFile { filename } => {
747                        // Copy File into archive
748                        tar_builder.append_file(
749                            Path::new("maps").join(filename),
750                            &mut File::open(maps_dir.as_ref().join(filename))?
751                        )?;
752                        Ok((id.clone(), map.clone()))
753                    },
754                }
755            })
756            .collect::<anyhow::Result<HashMap<String, AtcMap>>>()?;
757
758        // Create Package
759        let new_package = AtcScopePackage {
760            facilities: self.facilities.clone(),
761            maps: new_maps,
762            symbols: self.symbols.clone(),
763            display_types: self.display_types.clone()
764        };
765
766        // Save Package json
767        Self::write_json_to_targz(&mut tar_builder, "", &temp_dir, "ScopePackage.json", &new_package)?;
768
769        // Finish writing tar
770        tar_builder.finish().context("Writing TAR file.")?;
771
772        Ok(())
773    }
774
775    pub fn import_from_gzip(file_name: impl AsRef<Path>, out_dir: impl AsRef<Path>) -> anyhow::Result<Self> {
776        // Unzip tar
777        let tar_gz = File::open(file_name)?;
778        let tar = GzDecoder::new(tar_gz);
779        let mut archive = Archive::new(tar);
780        archive.unpack(&out_dir)?;
781
782        // Read package
783        let package = serde_json::from_reader(BufReader::new(
784            File::open(&out_dir.as_ref().join("ScopePackage.json"))?
785        ))?;
786
787        Ok(package)
788    }
789
790    /// Attempts to lazy load a map.
791    /// 
792    /// If the map is embedded it will return immediately, otherwise it will load the map from the JSON file.
793    /// 
794    /// The map in this package will be replaced with the embedded one for performance.
795    pub fn try_load_map_data(&mut self, map_id: &str, maps_dir: impl AsRef<Path>) -> anyhow::Result<Option<&AtcMap>> {
796        if let Some(map) = self.maps.get_mut(map_id) {
797            if let AtcMapData::ExternalFile { filename } = &map.data {
798                map.data = AtcMapData::Embedded { 
799                    features: serde_json::from_reader(
800                        BufReader::new(File::open(maps_dir.as_ref().join(&filename))?)
801                    )?
802                };
803            }
804
805            Ok(Some(map))
806        } else {
807            Ok(None)
808        }
809    }
810}