From 7d657b7222740092fda98e9908920b99ea961e37 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Tue, 13 Sep 2022 20:41:07 +0200 Subject: [PATCH 01/22] fixed the broken FMI file parser --- grids/grid_3x3.fmi | 25 +++++++++++ routes/10x10_1.json | 1 + routes/3x3_1.json | 1 + src/bin/generate_grid.rs | 11 ++++- src/bin/performance.rs | 6 +-- src/gridgraph.rs | 93 ++++++++++++++++++++++------------------ 6 files changed, 91 insertions(+), 46 deletions(-) create mode 100644 grids/grid_3x3.fmi create mode 100644 routes/10x10_1.json create mode 100644 routes/3x3_1.json diff --git a/grids/grid_3x3.fmi b/grids/grid_3x3.fmi new file mode 100644 index 0000000..ac0a31f --- /dev/null +++ b/grids/grid_3x3.fmi @@ -0,0 +1,25 @@ +7 +16 +0 -90 180 +1 -90 -60.00000000000001 +2 -90 59.999999999999986 +3 -30.000000000000004 180 +4 -30.000000000000004 59.999999999999986 +5 29.999999999999993 180 +6 29.999999999999993 -60.00000000000001 +0 2 0 +0 1 0 +0 3 6671695 +1 0 0 +1 2 0 +2 1 0 +2 0 0 +2 4 6671695 +3 4 10806007 +3 5 6671695 +3 0 6671695 +4 3 10806007 +4 2 6671695 +5 6 10806007 +5 3 6671695 +6 5 10806007 diff --git a/routes/10x10_1.json b/routes/10x10_1.json new file mode 100644 index 0000000..7d08745 --- /dev/null +++ b/routes/10x10_1.json @@ -0,0 +1 @@ +[{"source":51,"destination":11}] diff --git a/routes/3x3_1.json b/routes/3x3_1.json new file mode 100644 index 0000000..7f74b99 --- /dev/null +++ b/routes/3x3_1.json @@ -0,0 +1 @@ +[{"source":2,"destination":5}] diff --git a/src/bin/generate_grid.rs b/src/bin/generate_grid.rs index 3478bf0..a8dd190 100644 --- a/src/bin/generate_grid.rs +++ b/src/bin/generate_grid.rs @@ -15,6 +15,15 @@ struct Args { /// the output file to write the grid to #[clap(short, long)] output: String, + + + /// the latitude resolution + #[clap(short, long)] + lat: usize, + + /// the longitude resolution + #[clap(short, long)] + lon: usize, } fn main() { @@ -47,7 +56,7 @@ fn main() { println!("{:?}", coasts.polygons[0].bbox); - let grid = GridGraph::generate_regular_grid(10, 10, Some(&coasts)); + let grid = GridGraph::generate_regular_grid(args.lat, args.lon, Some(&coasts)); // let grid = GridGraph::generate_regular_grid(3, 4, None); match grid.write_fmi_file(output) { diff --git a/src/bin/performance.rs b/src/bin/performance.rs index e84ba38..39bf722 100644 --- a/src/bin/performance.rs +++ b/src/bin/performance.rs @@ -93,7 +93,7 @@ fn main() { let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - let _result = astar.shortest_path(&source, &destination, estimate_haversine); + let _result = astar.shortest_path(&source, &destination, estimate_latitude); } let elapsed = start.elapsed(); @@ -109,13 +109,13 @@ fn main() { let start = Instant::now(); for query in targets.iter() { - println!("working on {:?}", query); + // println!("working on {:?}", query); let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; let result = astar.graph.shortest_path(&source, &destination); - println!("{}", result.unwrap().to_geojson()); + //println!("{}", result.unwrap().to_geojson()); } let elapsed = start.elapsed(); diff --git a/src/gridgraph.rs b/src/gridgraph.rs index 1291462..c7a00f9 100644 --- a/src/gridgraph.rs +++ b/src/gridgraph.rs @@ -1,16 +1,16 @@ use crate::coordinates::{DegreeCoordinate, RadianCoordinate}; use crate::polygonset::PolygonSet; +use crate::utils::EARTH_RADIUS; use geojson::{Feature, FeatureCollection, Geometry, Position, Value}; +use rand::seq::SliceRandom; use serde::ser::{SerializeStruct, Serializer}; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::{BinaryHeap, HashMap}; use std::f64::consts::{FRAC_PI_2, PI, TAU}; use std::fs::File; use std::io::{BufRead, BufReader, Write}; use std::vec::Vec; -use crate::utils::EARTH_RADIUS; -use rand::seq::SliceRandom; -use serde::{Serialize, Deserialize}; /// Type for all edge costs /// Allows for easy adjustments for bigger/smaller number types if that is @@ -134,8 +134,6 @@ impl Route { } impl GridGraph { - - /// selects a single random graph node. pub fn get_random_node(&self) -> Option<&GraphNode> { let mut rng = rand::thread_rng(); @@ -143,14 +141,11 @@ impl GridGraph { self.nodes.choose(&mut rng) } - /// selects up to `amount` random but distinct nodes from the graph. - pub fn get_random_nodes(&self, amount: usize) -> Vec<&GraphNode>{ - + pub fn get_random_nodes(&self, amount: usize) -> Vec<&GraphNode> { let mut rng = rand::thread_rng(); self.nodes.choose_multiple(&mut rng, amount).collect() - } /// calculates the shortest path from the start node to the end node. @@ -169,7 +164,10 @@ impl GridGraph { // inverted cmp function, such that the Max-Heap provided by Rust // can be used as a Min-Heap fn cmp(&self, other: &Self) -> Ordering { - other.cost.cmp(&self.cost).then_with(|| self.index.cmp(&other.index)) + other + .cost + .cmp(&self.cost) + .then_with(|| self.index.cmp(&other.index)) } } @@ -190,11 +188,7 @@ impl GridGraph { let mut popcount = 0; - while let Some(DijkstraElement { - index, - cost, - }) = heap.pop() - { + while let Some(DijkstraElement { index, cost }) = heap.pop() { popcount += 1; if index == end.index { @@ -209,7 +203,7 @@ impl GridGraph { continue; }; - // println!("working on node {} with cost {}", index, cost); + //println!("working on node {} with cost {}", index, cost); let edge_start = self.edge_offsets[index as usize] as usize; let edge_end = self.edge_offsets[(index + 1) as usize] as usize; @@ -218,7 +212,7 @@ impl GridGraph { let new_cost = cost + edge.cost; if new_cost < distance[edge.neighbor as usize] { - // println!("found cheaper edge {:?} with cost {} than previous best {}", edge, new_cost, distance[edge.neighbor as usize]); + //println!("found cheaper edge {:?} with cost {} than previous best {}", edge, new_cost, distance[edge.neighbor as usize]); distance[edge.neighbor as usize] = new_cost; ancestor[edge.neighbor as usize] = Some(index); //println!("adding new element to heap"); @@ -227,7 +221,7 @@ impl GridGraph { cost: new_cost, }); } else { - // println!("edge {:?} is more expensive ({}) than the previous best {}", edge, new_cost, distance[edge.neighbor as usize]); + //println!("edge {:?} is more expensive ({}) than the previous best {}", edge, new_cost, distance[edge.neighbor as usize]); } } } @@ -305,7 +299,7 @@ impl GridGraph { let mut node_count: u32 = 0; let mut edge_count: u32 = 0; - let mut nodes: HashMap = HashMap::new(); + let mut node_map: HashMap = HashMap::new(); let mut edges: HashMap> = HashMap::new(); enum ParserState { @@ -316,6 +310,7 @@ impl GridGraph { Done, } + #[derive(Debug)] struct TemporaryGraphEdge { src: u32, dst: u32, @@ -388,25 +383,46 @@ impl GridGraph { match state { ParserState::NodeCount => { total_node_count = parse_int(line)?; + println!("found node count: {}", total_node_count); state = ParserState::EdgeCount {}; } ParserState::EdgeCount => { total_edge_count = parse_int(line)?; + println!("found edge count: {}", total_edge_count); state = ParserState::Nodes {}; } ParserState::Nodes => { - let node = parse_node(line)?; - nodes.entry(node.index).or_insert(node); + let mut node = parse_node(line)?; + // println!("parsed node: {:?}", node); node_count += 1; + + let new_index = gridgraph.nodes.len() as u32; + + node_map.insert(node.index, new_index); + node.index = new_index; + + gridgraph.nodes.push(node); + if node_count == total_node_count { + println!("done parsing nodes: {} nodes parsed", node_count); state = ParserState::Edges {}; } } ParserState::Edges => { - let edge = parse_edge(line)?; - edges.entry(edge.src).or_default().push(edge); + let mut edge = parse_edge(line)?; + // println!("parsed edge: {:?}", edge); edge_count += 1; + + let src = edge.src; + let dst = edge.dst; + + edge.src = node_map[&src]; + edge.dst = node_map[&dst]; + + edges.entry(edge.src).or_default().push(edge); + if edge_count == total_edge_count { + println!("done parsing edges: {} edges parsed", edge_count); state = ParserState::Done; } } @@ -423,31 +439,22 @@ impl GridGraph { return Err(FmiParsingError::WrongEdgeAmount); } - // add the nodes to the gridgraph - for (_, mut item) in nodes.iter_mut() { - item.index = gridgraph.nodes.len() as u32; - gridgraph.nodes.push(item.clone()); - } + // println!("{:?}", gridgraph); // add the edges gridgraph.edge_offsets.push(0); - for index in nodes.keys() { - for edge in edges.entry(*index).or_default() { - if !nodes.contains_key(&edge.dst) { - println!( - "An edge refers to the non-existing destination node {} skipping.", - edge.dst - ); - continue; - } - let dst_index = nodes[&edge.dst].index; + for index in 0..gridgraph.nodes.len() { + //println!("working on the edges for node {}", index); + for edge in edges.entry(index as u32).or_default() { + //println!("working on edge {:?}", edge); gridgraph.edges.push(GraphEdge { cost: edge.cost, - neighbor: dst_index, + neighbor: edge.dst, }); } gridgraph.edge_offsets.push(gridgraph.edges.len()); } + // println!("{:?}", gridgraph); gridgraph.lookup_grid = LookupGrid::new(100, 100, &gridgraph.nodes); @@ -485,7 +492,6 @@ impl GridGraph { lon_resolution: usize, polygons: Option<&PolygonSet>, ) -> Box { - #[derive(Debug)] struct TemporaryGraphNode { final_index: u32, @@ -518,7 +524,11 @@ impl GridGraph { Some(self.lon_index + (self.lat_index - 1) * lon_resolution) } - pub fn top_neighbor_index(&self, lat_resolution: usize, lon_resolution: usize) -> Option { + pub fn top_neighbor_index( + &self, + lat_resolution: usize, + lon_resolution: usize, + ) -> Option { if self.lat_index >= lat_resolution - 1 { return None; }; @@ -572,7 +582,6 @@ impl GridGraph { // add all edges to the final graph for node in temp_nodes.iter() { - println!("working on edges for node {:?}", node); if !node.used { continue; From b11eef1cf789bdfddebd06e4b0cf037a0db5f369 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 09:57:46 +0200 Subject: [PATCH 02/22] added working ALT --- src/alt.rs | 78 ++++++++++++++++++++++------------------ src/astar.rs | 65 +++++++++++++++++---------------- src/bin/performance.rs | 18 +++++----- src/bin/task6-rocket.rs | 67 ++++++++++++++++++++++++++++++---- src/bin/test_alt_gen.rs | 2 +- src/gridgraph.rs | 1 - static/js/script.js | 6 ++++ templates/index.html.hbs | 10 ++++++ 8 files changed, 167 insertions(+), 80 deletions(-) diff --git a/src/alt.rs b/src/alt.rs index 1050da4..3b3c0c6 100644 --- a/src/alt.rs +++ b/src/alt.rs @@ -12,6 +12,11 @@ pub struct Landmark { #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct LandmarkSet { pub landmarks: Vec, +} + +#[derive(Debug, Clone)] +pub struct LandmarkBestSet<'a> { + pub landmark_set: &'a LandmarkSet, pub best_size: usize, best_landmarks: Vec, } @@ -24,7 +29,7 @@ impl Landmark { }; landmark.node = node; - #[derive(Eq)] + #[derive(Eq, PartialEq)] struct DijkstraElement { index: u32, cost: EdgeCost, @@ -44,40 +49,23 @@ impl Landmark { } } - impl PartialEq for DijkstraElement { - fn eq(&self, other: &Self) -> bool { - self.cost == other.cost - } - } - let mut heap = BinaryHeap::new(); heap.push(DijkstraElement { cost: 0, index: landmark.node.index, }); - let mut counter = 0; - while let Some(DijkstraElement { index, cost }) = heap.pop() { // the heap does not support "update" operations, so we // insert elements again and if they come out of the heap but have // been processed earlier we simply skip them. - if landmark.distances[index as usize] <= cost { + if cost > landmark.distances[index as usize] { continue; }; - counter += 1; - - if counter % 1000 == 0 { - println!("Finished {} nodes", counter); - } - landmark.distances[index as usize] = cost; - let edge_start = graph.edge_offsets[index as usize] as usize; - let edge_end = graph.edge_offsets[(index + 1) as usize] as usize; - - for edge in graph.edges[edge_start..edge_end].iter() { + for edge in graph.get_edges(index as NodeId) { let new_cost = cost + edge.cost; if new_cost < landmark.distances[edge.neighbor as usize] { @@ -86,11 +74,11 @@ impl Landmark { index: edge.neighbor, cost: new_cost, }); + landmark.distances[edge.neighbor as usize] = new_cost; } } } - - // now the shortest paths to all reachable nodes is calculated. + // now the costs to all reachable nodes is calculated. landmark } @@ -103,18 +91,28 @@ impl Landmark { let l_to = self.distances[to]; let l_from = self.distances[from]; - if l_to == EdgeCost::MAX { - 0 + if l_to == EdgeCost::MAX || l_from == EdgeCost::MAX { + EdgeCost::MAX } else { - l_to.saturating_sub(l_from) + // since we are working on an undirected graph we can + // use the distances once from and once to the landmark. + // This leads to l_to - l_from and l_from - l_to (as signed subtractions) + // which except for the sign are the same value. + // We can simply take the bigger one, which is handled + // nicely the abs() function + let distance = (l_to as i64 - l_from as i64).abs() as EdgeCost; + //println!( + // "distance from {} to {} via landmark {} is at least {}", + // from, to, self.node.index, distance + //); + distance } } } impl LandmarkSet { - pub fn random_set(size: usize, best_size: usize, graph: &GridGraph) -> Self { + pub fn random_set(size: usize, graph: &GridGraph) -> Self { let mut set = LandmarkSet::default(); - set.best_size = best_size; let nodes = graph.get_random_nodes(size); @@ -125,11 +123,13 @@ impl LandmarkSet { set } +} +impl LandmarkBestSet<'_> { pub fn select_best(&mut self, from: NodeId, to: NodeId) { let mut results = vec![]; - for (index, landmark) in self.landmarks.iter().enumerate() { + for (index, landmark) in self.landmark_set.landmarks.iter().enumerate() { results.push((index, landmark.estimate(from, to))); } @@ -143,18 +143,28 @@ impl LandmarkSet { } pub fn estimate(&self, from: NodeId, to: NodeId) -> EdgeCost { - let mut distance = 0; for index in &self.best_landmarks { - distance = distance.max(self.landmarks[*index].estimate(from, to)); - }; + let candidate = self.landmark_set.landmarks[*index].estimate(from, to); - if distance == 0 { - distance = EdgeCost::MAX; + if candidate == EdgeCost::MAX { + continue; + } + + distance = distance.max(candidate) } - distance + //println!("calculated estimate {:?} for {} to {}", distance, from, to); + distance + } + + pub fn new<'a>(best_size: usize, landmark_set: &'a LandmarkSet) -> LandmarkBestSet<'a> { + LandmarkBestSet { + best_size, + landmark_set, + best_landmarks: Vec::new(), + } } } diff --git a/src/astar.rs b/src/astar.rs index d5e78a1..1d6afe4 100644 --- a/src/astar.rs +++ b/src/astar.rs @@ -4,17 +4,16 @@ use crate::utils::EARTH_RADIUS; use std::cmp::Ordering; use std::collections::BinaryHeap; -pub struct AStar { - pub graph: GridGraph, +pub struct AStar<'a> { + pub graph: &'a GridGraph, } -#[derive(Eq)] +#[derive(Eq, PartialEq)] struct HeapElement { index: u32, cost: EdgeCost, // the cost so far plus the estimated cost until we reach the - // destination + // destination path_cost: EdgeCost, // the cost to reach this node from the start node - ancestor: Option, } impl Ord for HeapElement { @@ -31,12 +30,6 @@ impl PartialOrd for HeapElement { } } -impl PartialEq for HeapElement { - fn eq(&self, other: &Self) -> bool { - self.cost == other.cost - } -} - pub fn estimate_haversine(node: &GraphNode, destination: &GraphNode) -> EdgeCost { // simple haversine distance (node.position.distance_to(&destination.position) * EARTH_RADIUS) as EdgeCost @@ -47,64 +40,72 @@ pub fn estimate_haversine(node: &GraphNode, destination: &GraphNode) -> EdgeCost } pub fn estimate_latitude(node: &GraphNode, destination: &GraphNode) -> EdgeCost { - let lat_dist_a = (node.position.lat - destination.position.lat).abs(); - let lat_dist_b = (destination.position.lat - node.position.lat).abs(); + let lat_dist = (node.position.lat - destination.position.lat).abs(); - (lat_dist_a.min(lat_dist_b) * EARTH_RADIUS) as EdgeCost + (lat_dist * EARTH_RADIUS) as EdgeCost } -impl AStar { - +impl AStar<'_> { pub fn shortest_path(&self, start: &GraphNode, end: &GraphNode, estimate: F) -> Option - where F: Fn(&GraphNode, &GraphNode) -> EdgeCost { + where + F: Fn(&GraphNode, &GraphNode) -> EdgeCost, + { let mut heap = BinaryHeap::new(); heap.push(HeapElement { cost: estimate(start, end), path_cost: 0, index: start.index, - ancestor: None, }); let mut distance = vec![EdgeCost::MAX; self.graph.nodes.len()]; let mut ancestor: Vec> = vec![None; self.graph.nodes.len()]; + distance[start.index as usize] = 0; + let mut popcount = 0; while let Some(HeapElement { index, cost, // the cost value, no longer needed, because it is only important for the - // distance estimate + // distance estimate path_cost, - ancestor: prev, }) = heap.pop() { + //println!("working on node {} with cost {} and path cost {}", index, cost, path_cost); + popcount += 1; // the heap does not support "update" operations, so we // insert elements again and if they come out of the heap but have // been processed earlier (which implies a shorter distance) // we simply skip them. - if distance[index as usize] <= path_cost { + if path_cost > distance[index as usize] { continue; }; - popcount += 1; - - distance[index as usize] = path_cost; - ancestor[index as usize] = prev; if index == end.index { break; } for edge in self.graph.get_edges(index as usize).iter() { + //println!("working on edge {:?}", edge); let new_cost = path_cost + edge.cost; if new_cost < distance[edge.neighbor as usize] { //println!("adding new element to heap"); - heap.push(HeapElement{ + let remaining_cost = estimate(&self.graph.nodes[edge.neighbor as usize], end); + + // check for unreachable nodes + if remaining_cost == EdgeCost::MAX { + continue; + } + + distance[edge.neighbor as usize] = new_cost; + ancestor[edge.neighbor as usize] = Some(index); + + heap.push(HeapElement { index: edge.neighbor, - cost: new_cost + estimate(&self.graph.nodes[edge.neighbor as usize], end), + cost: new_cost + remaining_cost, path_cost: new_cost, - ancestor: Some(index), }); } } @@ -122,10 +123,14 @@ impl AStar { let mut current = end.index; while current != start.index { - route.nodes.push(self.graph.nodes[current as usize].position); + route + .nodes + .push(self.graph.nodes[current as usize].position); current = ancestor[current as usize].unwrap(); } - route.nodes.push(self.graph.nodes[current as usize].position); + route + .nodes + .push(self.graph.nodes[current as usize].position); route.nodes.reverse(); diff --git a/src/bin/performance.rs b/src/bin/performance.rs index 39bf722..f8a3a5e 100644 --- a/src/bin/performance.rs +++ b/src/bin/performance.rs @@ -1,5 +1,5 @@ use clap::Parser; -use fapra_osm_2::alt::LandmarkSet; +use fapra_osm_2::alt::{LandmarkSet, LandmarkBestSet}; use fapra_osm_2::astar::{estimate_haversine, estimate_latitude, AStar}; use fapra_osm_2::gridgraph::{GridGraph, NodeId}; use fapra_osm_2::utils::RoutingQuery; @@ -77,10 +77,9 @@ fn main() { } }; - let mut landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); - landmarks.best_size = 4; + let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); - let astar = AStar { graph: *graph }; + let astar = AStar { graph: &(*graph) }; println!("{:?}", args); @@ -93,7 +92,7 @@ fn main() { let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - let _result = astar.shortest_path(&source, &destination, estimate_latitude); + let _result = astar.shortest_path(&source, &destination, estimate_haversine); } let elapsed = start.elapsed(); @@ -113,7 +112,7 @@ fn main() { let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - let result = astar.graph.shortest_path(&source, &destination); + let _result = astar.graph.shortest_path(&source, &destination); //println!("{}", result.unwrap().to_geojson()); } @@ -126,17 +125,20 @@ fn main() { if args.alt { println!("running ALT"); + + let mut best_landmarks = LandmarkBestSet::new(4, &landmarks); // Landmarks let start = Instant::now(); for query in targets.iter() { + println!("working on {:?}", query); let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - landmarks.select_best(source.index as NodeId, destination.index as NodeId); + best_landmarks.select_best(source.index as NodeId, destination.index as NodeId); let _result = astar.shortest_path(&source, &destination, |src, dest| { - landmarks.estimate(src.index as NodeId, dest.index as NodeId) + best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) }); } diff --git a/src/bin/task6-rocket.rs b/src/bin/task6-rocket.rs index b2aa5b4..29fd886 100644 --- a/src/bin/task6-rocket.rs +++ b/src/bin/task6-rocket.rs @@ -3,10 +3,14 @@ use clap::Parser; use std::process::exit; use std::fs::File; -use fapra_osm_2::gridgraph::{GridGraph, Route}; +use fapra_osm_2::gridgraph::{GridGraph, Route, NodeId}; use fapra_osm_2::coordinates::{RadianCoordinate, DegreeCoordinate}; +use fapra_osm_2::astar::{AStar, estimate_haversine}; +use fapra_osm_2::alt::{LandmarkSet, LandmarkBestSet}; use rand::seq::SliceRandom; use serde::Serialize; +use std::io::BufReader; +use fapra_osm_2::utils::EARTH_RADIUS; use rocket::State; use rocket::response::status::BadRequest; @@ -21,6 +25,10 @@ struct Args { /// name of the FMI file to read #[clap(short, long)] filename: String, + + /// the landmarks to load + #[clap(short, long)] + landmarks: String, } #[get("/")] @@ -47,6 +55,7 @@ fn random_route(graphwrapper: &State) -> String { struct RouteQuery<'r> { r#from: &'r str, r#to: &'r str, + r#algorithm: &'r str, } #[derive(Debug)] @@ -54,6 +63,7 @@ struct RouteQuery<'r> { pub struct RouteResponse { success: bool, route: Option, + algorithm: String, } #[post("/route", data = "")] @@ -71,16 +81,50 @@ fn route(routequery: Form>, graphwrapper: &State) - let from = graphwrapper.graph.get_nearest_node(from).unwrap(); let to = graphwrapper.graph.get_nearest_node(to).unwrap(); - let route = graphwrapper.graph.shortest_path(from, to); + println!("working on route from {:?} to {:?}", from, to); + let direct_distance = from.position.distance_to(&to.position) * EARTH_RADIUS; + println!("haversine distance: {}", direct_distance); + let graph_distance = graphwrapper.graph.shortest_path(from, to).unwrap().cost; + println!("graph distance is: {}", graph_distance); + + let mut algorithm = routequery.algorithm; + + let route = if algorithm == "astar-haversine" { + println!("running A* with haversine distance"); + let astar = AStar{graph: &graphwrapper.graph}; + + astar.shortest_path(from, to, estimate_haversine) + } else if algorithm == "alt" { + println!("running ALT"); + + let mut best_landmarks = LandmarkBestSet::new(4, &graphwrapper.landmarks); + + let astar = AStar{graph: &graphwrapper.graph}; + best_landmarks.select_best(from.index as usize, to.index as usize); + + println!("initial estimate: {:?}", best_landmarks.estimate(from.index as NodeId, to.index as NodeId)); + + astar.shortest_path(from, to, |src, dest| { + best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) + }) + + + } else { + println!("running dijkstra"); + algorithm = "dijkstra"; + graphwrapper.graph.shortest_path(from, to) + }; + println!("from: {:?}, to: {:?}", from, to); - let response = RouteResponse{success: route.is_some(), route}; + let response = RouteResponse{success: route.is_some(), route, algorithm: algorithm.to_string()}; Ok(Json(response)) } struct GraphWrapper { - graph: Box + graph: GridGraph, + landmarks: LandmarkSet, } #[launch] @@ -106,10 +150,21 @@ fn rocket() -> _ { println!("Loaded graph file"); - // let graph = GridGraph::generate_regular_grid(10,10); + let landmarks = match File::open(args.landmarks.clone()) { + Ok(f) => f, + Err(e) => { + println!( + "Error while opening landmark file {}: {:?}", + args.landmarks, e + ); + exit(1); + } + }; + + let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); rocket::build() - .manage(GraphWrapper{graph: graph}) + .manage(GraphWrapper{graph: *graph, landmarks}) .mount("/", routes![index]) .mount("/", routes![random_route]) .mount("/", routes![route]) diff --git a/src/bin/test_alt_gen.rs b/src/bin/test_alt_gen.rs index fb199ec..30951fe 100644 --- a/src/bin/test_alt_gen.rs +++ b/src/bin/test_alt_gen.rs @@ -44,7 +44,7 @@ fn main() { } }; - let set = LandmarkSet::random_set(args.amount, 4, &grid); + let set = LandmarkSet::random_set(args.amount, &grid); let encoded = bincode::serialize(&set).unwrap(); diff --git a/src/gridgraph.rs b/src/gridgraph.rs index c7a00f9..d469da7 100644 --- a/src/gridgraph.rs +++ b/src/gridgraph.rs @@ -291,7 +291,6 @@ impl GridGraph { let mut gridgraph = Box::new(GridGraph::default()); let reader = BufReader::new(file); - let lines = reader.lines(); let mut total_node_count: u32 = 0; diff --git a/static/js/script.js b/static/js/script.js index 627db96..47c6992 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -67,6 +67,11 @@ function set_route_cost(cost) { field.value = cost; } +function set_algorithm_type(alg) { + let field = document.getElementById("algorithm_used"); + field.value = alg; +} + async function get_route() { let form = document.getElementById("queryform"); data = new FormData(form); @@ -74,6 +79,7 @@ async function get_route() { let response = await(fetch("/route", { method: "POST", body: data })); let result = await response.json(); + set_algorithm_type(result.algorithm); if (result.success === true) { set_route_cost(result.route.cost); diff --git a/templates/index.html.hbs b/templates/index.html.hbs index d84b886..d62085a 100644 --- a/templates/index.html.hbs +++ b/templates/index.html.hbs @@ -31,10 +31,20 @@ +
+ + +
+

Route Cost:

+

Algorithm:

From f6198434c7e6e781e69810e0fe35b344110be911 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 12:21:32 +0200 Subject: [PATCH 03/22] added a set of handpicked landmarks --- landmarks/handpicked_44.json | 489 +++++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 landmarks/handpicked_44.json diff --git a/landmarks/handpicked_44.json b/landmarks/handpicked_44.json new file mode 100644 index 0000000..90daa82 --- /dev/null +++ b/landmarks/handpicked_44.json @@ -0,0 +1,489 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -68.5546875, + -56.237244700410315 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 19.84130859375, + -35.281500657891186 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 146.5576171875, + -44.18220395771564 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 168.4423828125, + -47.75409797968002 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -18.6328125, + 17.97873309555617 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -33.046875, + -6.315298538330033 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -83.84765625, + -3.513421045640032 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -33.57421875, + 83.81102365639774 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 21.62109375, + 80.64703474739618 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 103.974609375, + 77.89725517594933 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 191.42578125, + 65.94647177615738 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 26.894531249999996, + 71.46912418989677 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 68.115234375, + 77.07878389624943 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -43.681640625, + 59.265880628258095 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -90.439453125, + 25.085598897064752 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -126.38671874999999, + 40.17887331434696 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -155.7421875, + 71.74643171904148 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -160.02685546875, + 54.380557368630654 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -93.42773437499999, + 74.3785127963314 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 157.236328125, + 50.84757295365389 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 134.033203125, + 39.30029918615029 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 111.884765625, + 8.146242825034385 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 129.638671875, + -5.878332109674314 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 58.798828125, + 18.145851771694467 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 31.289062500000004, + 33.32134852669881 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 34.453125, + 43.16512263158296 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -9.9755859375, + 38.90813299596705 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 1.5380859375, + 51.01375465718821 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 13.2275390625, + 54.901882187385006 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 80.7275390625, + 5.528510525692801 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 119.99267578124999, + 24.647017162630366 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 154.51171875, + -28.613459424004414 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 111.4453125, + -26.03704188651583 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 142.55859375, + 74.44935750063425 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -42.1875, + 29.53522956294847 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 150.46875, + 20.96143961409684 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -18.984375, + -44.840290651397986 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 76.9921875, + -21.616579336740593 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -111.09374999999999, + -32.54681317351514 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -20.0390625, + 60.23981116999893 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -55.37109374999999, + -62.226996036319726 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 25.6640625, + -69.41124235697255 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 122.6953125, + -65.21989393613208 + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + -122.6953125, + -72.81607371878991 + ] + } + } + ] +} From 076b19215a55cf34f27b2a3d64abb9ba201a8708 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 12:42:23 +0200 Subject: [PATCH 04/22] cleaned up binaries --- ...est_alt_gen.rs => gen_landmarks_random.rs} | 0 src/bin/polygon_print.rs | 24 ------------------- 2 files changed, 24 deletions(-) rename src/bin/{test_alt_gen.rs => gen_landmarks_random.rs} (100%) delete mode 100644 src/bin/polygon_print.rs diff --git a/src/bin/test_alt_gen.rs b/src/bin/gen_landmarks_random.rs similarity index 100% rename from src/bin/test_alt_gen.rs rename to src/bin/gen_landmarks_random.rs diff --git a/src/bin/polygon_print.rs b/src/bin/polygon_print.rs deleted file mode 100644 index 98447d4..0000000 --- a/src/bin/polygon_print.rs +++ /dev/null @@ -1,24 +0,0 @@ -use fapra_osm_2::coordinates::RadianCoordinate; -use fapra_osm_2::polygon::Polygon; -use geojson::FeatureCollection; - - -/// a simple program to show the geojson export of polygons -fn main() { - let nodes = vec![ - RadianCoordinate::from_degrees(10.0, 15.0), - RadianCoordinate::from_degrees(20.0, 10.0), - RadianCoordinate::from_degrees(20.0, 25.0), - RadianCoordinate::from_degrees(10.0, 15.0), - ]; - - let outside1 = RadianCoordinate::from_degrees(30.0, 15.0); - let outside2 = RadianCoordinate::from_degrees(-10.0, 15.0); - - let polygon = - Polygon::build_polygon(nodes, outside1, outside2).expect("error while building polygon"); - - let fc: FeatureCollection = polygon.into(); - - println!("{}", fc.to_string()); -} From 2aeb770b8f73c9382cef0b44b9f599d868168bc6 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 12:42:40 +0200 Subject: [PATCH 05/22] added code to generate landmarks with the greedy algorithm --- src/bin/gen_landmarks_greedy.rs | 93 +++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/bin/gen_landmarks_greedy.rs diff --git a/src/bin/gen_landmarks_greedy.rs b/src/bin/gen_landmarks_greedy.rs new file mode 100644 index 0000000..c832d66 --- /dev/null +++ b/src/bin/gen_landmarks_greedy.rs @@ -0,0 +1,93 @@ +use bincode; +use clap::Parser; +use fapra_osm_2::alt::{LandmarkSet, Landmark}; +use fapra_osm_2::gridgraph::{GridGraph, EdgeCost}; +use std::fs::File; +use std::io::prelude::*; +use std::process::exit; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about=None)] +struct Args { + /// the FMI file to load + #[clap(short, long)] + input: String, + + /// the file to which to write the landmarks + #[clap(short, long)] + output: String, + + /// the amount of landmarks to generate + #[clap(short, long)] + amount: usize, +} + +fn main() { + let args = Args::parse(); + + let file = match File::open(args.input.clone()) { + Ok(f) => f, + Err(e) => { + println!("Error while opening file: {}", e); + exit(1); + } + }; + + let graph = GridGraph::from_fmi_file(file).unwrap(); + println!("finished loading graph from file"); + + let mut output = match File::create(args.output.clone()) { + Ok(f) => f, + Err(e) => { + println!("Error while creating the file {}: {}", args.output, e); + exit(2) + } + }; + + fn compute_next_index(costs: &mut Vec, landmark: &Landmark) -> usize { + + let mut max_index = 0; + let mut max_cost = 0; + + for (i, cost) in landmark.distances.iter().enumerate() { + if *cost < costs[i] { + costs[i] = *cost; + } + + if costs[i] > max_cost && costs[i] != EdgeCost::MAX { + max_cost = costs[i]; + max_index = i; + } + } + + max_index + } + + let initial_node = graph.get_random_node().unwrap(); + + println!("selected initial node: {:?}", initial_node); + + + let mut landmark = Landmark::generate(*initial_node, &graph); + + let mut costs: Vec = landmark.distances.clone(); + + let mut set = LandmarkSet::default(); + + let mut next = compute_next_index(&mut costs, &landmark); + set.landmarks.push(landmark); + + while set.landmarks.len() < args.amount { + let node = graph.nodes[next]; + + println!("the next node will be {:?} with a distance of {}", node, costs[node.index as usize]); + + landmark = Landmark::generate(node, &graph); + next = compute_next_index(&mut costs, &landmark); + set.landmarks.push(landmark); + } + + let encoded = bincode::serialize(&set).unwrap(); + + output.write_all(&encoded).expect("Error while writing LandmarkSet data"); +} From f11dda39395a51c22de21454fc0e7e7358d45fd6 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 12:43:25 +0200 Subject: [PATCH 06/22] cleaned up the benchmark programm --- src/bin/performance.rs | 88 +++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/src/bin/performance.rs b/src/bin/performance.rs index f8a3a5e..f629d42 100644 --- a/src/bin/performance.rs +++ b/src/bin/performance.rs @@ -1,6 +1,6 @@ use clap::Parser; -use fapra_osm_2::alt::{LandmarkSet, LandmarkBestSet}; -use fapra_osm_2::astar::{estimate_haversine, estimate_latitude, AStar}; +use fapra_osm_2::alt::{LandmarkBestSet, LandmarkSet}; +use fapra_osm_2::astar::{estimate_haversine, AStar}; use fapra_osm_2::gridgraph::{GridGraph, NodeId}; use fapra_osm_2::utils::RoutingQuery; use serde_json; @@ -20,9 +20,9 @@ struct Args { #[clap(short, long)] targets: String, - /// landmark file + /// landmark file. ALT Benchmarks are only run, if this is given. #[clap(short, long)] - landmarks: String, + landmarks: Option, /// run dijkstra #[clap(long, action)] @@ -32,9 +32,9 @@ struct Args { #[clap(long, action)] astar: bool, - /// run ALT - #[clap(long, action)] - alt: bool, + /// How many of the given landmarks to select + #[clap(long, action, default_value_t = 4)] + alt_best_size: usize, } fn main() { @@ -56,6 +56,8 @@ fn main() { } }; + let astar = AStar { graph: &(*graph) }; + let targets = match File::open(args.targets.clone()) { Ok(f) => f, Err(e) => { @@ -66,22 +68,44 @@ fn main() { let targets: Vec = serde_json::from_reader(BufReader::new(targets)).unwrap(); - let landmarks = match File::open(args.landmarks.clone()) { - Ok(f) => f, - Err(e) => { - println!( - "Error while opening landmark file {}: {:?}", - args.landmarks, e - ); - exit(1); + if let Some(landmark_path) = args.landmarks { + let landmarks = match File::open(landmark_path.clone()) { + Ok(f) => f, + Err(e) => { + println!( + "Error while opening landmark file {}: {:?}", + landmark_path, e + ); + exit(1); + } + }; + + let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); + + println!("running ALT"); + + let mut best_landmarks = LandmarkBestSet::new(args.alt_best_size, &landmarks); + + let start = Instant::now(); + + for query in targets.iter() { + // println!("working on {:?}", query); + let source = astar.graph.nodes[query.source]; + let destination = astar.graph.nodes[query.destination]; + + best_landmarks.select_best(source.index as NodeId, destination.index as NodeId); + + let _result = astar.shortest_path(&source, &destination, |src, dest| { + best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) + }); } - }; - let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); + let elapsed = start.elapsed(); - let astar = AStar { graph: &(*graph) }; + let time_per_route = elapsed.as_secs_f64() / (targets.len() as f64); - println!("{:?}", args); + println!("It took {} seconds per route for ALT.", time_per_route); + } if args.astar { println!("running A*"); @@ -122,30 +146,4 @@ fn main() { let time_per_route = elapsed.as_secs_f64() / (targets.len() as f64); println!("It took {} seconds per round for Dijkstra.", time_per_route); } - - if args.alt { - println!("running ALT"); - - let mut best_landmarks = LandmarkBestSet::new(4, &landmarks); - // Landmarks - let start = Instant::now(); - - for query in targets.iter() { - println!("working on {:?}", query); - let source = astar.graph.nodes[query.source]; - let destination = astar.graph.nodes[query.destination]; - - best_landmarks.select_best(source.index as NodeId, destination.index as NodeId); - - let _result = astar.shortest_path(&source, &destination, |src, dest| { - best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) - }); - } - - let elapsed = start.elapsed(); - - let time_per_route = elapsed.as_secs_f64() / (targets.len() as f64); - - println!("It took {} seconds per route for ALT.", time_per_route); - } } From 32a81b35619f4aa46c32491d1904603955adb07e Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 12:43:52 +0200 Subject: [PATCH 07/22] cleaned up the web UI code --- src/bin/task6-rocket.rs | 88 ++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/src/bin/task6-rocket.rs b/src/bin/task6-rocket.rs index 29fd886..5460806 100644 --- a/src/bin/task6-rocket.rs +++ b/src/bin/task6-rocket.rs @@ -1,23 +1,24 @@ -#[macro_use] extern crate rocket; +#[macro_use] +extern crate rocket; use clap::Parser; -use std::process::exit; -use std::fs::File; -use fapra_osm_2::gridgraph::{GridGraph, Route, NodeId}; -use fapra_osm_2::coordinates::{RadianCoordinate, DegreeCoordinate}; -use fapra_osm_2::astar::{AStar, estimate_haversine}; -use fapra_osm_2::alt::{LandmarkSet, LandmarkBestSet}; +use fapra_osm_2::alt::{LandmarkBestSet, LandmarkSet}; +use fapra_osm_2::astar::{estimate_haversine, AStar}; +use fapra_osm_2::coordinates::{DegreeCoordinate, RadianCoordinate}; +use fapra_osm_2::gridgraph::{GridGraph, NodeId, Route}; use rand::seq::SliceRandom; use serde::Serialize; +use std::fs::File; use std::io::BufReader; -use fapra_osm_2::utils::EARTH_RADIUS; +use std::process::exit; +use std::time::Instant; -use rocket::State; -use rocket::response::status::BadRequest; use rocket::form::Form; use rocket::fs::FileServer; +use rocket::response::status::BadRequest; use rocket::serde::json::Json; -use rocket_dyn_templates::{Template, context, Engines}; +use rocket::State; +use rocket_dyn_templates::{context, Engines, Template}; #[derive(Parser, Debug)] #[clap(author, version, about, long_about=None)] @@ -58,8 +59,7 @@ struct RouteQuery<'r> { r#algorithm: &'r str, } -#[derive(Debug)] -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct RouteResponse { success: bool, route: Option, @@ -67,64 +67,74 @@ pub struct RouteResponse { } #[post("/route", data = "")] -fn route(routequery: Form>, graphwrapper: &State) -> Result, BadRequest> { - +fn route( + routequery: Form>, + graphwrapper: &State, +) -> Result, BadRequest> { let from = match DegreeCoordinate::from_string_tuple(routequery.from) { Ok(from) => RadianCoordinate::from(from), - Err(e) => {return Result::Err(BadRequest(Some(format!("Error while parsing from field: {:?}", e))));} + Err(e) => { + return Result::Err(BadRequest(Some(format!( + "Error while parsing from field: {:?}", + e + )))); + } }; let to = match DegreeCoordinate::from_string_tuple(routequery.to) { Ok(to) => RadianCoordinate::from(to), - Err(e) => {return Result::Err(BadRequest(Some(format!("Error while parsing to field: {:?}", e))));} + Err(e) => { + return Result::Err(BadRequest(Some(format!( + "Error while parsing to field: {:?}", + e + )))); + } }; let from = graphwrapper.graph.get_nearest_node(from).unwrap(); let to = graphwrapper.graph.get_nearest_node(to).unwrap(); - println!("working on route from {:?} to {:?}", from, to); - let direct_distance = from.position.distance_to(&to.position) * EARTH_RADIUS; - println!("haversine distance: {}", direct_distance); - let graph_distance = graphwrapper.graph.shortest_path(from, to).unwrap().cost; - println!("graph distance is: {}", graph_distance); + println!("Working on a route from {:?} to {:?}", from, to); + + let astar = AStar { + graph: &graphwrapper.graph, + }; let mut algorithm = routequery.algorithm; + let start = Instant::now(); + let route = if algorithm == "astar-haversine" { println!("running A* with haversine distance"); - let astar = AStar{graph: &graphwrapper.graph}; - astar.shortest_path(from, to, estimate_haversine) } else if algorithm == "alt" { println!("running ALT"); - let mut best_landmarks = LandmarkBestSet::new(4, &graphwrapper.landmarks); - let astar = AStar{graph: &graphwrapper.graph}; best_landmarks.select_best(from.index as usize, to.index as usize); - - println!("initial estimate: {:?}", best_landmarks.estimate(from.index as NodeId, to.index as NodeId)); - astar.shortest_path(from, to, |src, dest| { best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) }) - - } else { println!("running dijkstra"); algorithm = "dijkstra"; graphwrapper.graph.shortest_path(from, to) }; + let time = start.elapsed(); - println!("from: {:?}, to: {:?}", from, to); + println!("The query took {} ms", time.as_millis()); - let response = RouteResponse{success: route.is_some(), route, algorithm: algorithm.to_string()}; + let response = RouteResponse { + success: route.is_some(), + route, + algorithm: algorithm.to_string(), + }; Ok(Json(response)) } struct GraphWrapper { graph: GridGraph, - landmarks: LandmarkSet, + landmarks: LandmarkSet, } #[launch] @@ -164,11 +174,15 @@ fn rocket() -> _ { let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); rocket::build() - .manage(GraphWrapper{graph: *graph, landmarks}) + .manage(GraphWrapper { + graph: *graph, + landmarks, + }) .mount("/", routes![index]) .mount("/", routes![random_route]) .mount("/", routes![route]) .mount("/static", FileServer::from("static")) - .attach(Template::custom(|engines: &mut Engines| {engines.handlebars.set_dev_mode(true);})) - + .attach(Template::custom(|engines: &mut Engines| { + engines.handlebars.set_dev_mode(true); + })) } From 367fc0ef3e0eac10e9878dd542ad11522abdb08f Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 12:44:54 +0200 Subject: [PATCH 08/22] moved the route reconstruction into the Route struct --- src/astar.rs | 41 +++++++++--------------------------- src/gridgraph.rs | 55 ++++++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/astar.rs b/src/astar.rs index 1d6afe4..abaf0ce 100644 --- a/src/astar.rs +++ b/src/astar.rs @@ -8,11 +8,17 @@ pub struct AStar<'a> { pub graph: &'a GridGraph, } +/// An element on the Heap for A*. +/// The Ord and PartialOrd Traits are inverted so that the MaxHeap works as a +/// MinHeap. +/// index is the index of the Node, cost is the cost which the heap sorts after. +/// path_cost is the actual cost of the path to that node. #[derive(Eq, PartialEq)] struct HeapElement { index: u32, - cost: EdgeCost, // the cost so far plus the estimated cost until we reach the - // destination + + // the cost so far plus the estimated cost until we reach the destination + cost: EdgeCost, path_cost: EdgeCost, // the cost to reach this node from the start node } @@ -64,10 +70,7 @@ impl AStar<'_> { let mut popcount = 0; while let Some(HeapElement { - index, - cost, // the cost value, no longer needed, because it is only important for the - // distance estimate - path_cost, + index, path_cost, .. }) = heap.pop() { //println!("working on node {} with cost {} and path cost {}", index, cost, path_cost); @@ -113,30 +116,6 @@ impl AStar<'_> { println!("popped {} elements from the heap", popcount); - // now the route calculation is done. If a route exist we can construct - // it from the ancestors. - if ancestor[end.index as usize].is_some() { - let mut route = Route { - cost: distance[end.index as usize], - nodes: Vec::new(), - }; - - let mut current = end.index; - while current != start.index { - route - .nodes - .push(self.graph.nodes[current as usize].position); - current = ancestor[current as usize].unwrap(); - } - route - .nodes - .push(self.graph.nodes[current as usize].position); - - route.nodes.reverse(); - - return Some(route); - } - - None + Route::construct(self.graph, &ancestor, &distance, start, end) } } diff --git a/src/gridgraph.rs b/src/gridgraph.rs index d469da7..2fc00fb 100644 --- a/src/gridgraph.rs +++ b/src/gridgraph.rs @@ -107,6 +107,34 @@ impl LookupGrid { } impl Route { + + /// Constructs a route from the start to the end node on the graph + /// based on the ancestor and distance lists of a routing algorithm. + pub fn construct(graph: &GridGraph, ancestors: &Vec>, distance: &Vec, start: &GraphNode, end: &GraphNode) -> Option{ + if ancestors[end.index as usize].is_some() { + let mut route = Route { + cost: distance[end.index as usize], + nodes: Vec::new(), + }; + + let mut current = end.index; + while current != start.index { + route + .nodes + .push(graph.nodes[current as usize].position); + current = ancestors[current as usize].unwrap(); + } + route + .nodes + .push(graph.nodes[current as usize].position); + + route.nodes.reverse(); + + return Some(route); + } + None + } + pub fn to_geojson(&self) -> Box { let mut features: Box = Box::new(FeatureCollection { bbox: None, @@ -205,10 +233,7 @@ impl GridGraph { //println!("working on node {} with cost {}", index, cost); - let edge_start = self.edge_offsets[index as usize] as usize; - let edge_end = self.edge_offsets[(index + 1) as usize] as usize; - - for edge in self.edges[edge_start..edge_end].iter() { + for edge in self.get_edges(index as usize).iter() { let new_cost = cost + edge.cost; if new_cost < distance[edge.neighbor as usize] { @@ -228,27 +253,7 @@ impl GridGraph { println!("popped {} elements from the heap", popcount); - // now the route calculation is done. If a route exist we can construct - // it from the ancestors. - if ancestor[end.index as usize].is_some() { - let mut route = Route { - cost: distance[end.index as usize], - nodes: Vec::new(), - }; - - let mut current = end.index; - while current != start.index { - route.nodes.push(self.nodes[current as usize].position); - current = ancestor[current as usize].unwrap(); - } - route.nodes.push(self.nodes[current as usize].position); - - route.nodes.reverse(); - - return Some(route); - } - - None + Route::construct(self, &ancestor, &distance, start, end) } /// returns the GraphNode nearest to that positon. From 0821515ab91b389024033ac2160227a34462fe08 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 14:18:11 +0200 Subject: [PATCH 09/22] added README and landmark generation helper --- README.md | 112 ++++++++++++++++++++++++++++++++++++ utils/generate_landmarks.py | 17 ++++++ 2 files changed, 129 insertions(+) create mode 100644 README.md create mode 100755 utils/generate_landmarks.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..20036e1 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# FaPra Algorithms on OSM Data + +This repository contains implementations for the Fachpraktikum Algorithms on +OSM Data. The code is written in Rust, a stable rust compiler and `cargo`, the +rust build tool/package manager is required. + +# Building + +simply run `cargo build --release` to build release versions of all binaries. + +# Tasks + +## Task 1 + +There is no real implementation work for task 1, next! + +## Task 2 + Task 3 + Task 4 + +Reading the data from an OSM PDF file and converting it to a graph is done in +`src/bin/generate_grid.rs`. + +The implementation of the spherical point in polygon test is done in `src/polygon.rs` +with the function `contains()`. + +There is one polygon in the graph, for which no valid outside polygon can be found. +I did not have the time to investigate this further. + +### Extracting Coastlines from the PBF file + +The code uses the osmpbfreader crate. +Sadly this module uses ~10GB of memory to extract the data from the PBF file +with all the coastlines. So far I did not have time to look into what happens +there. + +### Point in Polygon + +The test by Bevis and Chatelain is implemented. +Instead of using a point that is inside the polygon a point outside of the +polygon is used, because here we can simply specify several "well-known" outside +points that are somewhere in the ocean and therefore are outside of every polygon. + +### Grid Graph + +The Grid Graph is implemented in `gridgraph.rs`. + +A regular grid graph can be generated with the `generate_regular_grid` function. +Import and Export from/to a file can be done with the `from_fmi_file` and `write_fmi_file` functions. + +## Task 5 + +### Dijkstra Benchmarks + +The dijkstras algorithm is implenented in `gridgraph.rs`. +It uses a Heap to store the nodes. + +## Task 6 + +The UI is a Web-UI based on leaflet. +To start it, run `task6` with a .fmi file as a graph layout and a set of landmarks +(see Task 7 for details). + +The start and end nodes can be placed on arbitrary positions and an algorithm +searches for the closes grid node and then runs the Routing. + +The webserver listens on http://localhost:8000 + +Currently there is a display bug when routes wrap around the globe at 180 degrees +longitude. Instead of continuing the line as expected the line is drawn around +the globe in the "wrong" direction. +This is however just a display bug, the route itself is correct. + +## Task 7 + +I implemented ALT, as described in [1]. +Additionally A\* is available with a simple, unoptimized haversine distance +as the heuristic. + +### Landmarks for ALT + +currently 3 different landmark generation methods are available + +- random selection +- greedy, distance-maximizing selection +- manual selection (from a GeoJSON file) + +These can be generated with the `gen_landmarks_random`, `gen_landmarks_greedy` +and `gen_landmarks_geojson` binaries. The random and greedy methods take +the number of landmarks to select from a parameter. + +A handy wrapper for that can be found as a Python script in `utils/generate_landmarks.py` that +generates landmarks for 4, 8, 16, 32 and 64 landmarks, both greedy and random. + +# Running the benchmarks + +First a set of queries is needed. +This can be done with the `generate_benchmark_targets --graph > targets.json`. +This generates 1000 random, distinct source and destination pairs. +The `--amount` parameter allows to adjust the number of pairs generated. + +The actual benchmark is located in `benchmark`. +It needs a set of queries and a graph. + +If the `--dijkstra` option is given it runs Dijkstras algorithm. +If the `--astar` option is given it runs A\* with the haversine distance +If `--landmarks ` is given it runs ALT with that landmark file. +By setting `--alt_best_size ` one can select how many of the landmarks +are used to answer the query. + +The benchmark prints out how many nodes were popped from the heap for +each run and the average time per route. + +[1] Computing the Shortest Path: A* meets Graph Theory, A. Goldberg and C. Harrelson, Microsoft Research, Technical Report MSR-TR-2004-24, 2004 diff --git a/utils/generate_landmarks.py b/utils/generate_landmarks.py new file mode 100755 index 0000000..60fd000 --- /dev/null +++ b/utils/generate_landmarks.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +from os import system +from sys import argv, exit + +if len(argv) != 3: + print(f"usage: {argv[0]} ") + exit(1) + +graph = argv[1] +output = argv[2] + +for i in range(2, 7): + num = 2**i + + system(f"cargo run --release --bin=gen_landmarks_greedy -- --input={graph} --output={output}_greedy_{ num }.bin --amount { num }") + system(f"cargo run --release --bin=gen_landmarks_random -- --input={graph} --output={output}_random_{ num }.bin --amount { num }") From 6f6df97af8301b88e913db486bda0e5182784f84 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 14:19:38 +0200 Subject: [PATCH 10/22] updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 20036e1..c3ded3c 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Import and Export from/to a file can be done with the `from_fmi_file` and `write The dijkstras algorithm is implenented in `gridgraph.rs`. It uses a Heap to store the nodes. +On details on how to run benchmarks see the benchmarks session at the end. ## Task 6 From 1067c6a51478fc02fb73a837c130b22d7a957a57 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 15:42:06 +0200 Subject: [PATCH 11/22] refactored data loading code --- src/bin/gen_landmarks_greedy.rs | 29 ++++++++---------- src/bin/gen_landmarks_random.rs | 17 +++-------- src/bin/generate_benchmark_targets.rs | 19 ++---------- src/bin/grid_to_geojson.rs | 18 +++--------- src/bin/performance.rs | 35 ++++------------------ src/bin/task6-rocket.rs | 38 +++--------------------- src/utils.rs | 42 ++++++++++++++++++++++++++- utils/generate_landmarks.py | 4 +-- 8 files changed, 74 insertions(+), 128 deletions(-) diff --git a/src/bin/gen_landmarks_greedy.rs b/src/bin/gen_landmarks_greedy.rs index c832d66..9352115 100644 --- a/src/bin/gen_landmarks_greedy.rs +++ b/src/bin/gen_landmarks_greedy.rs @@ -1,7 +1,8 @@ use bincode; use clap::Parser; -use fapra_osm_2::alt::{LandmarkSet, Landmark}; -use fapra_osm_2::gridgraph::{GridGraph, EdgeCost}; +use fapra_osm_2::alt::{Landmark, LandmarkSet}; +use fapra_osm_2::gridgraph::EdgeCost; +use fapra_osm_2::utils::load_graph; use std::fs::File; use std::io::prelude::*; use std::process::exit; @@ -11,7 +12,7 @@ use std::process::exit; struct Args { /// the FMI file to load #[clap(short, long)] - input: String, + graph: String, /// the file to which to write the landmarks #[clap(short, long)] @@ -25,16 +26,7 @@ struct Args { fn main() { let args = Args::parse(); - let file = match File::open(args.input.clone()) { - Ok(f) => f, - Err(e) => { - println!("Error while opening file: {}", e); - exit(1); - } - }; - - let graph = GridGraph::from_fmi_file(file).unwrap(); - println!("finished loading graph from file"); + let graph = load_graph(&args.graph); let mut output = match File::create(args.output.clone()) { Ok(f) => f, @@ -45,7 +37,6 @@ fn main() { }; fn compute_next_index(costs: &mut Vec, landmark: &Landmark) -> usize { - let mut max_index = 0; let mut max_cost = 0; @@ -67,7 +58,6 @@ fn main() { println!("selected initial node: {:?}", initial_node); - let mut landmark = Landmark::generate(*initial_node, &graph); let mut costs: Vec = landmark.distances.clone(); @@ -80,7 +70,10 @@ fn main() { while set.landmarks.len() < args.amount { let node = graph.nodes[next]; - println!("the next node will be {:?} with a distance of {}", node, costs[node.index as usize]); + println!( + "the next node will be {:?} with a distance of {}", + node, costs[node.index as usize] + ); landmark = Landmark::generate(node, &graph); next = compute_next_index(&mut costs, &landmark); @@ -89,5 +82,7 @@ fn main() { let encoded = bincode::serialize(&set).unwrap(); - output.write_all(&encoded).expect("Error while writing LandmarkSet data"); + output + .write_all(&encoded) + .expect("Error while writing LandmarkSet data"); } diff --git a/src/bin/gen_landmarks_random.rs b/src/bin/gen_landmarks_random.rs index 30951fe..438fec7 100644 --- a/src/bin/gen_landmarks_random.rs +++ b/src/bin/gen_landmarks_random.rs @@ -1,7 +1,7 @@ use bincode; use clap::Parser; use fapra_osm_2::alt::LandmarkSet; -use fapra_osm_2::gridgraph::GridGraph; +use fapra_osm_2::utils::load_graph; use std::fs::File; use std::io::prelude::*; use std::process::exit; @@ -11,7 +11,7 @@ use std::process::exit; struct Args { /// the FMI file to load #[clap(short, long)] - input: String, + graph: String, /// the file to which to write the landmarks #[clap(short, long)] @@ -25,16 +25,7 @@ struct Args { fn main() { let args = Args::parse(); - let file = match File::open(args.input.clone()) { - Ok(f) => f, - Err(e) => { - println!("Error while opening file: {}", e); - exit(1); - } - }; - - let grid = GridGraph::from_fmi_file(file).unwrap(); - println!("finished loading grid from file"); + let graph = load_graph(&args.graph); let mut output = match File::create(args.output.clone()) { Ok(f) => f, @@ -44,7 +35,7 @@ fn main() { } }; - let set = LandmarkSet::random_set(args.amount, &grid); + let set = LandmarkSet::random_set(args.amount, &graph); let encoded = bincode::serialize(&set).unwrap(); diff --git a/src/bin/generate_benchmark_targets.rs b/src/bin/generate_benchmark_targets.rs index d2e58b6..92e566f 100644 --- a/src/bin/generate_benchmark_targets.rs +++ b/src/bin/generate_benchmark_targets.rs @@ -1,5 +1,4 @@ -use fapra_osm_2::gridgraph::GridGraph; -use std::fs::File; +use fapra_osm_2::utils::load_graph; use clap::Parser; use std::process::exit; use rand::seq::SliceRandom; @@ -21,21 +20,7 @@ struct Args { fn main() { let args = Args::parse(); - let file = match File::open(args.graph.clone()) { - Ok(f) => f, - Err(e) => { - println!("Error while opening the file {}: {}", args.graph, e); - exit(1) - } - }; - - let graph = match GridGraph::from_fmi_file(file) { - Ok(g) => g, - Err(e) => { - println!("Error while reading the graph: {:?}", e); - exit(1); - } - }; + let graph = load_graph(&args.graph); let mut rng = rand::thread_rng(); diff --git a/src/bin/grid_to_geojson.rs b/src/bin/grid_to_geojson.rs index e6fc2f6..ea78b91 100644 --- a/src/bin/grid_to_geojson.rs +++ b/src/bin/grid_to_geojson.rs @@ -1,28 +1,18 @@ -use fapra_osm_2::gridgraph::GridGraph; +use fapra_osm_2::utils::load_graph; use clap::Parser; -use std::fs::File; -use std::process::exit; #[derive(Parser, Debug)] #[clap(author, version, about, long_about=None)] struct Args { /// the FMI file to load #[clap(short, long)] - input: String, + graph: String, } fn main() { let args = Args::parse(); - let file = match File::open(args.input.clone()) { - Ok(f) => f, - Err(e) => { - println!("Error while opening file: {}", e); - exit(1); - } - }; + let graph = load_graph(&args.graph); - let grid = GridGraph::from_fmi_file(file).unwrap(); - - println!("{}", grid.to_geojson().to_string()); + println!("{}", graph.to_geojson().to_string()); } diff --git a/src/bin/performance.rs b/src/bin/performance.rs index f629d42..a284fa7 100644 --- a/src/bin/performance.rs +++ b/src/bin/performance.rs @@ -1,8 +1,8 @@ use clap::Parser; -use fapra_osm_2::alt::{LandmarkBestSet, LandmarkSet}; +use fapra_osm_2::alt::LandmarkBestSet; use fapra_osm_2::astar::{estimate_haversine, AStar}; -use fapra_osm_2::gridgraph::{GridGraph, NodeId}; -use fapra_osm_2::utils::RoutingQuery; +use fapra_osm_2::gridgraph::NodeId; +use fapra_osm_2::utils::{load_graph, load_landmarks, RoutingQuery}; use serde_json; use std::fs::File; use std::io::BufReader; @@ -40,21 +40,7 @@ struct Args { fn main() { let args = Args::parse(); - let file = match File::open(args.graph.clone()) { - Ok(f) => f, - Err(e) => { - println!("Error while opening the file {}: {}", args.graph, e); - exit(1) - } - }; - - let graph = match GridGraph::from_fmi_file(file) { - Ok(g) => g, - Err(e) => { - println!("Error while reading the graph: {:?}", e); - exit(1); - } - }; + let graph = load_graph(&args.graph); let astar = AStar { graph: &(*graph) }; @@ -69,18 +55,7 @@ fn main() { let targets: Vec = serde_json::from_reader(BufReader::new(targets)).unwrap(); if let Some(landmark_path) = args.landmarks { - let landmarks = match File::open(landmark_path.clone()) { - Ok(f) => f, - Err(e) => { - println!( - "Error while opening landmark file {}: {:?}", - landmark_path, e - ); - exit(1); - } - }; - - let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); + let landmarks = load_landmarks(&landmark_path); println!("running ALT"); diff --git a/src/bin/task6-rocket.rs b/src/bin/task6-rocket.rs index 5460806..fc10302 100644 --- a/src/bin/task6-rocket.rs +++ b/src/bin/task6-rocket.rs @@ -6,11 +6,9 @@ use fapra_osm_2::alt::{LandmarkBestSet, LandmarkSet}; use fapra_osm_2::astar::{estimate_haversine, AStar}; use fapra_osm_2::coordinates::{DegreeCoordinate, RadianCoordinate}; use fapra_osm_2::gridgraph::{GridGraph, NodeId, Route}; +use fapra_osm_2::utils::{load_graph, load_landmarks}; use rand::seq::SliceRandom; use serde::Serialize; -use std::fs::File; -use std::io::BufReader; -use std::process::exit; use std::time::Instant; use rocket::form::Form; @@ -25,7 +23,7 @@ use rocket_dyn_templates::{context, Engines, Template}; struct Args { /// name of the FMI file to read #[clap(short, long)] - filename: String, + graph: String, /// the landmarks to load #[clap(short, long)] @@ -141,37 +139,9 @@ struct GraphWrapper { fn rocket() -> _ { let args = Args::parse(); - println!("Loading file from {}", args.filename); - let file = match File::open(args.filename.clone()) { - Ok(f) => f, - Err(e) => { - println!("Error while opening the file {}: {}", args.filename, e); - exit(1) - } - }; + let graph = load_graph(&args.graph); - let graph = match GridGraph::from_fmi_file(file) { - Ok(g) => g, - Err(e) => { - println!("Error while reading the graph: {:?}", e); - exit(1); - } - }; - - println!("Loaded graph file"); - - let landmarks = match File::open(args.landmarks.clone()) { - Ok(f) => f, - Err(e) => { - println!( - "Error while opening landmark file {}: {:?}", - args.landmarks, e - ); - exit(1); - } - }; - - let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); + let landmarks = load_landmarks(&args.landmarks); rocket::build() .manage(GraphWrapper { diff --git a/src/utils.rs b/src/utils.rs index 7cc84e4..3d09969 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,49 @@ +use crate::alt::LandmarkSet; +use crate::gridgraph::GridGraph; use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::BufReader; +use std::process::exit; pub const EARTH_RADIUS: f64 = 6_371_000.0; // meters #[derive(Serialize, Deserialize, Debug, Copy, Clone)] -pub struct RoutingQuery{ +pub struct RoutingQuery { pub source: usize, pub destination: usize, } + +pub fn load_graph(path: &str) -> Box { + println!("Loading file from {}", path); + let file = match File::open(path) { + Ok(f) => f, + Err(e) => { + println!("Error while opening the file {}: {}", path, e); + exit(1) + } + }; + + let graph = match GridGraph::from_fmi_file(file) { + Ok(g) => g, + Err(e) => { + println!("Error while reading the graph: {:?}", e); + exit(1); + } + }; + + println!("Loaded graph file"); + + graph +} + +pub fn load_landmarks(path: &str) -> LandmarkSet { + let landmarks = match File::open(path) { + Ok(f) => f, + Err(e) => { + println!("Error while opening landmark file {}: {:?}", path, e); + exit(1); + } + }; + + bincode::deserialize_from(BufReader::new(landmarks)).unwrap() +} diff --git a/utils/generate_landmarks.py b/utils/generate_landmarks.py index 60fd000..88356b7 100755 --- a/utils/generate_landmarks.py +++ b/utils/generate_landmarks.py @@ -13,5 +13,5 @@ output = argv[2] for i in range(2, 7): num = 2**i - system(f"cargo run --release --bin=gen_landmarks_greedy -- --input={graph} --output={output}_greedy_{ num }.bin --amount { num }") - system(f"cargo run --release --bin=gen_landmarks_random -- --input={graph} --output={output}_random_{ num }.bin --amount { num }") + system(f"cargo run --release --bin=gen_landmarks_greedy -- --graph={graph} --output={output}_greedy_{ num }.bin --amount { num }") + system(f"cargo run --release --bin=gen_landmarks_random -- --graph={graph} --output={output}_random_{ num }.bin --amount { num }") From a2cb9520ccdd80936c4ca9b87e18a69c59b10fc4 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 16:29:42 +0200 Subject: [PATCH 12/22] added binary to generate landmarks from geojson file --- src/bin/gen_landmarks_geojson.rs | 94 ++++++++++++++++++++++++++++++++ src/coordinates.rs | 15 +++++ 2 files changed, 109 insertions(+) create mode 100644 src/bin/gen_landmarks_geojson.rs diff --git a/src/bin/gen_landmarks_geojson.rs b/src/bin/gen_landmarks_geojson.rs new file mode 100644 index 0000000..32c8fe0 --- /dev/null +++ b/src/bin/gen_landmarks_geojson.rs @@ -0,0 +1,94 @@ +use bincode; +use clap::Parser; +use fapra_osm_2::alt::{Landmark, LandmarkSet}; +use fapra_osm_2::utils::load_graph; +use fapra_osm_2::coordinates::{RadianCoordinate, DegreeCoordinate}; +use std::fs::{read_to_string, File}; +use std::io::prelude::*; +use std::process::exit; +use geojson::{GeoJson, Value}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about=None)] +struct Args { + /// the FMI file to load + #[clap(short, long)] + graph: String, + + /// the file to which to write the landmarks + #[clap(short, long)] + output: String, + + /// the geojson file from which to load the landmarks + #[clap(short, long)] + geojson: String, +} + +fn main() { + let args = Args::parse(); + + let graph = load_graph(&args.graph); + + let mut output = match File::create(args.output.clone()) { + Ok(f) => f, + Err(e) => { + println!("Error while creating the file {}: {}", args.output, e); + exit(2) + } + }; + + let geojson_str = match read_to_string(args.geojson.clone()) { + Ok(f) => f, + Err(e) => { + println!("Error while opening the file {}: {}", args.geojson, e); + exit(2) + } + }; + + let geojson: GeoJson = geojson_str.parse::().unwrap(); + + let features = match geojson { + GeoJson::FeatureCollection(features) => features.features, + _ => { + println!("Expected a FeatureCollection, didn't get one!"); + exit(3); + } + }; + + + // parse the GeoJSON + let mut points: Vec = Vec::new(); + + for feature in features.iter() { + if let Some(geometry) = &feature.geometry { + if let Value::Point(point) = &geometry.value { + if let Ok(coordinate) = DegreeCoordinate::from_geojson_position(point.clone()) { + points.push(RadianCoordinate::from(coordinate)); + continue; + } + } + } + println!("failed to parse {:?}", feature); + exit(4); + } + + let mut set = LandmarkSet::default(); + + // generate the landmarks + for position in points.iter() { + if let Some(node) = graph.get_nearest_node(*position) { + let landmark = Landmark::generate(*node, &graph); + set.landmarks.push(landmark); + } else { + println!("Could not find a nearest node to {:?}", position); + exit(5); + } + } + + + let encoded = bincode::serialize(&set).unwrap(); + + output + .write_all(&encoded) + .expect("Error while writing LandmarkSet data"); +} diff --git a/src/coordinates.rs b/src/coordinates.rs index 560ef87..0c7fbd5 100644 --- a/src/coordinates.rs +++ b/src/coordinates.rs @@ -51,6 +51,7 @@ impl From for DegreeCoordinate { } } + impl RadianCoordinate { /// Builds a RadianCoordinate from latitude and longitude given in /// degrees. @@ -154,6 +155,20 @@ impl DegreeCoordinate { Ok(DegreeCoordinate{lat, lon}) } + + + /// tries to parse a DegreeCoordinate from a GeoJSON Position + pub fn from_geojson_position(position: geojson::Position) -> Result { + + if position.len() != 2 { + return Err("String has more than 2 values".to_string()); + } + + let lat = position[1]; + let lon = position[0]; + + Ok(DegreeCoordinate { lat, lon }) + } } /// normalizes longitude values given in radians to the range (-PI, PI] From 0b3d2db13552defcebcf1f15f92165f6319aa800 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 19:47:01 +0200 Subject: [PATCH 13/22] renamed handpicked landmarks --- landmarks/{handpicked_44.json => ocean_handpicked_44.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename landmarks/{handpicked_44.json => ocean_handpicked_44.json} (100%) diff --git a/landmarks/handpicked_44.json b/landmarks/ocean_handpicked_44.json similarity index 100% rename from landmarks/handpicked_44.json rename to landmarks/ocean_handpicked_44.json From cab977451c0b6ee626afdc3f32c201a211c58ae4 Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 19:48:30 +0200 Subject: [PATCH 14/22] fixed ALT using more landmarks than available --- src/alt.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/alt.rs b/src/alt.rs index 3b3c0c6..4afb7f0 100644 --- a/src/alt.rs +++ b/src/alt.rs @@ -137,7 +137,8 @@ impl LandmarkBestSet<'_> { results.reverse(); self.best_landmarks.clear(); - for result in results[..self.best_size].iter() { + + for result in results[..(self.best_size.min(results.len()))].iter() { self.best_landmarks.push(result.0); } } From a9fd341b45c9d591d9a7d92b0b38f82a2e20d64f Mon Sep 17 00:00:00 2001 From: Johannes Erwerle Date: Thu, 15 Sep 2022 19:48:57 +0200 Subject: [PATCH 15/22] updated README, added benchmark and plotting functions --- README.md | 23 +++++++---- utils/plot_results.py | 90 +++++++++++++++++++++++++++++++++++++++++ utils/run_benchmarks.py | 28 +++++++++++++ 3 files changed, 134 insertions(+), 7 deletions(-) create mode 100755 utils/plot_results.py create mode 100755 utils/run_benchmarks.py diff --git a/README.md b/README.md index c3ded3c..fbda4e9 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Reading the data from an OSM PDF file and converting it to a graph is done in `src/bin/generate_grid.rs`. The implementation of the spherical point in polygon test is done in `src/polygon.rs` -with the function `contains()`. +with the function `Polygon::contains`. There is one polygon in the graph, for which no valid outside polygon can be found. I did not have the time to investigate this further. @@ -29,8 +29,7 @@ I did not have the time to investigate this further. The code uses the osmpbfreader crate. Sadly this module uses ~10GB of memory to extract the data from the PBF file -with all the coastlines. So far I did not have time to look into what happens -there. +with all the coastlines. ### Point in Polygon @@ -50,9 +49,9 @@ Import and Export from/to a file can be done with the `from_fmi_file` and `write ### Dijkstra Benchmarks -The dijkstras algorithm is implenented in `gridgraph.rs`. +Dijkstras algorithm is implenented in `gridgraph.rs` with `GridGraph::shortest_path`. It uses a Heap to store the nodes. -On details on how to run benchmarks see the benchmarks session at the end. +For details on how to run benchmarks see the benchmarks section at the end. ## Task 6 @@ -76,6 +75,9 @@ I implemented ALT, as described in [1]. Additionally A\* is available with a simple, unoptimized haversine distance as the heuristic. +A\* is implemented in `src/astar.rs` and the heuristics for ALT are implemented +in `src/alt.rs`. + ### Landmarks for ALT currently 3 different landmark generation methods are available @@ -94,7 +96,7 @@ generates landmarks for 4, 8, 16, 32 and 64 landmarks, both greedy and random. # Running the benchmarks First a set of queries is needed. -This can be done with the `generate_benchmark_targets --graph > targets.json`. +These can be generated with `generate_benchmark_targets --graph > targets.json`. This generates 1000 random, distinct source and destination pairs. The `--amount` parameter allows to adjust the number of pairs generated. @@ -110,4 +112,11 @@ are used to answer the query. The benchmark prints out how many nodes were popped from the heap for each run and the average time per route. -[1] Computing the Shortest Path: A* meets Graph Theory, A. Goldberg and C. Harrelson, Microsoft Research, Technical Report MSR-TR-2004-24, 2004 +`utils/run_benchmarks.py` is a wrapper script that runs the benchmarks for a +big set of parameters. + +`utils/plot_results.py` generates several plots of the results. + +# References + +[1](Computing the Shortest Path: A\* meets Graph Theory, A. Goldberg and C. Harrelson, Microsoft Research, Technical Report MSR-TR-2004-24, 2004) diff --git a/utils/plot_results.py b/utils/plot_results.py new file mode 100755 index 0000000..f72a1e1 --- /dev/null +++ b/utils/plot_results.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +from sys import argv, exit +import os +from csv import writer +from typing import Tuple, List +import re +import numpy as np + +import matplotlib.pyplot as plt + +if len(argv) != 2: + print(f"Usage: { argv[0] } ") + exit(1) + +path = argv[1] + +files = [f for f in os.listdir(path) if os.path.isfile(f"{ path }/{f}")] + +files = [f for f in files if re.match(r"greedy_64_.+", f) is not None ] + + +def parse_file(file: str) -> Tuple[float, List[int]]: + + pops = list() + time = None + with open(file) as f: + for line in f.readlines(): + m = re.match(r"popped\s(?P\d+)\s.*", line) + if m is not None: + pops.append(int(m.groupdict()["pops"])) + continue + + m = re.match(r"It took\s(?P