added current code

This commit is contained in:
Johannes Erwerle 2022-09-13 17:59:30 +02:00
parent 557558acc6
commit 41e5e9b032
39 changed files with 2431 additions and 215 deletions

330
Cargo.lock generated
View file

@ -122,6 +122,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -137,6 +146,18 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.10.0"
@ -155,6 +176,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.73"
@ -182,6 +209,17 @@ dependencies = [
"generic-array",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap 0.11.0",
"unicode-width",
]
[[package]]
name = "clap"
version = "3.2.16"
@ -196,7 +234,7 @@ dependencies = [
"once_cell",
"strsim",
"termcolor",
"textwrap",
"textwrap 0.15.0",
]
[[package]]
@ -257,6 +295,87 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap 2.34.0",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if 1.0.0",
"once_cell",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -267,6 +386,28 @@ dependencies = [
"typenum",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa 0.4.8",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "ctr"
version = "0.8.0"
@ -339,7 +480,9 @@ dependencies = [
name = "fapra_osm_2"
version = "0.1.0"
dependencies = [
"clap",
"bincode",
"clap 3.2.16",
"criterion",
"geojson",
"handlebars",
"osmpbfreader",
@ -347,6 +490,7 @@ dependencies = [
"rocket",
"rocket_dyn_templates",
"serde",
"serde_json",
]
[[package]]
@ -617,6 +761,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "handlebars"
version = "4.3.3"
@ -679,7 +829,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
dependencies = [
"bytes",
"fnv",
"itoa",
"itoa 1.0.3",
]
[[package]]
@ -720,7 +870,7 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
"itoa",
"itoa 1.0.3",
"pin-project-lite",
"socket2",
"tokio",
@ -785,10 +935,25 @@ dependencies = [
]
[[package]]
name = "itoa"
version = "1.0.2"
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
@ -823,9 +988,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.126"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
[[package]]
name = "lock_api"
@ -876,6 +1041,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.16"
@ -1038,6 +1212,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -1188,6 +1368,34 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "plotters"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615"
dependencies = [
"plotters-backend",
]
[[package]]
name = "polyval"
version = "0.5.3"
@ -1322,6 +1530,30 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -1333,18 +1565,18 @@ dependencies = [
[[package]]
name = "ref-cast"
version = "1.0.8"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "776c8940430cf563f66a93f9111d1cd39306dc6c68149ecc6b934742a44a828a"
checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.8"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f26c4704460286103bff62ea1fb78d137febc86aaf76952e6c5a2249af01f54"
checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa"
dependencies = [
"proc-macro2",
"quote",
@ -1520,18 +1752,28 @@ checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
[[package]]
name = "serde"
version = "1.0.141"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.141"
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
dependencies = [
"proc-macro2",
"quote",
@ -1540,11 +1782,11 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [
"itoa",
"itoa 1.0.3",
"ryu",
"serde",
]
@ -1700,6 +1942,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.15.0"
@ -1737,12 +1988,11 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f"
checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
dependencies = [
"itoa",
"js-sys",
"itoa 1.0.3",
"libc",
"num_threads",
"time-macros",
@ -1754,6 +2004,16 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tokio"
version = "1.20.1"
@ -1926,9 +2186,15 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
@ -2039,6 +2305,16 @@ version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
[[package]]
name = "web-sys"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.2.8"

View file

@ -13,4 +13,13 @@ rand = {version = "0.8", features = ["alloc"] }
rocket = {version="0.5.0-rc", features=["json"]}
rocket_dyn_templates = {version="0.1.0-rc", features=["handlebars"]}
serde = {version="1.0", features=["derive"]}
serde_json = "1.0"
bincode = "1.3.3"
osmpbfreader = "0.15.2"
[dev-dependencies]
criterion = "0.3.6"
[[bench]]
name = "task5"
harness = false

160
src/alt.rs Normal file
View file

@ -0,0 +1,160 @@
use crate::gridgraph::{EdgeCost, GraphNode, GridGraph, NodeId};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BinaryHeap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Landmark {
pub node: GraphNode,
pub distances: Vec<EdgeCost>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LandmarkSet {
pub landmarks: Vec<Landmark>,
pub best_size: usize,
best_landmarks: Vec<usize>,
}
impl Landmark {
pub fn generate(node: GraphNode, graph: &GridGraph) -> Landmark {
let mut landmark = Landmark {
node,
distances: vec![EdgeCost::MAX; graph.nodes.len()],
};
landmark.node = node;
#[derive(Eq)]
struct DijkstraElement {
index: u32,
cost: EdgeCost,
}
impl Ord for DijkstraElement {
// 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)
}
}
impl PartialOrd for DijkstraElement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
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 {
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() {
let new_cost = cost + edge.cost;
if new_cost < landmark.distances[edge.neighbor as usize] {
//println!("adding new element to heap");
heap.push(DijkstraElement {
index: edge.neighbor,
cost: new_cost,
});
}
}
}
// now the shortest paths to all reachable nodes is calculated.
landmark
}
/// calculates the lower-bounding distance estimate between the 2 nodes
/// via the landmark.
/// If one or more of the nodes are not reachable from the landmark
/// an estimate of `0` is returned.
pub fn estimate(&self, from: NodeId, to: NodeId) -> EdgeCost {
let l_to = self.distances[to];
let l_from = self.distances[from];
if l_to == EdgeCost::MAX {
0
} else {
l_to.saturating_sub(l_from)
}
}
}
impl LandmarkSet {
pub fn random_set(size: usize, best_size: usize, graph: &GridGraph) -> Self {
let mut set = LandmarkSet::default();
set.best_size = best_size;
let nodes = graph.get_random_nodes(size);
for node in nodes.into_iter() {
let landmark = Landmark::generate(*node, graph);
set.landmarks.push(landmark)
}
set
}
pub fn select_best(&mut self, from: NodeId, to: NodeId) {
let mut results = vec![];
for (index, landmark) in self.landmarks.iter().enumerate() {
results.push((index, landmark.estimate(from, to)));
}
results.sort_by_key(|k| k.1);
results.reverse();
self.best_landmarks.clear();
for result in results[..self.best_size].iter() {
self.best_landmarks.push(result.0);
}
}
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));
};
if distance == 0 {
distance = EdgeCost::MAX;
}
distance
}
}

137
src/astar.rs Normal file
View file

@ -0,0 +1,137 @@
use crate::gridgraph::{EdgeCost, GraphNode, GridGraph, Route};
use crate::utils::EARTH_RADIUS;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
pub struct AStar {
pub graph: GridGraph,
}
#[derive(Eq)]
struct HeapElement {
index: u32,
cost: EdgeCost, // the cost so far plus the estimated cost until we reach the
// destination
path_cost: EdgeCost, // the cost to reach this node from the start node
ancestor: Option<u32>,
}
impl Ord for HeapElement {
// 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)
}
}
impl PartialOrd for HeapElement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
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
// let lat_dist_a = (node.position.lat - destination.position.lat).abs();
// let lat_dist_b = (destination.position.lat - node.position.lat).abs();
// (lat_dist_a.min(lat_dist_b) * EARTH_RADIUS) as 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();
(lat_dist_a.min(lat_dist_b) * EARTH_RADIUS) as EdgeCost
}
impl AStar {
pub fn shortest_path<F>(&self, start: &GraphNode, end: &GraphNode, estimate: F) -> Option<Route>
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<Option<u32>> = vec![None; self.graph.nodes.len()];
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,
ancestor: prev,
}) = 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 (which implies a shorter distance)
// we simply skip them.
if distance[index as usize] <= path_cost {
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() {
let new_cost = path_cost + edge.cost;
if new_cost < distance[edge.neighbor as usize] {
//println!("adding new element to heap");
heap.push(HeapElement{
index: edge.neighbor,
cost: new_cost + estimate(&self.graph.nodes[edge.neighbor as usize], end),
path_cost: new_cost,
ancestor: Some(index),
});
}
}
}
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
}
}

