#![feature(seek_convenience)] use fnv::FnvHashMap; use humantime::format_duration; use indicatif::{ProgressBar, ProgressStyle}; use serde::{Deserialize, Serialize}; use serde_json::Result; use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Seek}; use std::path::PathBuf; use std::str; use std::time::Instant; use structopt::StructOpt; #[derive(Debug, Clone, Serialize)] struct Record { id: u64, star_type: String, name: String, mult: f32, distance: u32, x: f64, y: f64, z: f64, } #[derive(Debug, Deserialize)] #[allow(non_snake_case)] struct Body { name: String, subType: String, #[serde(rename = "type")] body_type: String, systemId: i32, systemId64: i64, #[serde(rename = "distanceToArrival")] distance: u32, } #[derive(Debug, Deserialize)] struct Coords { x: f64, y: f64, z: f64, } #[derive(Debug, Deserialize)] struct System { id: i32, id64: i64, name: String, coords: Coords, date: String, } #[derive(Debug, StructOpt)] #[structopt( name = "ed_lrr_pp", about = "Preprocessor for Elite: Dangerous Long-Range Router", rename_all = "snake_case" )] /// Preprocess data for ed_lrr struct Opt { #[structopt(short, long = "bodies")] /// Path to bodies.json bodies: PathBuf, #[structopt(short, long = "systems")] /// Path to systemsWithCoordinates.json systems: PathBuf, #[structopt(default_value = "stars")] /// outfile prefix prefix: String, } fn get_mult(star_type: &str) -> f32 { if star_type.contains("White Dwarf") { return 1.5; } if star_type.contains("Neutron") { return 4.0; } return 1.0; } fn process(path: &PathBuf, func: &mut dyn for<'r> FnMut(&'r str) -> ()) -> std::io::Result<()> { let mut cnt = 0; let mut buffer = String::new(); let t_start = Instant::now(); let fh = File::open(path)?; let prog_bar = ProgressBar::new(fh.metadata()?.len()); prog_bar.set_style( ProgressStyle::default_bar() .template( "[{elapsed_precise}/{eta_precise}]{spinner} [{wide_bar}] {binary_bytes}/{binary_total_bytes} ({percent}%)", ) .progress_chars("#9876543210 ") .tick_chars("/-\\|"), ); prog_bar.set_draw_delta(1024 * 1024); let mut reader = BufReader::new(fh); println!("Loading {} ...", path.to_str().unwrap()); while let Ok(n) = reader.read_line(&mut buffer) { if n == 0 { break; } buffer = buffer.trim_end().trim_end_matches(|c| c == ',').to_string(); if !buffer.is_empty() { func(&buffer); } prog_bar.set_position(reader.stream_position().unwrap()); cnt += 1; buffer.clear(); } prog_bar.finish_and_clear(); println!( "Processed {} lines in {} ...", cnt, format_duration(t_start.elapsed()) ); return Ok(()); } fn process_systems(path: &PathBuf) -> FnvHashMap { let mut ret = FnvHashMap::default(); process(path, &mut |line| { let sys_res: Result = serde_json::from_str(&line); if let Ok(sys) = sys_res { ret.insert(sys.id64, sys); } else { eprintln!("\nError parsing: {}\n\t{:?}\n", line, sys_res.unwrap_err()); } }) .unwrap(); return ret; } fn process_bodies( path: &PathBuf, out_prefix: &String, systems: &FnvHashMap, ) -> std::io::Result<()> { let out_path = PathBuf::from(format!("{}.csv", out_prefix)); println!( "Processing {} into {} ...", path.to_str().unwrap(), out_path.to_str().unwrap(), ); let mut n: u64 = 0; let mut wtr = csv::Writer::from_writer(BufWriter::new(File::create(out_path).unwrap())); process(path, &mut |line| { if !line.contains("Star") { return; } let body_res: Result = serde_json::from_str(&line); if let Ok(body) = body_res { if !body.body_type.contains("Star") { return; } if let Some(sys) = systems.get(&body.systemId64) { let sub_type = body.subType; let mult = get_mult(&sub_type); let body_name = body.name; let rec = Record { id: n, star_type: sub_type, name: body_name, mult, distance: body.distance, x: sys.coords.x, y: sys.coords.y, z: sys.coords.z, }; wtr.serialize(rec).unwrap(); n += 1; }; } else { eprintln!("\nError parsing: {}\n\t{:?}\n", line, body_res.unwrap_err()); } }) .unwrap(); println!("Total Systems: {}", n); return Ok(()); } fn main() -> std::io::Result<()> { let opts = Opt::from_args(); let systems = process_systems(&opts.systems); process_bodies(&opts.bodies, &opts.prefix, &systems)?; return Ok(()); }