use geo::{algorithm::contains::Contains, Point}; use geo::{point, Coordinate, LineString, Polygon}; use openaip::{parse, Airspace}; use simconnect::{self, SimConnector}; use std::fs::read_to_string; use std::mem::transmute_copy; use std::thread::sleep; use std::time::Duration; use std::{ env, fmt::{self, Display, Formatter}, io, }; #[derive(Debug)] pub struct Error(pub String); impl Display for Error { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for Error {} impl From for Error where io::Error: std::fmt::Debug, { fn from(error: io::Error) -> Self { Error(format!("{:?}", error)) } } impl From for Error where io::Error: std::fmt::Debug, { fn from(error: openaip::Error) -> Self { Error(format!("{:?}", error)) } } struct IndexedAirspace<'a> { airspace: &'a Airspace, polygon: Polygon, } fn open_aip_geometry_to_geo_polygon(poly: &openaip::Geometry) -> Polygon { match poly { openaip::Geometry::Polygon(coords) => { let cs: Vec> = coords .iter() .map(|p| Coordinate { x: p.longitude, y: p.latitude, }) .collect(); Polygon::new(LineString::from(cs), vec![]) } } } fn load_airspace_data(path: &str) -> Result, Error> { let content = read_to_string(path)?; let file = parse(&content)?; let airspaces: Vec = match file.airspaces { Some(airspace_results) => airspace_results.into_iter().collect(), _ => Ok(vec![]), }?; println!("Loaded {} airspaces from {}", airspaces.len(), path); Ok(airspaces) } macro_rules! sc_success { ($hr:expr) => { if !$hr { Err(Error(format!("simconnect call failed"))) } else { Ok(()) } } } fn register_sim_vars(conn: &SimConnector) -> Result<(), Error> { sc_success!(conn.add_data_definition( 0, "PLANE LATITUDE", "Degrees", simconnect::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, u32::MAX, ))?; // Assign a sim variable to a client defined id sc_success!(conn.add_data_definition( 0, "PLANE LONGITUDE", "Degrees", simconnect::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, u32::MAX, ))?; sc_success!(conn.add_data_definition( 0, "PLANE ALTITUDE", "Feet", simconnect::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, u32::MAX, ))?; //define_id, units, data_type, datum_id sc_success!(conn.add_data_definition( 0, "PLANE ALT ABOVE GROUND", "Feet", simconnect::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, u32::MAX, ))?; sc_success!(conn.request_data_on_sim_object( 0, 0, 0, simconnect::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SIM_FRAME, 0, 0, 0, 0, ))?; //request_id, define_id, object_id (user), period, falgs, origin, interval, limit - tells simconnect to send data for the defined id and on the user aircraft Ok(()) } #[derive(Debug, PartialEq)] struct SimPlaneState { lat: f64, lon: f64, alt: f64, agl: f64, } fn main() { let args: Vec<_> = env::args().collect(); // Load the Airspace data specified on the command line. let airspaces = load_airspace_data(&args[1]).unwrap(); let indexed_airspaces: Vec = airspaces .iter() .map(|a| IndexedAirspace { airspace: a, polygon: open_aip_geometry_to_geo_polygon(&a.geometry), }) .collect(); // Connect to the sim. let mut conn = simconnect::SimConnector::new(); sc_success!(conn.connect("Simple Program")).expect("Could not connect to sim"); // Intialize connection with SimConnect println!("Connected to sim"); register_sim_vars(&conn).unwrap(); let mut prev_state: Option = None; let mut prev_airspaces: Option = None; loop { match conn.get_next_message() { Ok(simconnect::DispatchResult::SimobjectData(data)) => unsafe { match data.dwDefineID { 0 => { let sim_data: SimPlaneState = transmute_copy(&data.dwData); let state_changed = match prev_state { Some(p) => p != sim_data, _ => true, }; if state_changed { // println!("{:?}", sim_data); let origin = point!(x: sim_data.lon, y: sim_data.lat); let airspaces = find_airspace(&origin, &indexed_airspaces); let descs: Vec = airspaces .iter() .map(|a| format!("{} {:?} {:?}", a.name, a.bottom, a.top)) .collect(); let desc = descs.join(", "); match &prev_airspaces { Some(d) => if *d != desc { println!("{}", desc); }, _ => { println!("{}", desc); } } prev_airspaces = Some(desc); } prev_state = Some(sim_data); } _ => (), } }, _ => (), } sleep(Duration::from_millis(16)); // Will use up lots of CPU if this is not included, as get_next_message() is non-blocking } } fn find_airspace<'a>(pos: &Point, airspaces: &'a Vec) -> Vec<&'a Airspace> { let mut v = vec![]; for ia in airspaces { if ia.polygon.contains(pos) { v.push(ia.airspace); } } v }