View file

@ -0,0 +1,74 @@
use fapra_osm_2::gridgraph::GridGraph;
use std::fs::File;
use clap::Parser;
use std::process::exit;
use rand::seq::SliceRandom;
use fapra_osm_2::utils::RoutingQuery;
use serde_json;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about=None)]
struct Args {
/// name of the FMI file to read
#[clap(short, long)]
graph: String,
/// number of samples to generate
#[clap(short, long, default_value_t = 1000)]
samples: usize,
}
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 mut rng = rand::thread_rng();
let mut targets = graph.nodes.choose_multiple(&mut rng, args.samples * 2);
let mut queries: Vec<RoutingQuery> = Vec::new();
for i in (0..targets.len()).step_by(2) {
let source = match targets.next() {
Some(t) => t.index as usize,
None => {
println!("Expected a source node with index {}, but there was None.", i);
exit(2);
},
};
let destination = match targets.next() {
Some(t) => t.index as usize,
None => {
println!("Expected a destination node with index {}, but there was None.", i+1);
exit(2);
},
};
queries.push(RoutingQuery{ source, destination})
}
match serde_json::to_string(&queries) {
Ok(s) => println!("{}", s),
Err(e) => {
println!("Error while serializeing: {}", e);
exit(2);
}
};
}

62
src/bin/generate_grid.rs Normal file
View file

@ -0,0 +1,62 @@
use clap::Parser;
use fapra_osm_2::gridgraph::GridGraph;
use fapra_osm_2::coordinates::RadianCoordinate;
use fapra_osm_2::polygonset::PolygonSet;
use std::fs::File;
use std::process::exit;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about=None)]
struct Args {
/// the PBF file to load
#[clap(short, long)]
input: String,
/// the output file to write the grid to
#[clap(short, long)]
output: String,
}
fn main() {
let args = Args::parse();
// This is a set of "well-known" points that are outside of all coastline
// polygon. (They are somewhere in the middle of the ocean, far from any
// coast.)
let outside_points = vec![
RadianCoordinate::from_degrees(44.94924926661153, -42.4072265625),
RadianCoordinate::from_degrees(36.82687474287728, -45.19775390625),
RadianCoordinate::from_degrees(17.219511333785114, -35.244140625),
RadianCoordinate::from_degrees(21.779905342529645, 142.20703125),
RadianCoordinate::from_degrees(7.710991655433217, 135.87890625),
RadianCoordinate::from_degrees(34.161818161230386, 150.1171875),
RadianCoordinate::from_degrees(39.98448618198461, -9.136505126953125),
];
let coasts = PolygonSet::from_pbf(&args.input, outside_points);
println!("{} polygons loaded", coasts.polygons.len());
let output = match File::create(args.output.clone()) {
Ok(f) => f,
Err(e) => {
println!("Error while opening the file {}: {}", args.output, e);
exit(1)
}
};
println!("{:?}", coasts.polygons[0].bbox);
let grid = GridGraph::generate_regular_grid(10, 10, Some(&coasts));
// let grid = GridGraph::generate_regular_grid(3, 4, None);
match grid.write_fmi_file(output) {
Ok(_) => {
println!("Wrote graph to {}", args.output);
}
Err(e) => {
println!("Error while opening the file {}: {}", args.output, e);
exit(1);
}
}
}

View file

@ -0,0 +1,28 @@
use fapra_osm_2::gridgraph::GridGraph;
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,
}
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!("{}", grid.to_geojson().to_string());
}

149
src/bin/performance.rs Normal file
View file

@ -0,0 +1,149 @@
use clap::Parser;
use fapra_osm_2::alt::LandmarkSet;
use fapra_osm_2::astar::{estimate_haversine, estimate_latitude, AStar};
use fapra_osm_2::gridgraph::{GridGraph, NodeId};
use fapra_osm_2::utils::RoutingQuery;
use serde_json;
use std::fs::File;
use std::io::BufReader;
use std::process::exit;
use std::time::Instant;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about=None)]
struct Args {
/// name of the FMI file to read
#[clap(short, long)]
graph: String,
/// sample target file
#[clap(short, long)]
targets: String,
/// landmark file
#[clap(short, long)]
landmarks: String,
/// run dijkstra
#[clap(long, action)]
dijkstra: bool,
/// run astar
#[clap(long, action)]
astar: bool,
/// run ALT
#[clap(long, action)]
alt: bool,
}
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 targets = match File::open(args.targets.clone()) {
Ok(f) => f,
Err(e) => {
println!("Error while opening target file {}: {:?}", args.targets, e);
exit(1);
}
};
let targets: Vec<RoutingQuery> = 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);
}
};
let mut landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap();
landmarks.best_size = 4;
let astar = AStar { graph: *graph };
println!("{:?}", args);
if args.astar {
println!("running A*");
let start = Instant::now();
for query in targets.iter() {
let source = astar.graph.nodes[query.source];
let destination = astar.graph.nodes[query.destination];
let _result = astar.shortest_path(&source, &destination, estimate_haversine);
}
let elapsed = start.elapsed();
let time_per_route = elapsed.as_secs_f64() / (targets.len() as f64);
println!("It took {} seconds per route for AStar.", time_per_route);
};
if args.dijkstra {
println!("running Dijkstra");
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];
let result = astar.graph.shortest_path(&source, &destination);
println!("{}", result.unwrap().to_geojson());
}
let elapsed = start.elapsed();
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");
// Landmarks
let start = Instant::now();
for query in targets.iter() {
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);
let _result = astar.shortest_path(&source, &destination, |src, dest| {
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);
}
}

119
src/bin/task6-rocket.rs Normal file
View file

@ -0,0 +1,119 @@
#[macro_use] extern crate rocket;
use clap::Parser;
use std::process::exit;
use std::fs::File;
use fapra_osm_2::gridgraph::{GridGraph, Route};
use fapra_osm_2::coordinates::{RadianCoordinate, DegreeCoordinate};
use rand::seq::SliceRandom;
use serde::Serialize;
use rocket::State;
use rocket::response::status::BadRequest;
use rocket::form::Form;
use rocket::fs::FileServer;
use rocket::serde::json::Json;
use rocket_dyn_templates::{Template, context, Engines};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about=None)]
struct Args {
/// name of the FMI file to read
#[clap(short, long)]
filename: String,
}
#[get("/")]
fn index() -> Template {
Template::render("index", context! {})
}
#[get("/random")]
fn random_route(graphwrapper: &State<GraphWrapper>) -> String {
let graph = &graphwrapper.graph;
let mut rng = rand::thread_rng();
let mut route = None;
while route.is_none() {
let start = graph.nodes.choose(&mut rng).unwrap();
let end = graph.nodes.choose(&mut rng).unwrap();
route = graph.shortest_path(&start, &end);
}
format!("{}", route.unwrap().to_geojson().to_string())
}
#[derive(FromForm)]
struct RouteQuery<'r> {
r#from: &'r str,
r#to: &'r str,
}
#[derive(Debug)]
#[derive(Serialize)]
pub struct RouteResponse {
success: bool,
route: Option<Route>,
}
#[post("/route", data = "<routequery>")]
fn route(routequery: Form<RouteQuery<'_>>, graphwrapper: &State<GraphWrapper>) -> Result<Json<RouteResponse>, BadRequest<String>> {
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))));}
};
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))));}
};
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!("from: {:?}, to: {:?}", from, to);
let response = RouteResponse{success: route.is_some(), route};
Ok(Json(response))
}
struct GraphWrapper {
graph: Box<GridGraph>
}
#[launch]
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 = 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 graph = GridGraph::generate_regular_grid(10,10);
rocket::build()
.manage(GraphWrapper{graph: graph})
.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);}))
}

