added current code
330
Cargo.lock
generated
|
|
@ -122,6 +122,15 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
|
@ -137,6 +146,18 @@ dependencies = [
|
||||||
"generic-array",
|
"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]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.10.0"
|
version = "3.10.0"
|
||||||
|
|
@ -155,6 +176,12 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cast"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.73"
|
version = "1.0.73"
|
||||||
|
|
@ -182,6 +209,17 @@ dependencies = [
|
||||||
"generic-array",
|
"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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.16"
|
version = "3.2.16"
|
||||||
|
|
@ -196,7 +234,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap 0.15.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -257,6 +295,87 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"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]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
@ -267,6 +386,28 @@ dependencies = [
|
||||||
"typenum",
|
"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]]
|
[[package]]
|
||||||
name = "ctr"
|
name = "ctr"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -339,7 +480,9 @@ dependencies = [
|
||||||
name = "fapra_osm_2"
|
name = "fapra_osm_2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"bincode",
|
||||||
|
"clap 3.2.16",
|
||||||
|
"criterion",
|
||||||
"geojson",
|
"geojson",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"osmpbfreader",
|
"osmpbfreader",
|
||||||
|
|
@ -347,6 +490,7 @@ dependencies = [
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_dyn_templates",
|
"rocket_dyn_templates",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -617,6 +761,12 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "handlebars"
|
name = "handlebars"
|
||||||
version = "4.3.3"
|
version = "4.3.3"
|
||||||
|
|
@ -679,7 +829,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itoa",
|
"itoa 1.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -720,7 +870,7 @@ dependencies = [
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa 1.0.3",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -785,10 +935,25 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itertools"
|
||||||
version = "1.0.2"
|
version = "0.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
|
|
@ -823,9 +988,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.126"
|
version = "0.2.127"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
|
|
@ -876,6 +1041,15 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
|
|
@ -1038,6 +1212,12 @@ version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oorandom"
|
||||||
|
version = "11.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -1188,6 +1368,34 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
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]]
|
[[package]]
|
||||||
name = "polyval"
|
name = "polyval"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -1322,6 +1530,30 @@ dependencies = [
|
||||||
"getrandom",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
|
@ -1333,18 +1565,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ref-cast"
|
name = "ref-cast"
|
||||||
version = "1.0.8"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "776c8940430cf563f66a93f9111d1cd39306dc6c68149ecc6b934742a44a828a"
|
checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ref-cast-impl",
|
"ref-cast-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ref-cast-impl"
|
name = "ref-cast-impl"
|
||||||
version = "1.0.8"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f26c4704460286103bff62ea1fb78d137febc86aaf76952e6c5a2249af01f54"
|
checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1520,18 +1752,28 @@ checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.141"
|
version = "1.0.143"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500"
|
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_cbor"
|
||||||
version = "1.0.141"
|
version = "0.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1540,11 +1782,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.82"
|
version = "1.0.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa 1.0.3",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
@ -1700,6 +1942,15 @@ dependencies = [
|
||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|
@ -1737,12 +1988,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f"
|
checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa 1.0.3",
|
||||||
"js-sys",
|
|
||||||
"libc",
|
"libc",
|
||||||
"num_threads",
|
"num_threads",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
|
|
@ -1754,6 +2004,16 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
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]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.20.1"
|
version = "1.20.1"
|
||||||
|
|
@ -1926,9 +2186,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.2"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
|
|
@ -2039,6 +2305,16 @@ version = "0.2.82"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
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]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,13 @@ rand = {version = "0.8", features = ["alloc"] }
|
||||||
rocket = {version="0.5.0-rc", features=["json"]}
|
rocket = {version="0.5.0-rc", features=["json"]}
|
||||||
rocket_dyn_templates = {version="0.1.0-rc", features=["handlebars"]}
|
rocket_dyn_templates = {version="0.1.0-rc", features=["handlebars"]}
|
||||||
serde = {version="1.0", features=["derive"]}
|
serde = {version="1.0", features=["derive"]}
|
||||||
|
serde_json = "1.0"
|
||||||
|
bincode = "1.3.3"
|
||||||
osmpbfreader = "0.15.2"
|
osmpbfreader = "0.15.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3.6"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "task5"
|
||||||
|
harness = false
|
||||||
|
|
|
||||||
160
src/alt.rs
Normal 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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/bin/generate_benchmark_targets.rs
Normal 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
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/bin/grid_to_geojson.rs
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
|
use crate::ACCURACY_BOUNDARY;
|
||||||
use geojson::{Position, Value};
|
use geojson::{Position, Value};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::f64::consts::{FRAC_PI_2, PI, TAU};
|
use std::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||||
use crate::ACCURACY_BOUNDARY;
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
|
||||||
/// Spherical coordinates in radians.
|
/// Spherical coordinates in radians.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||||
pub struct RadianCoordinate {
|
pub struct RadianCoordinate {
|
||||||
pub lat: f64,
|
pub lat: f64,
|
||||||
pub lon: f64,
|
pub lon: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spherical coordinates in degrees.
|
/// Spherical coordinates in degrees.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||||
pub struct DegreeCoordinate {
|
pub struct DegreeCoordinate {
|
||||||
pub lat: f64,
|
pub lat: f64,
|
||||||
pub lon: f64,
|
pub lon: f64,
|
||||||
|
|
@ -25,14 +27,14 @@ impl From<DegreeCoordinate> for RadianCoordinate {
|
||||||
|
|
||||||
impl From<DegreeCoordinate> for geojson::Value {
|
impl From<DegreeCoordinate> for geojson::Value {
|
||||||
fn from(coordinate: DegreeCoordinate) -> Self {
|
fn from(coordinate: DegreeCoordinate) -> Self {
|
||||||
Value::Point(vec![coordinate.lat, coordinate.lon])
|
Value::Point(vec![coordinate.lon, coordinate.lat])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RadianCoordinate> for Position {
|
impl From<RadianCoordinate> for Position {
|
||||||
fn from(coordinate: RadianCoordinate) -> Self {
|
fn from(coordinate: RadianCoordinate) -> Self {
|
||||||
let coordinate = DegreeCoordinate::from(coordinate);
|
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
|
/// returns the longitude of a point when the coordinate system is
|
||||||
/// transformed, such that `north_pole` is the north pole of that system.
|
/// transformed, such that `north_pole` is the north pole of that system.
|
||||||
pub fn get_transformed_longitude(&self, north_pole: &RadianCoordinate) -> f64 {
|
pub fn get_transformed_longitude(&self, north_pole: &RadianCoordinate) -> f64 {
|
||||||
|
|
||||||
if self.lat == FRAC_PI_2 {
|
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())
|
let bottom = (self.lat.sin() * north_pole.lat.cos())
|
||||||
- (self.lat.cos() * north_pole.lat.sin() * (self.lon - north_pole.lon).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())
|
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 {
|
impl DegreeCoordinate {
|
||||||
|
|
@ -163,6 +130,30 @@ impl DegreeCoordinate {
|
||||||
|
|
||||||
DegreeCoordinate { lat, lon }
|
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]
|
/// 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);
|
||||||
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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
pub mod coordinates;
|
pub mod coordinates;
|
||||||
pub mod polygon;
|
pub mod polygon;
|
||||||
|
pub mod polygonset;
|
||||||
|
pub mod gridgraph;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod astar;
|
||||||
|
pub mod alt;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
|
|
||||||
144
src/polygon.rs
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::coordinates::{RadianCoordinate, normalize_lon};
|
use crate::coordinates::{east_or_west, RadianCoordinate};
|
||||||
use geojson::{Feature, FeatureCollection, Position, Value};
|
use geojson::{Feature, FeatureCollection, Position, Value};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::f64;
|
use std::f64;
|
||||||
use std::f64::consts::{PI, TAU};
|
|
||||||
|
|
||||||
/// A spherical polygon
|
/// A spherical polygon
|
||||||
/// the first and last node have to be identical.
|
/// the first and last node have to be identical.
|
||||||
|
|
@ -139,6 +138,7 @@ impl Polygon {
|
||||||
point1: RadianCoordinate,
|
point1: RadianCoordinate,
|
||||||
point2: RadianCoordinate,
|
point2: RadianCoordinate,
|
||||||
) -> Result<Polygon, PolygonVerificationError> {
|
) -> Result<Polygon, PolygonVerificationError> {
|
||||||
|
println!("build_polygon");
|
||||||
let bbox = BoundingBox::from_nodes(&nodes);
|
let bbox = BoundingBox::from_nodes(&nodes);
|
||||||
|
|
||||||
let polygon = Polygon {
|
let polygon = Polygon {
|
||||||
|
|
@ -178,6 +178,11 @@ impl Polygon {
|
||||||
/// If the polygon does not have enough nodes, nothing can be inside
|
/// If the polygon does not have enough nodes, nothing can be inside
|
||||||
/// the polygon, so false is returned.
|
/// the polygon, so false is returned.
|
||||||
pub fn contains(&self, point: &RadianCoordinate) -> bool {
|
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));
|
let first_check = self.contains_internal(point, &(self.outside_point_1));
|
||||||
|
|
||||||
match first_check {
|
match first_check {
|
||||||
|
|
@ -203,77 +208,80 @@ impl Polygon {
|
||||||
|
|
||||||
let mut crossings = 0;
|
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];
|
let point_a = self.nodes[i];
|
||||||
|
|
||||||
// check whether the point is the vertex
|
// check whether we are on the vertex.
|
||||||
if *point == point_a {
|
if *point == point_a {
|
||||||
return Ok(true);
|
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 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 {
|
||||||
// is the great circle from outside to point crossing through
|
return Ok(true);
|
||||||
// the current vertex?
|
|
||||||
if point.on_great_circle(outside, &point_a) {
|
|
||||||
println!("we are on a great circle");
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
crossings += 1;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if point.strike_between(outside, &point_a, &point_b) {
|
let dir_bpoint = east_or_west(b_trans_lon, point_trans_lon);
|
||||||
println!("we have a matching strike");
|
let dir_boutside = east_or_west(b_trans_lon, outside_trans_lon);
|
||||||
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);
|
// are we walking into different directions from the arc AB to
|
||||||
|
// the other 2 points?
|
||||||
println!("initial lon_point: {}", lon_point);
|
// being on the arc has been excluded for the queried point
|
||||||
println!("initial lon_outside: {}", lon_outside);
|
// and may not happen for the outside point because otherwise
|
||||||
|
// it would not be outside after a successful strike check.
|
||||||
// the sign tells us on which side of the great circle from
|
if dir_bpoint != dir_boutside {
|
||||||
// a to b we are
|
crossings += 1;
|
||||||
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)
|
Ok(crossings % 2 == 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -357,18 +365,8 @@ mod tests {
|
||||||
println!("{}", features.to_string());
|
println!("{}", features.to_string());
|
||||||
|
|
||||||
let in_point = RadianCoordinate::from_degrees(5.0, 5.0);
|
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 out_point = RadianCoordinate::from_degrees(-17.0, 15.0);
|
||||||
|
let antipodal_point = RadianCoordinate::from_degrees(-30.0, -160.0);
|
||||||
let antipodal_point = RadianCoordinate {
|
|
||||||
lat: -0.1,
|
|
||||||
lon: -PI + 0.1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let outside_vertex_intersect = RadianCoordinate {
|
let outside_vertex_intersect = RadianCoordinate {
|
||||||
lat: -0.1,
|
lat: -0.1,
|
||||||
|
|
@ -380,7 +378,7 @@ mod tests {
|
||||||
lon: -0.1,
|
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(&out_point), false);
|
||||||
assert_eq!(polygon.contains(&antipodal_point), false);
|
assert_eq!(polygon.contains(&antipodal_point), false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -404,7 +402,7 @@ mod tests {
|
||||||
],
|
],
|
||||||
RadianCoordinate::from_degrees(1.0, -1.0),
|
RadianCoordinate::from_degrees(1.0, -1.0),
|
||||||
RadianCoordinate::from_degrees(2.0, -1.0),
|
RadianCoordinate::from_degrees(2.0, -1.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(polygon.is_ok());
|
assert!(polygon.is_ok());
|
||||||
let polygon = polygon.unwrap();
|
let polygon = polygon.unwrap();
|
||||||
|
|
|
||||||
284
src/polygonset.rs
Normal 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(¤t).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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::coordinates::RadianCoordinate;
|
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]
|
#[test]
|
||||||
fn on_great_circle() {
|
fn on_great_circle() {
|
||||||
|
|
@ -48,75 +48,3 @@ fn antipodal() {
|
||||||
assert!(!point1.antipodal(&point4));
|
assert!(!point1.antipodal(&point4));
|
||||||
assert!(point6.antipodal(&point7));
|
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
|
|
@ -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
1
static/css/bootstrap.min.css.map
Normal file
BIN
static/img/marker-icon-2x-black.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
static/img/marker-icon-2x-blue.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
static/img/marker-icon-2x-green.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/img/marker-icon-2x-grey.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
static/img/marker-icon-2x-orange.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/img/marker-icon-2x-red.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/img/marker-icon-2x-violet.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/img/marker-icon-2x-yellow.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/img/marker-icon-black.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
static/img/marker-icon-blue.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/img/marker-icon-green.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/marker-icon-grey.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/img/marker-icon-orange.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/marker-icon-red.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/marker-icon-violet.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/marker-icon-yellow.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/img/marker-shadow.png
Normal file
|
After Width: | Height: | Size: 608 B |
71
static/js/leaflet-color-markers.js
Normal 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
|
|
@ -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: '© <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
|
|
@ -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>
|
||||||