diff --git a/.gitignore b/.gitignore index ea8c4bf..5eb854d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/landmarks diff --git a/Cargo.lock b/Cargo.lock index 18d4047..a425b74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,24 +146,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - [[package]] name = "byteorder" version = "1.4.3" @@ -176,12 +158,6 @@ 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" @@ -209,17 +185,6 @@ 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" @@ -234,7 +199,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "textwrap 0.15.0", + "textwrap", ] [[package]] @@ -295,87 +260,6 @@ 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" @@ -386,28 +270,6 @@ 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" @@ -481,8 +343,7 @@ name = "fapra_osm_2" version = "0.1.0" dependencies = [ "bincode", - "clap 3.2.16", - "criterion", + "clap", "geojson", "handlebars", "osmpbfreader", @@ -761,12 +622,6 @@ 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" @@ -829,7 +684,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.3", + "itoa", ] [[package]] @@ -870,7 +725,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.3", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -934,36 +789,12 @@ dependencies = [ "libc", ] -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -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" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1041,15 +872,6 @@ 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" @@ -1212,12 +1034,6 @@ 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" @@ -1368,34 +1184,6 @@ 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" @@ -1530,30 +1318,6 @@ 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" @@ -1759,16 +1523,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.143" @@ -1786,7 +1540,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ - "itoa 1.0.3", + "itoa", "ryu", "serde", ] @@ -1942,15 +1696,6 @@ 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" @@ -1992,7 +1737,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" dependencies = [ - "itoa 1.0.3", + "itoa", "libc", "num_threads", "time-macros", @@ -2004,16 +1749,6 @@ 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" @@ -2190,12 +1925,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" 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" version = "0.2.3" @@ -2251,70 +1980,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -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" diff --git a/Cargo.toml b/Cargo.toml index 966732a..72036b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,3 @@ 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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3405b2b --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# 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 `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. + +### 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. + +### 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 + +Dijkstras algorithm is implenented in `gridgraph.rs` with `GridGraph::shortest_path`. +It uses a Heap to store the nodes. +For details on how to run benchmarks see the benchmarks section at the end. + +## 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. + +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 + +- 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. +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. + +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. + +`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. + +# Results + +These are some quick tests, further results will be presented later. +Everything was run on a Thinkpad X260 laptop with an Intel i7-6600U CPU @ 2.60GHz +processor. +Each test used the same 1000 queries. +Rust v1.57.0 was used for all tests. + +The ALT variants were used with the 4 best landmarks. +Further tests on the performance of more landmarks will be presented laster. +The set of 44 handpicked landmarks were spread around the extremeties of the +continents and into "dead ends" like the Mediteranean and the Gulf of Mexico +with the goal to provide landmarks that are "behind" the source or target +node. + +All benchmarks were run on the provided benchmark graph. + +## raw data: +``` +# name, (avg. heap pops per query, avg. time) +{'astar': (155019.451, 0.044386497025), + 'dijkstra': (423046.796, 0.058129875474999995), + 'greedy_32': (42514.751, 0.013299024275000002), + 'greedy_64': (35820.461, 0.011887869759), + 'handpicked_44': (70868.721, 0.01821366828), + 'random_32': (58830.082, 0.016845884717), + 'random_64': (51952.261, 0.015234422699)} +``` + +## Interpretation + +Dijkstra needs ~58ms per route, while the best version is greedy\_64 (that is +with 64 landmarks) needs only 12 seconds, which is ~5 times faster. +We also see, that the greedy versions perform slightly better than their +random counterparts with the same amount of nodes. +While the 44 handpicked landmarks outperformed A\* and Dijkstra, they are beaten +by both the random and greedy landmark selections which had fewer nodes. + +## Memory Consumption + +The landmarks are basically arrays of the cost to each node. +Since the distances are currently calculates with 64 bit integers +each landmark needs 8 byte per node in the graph. +With a graph that has about 700k nodes this leads to ~5.5MB of memory per +landmark. +So 64 landmarks need ~350MB of memory. + +One could also use 32 bit integers which would half the memory requirements. + +# 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/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/landmarks/ocean_handpicked_44.json b/landmarks/ocean_handpicked_44.json new file mode 100644 index 0000000..90daa82 --- /dev/null +++ b/landmarks/ocean_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 + ] + } + } + ] +} 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/alt.rs b/src/alt.rs index 1050da4..30aac24 100644 --- a/src/alt.rs +++ b/src/alt.rs @@ -1,83 +1,64 @@ use crate::gridgraph::{EdgeCost, GraphNode, GridGraph, NodeId}; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; use std::collections::BinaryHeap; +use crate::utils::DijkstraElement; +/// a single Landmark #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Landmark { pub node: GraphNode, pub distances: Vec, } + +/// A set of Landmarks #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct LandmarkSet { pub landmarks: Vec, +} + + +/// The LandmarkBestSet is the datastructure in which the indices of the +/// best landmarks for a certain query are stored. +#[derive(Debug, Clone)] +pub struct LandmarkBestSet<'a> { + pub landmark_set: &'a LandmarkSet, pub best_size: usize, best_landmarks: Vec, } impl Landmark { + + /// generates a landmark (calculates all distances) for a given node. pub fn generate(node: GraphNode, graph: &GridGraph) -> Landmark { + + // This is running a simplified version of dijkstra. + // It also does not track the ancestors of a node, because it is + // not needed for generating hte landmarks. + 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 { - 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 { + 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 +67,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 } @@ -98,23 +79,28 @@ impl 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. + /// an estimate of `EdgeCost::MAX` 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 + 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 + (l_to as i64 - l_from as i64).abs() as EdgeCost } } } 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 +111,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))); } @@ -137,24 +125,35 @@ impl LandmarkSet { 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); } } 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..f919459 100644 --- a/src/astar.rs +++ b/src/astar.rs @@ -4,22 +4,27 @@ use crate::utils::EARTH_RADIUS; use std::cmp::Ordering; use std::collections::BinaryHeap; -pub struct AStar { - pub graph: GridGraph, + +/// datastructure to hold data required by the A* algorithm +pub struct AStar<'a> { + pub graph: &'a GridGraph, } -#[derive(Eq)] +/// 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 - ancestor: Option, } 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) } @@ -31,80 +36,84 @@ impl PartialOrd for HeapElement { } } -impl PartialEq for HeapElement { - fn eq(&self, other: &Self) -> bool { - self.cost == other.cost - } -} - +/// A simple haversine distance heuristic. 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 } +/// a simple heuristic based on the difference in lattitude between two points +/// The idea is that it is cheaper to calculate than the haversine distance. 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<'_> { + + /// calculates the shortest path from start to end given the `estimate` + /// heuristic function. + /// + /// Returns `None` if no path exists. 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 - path_cost, - ancestor: prev, + index, path_cost, .. }) = 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), }); } } @@ -112,26 +121,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/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/bin/gen_landmarks_greedy.rs b/src/bin/gen_landmarks_greedy.rs new file mode 100644 index 0000000..9352115 --- /dev/null +++ b/src/bin/gen_landmarks_greedy.rs @@ -0,0 +1,88 @@ +use bincode; +use clap::Parser; +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; + +#[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 amount of landmarks to generate + #[clap(short, long)] + amount: usize, +} + +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) + } + }; + + 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"); +} diff --git a/src/bin/test_alt_gen.rs b/src/bin/gen_landmarks_random.rs similarity index 67% rename from src/bin/test_alt_gen.rs rename to src/bin/gen_landmarks_random.rs index fb199ec..438fec7 100644 --- a/src/bin/test_alt_gen.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, 4, &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/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/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 e84ba38..a284fa7 100644 --- a/src/bin/performance.rs +++ b/src/bin/performance.rs @@ -1,8 +1,8 @@ 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 fapra_osm_2::alt::LandmarkBestSet; +use fapra_osm_2::astar::{estimate_haversine, AStar}; +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; @@ -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,29 +32,17 @@ 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() { 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 = 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); - } - }; + let astar = AStar { graph: &(*graph) }; let targets = match File::open(args.targets.clone()) { Ok(f) => f, @@ -66,23 +54,33 @@ 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 = load_landmarks(&landmark_path); + + 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 mut landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); - landmarks.best_size = 4; + 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*"); @@ -109,13 +107,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); + let _result = astar.graph.shortest_path(&source, &destination); - println!("{}", result.unwrap().to_geojson()); + //println!("{}", result.unwrap().to_geojson()); } let elapsed = start.elapsed(); @@ -123,27 +121,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"); - // 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); - } } 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()); -} diff --git a/src/bin/task6-rocket.rs b/src/bin/task6-rocket.rs index b2aa5b4..11dbae3 100644 --- a/src/bin/task6-rocket.rs +++ b/src/bin/task6-rocket.rs @@ -1,26 +1,33 @@ -#[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}; -use fapra_osm_2::coordinates::{RadianCoordinate, DegreeCoordinate}; +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::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)] struct Args { /// name of the FMI file to read #[clap(short, long)] - filename: String, + graph: String, + + /// the landmarks to load + #[clap(short, long)] + landmarks: String, } #[get("/")] @@ -47,73 +54,107 @@ fn random_route(graphwrapper: &State) -> String { struct RouteQuery<'r> { r#from: &'r str, r#to: &'r str, + r#algorithm: &'r str, } -#[derive(Debug)] -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct RouteResponse { success: bool, route: Option, + algorithm: String, } #[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(); - let route = graphwrapper.graph.shortest_path(from, to); + println!("Working on a route from {:?} to {:?}", from, to); - println!("from: {:?}, to: {:?}", from, to); + let astar = AStar { + graph: &graphwrapper.graph, + }; - let response = RouteResponse{success: route.is_some(), route}; + let mut algorithm = routequery.algorithm; + + let start = Instant::now(); + + let route = if algorithm == "astar-haversine" { + println!("running A* with haversine distance"); + astar.shortest_path(from, to, estimate_haversine) + } else if algorithm == "alt" { + println!("running ALT"); + let mut best_landmarks = LandmarkBestSet::new(4, &graphwrapper.landmarks); + + best_landmarks.select_best(from.index as usize, to.index as usize); + 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!("The query took {} ms", time.as_millis()); + + let response = RouteResponse { + success: route.is_some(), + route, + algorithm: algorithm.to_string(), + }; Ok(Json(response)) } struct GraphWrapper { - graph: Box + graph: GridGraph, + landmarks: LandmarkSet, } #[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 = 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); - } - }; + let landmarks = load_landmarks(&args.landmarks); - println!("Loaded graph file"); - - // let graph = GridGraph::generate_regular_grid(10,10); + println!("Listening on http://localhost:8000"); rocket::build() - .manage(GraphWrapper{graph: graph}) + .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); + })) } 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] diff --git a/src/gridgraph.rs b/src/gridgraph.rs index 1291462..2fc00fb 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 @@ -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, @@ -134,8 +162,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 +169,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 +192,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 +216,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,16 +231,13 @@ 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; - - 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] { - // 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,34 +246,14 @@ 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]); } } } 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. @@ -297,7 +296,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; @@ -305,7 +303,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 +314,7 @@ impl GridGraph { Done, } + #[derive(Debug)] struct TemporaryGraphEdge { src: u32, dst: u32, @@ -388,25 +387,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 +443,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 +496,6 @@ impl GridGraph { lon_resolution: usize, polygons: Option<&PolygonSet>, ) -> Box { - #[derive(Debug)] struct TemporaryGraphNode { final_index: u32, @@ -518,7 +528,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 +586,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; diff --git a/src/utils.rs b/src/utils.rs index 7cc84e4..fd63548 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,78 @@ +use crate::alt::LandmarkSet; +use crate::gridgraph::{EdgeCost, GridGraph}; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fs::File; +use std::io::BufReader; +use std::process::exit; +/// an approximation of the earths radius. pub const EARTH_RADIUS: f64 = 6_371_000.0; // meters +/// serialization format for routing queries. #[derive(Serialize, Deserialize, Debug, Copy, Clone)] -pub struct RoutingQuery{ +pub struct RoutingQuery { pub source: usize, pub destination: usize, } + +/// loads the graph from the given path. +/// exits if an error occurs during loading. +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 +} + +/// loads a set of landmarks from the given path. +/// exits if an error occurs during loading. +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() +} + +/// A heap element for Dijkstra's algorithm. +/// +/// The comparison functions are inverted, so that Rusts MaxHeap works as a +/// MinHeap. +#[derive(Eq, PartialEq)] +pub struct DijkstraElement { + pub index: u32, + pub cost: EdgeCost, +} + +impl Ord for DijkstraElement { + fn cmp(&self, other: &Self) -> Ordering { + other.cost.cmp(&self.cost) + } +} + +impl PartialOrd for DijkstraElement { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} 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:

diff --git a/utils/generate_landmarks.py b/utils/generate_landmarks.py new file mode 100755 index 0000000..88356b7 --- /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 -- --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 }") diff --git a/utils/plot_results.py b/utils/plot_results.py new file mode 100755 index 0000000..9e99208 --- /dev/null +++ b/utils/plot_results.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 + +from sys import argv, exit +import os +from typing import Tuple, List +import re + +import numpy as np +import pandas as pd +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}")] + + +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