52
src/bin/test_alt_gen.rs Normal file
View file

@ -0,0 +1,52 @@
use bincode;
use clap::Parser;
use fapra_osm_2::alt::LandmarkSet;
use fapra_osm_2::gridgraph::GridGraph;
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 grid = GridGraph::from_fmi_file(file).unwrap();
println!("finished loading grid 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)
}
};
let set = LandmarkSet::random_set(args.amount, 4, &grid);
let encoded = bincode::serialize(&set).unwrap();
output.write_all(&encoded).expect("Error while writing LandmarkSet data");
}

View file

@ -1,17 +1,19 @@
use crate::ACCURACY_BOUNDARY;
use geojson::{Position, Value};
use std::convert::From;
use std::f64::consts::{FRAC_PI_2, PI, TAU};
use crate::ACCURACY_BOUNDARY;
use serde::{Serialize, Deserialize};
/// Spherical coordinates in radians.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct RadianCoordinate {
pub lat: f64,
pub lon: f64,
}
/// Spherical coordinates in degrees.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct DegreeCoordinate {
pub lat: f64,
pub lon: f64,
@ -25,14 +27,14 @@ impl From<DegreeCoordinate> for RadianCoordinate {
impl From<DegreeCoordinate> for geojson::Value {
fn from(coordinate: DegreeCoordinate) -> Self {
Value::Point(vec![coordinate.lat, coordinate.lon])
Value::Point(vec![coordinate.lon, coordinate.lat])
}
}
impl From<RadianCoordinate> for Position {
fn from(coordinate: RadianCoordinate) -> Self {
let coordinate = DegreeCoordinate::from(coordinate);
vec![coordinate.lat, coordinate.lon]
vec![coordinate.lon, coordinate.lat]
}
}
@ -70,12 +72,11 @@ impl RadianCoordinate {
/// returns the longitude of a point when the coordinate system is
/// transformed, such that `north_pole` is the north pole of that system.
pub fn get_transformed_longitude(&self, north_pole: &RadianCoordinate) -> f64 {
if self.lat == FRAC_PI_2 {
return self.lon
return self.lon;
};
let top = (self.lon- north_pole.lon).sin() * self.lat.cos();
let top = (self.lon - north_pole.lon).sin() * self.lat.cos();
let bottom = (self.lat.sin() * north_pole.lat.cos())
- (self.lat.cos() * north_pole.lat.sin() * (self.lon - north_pole.lon).cos());
@ -118,40 +119,6 @@ impl RadianCoordinate {
2.0 * a.sqrt().atan2((1.0_f64 - a).sqrt())
}
/// checks whether the strike is between the shorter angle between the points
/// `a` and `b` while using `pole` as the north pole.
pub fn strike_between(
&self,
pole: &RadianCoordinate,
a: &RadianCoordinate,
b: &RadianCoordinate,
) -> bool {
let mut lon = self.get_transformed_longitude(pole).rem_euclid(2.0 * PI);
let a_lon = a.get_transformed_longitude(pole).rem_euclid(2.0 * PI);
let b_lon = b.get_transformed_longitude(pole).rem_euclid(2.0 * PI);
// select the start boundary of the smaller circle segment in the
// positive direction
let start;
let mut end;
if (b_lon - a_lon) > PI {
start = b_lon;
end = a_lon;
} else {
start = a_lon;
end = b_lon;
}
// rotate the values such that the start is interpreted as 0 and
// handle the wrap around.
// The start of the interval is now at 0 and the end should be smaller
// than PI.
end = (end - start).rem_euclid(2.0 * PI);
lon = (lon - start).rem_euclid(2.0 * PI);
0_f64 <= lon && lon <= end
}
}
impl DegreeCoordinate {
@ -163,6 +130,30 @@ impl DegreeCoordinate {
DegreeCoordinate { lat, lon }
}
/// returns a SphericalCoordinate parsed from the given string.
pub fn from_string_tuple(input: &str) -> Result<DegreeCoordinate, CoordinateParsingError> {
let splits: Vec<&str> = input.split(",").collect();
if splits.len() != 2 {
return Err(CoordinateParsingError::NoSemicolon);
}
let lat = splits[0];
let lon = splits[1];
let lat = match lat.parse::<f64>() {
Ok(lat) => lat,
Err(_) => { return Err(CoordinateParsingError::NotAFloat); }
};
let lon = match lon.parse::<f64>() {
Ok(lon) => lon,
Err(_) => { return Err(CoordinateParsingError::NotAFloat); }
};
Ok(DegreeCoordinate{lat, lon})
}
}
/// normalizes longitude values given in radians to the range (-PI, PI]
@ -190,3 +181,45 @@ fn test_normalize_lon() {
assert!((normalize_lon(PI + FRAC_PI_2) - (-FRAC_PI_2)).abs() < 10e-12);
assert!((normalize_lon(-PI - FRAC_PI_2) - (FRAC_PI_2)).abs() < 10e-12);
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LongitudeDirection {
East,
West,
None,
}
pub fn east_or_west(from: f64, to: f64) -> LongitudeDirection {
let delta = normalize_lon(from - to);
if delta > 0.0 && delta < PI {
LongitudeDirection::West
} else if delta < 0.0 && delta > -PI {
LongitudeDirection::East
} else {
LongitudeDirection::None
}
}
#[derive(Clone, Debug)]
pub enum CoordinateParsingError {
NoSemicolon,
NotAFloat,
}
#[test]
fn test_east_or_west() {
assert_eq!(east_or_west(0.0, 0.0), LongitudeDirection::None);
assert_eq!(east_or_west(0.0, 1.0), LongitudeDirection::East);
assert_eq!(east_or_west(0.0, -1.0), LongitudeDirection::West);
assert_eq!(east_or_west(1.0, -1.0), LongitudeDirection::West);
assert_eq!(east_or_west(-1.0, 1.0), LongitudeDirection::East);
assert_eq!(east_or_west(0.5, 1.0), LongitudeDirection::East);
assert_eq!(east_or_west(-1.0, -0.5), LongitudeDirection::East);
// wrap around tests
assert_eq!(east_or_west(3.0, -3.0), LongitudeDirection::East);
assert_eq!(east_or_west(-3.0, 3.0), LongitudeDirection::West);
}

682
src/gridgraph.rs Normal file
View file

@ -0,0 +1,682 @@
use crate::coordinates::{DegreeCoordinate, RadianCoordinate};
use crate::polygonset::PolygonSet;
use geojson::{Feature, FeatureCollection, Geometry, Position, Value};
use serde::ser::{SerializeStruct, Serializer};
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
/// neccessary/sufficient.
/// All distance measurements on the grid should use this type.
pub type EdgeCost = u64;
pub type NodeId = usize;
#[derive(Debug, Clone, Default)]
pub struct GridGraph {
pub nodes: Vec<GraphNode>,
pub edges: Vec<GraphEdge>,
pub edge_offsets: Vec<usize>,
lookup_grid: LookupGrid,
}
#[derive(Debug, Clone, Default)]
pub struct LookupGrid {
lat_size: usize,
lon_size: usize,
grid: Vec<Vec<Vec<NodeId>>>,
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct GraphNode {
pub index: u32,
pub position: RadianCoordinate,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct GraphEdge {
// index of the neighbor in the data structure
pub neighbor: u32,
pub cost: EdgeCost,
}
#[derive(Debug)]
pub enum FmiParsingError {
FormatError(String),
NotAnInteger(String),
NotAFloat(String),
WrongNodeAmount,
WrongEdgeAmount,
}
#[derive(Debug)]
pub struct Route {
pub nodes: Vec<RadianCoordinate>,
pub cost: EdgeCost,
}
impl Serialize for Route {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Route", 2)?;
state.serialize_field("geojson", &self.to_geojson())?;
state.serialize_field("cost", &self.cost)?;
state.end()
}
}
impl LookupGrid {
/// returns the latitude index and longitude index of a position
pub fn get_grid_cell(&self, position: RadianCoordinate) -> (usize, usize) {
let lat_index = ((position.lat + 90.0) / 180.0 * self.lat_size as f64).floor() as usize;
let lon_index = ((position.lon + 180.0) / 360.0 * self.lon_size as f64).floor() as usize;
(lat_index, lon_index)
}
/// inserts the nodes in to the grid
pub fn insert_nodes(&mut self, nodes: &[GraphNode]) {
for node in nodes.iter() {
let (lat, lon) = self.get_grid_cell(node.position);
self.grid[lat][lon].push(node.index as usize);
}
}
/// creates a new lookup grid with the given size and adds the given nodes
pub fn new(lat_size: usize, lon_size: usize, nodes: &[GraphNode]) -> LookupGrid {
let mut instance = LookupGrid {
lat_size,
lon_size,
grid: vec![vec!(Vec::new(); lon_size); lat_size],
};
instance.insert_nodes(nodes);
instance
}
}
impl Route {
pub fn to_geojson(&self) -> Box<FeatureCollection> {
let mut features: Box<FeatureCollection> = Box::new(FeatureCollection {
bbox: None,
features: vec![],
foreign_members: None,
});
let mut points = geojson::LineStringType::new();
for node in self.nodes.iter() {
let position: Position = (*node).into();
points.push(position);
}
let geometry = Geometry::new(Value::LineString(points));
features.features.push(Feature {
bbox: None,
geometry: Some(geometry),
id: None,
properties: None,
foreign_members: None,
});
features
}
}
impl GridGraph {
/// selects a single random graph node.
pub fn get_random_node(&self) -> Option<&GraphNode> {
let mut rng = rand::thread_rng();
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>{
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.
/// start and end nodes have to be references to the nodes contained in the
/// grid graph.
/// `start` and `end` have to be different nodes.
/// Returns the route.
pub fn shortest_path(&self, start: &GraphNode, end: &GraphNode) -> Option<Route> {
#[derive(Eq, PartialEq, Debug)]
struct DijkstraElement {
index: u32,
cost: EdgeCost,
}
impl Ord for DijkstraElement {
// 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))
}
}
impl PartialOrd for DijkstraElement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
let mut heap = BinaryHeap::new();
heap.push(DijkstraElement {
cost: 0,
index: start.index,
});
let mut distance = vec![EdgeCost::MAX; self.nodes.len()];
let mut ancestor: Vec<Option<u32>> = vec![None; self.nodes.len()];
let mut popcount = 0;
while let Some(DijkstraElement {
index,
cost,
}) = heap.pop()
{
popcount += 1;
if index == end.index {
// println!("found the end");
break;
}
// 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 cost > distance[index as usize] {
//println!("skipping {} because of cost {}, it can be reached with {}", index, cost, distance[index as usize]);
continue;
};
// 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() {
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]);
distance[edge.neighbor as usize] = new_cost;
ancestor[edge.neighbor as usize] = Some(index);
//println!("adding new element to heap");
heap.push(DijkstraElement {
index: edge.neighbor,
cost: new_cost,
});
} else {
// println!("edge {:?} is more expensive ({}) than the previous best {}", edge, new_cost, distance[edge.neighbor as usize]);
}
}
}
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
}
/// returns the GraphNode nearest to that positon.
/// There might be cornercases where other nodes are actually closer due
/// to the spherical projection.
/// There also might be cases where no neares position is found because
/// the search only works on a 3x3 grid around the position.
pub fn get_nearest_node(&self, position: RadianCoordinate) -> Option<&GraphNode> {
let (lat, lon) = self.lookup_grid.get_grid_cell(position);
let lat = lat as isize;
let lon = lon as isize;
let lookup_distance = 1;
let mut best_node: Option<&GraphNode> = None;
let mut best_distance = f64::MAX;
// iterate over the grid and handle the wrap-aroung at 180 degrees
for lat_index in (lat - lookup_distance)..(lat + lookup_distance + 1) {
let lat_index = lat_index.rem_euclid(self.lookup_grid.lat_size as isize) as usize;
for lon_index in (lon - lookup_distance)..(lon + lookup_distance + 1) {
let lon_index = lon_index.rem_euclid(self.lookup_grid.lon_size as isize) as usize;
// actual check of the grid nodes in the cell
for node_id in self.lookup_grid.grid[lat_index][lon_index].iter() {
let node = &(self.nodes[*node_id as usize]);
let dist = node.position.distance_to(&position);
if dist < best_distance {
best_node = Some(node);
best_distance = dist;
}
}
}
}
best_node
}
/// reads a graph from an FMI file.
pub fn from_fmi_file(file: File) -> Result<Box<GridGraph>, FmiParsingError> {
let mut gridgraph = Box::new(GridGraph::default());
let reader = BufReader::new(file);
let lines = reader.lines();
let mut total_node_count: u32 = 0;
let mut total_edge_count: u32 = 0;
let mut node_count: u32 = 0;
let mut edge_count: u32 = 0;
let mut nodes: HashMap<u32, GraphNode> = HashMap::new();
let mut edges: HashMap<u32, Vec<TemporaryGraphEdge>> = HashMap::new();
enum ParserState {
NodeCount,
EdgeCount,
Nodes,
Edges,
Done,
}
struct TemporaryGraphEdge {
src: u32,
dst: u32,
cost: EdgeCost,
}
fn parse_int(line: &str) -> Result<u32, FmiParsingError> {
match line.parse::<u32>() {
Ok(result) => Ok(result),
Err(_) => Err(FmiParsingError::NotAnInteger(format!(
"Line is not an integer: '{}'",
line
))),
}
}
fn parse_float(line: &str) -> Result<f64, FmiParsingError> {
match line.parse::<f64>() {
Ok(result) => Ok(result),
Err(_) => Err(FmiParsingError::NotAFloat(format!(
"Line is not an float: '{}'",
line
))),
}
}
fn parse_node(line: &str) -> Result<GraphNode, FmiParsingError> {
let splits = line.split(" ").collect::<Vec<&str>>();
if splits.len() < 3 {
return Err(FmiParsingError::FormatError(format!(
"Line '{}' does not have at least 3 parts",
line
)));
}
let index = parse_int(splits[0])?;
let lat = parse_float(splits[1])?;
let lon = parse_float(splits[2])?;
Ok(GraphNode {
index,
position: RadianCoordinate::from_degrees(lat, lon),
})
}
fn parse_edge(line: &str) -> Result<TemporaryGraphEdge, FmiParsingError> {
let splits = line.split(" ").collect::<Vec<&str>>();
if splits.len() < 3 {
return Err(FmiParsingError::FormatError(format!(
"Line '{}' does not have at least 3 parts",
line
)));
}
let src = parse_int(splits[0])?;
let dst = parse_int(splits[1])?;
let cost = parse_int(splits[2])? as EdgeCost;
Ok(TemporaryGraphEdge { src, dst, cost })
}
let mut state = ParserState::NodeCount {};
for line in lines {
let line = line.unwrap();
let line = line.trim();
if line.is_empty() || line.starts_with("#") {
continue;
}
match state {
ParserState::NodeCount => {
total_node_count = parse_int(line)?;
state = ParserState::EdgeCount {};
}
ParserState::EdgeCount => {
total_edge_count = parse_int(line)?;
state = ParserState::Nodes {};
}
ParserState::Nodes => {
let node = parse_node(line)?;
nodes.entry(node.index).or_insert(node);
node_count += 1;
if node_count == total_node_count {
state = ParserState::Edges {};
}
}
ParserState::Edges => {
let edge = parse_edge(line)?;
edges.entry(edge.src).or_default().push(edge);
edge_count += 1;
if edge_count == total_edge_count {
state = ParserState::Done;
}
}
ParserState::Done => {
break;
}
}
}
if node_count != total_node_count {
return Err(FmiParsingError::WrongNodeAmount);
}
if edge_count != total_edge_count {
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());
}
// 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;
gridgraph.edges.push(GraphEdge {
cost: edge.cost,
neighbor: dst_index,
});
}
gridgraph.edge_offsets.push(gridgraph.edges.len());
}
gridgraph.lookup_grid = LookupGrid::new(100, 100, &gridgraph.nodes);
Ok(gridgraph)
}
pub fn write_fmi_file(&self, mut file: File) -> std::io::Result<()> {
writeln!(file, "{}", self.nodes.len())?;
writeln!(file, "{}", self.edges.len())?;
for node in self.nodes.iter() {
let deg_pos = DegreeCoordinate::from(node.position);
writeln!(file, "{} {} {}", node.index, deg_pos.lat, deg_pos.lon,)?;
}
for node in self.nodes.iter() {
for edge in self.get_edges(node.index as NodeId) {
let neighbor = self.nodes[edge.neighbor as usize];
writeln!(file, "{} {} {}", node.index, neighbor.index, edge.cost)?;
}
}
Ok(())
}
pub fn get_edges(&self, node: NodeId) -> &[GraphEdge] {
let start = self.edge_offsets[node];
let end = self.edge_offsets[node + 1];
&self.edges[start..end]
}
/// generates a regular grid over the globe with the given grid size.
pub fn generate_regular_grid(
lat_resolution: usize,
lon_resolution: usize,
polygons: Option<&PolygonSet>,
) -> Box<GridGraph> {
#[derive(Debug)]
struct TemporaryGraphNode {
final_index: u32,
position: RadianCoordinate,
lat_index: usize,
lon_index: usize,
// specifies whether or not the node will be in the final graph or
// not, because it might be on land.
used: bool,
}
impl TemporaryGraphNode {
pub fn left_neighbor_index(&self, lon_resolution: usize) -> usize {
let left_lon_index =
(self.lon_index as isize - 1).rem_euclid(lon_resolution as isize) as usize;
left_lon_index + (self.lat_index * lon_resolution)
}
pub fn right_neighbor_index(&self, lon_resolution: usize) -> usize {
let left_lon_index = (self.lon_index + 1).rem_euclid(lon_resolution);
left_lon_index + (self.lat_index * lon_resolution)
}
pub fn bottom_neighbor_index(&self, lon_resolution: usize) -> Option<usize> {
if self.lat_index <= 0 {
return None;
};
Some(self.lon_index + (self.lat_index - 1) * lon_resolution)
}
pub fn top_neighbor_index(&self, lat_resolution: usize, lon_resolution: usize) -> Option<usize> {
if self.lat_index >= lat_resolution - 1 {
return None;
};
Some(self.lon_index + (self.lat_index + 1) * lon_resolution)
}
}
let mut temp_nodes = Vec::new();
for lat_index in 0..lat_resolution {
let lat = ((lat_index as f64 / lat_resolution as f64) * PI) - FRAC_PI_2;
for lon_index in 0..lon_resolution {
let lon = ((lon_index as f64 / lon_resolution as f64) * TAU) - PI;
let position = RadianCoordinate { lat, lon };
let used = match polygons {
None => true,
Some(p) => !p.in_any(position),
};
temp_nodes.push(TemporaryGraphNode {
final_index: 0,
position,
used,
lat_index,
lon_index,
})
}
}
let mut final_graph: Box<GridGraph> = Box::new(GridGraph::default());
// add all the nodes to the final graph
for mut node in temp_nodes.iter_mut() {
if !node.used {
continue;
};
let new_node = GraphNode {
index: final_graph.nodes.len() as u32,
position: node.position,
};
node.final_index = new_node.index;
final_graph.nodes.push(new_node);
}
// add all edges to the final graph
for node in temp_nodes.iter() {
println!("working on edges for node {:?}", node);
if !node.used {
continue;
};
final_graph.edge_offsets.push(final_graph.edges.len());
fn add_neighbor(
node: &TemporaryGraphNode,
neighbor: &TemporaryGraphNode,
grid: &mut GridGraph,
) {
if neighbor.used {
grid.edges.push(GraphEdge {
neighbor: neighbor.final_index,
cost: (node.position.distance_to(&(neighbor.position)) * EARTH_RADIUS)
as EdgeCost,
});
}
}
// add neighbors
let left_neighbor = &temp_nodes[node.left_neighbor_index(lon_resolution)];
add_neighbor(&node, &left_neighbor, &mut final_graph);
let right_neighbor = &temp_nodes[node.right_neighbor_index(lon_resolution)];
add_neighbor(&node, &right_neighbor, &mut final_graph);
let top_index = node.top_neighbor_index(lat_resolution, lon_resolution);
match top_index {
Some(index) => {
println!("top neighbor is {}", index);
add_neighbor(&node, &temp_nodes[index], &mut final_graph);
}
None => (),
}
let bottom_index = node.bottom_neighbor_index(lon_resolution);
match bottom_index {
Some(index) => {
add_neighbor(&node, &temp_nodes[index], &mut final_graph);
}
None => (),
}
}
// pushing the end offset
final_graph.edge_offsets.push(final_graph.edges.len());
// making the cells of the lookup grid 4 times the size of the regular
// grid is a heuristic that worked well.
final_graph.lookup_grid = LookupGrid::new(
(lat_resolution / 4).max(1),
(lon_resolution / 4).max(1),
&final_graph.nodes,
);
final_graph
}
/// Returns a GeoJSON representation of the grid.
/// This gets very large very soon and might be hard to render.
pub fn to_geojson(&self) -> Box<FeatureCollection> {
let mut features: Box<FeatureCollection> = Box::new(FeatureCollection {
bbox: None,
features: vec![],
foreign_members: None,
});
for node in self.nodes.iter() {
let value = Value::from(node.position);
features.features.push(Feature::from(value));
}
for node in self.nodes.iter() {
for edge_index in
self.edge_offsets[node.index as usize]..self.edge_offsets[(node.index + 1) as usize]
{
let edge = self.edges[edge_index as usize];
// the edges are stored as directed edges, but we only want to
// draw one of them
if node.index < edge.neighbor {
continue;
}
let neighbor = self.nodes[edge.neighbor as usize];
let mut points = geojson::LineStringType::new();
points.push(Position::from(node.position));
points.push(Position::from(neighbor.position));
let geometry = Geometry::new(Value::LineString(points));
features.features.push(Feature {
bbox: None,
geometry: Some(geometry),
id: None,
properties: None,
foreign_members: None,
})
}
}
features
}
}

