diff --git a/download.sh b/download.sh deleted file mode 100644 index ea5226e..0000000 --- a/download.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/bash -rm systemsWithCoordinates.json bodies.json *.aria2 -wget https://www.edsm.net/dump/systemsWithCoordinates.json https://www.edsm.net/dump/bodies.json \ No newline at end of file diff --git a/process.py b/dumps/process.py similarity index 90% rename from process.py rename to dumps/process.py index 6416cbc..02b8102 100644 --- a/process.py +++ b/dumps/process.py @@ -7,6 +7,7 @@ import sys import csv import sqlite3 import pandas as pd +from urllib.parse import urljoin def is_scoopable(entry): @@ -89,8 +90,15 @@ def process_file(fn, show_progbar=False): yield line +if not ( + os.path.isfile("bodies.json") and os.path.isfile("systemsWithCoordinates.json") +): + exit( + "Please download bodies.json and systemsWithCoordinates.json from https://www.edsm.net/en/nightly-dumps/" + ) + if not os.path.isfile("stars.jl"): - print("Filtering for Neutron Stars") + print("Filtering for Stars") with open("stars.jl", "w") as neut: for body in process_file("bodies.json", True): T = body.get("type") or "" diff --git a/src/main.rs b/src/main.rs index 572c243..fec16cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,8 @@ extern crate csv; extern crate serde; #[macro_use] extern crate serde_derive; - extern crate fnv; extern crate humantime; -// extern crate rayon; -// use rayon::prelude::*; use fnv::{FnvHashMap, FnvHashSet}; use humantime::format_duration; use rstar::{PointDistance, RTree, RTreeObject, AABB}; @@ -25,7 +22,7 @@ struct Record { y: f32, z: f32, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct System { id: i64, star_type: String, @@ -33,7 +30,7 @@ struct System { mult: f32, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] struct Point { id: i64, x: f32, @@ -41,6 +38,14 @@ struct Point { z: f32, } +fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { + let dx = p1[0] - p2[0]; + let dy = p1[1] - p2[1]; + let dz = p1[2] - p2[2]; + + return (dx * dx + dy * dy + dz * dz).sqrt(); +} + impl Point { pub fn dist2(&self, p: &[f32; 3]) -> f32 { let dx = self.x - p[0]; @@ -87,12 +92,11 @@ impl PointDistance for Point { struct Router { tree: RTree, systems: FnvHashMap, - range: f32, scoopable: FnvHashSet, } impl Router { - pub fn new(path: &str, range: f32) -> Self { + pub fn new(path: &str) -> Self { let mut scoopable = FnvHashSet::default(); let mut systems: FnvHashMap = FnvHashMap::default(); let mut reader = csv::ReaderBuilder::new() @@ -134,7 +138,6 @@ impl Router { return Self { tree: RTree::::bulk_load(points), systems, - range, scoopable, }; } @@ -175,17 +178,88 @@ impl Router { }; return 1.0; } - fn neighbours(&self, sys: &Point) -> impl Iterator { - return self.points_in_sphere(&[sys.x, sys.y, sys.z], self.mult(sys.id) * self.range); + fn neighbours(&self, sys: &Point, r: f32) -> impl Iterator { + return self.points_in_sphere(&[sys.x, sys.y, sys.z], self.mult(sys.id) * r); } fn valid(&self, sys: &Point) -> bool { return self.scoopable.contains(&sys.id); } - pub fn route(&mut self, src: &[f32; 3], dst: &[f32; 3]) -> Option> { + pub fn name_multiroute(&self, waypoints: &[&str], range: f32) -> Vec<(&System, &Point)> { + let mut coords = Vec::new(); + for p in waypoints { + let p = self.name_to_point(p); + let s = [p.x, p.y, p.z]; + coords.push(s); + } + return self.multiroute(coords.as_slice(), range); + } + pub fn multiroute(&self, waypoints: &[[f32; 3]], range: f32) -> Vec<(&System, &Point)> { + let mut route = Vec::new(); + for pair in waypoints.windows(2) { + match pair { + &[src, dst] => { + let block = self.route(&src, &dst, range); + if route.is_empty() { + route.extend(block.iter()); + } else { + route.extend(block.iter().skip(1)); + } + } + _ => panic!("Invalid routing parameters!"), + } + } + return route; + } + + fn sys_to_point(&self, id: i64) -> Option<&Point> { + for p in &self.tree { + if p.id == id { + return Some(p); + } + } + return None; + } + + fn name_to_point(&self, name: &str) -> &Point { + for sys in self.systems.values() { + if sys.name == name { + return self.sys_to_point(sys.id).unwrap(); + } + } + eprintln!("Sytem not found: {}", name); + std::process::exit(1); + } + + pub fn route_by_name(&self, src: &str, dst: &str, range: f32) -> Vec<(&System, &Point)> { + let start_sys = self.name_to_point(src); + let goal_sys = self.name_to_point(dst); + return self.route( + &[start_sys.x, start_sys.y, start_sys.z], + &[goal_sys.x, goal_sys.y, goal_sys.z], + range, + ); + } + + pub fn route(&self, src: &[f32; 3], dst: &[f32; 3], range: f32) -> Vec<(&System, &Point)> { let start_sys = self.closest(src); let goal_sys = self.closest(dst); + { + let d = dist(src, dst); + let start_sys_name = self.systems.get(&start_sys.id).unwrap().name.clone(); + let goal_sys_name = self.systems.get(&goal_sys.id).unwrap().name.clone(); + println!( + "Computing route from {} to {}...", + start_sys_name, goal_sys_name + ); + println!( + "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", + range, + d, + d / range + ); + } let total = self.tree.size() as f32; let mut prev = FnvHashMap::default(); let mut seen = FnvHashSet::default(); @@ -197,42 +271,41 @@ impl Router { queue.push_front((0, &start_sys)); seen.insert(start_sys.id); while !queue.is_empty() { + print!( + "[{}] Depth: {}, Queue: {}, Seen: {} ({:.02}%) \r", + format_duration(t_start.elapsed()), + depth, + queue.len(), + seen.len(), + ((seen.len() * 100) as f32) / total + ); + std::io::stdout().flush().unwrap(); while let Some((d, sys)) = queue.pop_front() { - if d != depth { - depth = d; - print!( - "\r[{}] Depth: {}, Queue: {}, Seen: {} ({:.2}%) ", - format_duration(t_start.elapsed()), - d, - queue.len(), - prev.len(), - ((prev.len() as f32) / total) * 100.0 - ); - std::io::stdout().flush().unwrap(); - } if sys.id == goal_sys.id { found = true; break; } - let nbs = self - .neighbours(&sys) - .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))); - for nb in nbs { - if seen.insert(nb.id) { - prev.insert(nb.id, sys.id); - queue_next.push_back((d + 1, nb)); - } - } + queue_next.extend( + self.neighbours(&sys, range) + .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) + .filter(|&nb| seen.insert(nb.id)) + .map(|nb| { + prev.insert(nb.id, sys.id); + return (d + 1, nb); + }), + ); } std::mem::swap(&mut queue, &mut queue_next); + depth += 1; } println!(); if !found { - return None; + return Vec::new(); } let points = self.preload_points(); let mut v: Vec<(&System, &Point)> = Vec::new(); let mut prev_sys_id = goal_sys.id; + let prev = prev; loop { if let Some(sys) = self.systems.get(&prev_sys_id) { v.push((sys, points[&sys.id])); @@ -247,36 +320,30 @@ impl Router { } } v.reverse(); - return Some(v); + return v; } } fn main() { let path = r#"D:\devel\files\python\EDSM\stars.csv"#; let t_load = Instant::now(); - let mut router: Router = Router::new(path, 48.0); + let router: Router = Router::new(path); println!("Done in {}!", format_duration(t_load.elapsed())); let t_route = Instant::now(); - let route = router.route( - &[-65.21875, 7.75, -111.03125], // Ix - &[-1111.5625, -134.21875, 65269.75], // Beagle Point - // &[-7095.375, 401.25, 2396.8125], // V1357 Cygni, - ); // Ix -> BP 537 - let route = match route { - Some(r) => r, - None => Vec::new(), - }; + let route = router.name_multiroute(&["Ix", "Colonia", "Sol"], 48.0); println!( "Done in {} ({} Jumps)!\n", format_duration(t_route.elapsed()), - route.len() + route.len(), ); - let mut total: f32 = 0.0; for ((sys1, p1), (_, p2)) in route.iter().zip(route.iter().skip(1)) { let dist = p1.distp(p2); total += dist; - println!("{} [{}]: {:.2} Ly", sys1.name, sys1.star_type, dist); + println!( + "{} [{}] ({},{},{}): {:.2} Ly", + sys1.name, sys1.star_type, p1.x, p1.y, p1.z, dist + ); } let sys = route.iter().last().unwrap().0; println!("{} [{}]: {:.2} Ly\n", sys.name, sys.star_type, 0.0);