osm-to-euroscope

A tool to generate Radar Screens from OSM Data
git clone git://archive.git.mtrnord.blog/MTRNord/osm-to-euroscope.git
Log | Files | Refs | README | LICENSE

commit 5bb7bf397b0df39cf40ca2d958a6d2a78750ca7e
parent 35734a7e6a83926aa03312cfb66a9f62c2937e6a
Author: Marcel <mtrnord1@gmail.com>
Date:   Tue, 31 Dec 2019 01:24:02 +0100

Add a nicer terminal feel and cleanup some parts

Took 27 minutes

Diffstat:
MCargo.lock | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCargo.toml | 1+
Asrc/euroscope_ground.rs | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 193+++++++++++++++----------------------------------------------------------------
Dsrc/main_lahm.rs | 46----------------------------------------------
Asrc/types.rs | 14++++++++++++++
Asrc/utils.rs | 24++++++++++++++++++++++++
7 files changed, 267 insertions(+), 202 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4,6 +4,7 @@ name = "OSM_to_Euroscope" version = "0.1.0" dependencies = [ + "clap", "fnv", "osmpbf", ] @@ -15,12 +16,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" [[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -33,6 +59,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -253,6 +294,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] osmpbf = "0.1" fnv = "1.0.6" +clap = "2.31.2" diff --git a/src/euroscope_ground.rs b/src/euroscope_ground.rs @@ -0,0 +1,123 @@ +use std::fs; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use fnv::FnvHashMap; +use osmpbf::*; + +use crate::types::{Coords, LatLon, Refs}; +use crate::types; +use crate::utils::format_coordinate; + +pub(crate) fn generate_ese_ground_taxiway<P: AsRef<Path> + Copy>(source_path: P, airport_name: &str) { + let out_path_string = format!("./out/{}.ese", airport_name); + let out_path = Path::new(&out_path_string); + if !out_path.parent().unwrap().exists() { + fs::create_dir_all(out_path.parent().unwrap()).expect("unable to generate dir"); + } + let out_file = &mut File::create(out_path).expect("unable to open file"); + + out_file + .write_all(b"[GROUND]\n") + .expect("failed to write file"); + let taxiways = get_taxiways(source_path); + + let coords = get_taxiways_coords(source_path, taxiways); + write_taxiways(out_file, airport_name, coords); + + out_file.sync_all().expect("unable to sync to file"); +} + +fn write_taxiways(out_file: &mut File, airport_name: &str, taxiways: FnvHashMap<String, Coords>) { + taxiways.iter().for_each(|(name, coordinates)| { + let type_string = format!("TAXI:{} {}:20:1\n", airport_name, name); + out_file + .write_all(type_string.as_bytes()) + .expect("failed to write file"); + + let local_coords: Vec<&LatLon> = coordinates.iter().clone().collect(); + local_coords.iter().for_each(|latlon| { + let lat_str = format_coordinate(latlon.lat); + + let lon_str = format_coordinate(latlon.lon); + + let coordinate_string: String; + + coordinate_string = format!("COORD:N{}:E{}\n", lat_str, lon_str); + out_file + .write_all(coordinate_string.as_bytes()) + .expect("failed to write file"); + }); + }); +} + +fn get_taxiways_coords<P: AsRef<Path> + Copy>( + path: P, + refs: FnvHashMap<String, Refs>, +) -> FnvHashMap<String, Coords> { + let mut taxiways: FnvHashMap<String, Coords> = FnvHashMap::default(); + let mut nodes: Vec<types::Node> = Vec::new(); + + let reader = ElementReader::from_path(path).unwrap(); + reader + .for_each(|element| { + if let Element::DenseNode(node) = element { + let new_node = types::Node { + id: node.id, + lat: node.lat(), + lon: node.lon(), + }; + nodes.push(new_node); + } + }) + .unwrap(); + + for (key, val) in refs.iter() { + val.iter().for_each(|refval| { + if let Some(node) = nodes.iter().find(|x| x.id == *refval) { + let lat_lon = LatLon { + lat: node.lat, + lon: node.lon, + }; + + if taxiways.contains_key(key.as_str()) { + (*(taxiways.get_mut(key.as_str()).unwrap())).push(lat_lon); + } else { + let mut coords: Coords = Vec::new(); + coords.push(lat_lon); + taxiways.insert(key.clone(), coords); + } + } + }) + } + + taxiways +} + +fn get_taxiways<P: AsRef<Path> + Copy>(path: P) -> FnvHashMap<String, Refs> { + let mut taxiways: FnvHashMap<String, Refs> = FnvHashMap::default(); + + let reader = ElementReader::from_path(path).unwrap(); + reader + .for_each(|element| { + if let Element::Way(way) = element { + if way + .tags() + .any(|(key, value)| key == "aeroway" && value == "taxiway") + { + if let Some((_, value)) = way.tags().find(|(key, _)| *key == "ref") { + let mut new_vals = way.refs().collect::<Vec<i64>>(); + + if taxiways.contains_key(value) { + taxiways.get_mut(value).unwrap().append(&mut new_vals); + } else { + taxiways.insert(value.to_string(), new_vals); + } + } + } + } + }) + .unwrap(); + taxiways +} diff --git a/src/main.rs b/src/main.rs @@ -1,168 +1,49 @@ //#![feature(test)] #![allow(non_snake_case)] -use std::fs; -use std::fs::File; -use std::io::Write; -use std::path::Path; +extern crate clap; -use fnv::FnvHashMap; -use osmpbf::*; +use std::process::exit; -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] -struct LatLon { - lat: f64, - lon: f64, -} - -type Coords = Vec<LatLon>; -type Refs = Vec<i64>; - -fn main() { - generate_ese_ground_taxiway("./data/EDDH_cleaned.osm.pbf", "EDDH") -} - -fn generate_ese_ground_taxiway<P: AsRef<Path> + Copy>(source_path: P, airport_name: &str) { - let out_path_string = format!("./out/{}.ese", airport_name); - let out_path = Path::new(&out_path_string); - if !out_path.parent().unwrap().exists() { - fs::create_dir_all(out_path.parent().unwrap()).expect("unable to generate dir"); - } - let out_file = &mut File::create(out_path).expect("unable to open file"); - - out_file - .write_all(b"[GROUND]\n") - .expect("failed to write file"); - let taxiways = get_taxiways(source_path); - - let coords = get_taxiways_coords(source_path, taxiways); - write_taxiways(out_file, airport_name, coords); - - out_file.sync_all().expect("unable to sync to file"); -} - -fn to_2_digits(n: i32) -> i32 { - let n = n as f32; - let digit_offset = std::cmp::max((n.abs().log10() - 2.0).ceil() as i32, 0); - let rounded = n / 10f32.powi(digit_offset); - rounded as i32 -} - -fn format_coordinate(coordinate_raw: f64) -> String { - let coordinate = coordinate_raw; - let degree = coordinate.trunc() as i64; - let minute = ((coordinate - degree as f64).abs() * 60.0).trunc() as i64; - let seconds = 3600.0 * (coordinate - degree as f64).abs() - 60.0 * (minute as f64); - let decimal_part_string = format!("{:.2}", seconds - (seconds.trunc() as f64) as f64); - let decimal_part_split = decimal_part_string.split('.').collect::<Vec<&str>>(); - let decimal_part = decimal_part_split.last().unwrap(); - - format!( - "{:03}.{:02}.{:02}.{:02}", - degree, - minute, - to_2_digits(seconds as i32), - decimal_part - ) -} +use clap::{App, Arg, SubCommand}; -fn write_taxiways(out_file: &mut File, airport_name: &str, taxiways: FnvHashMap<String, Coords>) { - taxiways.iter().for_each(|(name, coordinates)| { - let type_string = format!("TAXI:{} {}:20:1\n", airport_name, name); - out_file - .write_all(type_string.as_bytes()) - .expect("failed to write file"); +use crate::euroscope_ground::generate_ese_ground_taxiway; - let local_coords: Vec<&LatLon> = coordinates.iter().clone().collect(); - local_coords.iter().for_each(|latlon| { - let lat_str = format_coordinate(latlon.lat); +mod types; +mod utils; +mod euroscope_ground; - let lon_str = format_coordinate(latlon.lon); - - let coordinate_string: String; - - coordinate_string = format!("COORD:N{}:E{}\n", lat_str, lon_str); - out_file - .write_all(coordinate_string.as_bytes()) - .expect("failed to write file"); - }); - }); -} - -struct Node { - id: i64, - lat: f64, - lon: f64, -} - -fn get_taxiways_coords<P: AsRef<Path> + Copy>( - path: P, - refs: FnvHashMap<String, Refs>, -) -> FnvHashMap<String, Coords> { - let mut taxiways: FnvHashMap<String, Coords> = FnvHashMap::default(); - let mut nodes: Vec<Node> = Vec::new(); - - let reader = ElementReader::from_path(path).unwrap(); - reader - .for_each(|element| { - if let Element::DenseNode(node) = element { - let new_node = Node { - id: node.id, - lat: node.lat(), - lon: node.lon(), - }; - nodes.push(new_node); - } - }) - .unwrap(); - - for (key, val) in refs.iter() { - val.iter().for_each(|refval| { - if let Some(node) = nodes.iter().find(|x| x.id == *refval) { - let lat_lon = LatLon { - lat: node.lat, - lon: node.lon, - }; - - if taxiways.contains_key(key.as_str()) { - (*(taxiways.get_mut(key.as_str()).unwrap())).push(lat_lon); - } else { - let mut coords: Coords = Vec::new(); - coords.push(lat_lon); - taxiways.insert(key.clone(), coords); - } - } - }) +fn main() { + let matches = App::new("OSM_to_Euroscope") + .version("0.1.0") + .author("MTRNord <info@nordgedanken.de>") + .about("Converts OpenStreetMap Data to Euroscope Radar Screens") + .subcommand(SubCommand::with_name("taxiways") + .about("Converts taxiways to Euroscope Ground Networks") + .arg(Arg::with_name("osm_data") + .short("i") + .long("osm_data") + .value_name("osm.pbf FILE") + .takes_value(true) + .required(true) + .help("The input data used to convert from. (smaller data is faster). Needs to be .osm.pbf!")) + .arg(Arg::with_name("airport") + .short("a") + .long("airport") + .value_name("ICAO ID") + .takes_value(true) + .required(true) + .help("The ICAO identifier of the Airport which should be converted"))) + .get_matches(); + + + if let Some(matches) = matches.subcommand_matches("taxiways") { + //generate_ese_ground_taxiway("./data/EDDH_cleaned.osm.pbf", "EDDH"); + generate_ese_ground_taxiway(matches.value_of("osm_data").unwrap(), matches.value_of("airport").unwrap()); + } else { + println!("Please use the taxiways sub command!"); + exit(1); } - - taxiways -} - -fn get_taxiways<P: AsRef<Path> + Copy>(path: P) -> FnvHashMap<String, Refs> { - let mut taxiways: FnvHashMap<String, Refs> = FnvHashMap::default(); - - let reader = ElementReader::from_path(path).unwrap(); - reader - .for_each(|element| { - if let Element::Way(way) = element { - if way - .tags() - .any(|(key, value)| key == "aeroway" && value == "taxiway") - { - if let Some((_, value)) = way.tags().find(|(key, _)| *key == "ref") { - let mut new_vals = way.refs().collect::<Vec<i64>>(); - - if taxiways.contains_key(value) { - taxiways.get_mut(value).unwrap().append(&mut new_vals); - } else { - taxiways.insert(value.to_string(), new_vals); - } - } - } - } - }) - .unwrap(); - taxiways } #[cfg(test)] diff --git a/src/main_lahm.rs b/src/main_lahm.rs @@ -1,46 +0,0 @@ -use std::fs::File; - -use osm_xml::{Reference, Tag, OSM, Way}; - -fn main() { - let f = File::open("./data/EDDH.osm").unwrap(); - let doc = OSM::parse(f).unwrap(); - - let way_info = way_reference_statistics(&doc); - - println!( - "Way count {}", - doc.ways - .values() - .filter(|way| { - way.tags.contains(&Tag { - key: "aeroway".to_string(), - val: "taxiway".to_string(), - }) - }) - .collect::<Vec<&Way>>() - .len() - ); - println!( - "Way reference count: {}, invalid references: {}", - way_info.0, way_info.1 - ); -} - -fn way_reference_statistics(doc: &OSM) -> (usize, usize) { - doc.ways - .values() - .filter(|way| { - way.tags.contains(&Tag { - key: "aeroway".to_string(), - val: "taxiway".to_string(), - }) - }) - .flat_map(|way| way.nodes.iter()) - .fold((0, 0), |acc, node| match doc.resolve_reference(&node) { - Reference::Node(_) => (acc.0 + 1, acc.1), - Reference::Unresolved | Reference::Way(_) | Reference::Relation(_) => { - (acc.0, acc.1 + 1) - } - }) -} diff --git a/src/types.rs b/src/types.rs @@ -0,0 +1,14 @@ +#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] +pub(crate) struct LatLon { + pub(crate) lat: f64, + pub(crate) lon: f64, +} + +pub(crate) struct Node { + pub(crate) id: i64, + pub(crate) lat: f64, + pub(crate) lon: f64, +} + +pub(crate) type Coords = Vec<LatLon>; +pub(crate) type Refs = Vec<i64>; diff --git a/src/utils.rs b/src/utils.rs @@ -0,0 +1,24 @@ +pub(crate) fn to_2_digits(n: i32) -> i32 { + let n = n as f32; + let digit_offset = std::cmp::max((n.abs().log10() - 2.0).ceil() as i32, 0); + let rounded = n / 10f32.powi(digit_offset); + rounded as i32 +} + +pub(crate) fn format_coordinate(coordinate_raw: f64) -> String { + let coordinate = coordinate_raw; + let degree = coordinate.trunc() as i64; + let minute = ((coordinate - degree as f64).abs() * 60.0).trunc() as i64; + let seconds = 3600.0 * (coordinate - degree as f64).abs() - 60.0 * (minute as f64); + let decimal_part_string = format!("{:.2}", seconds - (seconds.trunc() as f64) as f64); + let decimal_part_split = decimal_part_string.split('.').collect::<Vec<&str>>(); + let decimal_part = decimal_part_split.last().unwrap(); + + format!( + "{:03}.{:02}.{:02}.{:02}", + degree, + minute, + to_2_digits(seconds as i32), + decimal_part + ) +}