View file

@ -1,5 +1,11 @@
pub mod coordinates;
pub mod polygon;
pub mod polygonset;
pub mod gridgraph;
pub mod utils;
pub mod astar;
pub mod alt;
#[cfg(test)]
pub mod tests;

View file

@ -1,8 +1,7 @@
use crate::coordinates::{RadianCoordinate, normalize_lon};
use crate::coordinates::{east_or_west, RadianCoordinate};
use geojson::{Feature, FeatureCollection, Position, Value};
use std::convert::From;
use std::f64;
use std::f64::consts::{PI, TAU};
/// A spherical polygon
/// the first and last node have to be identical.
@ -139,6 +138,7 @@ impl Polygon {
point1: RadianCoordinate,
point2: RadianCoordinate,
) -> Result<Polygon, PolygonVerificationError> {
println!("build_polygon");
let bbox = BoundingBox::from_nodes(&nodes);
let polygon = Polygon {
@ -178,6 +178,11 @@ impl Polygon {
/// If the polygon does not have enough nodes, nothing can be inside
/// the polygon, so false is returned.
pub fn contains(&self, point: &RadianCoordinate) -> bool {
if !self.bbox.contains(*point) {
return false;
};
let first_check = self.contains_internal(point, &(self.outside_point_1));
match first_check {
@ -203,77 +208,80 @@ impl Polygon {
let mut crossings = 0;
for i in 0..(self.nodes.len()-1) {
let mut trans_lons = vec![];
let point_trans_lon = point.get_transformed_longitude(outside);
// precalculate all the transformed longitudes with the outside point
// as their north pole
for node in self.nodes.iter() {
trans_lons.push(node.get_transformed_longitude(outside));
}
for i in 0..(self.nodes.len() - 1) {
let mut strike_match = false;
// strike check
let a_trans_lon = trans_lons[i];
// this first check, checks whether the line from outside to point
// might go through A
if a_trans_lon == point_trans_lon {
strike_match = true;
} else {
// to test whether we are between A and B we check if we
// move into the same direction from A to B, from A to P and from
// P to B.
let b_trans_lon = trans_lons[i + 1];
let dir_ab = east_or_west(a_trans_lon, b_trans_lon);
let dir_ap = east_or_west(a_trans_lon, point_trans_lon);
let dir_pb = east_or_west(point_trans_lon, b_trans_lon);
if dir_ab == dir_ap && dir_ap == dir_pb {
strike_match = true;
}
}
if !strike_match {
continue;
}
let point_a = self.nodes[i];
// check whether the point is the vertex
// check whether we are on the vertex.
if *point == point_a {
return Ok(true);
}
// now check whether outside and the query point are on different
// sides of the arc AB.
// This is done by transforming the longitudes and checking the
// walking directions
//
let point_b = self.nodes[i + 1];
let b_trans_lon = point_b.get_transformed_longitude(&point_a);
let outside_trans_lon = outside.get_transformed_longitude(&point_a);
let point_trans_lon = point.get_transformed_longitude(&point_a);
println!("working on arc from {:?} to {:?}", point_a, point_b);
// the queried point is exactly on the arc
if point_trans_lon == b_trans_lon {
return Ok(true);
}
// is the great circle from outside to point crossing through
// the current vertex?
if point.on_great_circle(outside, &point_a) {
println!("we are on a great circle");
let dir_bpoint = east_or_west(b_trans_lon, point_trans_lon);
let dir_boutside = east_or_west(b_trans_lon, outside_trans_lon);
// point c is the previous vertex.
// the second -1 is needed, because the polygon has explict
// start/end points
let point_c = if i == 0 {
self.nodes[self.nodes.len() -2]
} else {
self.nodes[i - 1]
};
let lon_p = point.get_transformed_longitude(&point_a);
let mut lon_b = point_b.get_transformed_longitude(&point_a);
let mut lon_c = point_c.get_transformed_longitude(&point_a);
// the sign tell us on which side of the great circle from
// a to point we are
lon_b = (lon_b - lon_p) % (TAU);
lon_c = (lon_c - lon_p) % (TAU);
if lon_b.signum() != lon_c.signum() {
// are we walking into different directions from the arc AB to
// the other 2 points?
// being on the arc has been excluded for the queried point
// and may not happen for the outside point because otherwise
// it would not be outside after a successful strike check.
if dir_bpoint != dir_boutside {
crossings += 1;
continue;
};
}
if point.strike_between(outside, &point_a, &point_b) {
println!("we have a matching strike");
let lon_b = point_b.get_transformed_longitude(&point_a);
let mut lon_point = point.get_transformed_longitude(&point_a);
let mut lon_outside = outside.get_transformed_longitude(&point_a);
println!("lon_b: {}", lon_b);
println!("initial lon_point: {}", lon_point);
println!("initial lon_outside: {}", lon_outside);
// the sign tells us on which side of the great circle from
// a to b we are
lon_point = lon_point - lon_b;
lon_outside = lon_outside - lon_b;
println!("lon_point: {}", lon_point);
println!("lon_outside: {}", lon_outside);
lon_point = normalize_lon(lon_point);
lon_outside = normalize_lon(lon_outside);
println!("lon_point: {}", lon_point);
println!("lon_outside: {}", lon_outside);
if lon_point.signum() != lon_outside.signum() {
crossings += 1;
};
}
}
Ok(crossings % 2 == 1)
}
}
@ -357,18 +365,8 @@ mod tests {
println!("{}", features.to_string());
let in_point = RadianCoordinate::from_degrees(5.0, 5.0);
let out_point = RadianCoordinate {
lat: -0.1,
lon: 0.1,
};
let out_point = RadianCoordinate::from_degrees(-17.0, 15.0);
let antipodal_point = RadianCoordinate {
lat: -0.1,
lon: -PI + 0.1,
};
let antipodal_point = RadianCoordinate::from_degrees(-30.0, -160.0);
let outside_vertex_intersect = RadianCoordinate {
lat: -0.1,
@ -380,7 +378,7 @@ mod tests {
lon: -0.1,
};
//assert_eq!(polygon.contains(&in_point), true);
assert_eq!(polygon.contains(&in_point), true);
assert_eq!(polygon.contains(&out_point), false);
assert_eq!(polygon.contains(&antipodal_point), false);
assert_eq!(

284
src/polygonset.rs Normal file
View file

@ -0,0 +1,284 @@
use crate::coordinates::RadianCoordinate;
use crate::polygon::{Polygon, PolygonVerificationError, BoundingBox};
use osmpbfreader::objects::{NodeId, OsmId, OsmObj, Way, WayId};
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use std::fs::File;
use std::process::exit;
pub type OsmObjectMap = BTreeMap<OsmId, OsmObj>;
/// A Set of Polygons
/// used to run a point in polygon check on many polygons at once.
#[derive(Debug, Default)]
pub struct PolygonSet {
pub polygons: Vec<Polygon>,
}
impl PolygonSet {
/// reads the given input file and generates a PolygonSet.
/// The `outside_points` have to be outside of all polygons.
/// There must be at least 2 outside points, but depending on their
/// placement relative to the edges of the polygons, more might be needed.
pub fn from_pbf(input: &str, outside_points: Vec<RadianCoordinate>) -> PolygonSet {
let file = match File::open(input.clone()) {
Ok(f) => f,
Err(e) => {
println!("Error while opening the file {}: {}", input, e);
exit(1)
}
};
let mut pbf = osmpbfreader::OsmPbfReader::new(file);
let objects: OsmObjectMap = pbf
.get_objs_and_deps(|obj| obj.is_way() && obj.tags().contains("natural", "coastline"))
.unwrap();
println!("Done unwrapping the OSM objects");
// aggregate all coast segments WayIDs into a Set
let coast_segments = aggregate_coasts(&objects);
println!(
"Done aggregating coast segments: {} segments",
coast_segments.len()
);
// build maps from start/end points of a segment to the segment ID
let (starts, _ends) = collects_start_end(&coast_segments, &objects);
// Put all WayIds into a Set that is used as a work queue of unprocessed
// segments
let mut work_queue = BTreeSet::new();
for segment_id in coast_segments.iter() {
let segment_id = segment_id.way().unwrap();
work_queue.insert(segment_id);
}
println!("Done setting up the work queue");
// the PolygonSet in which we will collect our Polygons.
let mut polyset = PolygonSet::default();
while work_queue.is_empty() == false {
let mut assembly = vec![];
// dirty way to pop an element from the set.
// currently (2022-04-18) there is a feature in nightly
// https://github.com/rust-lang/rust/issues/62924
let current = work_queue.iter().next().cloned().unwrap();
work_queue.take(&current).unwrap();
let segment = objects.get(&OsmId::Way(current)).unwrap().way().unwrap();
assembly.push(segment);
// println!("starting to work on a segment {:?}", segment);
let (mut start, mut end) = find_start_end(&assembly);
while start != end {
let next_ids = starts.get(&end).unwrap();
for next_id in next_ids {
let next_segment = objects.get(&OsmId::Way(*next_id)).unwrap().way().unwrap();
assembly.push(next_segment);
work_queue.remove(next_id);
let (start_new, end_new) = find_start_end(&assembly);
start = start_new;
end = end_new;
if start == end {
break;
}
}
}
polyset
.polygons
.push(build_polygon(&assembly, &objects, &outside_points));
}
polyset
}
/// Returns `true` when the `input` is in any of the polygons.
pub fn in_any(&self, input: RadianCoordinate) -> bool {
for polygon in self.polygons.iter() {
if polygon.contains(&input) {
return true;
}
}
return false;
}
}
/// aggregate all coast segment WayIDs into a Set
fn aggregate_coasts(objects: &OsmObjectMap) -> BTreeSet<&OsmId> {
let mut coast_segments = BTreeSet::new();
for (key, value) in objects.iter() {
match value {
OsmObj::Way(_) => {
coast_segments.insert(key);
}
OsmObj::Node(_) => {}
OsmObj::Relation(r) => {
println!("Found unexpected relation: {:?}", r);
}
}
}
coast_segments
}
fn collects_start_end(
coast_segments: &BTreeSet<&OsmId>,
objects: &OsmObjectMap,
) -> (BTreeMap<NodeId, Vec<WayId>>, BTreeMap<NodeId, Vec<WayId>>) {
// Create a Map from NodeIDs of the start and ends of a way to WayIDs
let mut starts: BTreeMap<NodeId, Vec<WayId>> = BTreeMap::new();
let mut ends: BTreeMap<NodeId, Vec<WayId>> = BTreeMap::new();
for segment_id in coast_segments.iter() {
let segment = objects.get(&segment_id).unwrap().way().unwrap();
let segment_id = segment_id.way().unwrap();
let start = segment.nodes.first().unwrap();
let end = segment.nodes.last().unwrap();
// TODO cleanup
match starts.entry(*start) {
Entry::Vacant(e) => {
e.insert(vec![segment_id]);
}
Entry::Occupied(mut e) => {
e.get_mut().push(segment_id);
}
};
match ends.entry(*end) {
Entry::Vacant(e) => {
e.insert(vec![segment_id]);
}
Entry::Occupied(mut e) => {
e.get_mut().push(segment_id);
}
};
}
(starts, ends)
}
fn find_start_end(assembly: &Vec<&Way>) -> (NodeId, NodeId) {
let start = *assembly.first().unwrap().nodes.first().unwrap();
let end = *assembly.last().unwrap().nodes.last().unwrap();
(start, end)
}
/// builds a polygon from the given vector of ways.
fn build_polygon(
assembly: &Vec<&Way>,
objects: &OsmObjectMap,
outside_points: &Vec<RadianCoordinate>,
) -> Polygon {
let mut polygon = Polygon::default();
let first_way = assembly.first().unwrap();
add_points(first_way.nodes.iter(), &mut polygon.nodes, objects);
for way in assembly.iter().skip(1) {
add_points(way.nodes.iter().skip(1), &mut polygon.nodes, objects);
}
polygon.outside_point_1 = outside_points[0];
polygon.outside_point_2 = outside_points[1];
let iterator = outside_points.iter().skip(2);
polygon.bbox = BoundingBox::from_nodes(&polygon.nodes);
let mut verification = polygon.verify();
for new_point in iterator {
match verification {
Result::Ok(_) => {
return polygon;
}
Err(PolygonVerificationError::Antipodal) => {
polygon.outside_point_1 = *new_point;
}
Err(PolygonVerificationError::OutSide1SameCircle) => {
polygon.outside_point_1 = *new_point;
}
Err(PolygonVerificationError::OutSide2SameCircle) => {
polygon.outside_point_2 = *new_point;
}
}
verification = polygon.verify();
}
println!(
"No valid outside points found for a polygon with {:?} edges",
polygon.nodes.len()
);
polygon
}
fn add_points<'a>(
nodes: impl Iterator<Item = &'a NodeId>,
target: &mut Vec<RadianCoordinate>,
objects: &OsmObjectMap,
) {
for node in nodes {
let point = match objects.get(&OsmId::Node(*node)) {
Some(p) => p.node().unwrap(),
None => {
println!("No node found with id {:?}", node);
continue;
}
};
let lat = point.lat();
let lon = point.lon();
target.push(RadianCoordinate::from_degrees(lat, lon));
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::polygon::Polygon;
#[test]
fn test_in_any() {
let inside = RadianCoordinate::from_degrees(0.0, 10.0);
let outside = RadianCoordinate::from_degrees(60.0, 30.0);
let polygon = Polygon::build_polygon(
vec![
RadianCoordinate::from_degrees(45.0, 40.0),
RadianCoordinate::from_degrees(45.0, -45.0),
RadianCoordinate::from_degrees(-45.0, -40.0),
RadianCoordinate::from_degrees(-45.0, 45.0),
RadianCoordinate::from_degrees(45.0, 40.0),
],
RadianCoordinate::from_degrees(30.0, 60.0),
RadianCoordinate::from_degrees(-30.0, 60.0),
);
assert!(polygon.is_ok());
let polygon = polygon.unwrap();
assert!(polygon.contains(&inside));
let polyset = PolygonSet {
polygons: vec![polygon],
};
assert!(polyset.in_any(inside));
assert!(!polyset.in_any(outside));
}
}

View file

@ -1,5 +1,5 @@
use crate::coordinates::RadianCoordinate;
use std::f64::consts::{PI, FRAC_PI_2, FRAC_PI_4, FRAC_PI_8};
use std::f64::consts::{PI, FRAC_PI_2};
#[test]
fn on_great_circle() {
@ -48,75 +48,3 @@ fn antipodal() {
assert!(!point1.antipodal(&point4));
assert!(point6.antipodal(&point7));
}
#[test]
fn transformed_longitude() {
// test with regular north pole
let point0 = RadianCoordinate { lat: 0.0, lon: 0.0 };
let north_pole = RadianCoordinate {
lat: FRAC_PI_2,
lon: 0.0,
};
let difference = (point0.get_transformed_longitude(&north_pole) - point0.lon).abs() % PI;
assert!(difference < 1e-10);
// example with 4 points, where the X->P is between X->A and X->B
let point_x = RadianCoordinate {
lat: 0.0,
lon: -0.1,
};
let point_a = RadianCoordinate { lat: 0.1, lon: 0.0 };
let point_p = RadianCoordinate { lat: 0.0, lon: 0.1 };
let point_b = RadianCoordinate {
lat: -0.1,
lon: 0.0,
};
assert!(
point_a.get_transformed_longitude(&point_x)
< point_p.get_transformed_longitude(&point_x)
);
assert!(
point_p.get_transformed_longitude(&point_x)
< point_b.get_transformed_longitude(&point_x)
);
}
#[test]
fn strike_check() {
let pole = RadianCoordinate {
lat: FRAC_PI_4,
lon: 0.0,
};
let a = RadianCoordinate { lat: 0.0, lon: 0.1 };
let b = RadianCoordinate {
lat: 0.0,
lon: -0.1,
};
// Point that is between a and b and crosses the line between a and b
let p1 = RadianCoordinate {
lat: -FRAC_PI_8,
lon: 0.0,
};
// Point that is between a and b and does not cross the line between a and b
let p2 = RadianCoordinate {
lat: FRAC_PI_8,
lon: 0.0,
};
// Point that is left of a and b
let p3 = RadianCoordinate {
lat: 0.0,
lon: -0.2,
};
// Point that is right of a and b
let p4 = RadianCoordinate { lat: 0.0, lon: 0.2 };
assert!(p1.strike_between(&pole, &a, &b));
assert!(p2.strike_between(&pole, &a, &b));
assert!(!p3.strike_between(&pole, &a, &b));
assert!(!p4.strike_between(&pole, &a, &b));
}

9
src/utils.rs Normal file
View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
pub const EARTH_RADIUS: f64 = 6_371_000.0; // meters
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct RoutingQuery{
pub source: usize,
pub destination: usize,
}

7
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

View file

@ -0,0 +1,71 @@
var blueIcon = new L.Icon({
iconUrl: '/static/img/marker-icon-2x-blue.png',
shadowUrl: '/static/img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var redIcon = new L.Icon({
iconUrl: 'img/marker-icon-2x-red.png',
shadowUrl: 'img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var greenIcon = new L.Icon({
iconUrl: 'img/marker-icon-2x-green.png',
shadowUrl: 'img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var orangeIcon = new L.Icon({
iconUrl: 'img/marker-icon-2x-orange.png',
shadowUrl: 'img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var yellowIcon = new L.Icon({
iconUrl: 'img/marker-icon-2x-yellow.png',
shadowUrl: 'img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var violetIcon = new L.Icon({
iconUrl: 'img/marker-icon-2x-violet.png',
shadowUrl: 'img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var greyIcon = new L.Icon({
iconUrl: '/static/img/marker-icon-2x-grey.png',
shadowUrl: '/static/img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
var blackIcon = new L.Icon({
iconUrl: 'img/marker-icon-2x-black.png',
shadowUrl: 'img/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});

87
static/js/script.js Normal file
View file

@ -0,0 +1,87 @@
var map = L.map('map').setView([0, 0], 2);
L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
subdomains: ['a','b','c']
}).addTo( map );
var popup = L.popup();
var fromMarker = L.marker([0,0], {icon: blueIcon});
var toMarker = L.marker([0,0], {icon: greyIcon});
function setFrom(e) {
setField("from");
fromMarker.setLatLng(popup.getLatLng()).addTo(map);
}
function setTo(e) {
setField("to");
toMarker.setLatLng(popup.getLatLng()).addTo(map);
}
function setField(field) {
var latlng = sanitizeLatLng(popup.getLatLng())
document.getElementById(field).value = latlng.lat + "," + latlng.lng;
popup.close()
}
function sanitizeLatLng(latlng){
// "modulo around the world" while keeping the range from -180 to 180
latlng.lng = (((latlng.lng +180) % 360) + 360) % 360 - 180
return latlng
}
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("<p>Position " + e.latlng + '</p><button class="btn btn-primary" onclick="setFrom()">From</button> <button class="btn btn-secondary" onClick="setTo()">To</button>')
.openOn(map);
}
map.on("click", onMapClick);
let routeObject = L.geoJSON();
async function get_random_route() {
let result = await fetch("/random")
if (result.ok) {
let json = await result.json()
show_route(json)
} else {
alert("an error occured while finding the route");
}
}
function show_route(route){
clear_route();
routeObject = L.geoJSON(route).addTo(map);
}
function clear_route() {
routeObject.remove()
}
function set_route_cost(cost) {
let field = document.getElementById("routecost");
field.value = cost;
}
async function get_route() {
let form = document.getElementById("queryform");
data = new FormData(form);
let response = await(fetch("/route", { method: "POST", body: data }));
let result = await response.json();
if (result.success === true) {
set_route_cost(result.route.cost);
show_route(result.route.geojson);
} else {
set_route_cost(null);
clear_route();
alert("No Route found!");
}
}

44
templates/index.html.hbs Normal file
View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/css/bootstrap.min.css"/>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
crossorigin=""></script>
<script src="/static/js/leaflet-color-markers.js"></script>
<style>
</style>
</head>
<body>
<header>
<nav class="navbar bg-secondary">
<h1>FaPra OSM Ocean Routing</h1>
</nav>
</header>
<div class="container-fluid d-flex flex-column min-vh-100">
<div class="flex-grow-1 row">
<div class="col-3">
<form class="" id="queryform">
<div class="form group">
<label for="from">From</label>
<input type="text" class="form-control" name="from" id="from">
</div>
<div class="form group">
<label for="to">To</label>
<input type="text" class="form-control" name="to" id="to">
</div>
</form>
<button class="btn btn-primary" onclick="get_route()">Submit</button>
<button class="btn btn-primary" onclick="get_random_route()">Random Route</button>
<p>Route Cost: <input class="form-control " type="text" id="routecost" readonly></p>
</div>
<div id="map" class="col-9"></div>
<script type="text/javascript" src="/static/js/script.js"></script>
</div>
</div>
</body>
</html>