├── docs ├── car.png ├── levels.png ├── data_model.png ├── screenshot.png ├── mmap.supp └── pthread.supp ├── web ├── sprite.png ├── sprite@2x.png ├── sprite.json ├── sprite@2x.json ├── elevator.svg └── icons │ └── elevator.svg ├── .gitignore ├── test ├── test_dir.h.in ├── ajaccio-ferry.osm.pbf ├── station-border.osm.pbf ├── london-corridor.osm.pbf ├── luisenplatz-darmstadt.osm.pbf ├── restriction_test_elevation │ ├── elevations_1.bil │ ├── elevations_1.hdr │ └── create_data.py ├── routing │ └── profile.cc ├── level_test.cc ├── main.cc ├── restriction_test.cc ├── dijkstra_astarbidir_test.cc └── routing_tests.cc ├── include └── osr │ ├── routing │ ├── algorithms.h │ ├── mode.h │ ├── additional_edge.h │ ├── sharing_data.h │ ├── path.h │ ├── tracking.h │ ├── parameters.h │ ├── with_profile.h │ ├── dial.h │ ├── route.h │ ├── dijkstra.h │ ├── profile.h │ └── profiles │ │ ├── car.h │ │ └── bike.h │ ├── extract │ └── extract.h │ ├── location.h │ ├── preprocessing │ └── elevation │ │ ├── resolution.h │ │ ├── dem_driver.h │ │ ├── provider.h │ │ ├── hgt_driver.h │ │ ├── dem_tile.h │ │ ├── hgt_tile.h │ │ ├── shared.h │ │ └── hgt_tile_def.h │ ├── util │ ├── multi_counter.h │ ├── reverse.h │ └── infinite.h │ ├── point.h │ ├── elevation_storage.h │ ├── types.h │ ├── platforms.h │ ├── geojson.h │ └── ways.h ├── src ├── preprocessing │ └── elevation │ │ ├── hgt_tile.cc │ │ ├── dem_driver.cc │ │ ├── provider.cc │ │ ├── hgt_driver.cc │ │ └── dem_tile.cc ├── routing │ ├── mode.cc │ ├── parameters.cc │ └── profile.cc ├── lookup.cc └── ways.cc ├── Dockerfile ├── cmake ├── clang-tidy.cmake ├── pkg.cmake └── buildcache.cmake ├── exe ├── backend │ ├── include │ │ └── osr │ │ │ └── backend │ │ │ └── http_server.h │ └── src │ │ └── main.cc └── extract.cc ├── LICENSE ├── .pkg.lock ├── .clang-format ├── .pkg ├── CMakeLists.txt ├── CMakePresets.json └── README.md /docs/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/docs/car.png -------------------------------------------------------------------------------- /docs/levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/docs/levels.png -------------------------------------------------------------------------------- /web/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/web/sprite.png -------------------------------------------------------------------------------- /web/sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/web/sprite@2x.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /*build* 3 | /deps 4 | /.pkg.mutex 5 | /.clang-tidy 6 | /.vs 7 | /out -------------------------------------------------------------------------------- /docs/data_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/docs/data_model.png -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /test/test_dir.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define OSR_TEST_EXECUTION_DIR "@CMAKE_CURRENT_SOURCE_DIR@" -------------------------------------------------------------------------------- /test/ajaccio-ferry.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/test/ajaccio-ferry.osm.pbf -------------------------------------------------------------------------------- /test/station-border.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/test/station-border.osm.pbf -------------------------------------------------------------------------------- /test/london-corridor.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/test/london-corridor.osm.pbf -------------------------------------------------------------------------------- /test/luisenplatz-darmstadt.osm.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/motis-project/osr/HEAD/test/luisenplatz-darmstadt.osm.pbf -------------------------------------------------------------------------------- /test/restriction_test_elevation/elevations_1.bil: -------------------------------------------------------------------------------- 1 | A_A_A_A_A_A_A_A_A_A_A_A_D_C_C_A_A_A_A_A_A_A_C_A_A_A_A_A_A_A_D_A_A_A_A_B_A_C_F_A_A_A_A_A_A_A_A_A_ -------------------------------------------------------------------------------- /web/sprite.json: -------------------------------------------------------------------------------- 1 | { 2 | "elevator": { 3 | "height": 20, 4 | "pixelRatio": 1, 5 | "width": 20, 6 | "x": 0, 7 | "y": 0 8 | } 9 | } -------------------------------------------------------------------------------- /web/sprite@2x.json: -------------------------------------------------------------------------------- 1 | { 2 | "elevator": { 3 | "height": 40, 4 | "pixelRatio": 2, 5 | "width": 40, 6 | "x": 0, 7 | "y": 0 8 | } 9 | } -------------------------------------------------------------------------------- /docs/mmap.supp: -------------------------------------------------------------------------------- 1 | { 2 | uninitialized_values_written_to_memory_map 3 | Memcheck:Param 4 | msync(start) 5 | fun:msync 6 | fun:_ZN5cista4mmap4syncEv 7 | } -------------------------------------------------------------------------------- /docs/pthread.supp: -------------------------------------------------------------------------------- 1 | { 2 | pthread thread creation leak 3 | Memcheck:Leak 4 | match-leak-kinds: possible 5 | fun:calloc 6 | fun:calloc 7 | fun:allocate_dtv 8 | fun:_dl_allocate_tls 9 | fun:allocate_stack 10 | fun:pthread_create@@GLIBC_2.34 11 | } -------------------------------------------------------------------------------- /include/osr/routing/algorithms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace osr { 7 | 8 | enum class routing_algorithm : std::uint8_t { kDijkstra, kAStarBi }; 9 | 10 | routing_algorithm to_algorithm(std::string_view); 11 | 12 | } // namespace osr 13 | -------------------------------------------------------------------------------- /web/elevator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /include/osr/routing/mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace osr { 7 | 8 | enum class mode : std::uint8_t { 9 | kFoot, 10 | kWheelchair, 11 | kBike, 12 | kCar, 13 | }; 14 | 15 | std::string_view to_str(mode); 16 | 17 | } // namespace osr 18 | -------------------------------------------------------------------------------- /include/osr/extract/extract.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace osr { 4 | 5 | void extract(bool with_platforms, 6 | std::filesystem::path const& in, 7 | std::filesystem::path const& out, 8 | std::filesystem::path const& elevation_dir); 9 | 10 | } // namespace osr -------------------------------------------------------------------------------- /include/osr/routing/additional_edge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osr/types.h" 4 | 5 | namespace osr { 6 | 7 | struct additional_edge { 8 | friend bool operator==(additional_edge, additional_edge) = default; 9 | 10 | node_idx_t node_{}; 11 | distance_t distance_{}; 12 | }; 13 | 14 | } // namespace osr 15 | -------------------------------------------------------------------------------- /src/preprocessing/elevation/hgt_tile.cc: -------------------------------------------------------------------------------- 1 | #include "osr/preprocessing/elevation/hgt_tile.h" 2 | #include "osr/preprocessing/elevation/hgt_tile_def.h" 3 | 4 | namespace osr::preprocessing::elevation { 5 | 6 | template struct hgt_tile<3601U>; 7 | template struct hgt_tile<1201U>; 8 | 9 | } // namespace osr::preprocessing::elevation 10 | -------------------------------------------------------------------------------- /test/routing/profile.cc: -------------------------------------------------------------------------------- 1 | #include "osr/routing/profile.h" 2 | #include "osr/routing/parameters.h" 3 | 4 | template 5 | constexpr bool is_parameters_variant(std::variant const&) { 6 | static_assert((osr::ProfileParameters && ...)); 7 | return (osr::ProfileParameters && ...); 8 | } 9 | 10 | static_assert(is_parameters_variant(osr::profile_parameters{})); 11 | -------------------------------------------------------------------------------- /test/restriction_test_elevation/elevations_1.hdr: -------------------------------------------------------------------------------- 1 | 2 | BYTEORDER I 3 | LAYOUT BIL 4 | NROWS 6 5 | NCOLS 8 6 | NBANDS 1 7 | NBITS 16 8 | BANDROWBYTES 16 9 | TOTALROWBYTES 16 10 | PIXELTYPE SIGNEDINT 11 | ULXMAP 8.65617 12 | ULYMAP 49.8838761 13 | XDIM 0.00019892857142852653 14 | YDIM 0.00021848000000090906 15 | NODATA -32767 16 | -------------------------------------------------------------------------------- /web/icons/elevator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/routing/mode.cc: -------------------------------------------------------------------------------- 1 | #include "osr/routing/mode.h" 2 | 3 | #include "utl/verify.h" 4 | 5 | #include "cista/hash.h" 6 | 7 | namespace osr { 8 | 9 | std::string_view to_str(mode const m) { 10 | switch (m) { 11 | case mode::kFoot: return "foot"; 12 | case mode::kWheelchair: return "wheelchair"; 13 | case mode::kCar: return "car"; 14 | case mode::kBike: return "bike"; 15 | } 16 | throw utl::fail("{} is not a valid mode", static_cast(m)); 17 | } 18 | 19 | } // namespace osr 20 | -------------------------------------------------------------------------------- /include/osr/location.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "geo/latlng.h" 6 | 7 | #include "cista/reflection/comparable.h" 8 | 9 | #include "osr/types.h" 10 | 11 | namespace osr { 12 | 13 | struct location { 14 | CISTA_FRIEND_COMPARABLE(location) 15 | 16 | friend std::ostream& operator<<(std::ostream& out, location const& l) { 17 | return out << "{ pos=" << l.pos_ << ", lvl=" << l.lvl_.to_float() << " }"; 18 | } 19 | 20 | geo::latlng pos_; 21 | level_t lvl_; 22 | }; 23 | 24 | } // namespace osr -------------------------------------------------------------------------------- /test/level_test.cc: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include "windows.h" 3 | #endif 4 | 5 | #include "gtest/gtest.h" 6 | 7 | #include "osr/ways.h" 8 | 9 | using namespace osr; 10 | 11 | TEST(osr, level) { 12 | auto const lvl_0 = level_t{0.1F}.to_float(); 13 | EXPECT_EQ(0.0F, lvl_0); 14 | 15 | auto const lvl_neg4 = level_t{-4.0F}.to_float(); 16 | EXPECT_EQ(-4.0F, lvl_neg4); 17 | 18 | auto const lvl_4 = level_t{4.0F}.to_float(); 19 | EXPECT_EQ(4.0F, lvl_4); 20 | 21 | auto const lvl_minus_3 = level_t{-3.0F}.to_float(); 22 | EXPECT_EQ(-3.0F, lvl_minus_3); 23 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.17 2 | ARG TARGETARCH 3 | ADD osr-linux-$TARGETARCH/osr-linux-$TARGETARCH.tar.bz2 / 4 | RUN addgroup -S osr && adduser -S osr -G osr && \ 5 | mkdir /data && chown osr:osr /data && \ 6 | chown osr:osr /data && \ 7 | echo -e "#!/bin/sh\n\ 8 | if [ ! -d '/data/osr' ]; then\n\ 9 | /osr/osr-extract -i /input/osm.pbf -o /data/osr\n\ 10 | fi\n\ 11 | /osr/osr-backend -d /data/osr -s /osr/web\n\ 12 | " > /run.sh && \ 13 | chmod +x /run.sh 14 | EXPOSE 8080 15 | VOLUME ["/data"] 16 | VOLUME ["/input"] 17 | WORKDIR /osr 18 | USER osr 19 | CMD ["/run.sh"] -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/resolution.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace osr::preprocessing::elevation { 7 | 8 | struct resolution { 9 | resolution& update(resolution const& o) { 10 | if (std::isnan(x_) || o.x_ < x_) { 11 | x_ = o.x_; 12 | } 13 | if (std::isnan(y_) || o.y_ < y_) { 14 | y_ = o.y_; 15 | } 16 | return *this; 17 | } 18 | double x_{std::numeric_limits::quiet_NaN()}; 19 | double y_{std::numeric_limits::quiet_NaN()}; 20 | }; 21 | 22 | } // namespace osr::preprocessing::elevation -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/dem_driver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "cista/containers/rtree.h" 6 | 7 | #include "geo/latlng.h" 8 | 9 | #include "osr/preprocessing/elevation/dem_tile.h" 10 | #include "osr/preprocessing/elevation/resolution.h" 11 | #include "osr/preprocessing/elevation/shared.h" 12 | 13 | namespace osr::preprocessing::elevation { 14 | 15 | struct dem_driver { 16 | dem_driver() = default; 17 | bool add_tile(std::filesystem::path const&); 18 | elevation_meters_t get(geo::latlng const&) const; 19 | tile_idx_t tile_idx(geo::latlng const&) const; 20 | resolution max_resolution() const; 21 | std::size_t n_tiles() const; 22 | 23 | cista::raw::rtree rtree_{}; 24 | std::vector tiles_{}; 25 | }; 26 | 27 | } // namespace osr::preprocessing::elevation 28 | -------------------------------------------------------------------------------- /cmake/clang-tidy.cmake: -------------------------------------------------------------------------------- 1 | if (CMake_SOURCE_DIR STREQUAL CMake_BINARY_DIR) 2 | message(FATAL_ERROR "CMake_RUN_CLANG_TIDY requires an out-of-source build!") 3 | endif () 4 | 5 | file(RELATIVE_PATH RELATIVE_SOURCE_DIR ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) 6 | 7 | if (OSR_CLANG_TIDY_COMMAND) 8 | set(CLANG_TIDY_COMMAND "${OSR_CLANG_TIDY_COMMAND}") 9 | else () 10 | find_program(CLANG_TIDY_COMMAND NAMES clang-tidy-18) 11 | endif () 12 | 13 | if (NOT CLANG_TIDY_COMMAND) 14 | message(FATAL_ERROR "CMake_RUN_CLANG_TIDY is ON but clang-tidy is not found!") 15 | endif () 16 | 17 | set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}") 18 | 19 | file(SHA1 ${CMAKE_CURRENT_SOURCE_DIR}/.clang-tidy.in clang_tidy_sha1) 20 | set(CLANG_TIDY_DEFINITIONS "CLANG_TIDY_SHA1=${clang_tidy_sha1}") 21 | unset(clang_tidy_sha1) 22 | 23 | configure_file(.clang-tidy.in ${CMAKE_CURRENT_SOURCE_DIR}/.clang-tidy) -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/provider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "geo/latlng.h" 6 | 7 | #include "osr/preprocessing/elevation/resolution.h" 8 | #include "osr/preprocessing/elevation/shared.h" 9 | 10 | namespace osr::preprocessing::elevation { 11 | 12 | struct provider { 13 | provider(std::filesystem::path const&); 14 | ~provider(); 15 | provider(provider const&) = delete; 16 | provider& operator=(provider const&) = delete; 17 | provider(provider&&) = delete; 18 | provider& operator=(provider&&) = delete; 19 | 20 | elevation_meters_t get(geo::latlng const&) const; 21 | tile_idx_t tile_idx(geo::latlng const&) const; 22 | std::size_t driver_count() const; 23 | resolution max_resolution() const; 24 | 25 | private: 26 | struct impl; 27 | std::unique_ptr impl_; 28 | }; 29 | 30 | } // namespace osr::preprocessing::elevation 31 | -------------------------------------------------------------------------------- /include/osr/routing/sharing_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "geo/latlng.h" 4 | 5 | #include "osr/routing/additional_edge.h" 6 | #include "osr/types.h" 7 | 8 | namespace osr { 9 | 10 | struct sharing_data { 11 | geo::latlng get_additional_node_coordinates(node_idx_t const n) const { 12 | return additional_node_coordinates_.at(to_idx(n) - additional_node_offset_); 13 | } 14 | 15 | bitvec const* start_allowed_; 16 | bitvec const* end_allowed_; 17 | bitvec const* through_allowed_; 18 | 19 | node_idx_t::value_t additional_node_offset_{}; 20 | std::vector const& additional_node_coordinates_; 21 | hash_map> const& additional_edges_; 22 | }; 23 | 24 | inline bool is_allowed(bitvec const* b, node_idx_t const n) { 25 | return b == nullptr || b->test(n); 26 | } 27 | 28 | } // namespace osr 29 | -------------------------------------------------------------------------------- /include/osr/util/multi_counter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "osr/types.h" 6 | 7 | namespace osr { 8 | 9 | struct multi_counter { 10 | using size_type = bitvec64::size_type; 11 | 12 | bool is_multi(size_type const i) const { return multi_[i]; } 13 | 14 | void increment(size_type const i) { 15 | once_.resize(std::max(once_.size(), i + 1U)); 16 | multi_.resize(std::max(multi_.size(), i + 1U)); 17 | if (once_[i]) { 18 | if (!multi_[i]) { 19 | multi_.set(i, true); 20 | } 21 | } else { 22 | once_.set(i, true); 23 | } 24 | } 25 | 26 | size_type size() const noexcept { return once_.size(); } 27 | 28 | void reserve(size_type const size) { 29 | once_.blocks_.reserve(size / bitvec64::bits_per_block); 30 | multi_.blocks_.reserve(size / bitvec64::bits_per_block); 31 | } 32 | 33 | bitvec64 once_, multi_; 34 | }; 35 | 36 | } // namespace osr -------------------------------------------------------------------------------- /include/osr/routing/path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "geo/polyline.h" 6 | 7 | #include "osr/elevation_storage.h" 8 | #include "osr/routing/mode.h" 9 | #include "osr/types.h" 10 | 11 | namespace osr { 12 | 13 | struct path { 14 | struct segment { 15 | geo::polyline polyline_; 16 | level_t from_level_{kNoLevel}; 17 | level_t to_level_{kNoLevel}; 18 | node_idx_t from_{node_idx_t::invalid()}; 19 | node_idx_t to_{node_idx_t::invalid()}; 20 | way_idx_t way_{way_idx_t::invalid()}; 21 | cost_t cost_{kInfeasible}; 22 | distance_t dist_{0}; 23 | elevation_storage::elevation elevation_{}; 24 | mode mode_{mode::kFoot}; 25 | }; 26 | 27 | cost_t cost_{kInfeasible}; 28 | double dist_{0.0}; 29 | elevation_storage::elevation elevation_{}; 30 | std::vector segments_{}; 31 | bool uses_elevator_{false}; 32 | node_idx_t track_node_{node_idx_t::invalid()}; 33 | }; 34 | 35 | } // namespace osr 36 | -------------------------------------------------------------------------------- /test/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include "utl/progress_tracker.h" 7 | 8 | #include "test_dir.h" 9 | 10 | #ifdef PROTOBUF_LINKED 11 | #include "google/protobuf/stubs/common.h" 12 | #endif 13 | 14 | namespace fs = std::filesystem; 15 | 16 | int main(int argc, char** argv) { 17 | std::clog.rdbuf(std::cout.rdbuf()); 18 | 19 | utl::get_active_progress_tracker_or_activate("test"); 20 | 21 | try { 22 | fs::current_path(OSR_TEST_EXECUTION_DIR); 23 | std::cout << "executing tests in " << fs::current_path() << '\n'; 24 | } catch (std::exception const& e) { 25 | std::cout << "could not change directory to " << OSR_TEST_EXECUTION_DIR 26 | << ": " << e.what() << "\n"; 27 | return 1; 28 | } 29 | 30 | ::testing::InitGoogleTest(&argc, argv); 31 | auto test_result = RUN_ALL_TESTS(); 32 | 33 | #ifdef PROTOBUF_LINKED 34 | google::protobuf::ShutdownProtobufLibrary(); 35 | #endif 36 | 37 | return test_result; 38 | } -------------------------------------------------------------------------------- /exe/backend/include/osr/backend/http_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "boost/asio/io_context.hpp" 7 | 8 | #include "osr/elevation_storage.h" 9 | #include "osr/lookup.h" 10 | #include "osr/platforms.h" 11 | #include "osr/ways.h" 12 | 13 | namespace osr::backend { 14 | 15 | struct http_server { 16 | http_server(boost::asio::io_context& ioc, 17 | boost::asio::io_context& thread_pool, 18 | ways const&, 19 | lookup const&, 20 | platforms const*, 21 | elevation_storage const*, 22 | std::string const& static_file_path); 23 | ~http_server(); 24 | http_server(http_server const&) = delete; 25 | http_server& operator=(http_server const&) = delete; 26 | http_server(http_server&&) = delete; 27 | http_server& operator=(http_server&&) = delete; 28 | 29 | void listen(std::string const& host, std::string const& port); 30 | void stop(); 31 | 32 | private: 33 | struct impl; 34 | std::unique_ptr impl_; 35 | }; 36 | 37 | } // namespace osr::backend 38 | -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/hgt_driver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "cista/containers/rtree.h" 8 | 9 | #include "geo/latlng.h" 10 | 11 | #include "osr/preprocessing/elevation/hgt_tile.h" 12 | #include "osr/preprocessing/elevation/resolution.h" 13 | #include "osr/preprocessing/elevation/shared.h" 14 | 15 | namespace osr::preprocessing::elevation { 16 | 17 | static_assert(IsTile>); 18 | static_assert(IsTile>); 19 | 20 | struct hgt_driver { 21 | using hgt_tile_t = std::variant, hgt_tile<1201>>; 22 | 23 | hgt_driver() = default; 24 | bool add_tile(std::filesystem::path const&); 25 | elevation_meters_t get(geo::latlng const&) const; 26 | tile_idx_t tile_idx(geo::latlng const&) const; 27 | resolution max_resolution() const; 28 | std::size_t n_tiles() const; 29 | static std::optional open(std::filesystem::path const&); 30 | 31 | cista::raw::rtree rtree_; 32 | std::vector tiles_; 33 | }; 34 | 35 | } // namespace osr::preprocessing::elevation 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MOTIS Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/osr/routing/tracking.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osr/routing/path.h" 4 | #include "osr/ways.h" 5 | 6 | namespace osr { 7 | 8 | struct elevator_tracking { 9 | void write(path& p) const { p.uses_elevator_ = uses_elevator_; } 10 | void track(elevator_tracking const& l, 11 | ways::routing const& r, 12 | way_idx_t, 13 | node_idx_t const n, 14 | bool) { 15 | uses_elevator_ = l.uses_elevator_ || r.node_properties_[n].is_elevator(); 16 | } 17 | 18 | bool uses_elevator_{false}; 19 | }; 20 | 21 | struct track_node_tracking { 22 | void write(path& p) const { p.track_node_ = track_node_; } 23 | void track(track_node_tracking const& l, 24 | ways::routing const&, 25 | way_idx_t, 26 | node_idx_t const n, 27 | bool const track) { 28 | track_node_ = track ? n : l.track_node_; 29 | } 30 | 31 | node_idx_t track_node_{node_idx_t::invalid()}; 32 | }; 33 | 34 | struct noop_tracking { 35 | void write(path&) const {} 36 | void track( 37 | noop_tracking const&, ways::routing const&, way_idx_t, node_idx_t, bool) { 38 | } 39 | }; 40 | 41 | } // namespace osr 42 | -------------------------------------------------------------------------------- /include/osr/point.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "osmium/osm/location.hpp" 6 | 7 | #include "geo/latlng.h" 8 | 9 | namespace osr { 10 | 11 | struct point { 12 | friend std::ostream& operator<<(std::ostream& out, point const c) { 13 | auto const l = c.as_location(); 14 | return out << '(' << l.lat() << ", " << l.lon() << ')'; 15 | } 16 | 17 | static point from_latlng(geo::latlng const& x) { 18 | auto const l = osmium::Location{x.lng_, x.lat_}; 19 | return point{.lat_ = l.x(), .lng_ = l.y()}; 20 | } 21 | 22 | static point from_location(osmium::Location const l) { 23 | return {l.x(), l.y()}; 24 | } 25 | 26 | osmium::Location as_location() const { return osmium::Location{lat_, lng_}; } 27 | 28 | geo::latlng as_latlng() const { 29 | auto const l = as_location(); 30 | return {l.lat(), l.lon()}; 31 | } 32 | 33 | operator geo::latlng() const { return as_latlng(); } 34 | 35 | double lat() const { return as_latlng().lat_; } 36 | double lng() const { return as_latlng().lng_; } 37 | 38 | std::int32_t lat_, lng_; 39 | }; 40 | 41 | inline auto format_as(point const p) { return std::pair{p.lat(), p.lng()}; } 42 | 43 | } // namespace osr -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/dem_tile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "geo/box.h" 8 | #include "geo/latlng.h" 9 | 10 | #include "osr/preprocessing/elevation/resolution.h" 11 | #include "osr/preprocessing/elevation/shared.h" 12 | 13 | namespace osr::preprocessing::elevation { 14 | 15 | enum class pixel_type : std::uint8_t { int16, float32 }; 16 | 17 | union pixel_value { 18 | std::int16_t int16_; 19 | float float32_; 20 | }; 21 | 22 | struct dem_tile { 23 | explicit dem_tile(std::filesystem::path const&); 24 | ~dem_tile(); 25 | dem_tile(dem_tile&& grid) noexcept; 26 | dem_tile(dem_tile const&) = delete; 27 | dem_tile& operator=(dem_tile const&) = delete; 28 | dem_tile& operator=(dem_tile&&) = delete; 29 | 30 | elevation_meters_t get(geo::latlng const&) const; 31 | tile_idx_t tile_idx(geo::latlng const&) const; 32 | geo::box get_box() const; 33 | 34 | pixel_value get_raw(geo::latlng const&) const; 35 | pixel_type get_pixel_type() const; 36 | resolution max_resolution() const; 37 | 38 | private: 39 | struct impl; 40 | std::unique_ptr impl_; 41 | }; 42 | 43 | } // namespace osr::preprocessing::elevation 44 | -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/hgt_tile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "geo/box.h" 8 | #include "geo/latlng.h" 9 | 10 | #include "osr/preprocessing/elevation/resolution.h" 11 | #include "osr/preprocessing/elevation/shared.h" 12 | 13 | namespace osr::preprocessing::elevation { 14 | 15 | template 16 | struct hgt_tile { 17 | constexpr static auto const kBytesPerPixel = std::size_t{2U}; 18 | 19 | explicit hgt_tile(std::string const& path, 20 | std::int8_t const lat, 21 | std::int16_t const lng); 22 | ~hgt_tile(); 23 | hgt_tile(hgt_tile&& grid) noexcept; 24 | hgt_tile(hgt_tile const&) = delete; 25 | hgt_tile& operator=(hgt_tile const&) = delete; 26 | hgt_tile& operator=(hgt_tile&&) = delete; 27 | 28 | elevation_meters_t get(geo::latlng const&) const; 29 | tile_idx_t tile_idx(geo::latlng const&) const; 30 | 31 | resolution max_resolution() const; 32 | 33 | geo::box get_box() const; 34 | 35 | static constexpr std::size_t file_size() { 36 | return RasterSize * RasterSize * kBytesPerPixel; 37 | } 38 | 39 | private: 40 | struct impl; 41 | std::unique_ptr impl_; 42 | }; 43 | 44 | } // namespace osr::preprocessing::elevation 45 | -------------------------------------------------------------------------------- /include/osr/routing/parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "osr/routing/profile.h" 6 | #include "osr/routing/profiles/bike.h" 7 | #include "osr/routing/profiles/bike_sharing.h" 8 | #include "osr/routing/profiles/car.h" 9 | #include "osr/routing/profiles/car_parking.h" 10 | #include "osr/routing/profiles/car_sharing.h" 11 | #include "osr/routing/profiles/foot.h" 12 | 13 | namespace osr { 14 | 15 | using profile_parameters = 16 | std::variant::parameters, 17 | foot::parameters, 18 | foot::parameters, 19 | foot::parameters, 20 | bike::parameters, 21 | bike::parameters, 22 | bike::parameters, 23 | bike::parameters, 24 | car::parameters, 25 | car_parking::parameters, 26 | car_parking::parameters, 27 | car_parking::parameters, 28 | car_parking::parameters, 29 | bike_sharing::parameters, 30 | car_sharing::parameters>; 31 | 32 | profile_parameters get_parameters(search_profile const p); 33 | 34 | } // namespace osr 35 | -------------------------------------------------------------------------------- /include/osr/elevation_storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "cista/mmap.h" 8 | 9 | #include "osr/types.h" 10 | #include "osr/ways.h" 11 | 12 | namespace osr { 13 | 14 | namespace preprocessing::elevation { 15 | struct provider; 16 | } 17 | 18 | struct elevation_storage { 19 | struct elevation { 20 | elevation& operator+=(elevation const&); 21 | elevation swapped() const; 22 | elevation_monotonic_t up_{0U}; 23 | elevation_monotonic_t down_{0U}; 24 | }; 25 | struct encoding { 26 | using coding = std::uint8_t; 27 | explicit encoding(elevation const&); 28 | encoding() = default; 29 | elevation decode() const; 30 | explicit operator bool() const; 31 | coding up_ : 4 {}; 32 | coding down_ : 4 {}; 33 | }; 34 | elevation_storage(std::filesystem::path const&, 35 | cista::mmap::protection const mode); 36 | static std::unique_ptr try_open( 37 | std::filesystem::path const&); 38 | void set_elevations(ways const&, preprocessing::elevation::provider const&); 39 | elevation get_elevations(way_idx_t const way, 40 | std::uint16_t const segment) const; 41 | 42 | mm_vecvec elevations_; 43 | }; 44 | 45 | elevation_storage::elevation get_elevations(elevation_storage const*, 46 | way_idx_t const way, 47 | std::uint16_t const segment); 48 | 49 | } // namespace osr -------------------------------------------------------------------------------- /exe/extract.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fmt/core.h" 4 | #include "fmt/std.h" 5 | 6 | #include "conf/options_parser.h" 7 | 8 | #include "utl/progress_tracker.h" 9 | 10 | #include "osr/extract/extract.h" 11 | 12 | using namespace osr; 13 | using namespace boost::program_options; 14 | namespace fs = std::filesystem; 15 | 16 | struct config : public conf::configuration { 17 | config(std::filesystem::path in, std::filesystem::path out) 18 | : configuration{"Options"}, in_{std::move(in)}, out_{std::move(out)} { 19 | param(in_, "in,i", "OpenStreetMap .osm.pbf input path"); 20 | param(out_, "out,o", "output directory"); 21 | param(elevation_data_, "elevation_data,e", "directory with elevation data"); 22 | param(with_platforms_, "with_platforms,p", "extract platform info"); 23 | } 24 | 25 | std::filesystem::path in_, out_, elevation_data_; 26 | bool with_platforms_{false}; 27 | }; 28 | 29 | int main(int ac, char const** av) { 30 | auto c = config{"./planet-latest.osm.pbf", "./osr"}; 31 | 32 | conf::options_parser parser({&c}); 33 | parser.read_command_line_args(ac, av); 34 | 35 | parser.read_configuration_file(); 36 | 37 | parser.print_unrecognized(std::cout); 38 | parser.print_used(std::cout); 39 | 40 | if (!fs::is_regular_file(c.in_)) { 41 | fmt::println("input file {} not found", c.in_); 42 | return 1; 43 | } 44 | 45 | utl::activate_progress_tracker("osr"); 46 | auto const silencer = utl::global_progress_bars{false}; 47 | 48 | extract(c.with_platforms_, c.in_, c.out_, c.elevation_data_); 49 | } -------------------------------------------------------------------------------- /src/routing/parameters.cc: -------------------------------------------------------------------------------- 1 | #include "osr/routing/parameters.h" 2 | 3 | namespace osr { 4 | 5 | profile_parameters get_parameters(search_profile const p) { 6 | switch (p) { 7 | case search_profile::kFoot: 8 | return foot::parameters{}; 9 | case search_profile::kWheelchair: 10 | return foot::parameters{}; 11 | case search_profile::kBike: 12 | return bike::parameters{}; 13 | case search_profile::kBikeFast: 14 | return bike::parameters{}; 15 | case search_profile::kBikeElevationLow: 16 | return bike::parameters{}; 17 | case search_profile::kBikeElevationHigh: 18 | return bike::parameters{}; 19 | case search_profile::kCar: return car::parameters{}; 20 | case search_profile::kCarDropOff: 21 | return car_parking::parameters{}; 22 | case search_profile::kCarDropOffWheelchair: 23 | return car_parking::parameters{}; 24 | case search_profile::kCarParking: 25 | return car_parking::parameters{}; 26 | case search_profile::kCarParkingWheelchair: 27 | return car_parking::parameters{}; 28 | case search_profile::kBikeSharing: return bike_sharing::parameters{}; 29 | case search_profile::kCarSharing: 30 | return car_sharing::parameters{}; 31 | } 32 | throw utl::fail("with_profile not implemented for {}", to_str(p)); 33 | } 34 | 35 | } // namespace osr 36 | -------------------------------------------------------------------------------- /.pkg.lock: -------------------------------------------------------------------------------- 1 | 13962483139726024292 2 | cista 02fc087619fc913aca2c3fd3e07ec12279b8d6c6 3 | zlib-ng d68e1d908e789c31c1f2fafe4bd8e09cb91e21c5 4 | boost f0eca85b563887be8f26c4856c53d4e5080e6976 5 | conf f9bf4bd83bf55a2170725707e526cbacc45dcc66 6 | expat 636c9861e8e7c119f3626d1e6c260603ab624516 7 | fmt dc10f83be70ac2873d5f8d1ce317596f1fd318a2 8 | doctest 832431f2d9dc77e51cc8b364337c186660bf267d 9 | docs 75dc89a53e9c2d78574fc0ffda698e69f1682ed2 10 | googletest 9758d168fd1ccff2d76f3bd6ef802943d0f8f7e5 11 | utl e3cfd4286ac0080e8f9694ebf7f7dc44b87a1803 12 | geo d862550fd867cd5435225b5f0998981ec4fc2ae3 13 | libosmium 6e6d6b3081cc8bdf25dda89730e25c36eb995516 14 | mimalloc 189b0ac416b94848bc0a7702f7e97fe6ebd91829 15 | libressl 39c1bf084d5c179d7bbce7ba902fffbebff0ee15 16 | res b759b93316afeb529b6cb5b2548b24c41e382fb0 17 | date ce88cc33b5551f66655614eeebb7c5b7189025fb 18 | yaml-cpp 1d8ca1f35eb3a9c9142462b28282a848e5d29a91 19 | openapi-cpp dac46d043f07a119d8b7d9ccb47e51049b259bfe 20 | net 6cb1613c8a5c7b1cc22f00c0367db432b3ccd77e 21 | protozero a94574f49d3820dc2f90121f8d5c5ffb6210b5b2 22 | rapidjson 8b92de0bfdcf2127e67903959c9c48d580829da7 23 | LuaJIT f4bc3f109a4cc5bc262b2a2c683dae5a75f61377 24 | clipper 2abf72015b85f1cfd38706b21d4f28b8c359c7d5 25 | concurrentqueue ab6b0b5334a005ef6ff1365ba5af87404de39d29 26 | lmdb 39d8127e5697b1323a67e61c3ad8f087384c7429 27 | miniz e857234a23de8e5a2d05d266e518220a495db0ce 28 | oneTBB cda5d37ce8303c58ac506259387e75de4ffcf26b 29 | pbf-sdf-fonts 66f0c760272c7bdd0efeaac5aab5520b9194225e 30 | sol2 40c7cbc7c5cfed1e8c7f1bbe6fcbe23d7a67fc75 31 | variant 5aa73631dc969087c77433a5cdef246303051f69 32 | tiles fb8187e7b1ff30f0fffa6e508575b6b1d6c33791 33 | unordered_dense 2c7230ae7f9c30849a5b089fb4a5d11896b45dcf 34 | rtree.c 6ed73a7dc4f1184f2b5b2acd8ac1c2b28a273057 35 | -------------------------------------------------------------------------------- /include/osr/routing/with_profile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osr/routing/profile.h" 4 | #include "osr/routing/profiles/bike.h" 5 | #include "osr/routing/profiles/bike_sharing.h" 6 | #include "osr/routing/profiles/car.h" 7 | #include "osr/routing/profiles/car_parking.h" 8 | #include "osr/routing/profiles/car_sharing.h" 9 | #include "osr/routing/profiles/foot.h" 10 | 11 | namespace osr { 12 | 13 | template 14 | auto with_profile(search_profile const p, Fn&& fn) { 15 | switch (p) { 16 | case search_profile::kFoot: return fn(foot{}); 17 | case search_profile::kWheelchair: 18 | return fn(foot{}); 19 | case search_profile::kBike: 20 | return fn(bike{}); 21 | case search_profile::kBikeFast: 22 | return fn(bike{}); 23 | case search_profile::kBikeElevationLow: 24 | return fn(bike{}); 25 | case search_profile::kBikeElevationHigh: 26 | return fn(bike{}); 27 | case search_profile::kCar: return fn(car{}); 28 | case search_profile::kCarDropOff: return fn(car_parking{}); 29 | case search_profile::kCarDropOffWheelchair: 30 | return fn(car_parking{}); 31 | case search_profile::kCarParking: return fn(car_parking{}); 32 | case search_profile::kCarParkingWheelchair: 33 | return fn(car_parking{}); 34 | case search_profile::kBikeSharing: return fn(bike_sharing{}); 35 | case search_profile::kCarSharing: 36 | return fn(car_sharing{}); 37 | } 38 | throw utl::fail("with_profile not implemented for {}", to_str(p)); 39 | } 40 | 41 | } // namespace osr -------------------------------------------------------------------------------- /include/osr/routing/dial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace osr { 8 | 9 | template size_t <= MaxBucket */> 11 | struct dial { 12 | using dist_t = 13 | std::decay_t()(std::declval()))>; 14 | 15 | dial() = default; 16 | 17 | explicit dial(GetBucketFn get_bucket = GetBucketFn()) 18 | : get_bucket_(std::forward(get_bucket)) {} 19 | 20 | template 21 | void push(El&& el) { 22 | auto const dist = get_bucket_(el); 23 | assert(dist < buckets_.size()); 24 | 25 | buckets_[dist].emplace_back(std::forward(el)); 26 | current_bucket_ = std::min(current_bucket_, dist); 27 | ++size_; 28 | } 29 | 30 | T pop() { 31 | assert(!empty()); 32 | current_bucket_ = get_next_bucket(); 33 | assert(!buckets_[current_bucket_].empty()); 34 | auto item = buckets_[current_bucket_].back(); 35 | buckets_[current_bucket_].pop_back(); 36 | --size_; 37 | return item; 38 | } 39 | 40 | std::size_t size() const { return size_; } 41 | 42 | bool empty() const { return size_ == 0; } 43 | 44 | void clear() { 45 | current_bucket_ = 0U; 46 | size_ = 0U; 47 | for (auto& b : buckets_) { 48 | b.clear(); 49 | } 50 | } 51 | 52 | void n_buckets(dist_t const n) { buckets_.resize(n); } 53 | 54 | dist_t n_buckets() const { return static_cast(buckets_.size()); } 55 | 56 | public: 57 | dist_t get_next_bucket() const { 58 | assert(size_ != 0); 59 | auto bucket = current_bucket_; 60 | while (bucket < buckets_.size() && buckets_[bucket].empty()) { 61 | ++bucket; 62 | } 63 | return bucket; 64 | } 65 | 66 | GetBucketFn get_bucket_; 67 | dist_t current_bucket_; 68 | std::size_t size_; 69 | std::vector> buckets_; 70 | }; 71 | 72 | } // namespace osr -------------------------------------------------------------------------------- /src/preprocessing/elevation/dem_driver.cc: -------------------------------------------------------------------------------- 1 | #include "osr/preprocessing/elevation/dem_driver.h" 2 | 3 | namespace fs = std::filesystem; 4 | 5 | namespace osr::preprocessing::elevation { 6 | 7 | bool dem_driver::add_tile(fs::path const& path) { 8 | auto const ext = path.extension().string(); 9 | if (ext != ".hdr") { 10 | return false; 11 | } 12 | 13 | auto const idx = static_cast(tiles_.size()); 14 | auto const& tile = tiles_.emplace_back(dem_tile{path}); 15 | auto const box = tile.get_box(); 16 | rtree_.insert(box.min_.lnglat_float(), box.max_.lnglat_float(), idx); 17 | return true; 18 | } 19 | 20 | elevation_meters_t dem_driver::get(geo::latlng const& pos) const { 21 | auto const p = pos.lnglat_float(); 22 | auto meters = elevation_meters_t::invalid(); 23 | rtree_.search(p, p, 24 | [&](auto const&, auto const&, std::size_t const& tile_idx) { 25 | meters = tiles_[tile_idx].get(pos); 26 | return meters != elevation_meters_t::invalid(); 27 | }); 28 | return meters; 29 | } 30 | 31 | tile_idx_t dem_driver::tile_idx(geo::latlng const& pos) const { 32 | auto const p = pos.lnglat_float(); 33 | auto idx = tile_idx_t::invalid(); 34 | rtree_.search(p, p, 35 | [&](auto const&, auto const&, std::size_t const& tile_idx) { 36 | idx = tiles_[tile_idx].tile_idx(pos); 37 | if (idx == tile_idx_t::invalid()) { 38 | return false; 39 | } 40 | idx.tile_idx_ = static_cast(tile_idx); 41 | return true; 42 | }); 43 | return idx; 44 | } 45 | 46 | resolution dem_driver::max_resolution() const { 47 | auto res = resolution{}; 48 | for (auto const& tile : tiles_) { 49 | res.update(tile.max_resolution()); 50 | } 51 | return res; 52 | } 53 | 54 | std::size_t dem_driver::n_tiles() const { return tiles_.size(); } 55 | 56 | } // namespace osr::preprocessing::elevation 57 | -------------------------------------------------------------------------------- /include/osr/util/reverse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace osr { 6 | 7 | template 8 | struct reverse_iterator { 9 | using difference_type = typename It::difference_type; 10 | using value_type = typename It::value_type; 11 | using pointer = typename It::pointer; 12 | using reference = typename It::reference; 13 | using iterator_category = typename It::iterator_category; 14 | 15 | reverse_iterator(It it, bool const is_reverse) 16 | : it_{it}, 17 | rit_{std::make_reverse_iterator(it)}, 18 | is_reverse_{is_reverse} {} 19 | 20 | reverse_iterator& operator++() { 21 | if (is_reverse_) { 22 | ++rit_; 23 | } else { 24 | ++it_; 25 | } 26 | return *this; 27 | } 28 | reference operator*() { return is_reverse_ ? *rit_ : *it_; } 29 | pointer operator->() { return is_reverse_ ? rit_ : it_; } 30 | 31 | friend bool operator==(reverse_iterator const& a, reverse_iterator const& b) { 32 | assert(a.is_reverse_ == b.is_reverse_); 33 | return a.is_reverse_ ? a.rit_ == b.rit_ : a.it_ == b.it_; 34 | } 35 | 36 | It it_; 37 | std::reverse_iterator rit_; 38 | bool is_reverse_; 39 | }; 40 | 41 | template 42 | struct reverse_range { 43 | reverse_range(It from, It to, bool const is_reverse) 44 | : begin_{is_reverse ? to : from, is_reverse}, 45 | end_{is_reverse ? from : to, is_reverse} {} 46 | 47 | reverse_iterator begin() { return begin_; } 48 | reverse_iterator end() { return end_; } 49 | 50 | friend reverse_iterator begin(reverse_range const& r) { 51 | return r.begin(); 52 | } 53 | 54 | friend reverse_iterator end(reverse_range const& r) { return r.end(); } 55 | 56 | reverse_iterator begin_; 57 | reverse_iterator end_; 58 | }; 59 | 60 | template 61 | reverse_range(It, It, bool) -> reverse_range; 62 | 63 | template 64 | auto reverse(Collection&& c, bool const is_reverse) { 65 | using std::begin; 66 | using std::end; 67 | return reverse_range{begin(c), end(c), is_reverse}; 68 | } 69 | 70 | } // namespace osr -------------------------------------------------------------------------------- /include/osr/util/infinite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace osr { 6 | 7 | template 8 | struct infinite_iterator { 9 | using difference_type = typename BeginIt::difference_type; 10 | using value_type = typename BeginIt::value_type; 11 | using pointer = typename BeginIt::pointer; 12 | using reference = typename BeginIt::reference; 13 | using iterator_category = typename BeginIt::iterator_category; 14 | 15 | infinite_iterator(BeginIt from, EndIt to, bool const is_infinite) 16 | : it_{from}, begin_{from}, end_{to}, is_infinite_{is_infinite} {} 17 | 18 | infinite_iterator& operator++() { 19 | if (it_ == end_ && is_infinite_) { 20 | it_ = begin_; 21 | } else { 22 | ++it_; 23 | } 24 | return *this; 25 | } 26 | reference operator*() { return *it_; } 27 | pointer operator->() { return it_; } 28 | friend bool operator==(infinite_iterator const& a, 29 | infinite_iterator const& b) { 30 | assert(a.is_infinite_ == b.is_infinite_); 31 | return !a.is_infinite_ && a.it_ == b.it_; 32 | } 33 | 34 | BeginIt it_; 35 | BeginIt begin_; 36 | EndIt end_; 37 | bool is_infinite_; 38 | }; 39 | 40 | template 41 | struct infinite_range { 42 | infinite_range(BeginIt from, EndIt to, bool const is_infinite) 43 | : begin_{from, to, is_infinite}, end_{to, to, is_infinite} {} 44 | 45 | infinite_iterator begin() const { return begin_; } 46 | infinite_iterator end() const { return end_; } 47 | 48 | friend infinite_iterator begin(infinite_range const& r) { 49 | return r.begin(); 50 | } 51 | 52 | friend infinite_iterator end(infinite_range const& r) { 53 | return r.end(); 54 | } 55 | 56 | infinite_iterator begin_; 57 | infinite_iterator end_; 58 | }; 59 | 60 | template 61 | infinite_range(BeginIt, EndIt, bool) -> infinite_range; 62 | 63 | template 64 | auto infinite(Collection&& c, bool const is_reverse) { 65 | using std::begin; 66 | using std::end; 67 | return infinite_range{begin(c), end(c), is_reverse}; 68 | } 69 | 70 | } // namespace osr -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 2 3 | Language: Cpp 4 | DerivePointerAlignment: false 5 | PointerAlignment: Left 6 | AccessModifierOffset: -2 7 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 8 | AlignTrailingComments: false 9 | KeepEmptyLinesAtTheStartOfBlocks: true 10 | AllowShortCaseLabelsOnASingleLine: true 11 | AlwaysBreakTemplateDeclarations: true 12 | SpacesBeforeTrailingComments: 2 13 | IncludeBlocks: Preserve 14 | BinPackParameters: false 15 | IncludeCategories: 16 | - Regex: '^<.*\.h>' 17 | Priority: 1 18 | - Regex: '^' 58 | Priority: 1 59 | - Regex: '^ 4 | #include 5 | 6 | #include "cista/strong.h" 7 | 8 | #include "geo/box.h" 9 | #include "geo/latlng.h" 10 | 11 | #include "osr/preprocessing/elevation/resolution.h" 12 | 13 | namespace osr::preprocessing::elevation { 14 | 15 | using elevation_meters_t = 16 | cista::strong; 17 | 18 | struct tile_idx_t { 19 | using data_t = std::uint32_t; 20 | constexpr static auto const kDriverIdxSize = 4; 21 | constexpr static auto const kTileIdxSize = 16; 22 | constexpr static auto const kSubTileIdxSize = 23 | sizeof(data_t) * CHAR_BIT - kDriverIdxSize - kTileIdxSize; 24 | 25 | constexpr static tile_idx_t invalid() { 26 | return { 27 | .driver_idx_ = (1 << kDriverIdxSize) - 1, 28 | .tile_idx_ = (1 << kTileIdxSize) - 1, 29 | .sub_tile_idx_ = (1 << kSubTileIdxSize) - 1, 30 | }; 31 | } 32 | static tile_idx_t from_sub_tile(data_t const& idx) { 33 | auto t = invalid(); 34 | t.sub_tile_idx_ = idx; 35 | return t; 36 | } 37 | bool operator==(tile_idx_t const&) const = default; 38 | std::strong_ordering operator<=>(tile_idx_t const& o) const { 39 | return std::tie(driver_idx_, tile_idx_, sub_tile_idx_) <=> 40 | std::tie(o.driver_idx_, o.tile_idx_, o.sub_tile_idx_); 41 | } 42 | 43 | data_t driver_idx_ : kDriverIdxSize; 44 | data_t tile_idx_ : kTileIdxSize; 45 | data_t sub_tile_idx_ : kSubTileIdxSize; 46 | }; 47 | static_assert(sizeof(tile_idx_t) == sizeof(tile_idx_t::data_t)); 48 | 49 | template 50 | concept IsProvider = 51 | requires(ElevationProvider const& provider, geo::latlng const& pos) { 52 | { provider.get(pos) } -> std::same_as; 53 | { provider.tile_idx(pos) } -> std::same_as; 54 | { provider.max_resolution() } -> std::same_as; 55 | }; 56 | 57 | template 58 | concept IsTile = IsProvider && requires(Tile const& tile) { 59 | { tile.get_box() } -> std::same_as; 60 | }; 61 | 62 | template 63 | concept IsDriver = IsProvider && requires(Driver const& driver) { 64 | { driver.n_tiles() } -> std::same_as; 65 | }; 66 | 67 | } // namespace osr::preprocessing::elevation 68 | -------------------------------------------------------------------------------- /.pkg: -------------------------------------------------------------------------------- 1 | [boost] 2 | url=git@github.com:motis-project/boost.git 3 | branch=master 4 | commit=f0eca85b563887be8f26c4856c53d4e5080e6976 5 | [osmium] 6 | url=git@github.com:motis-project/libosmium.git 7 | branch=master 8 | commit=6e6d6b3081cc8bdf25dda89730e25c36eb995516 9 | [conf] 10 | url=git@github.com:motis-project/conf.git 11 | branch=master 12 | commit=f9bf4bd83bf55a2170725707e526cbacc45dcc66 13 | [geo] 14 | url=git@github.com:motis-project/geo.git 15 | branch=master 16 | commit=d862550fd867cd5435225b5f0998981ec4fc2ae3 17 | [cista] 18 | url=git@github.com:felixguendling/cista.git 19 | branch=master 20 | commit=02fc087619fc913aca2c3fd3e07ec12279b8d6c6 21 | [googletest] 22 | url=git@github.com:motis-project/googletest.git 23 | branch=master 24 | commit=9758d168fd1ccff2d76f3bd6ef802943d0f8f7e5 25 | [utl] 26 | url=git@github.com:motis-project/utl.git 27 | branch=master 28 | commit=e3cfd4286ac0080e8f9694ebf7f7dc44b87a1803 29 | [protozero] 30 | url=git@github.com:motis-project/protozero.git 31 | branch=master 32 | commit=a94574f49d3820dc2f90121f8d5c5ffb6210b5b2 33 | [zlib-ng] 34 | url=git@github.com:motis-project/zlib-ng.git 35 | branch=develop 36 | commit=d68e1d908e789c31c1f2fafe4bd8e09cb91e21c5 37 | [tiles] 38 | url=git@github.com:motis-project/tiles.git 39 | branch=master 40 | commit=fb8187e7b1ff30f0fffa6e508575b6b1d6c33791 41 | [mimalloc] 42 | url=git@github.com:motis-project/mimalloc.git 43 | branch=master 44 | commit=189b0ac416b94848bc0a7702f7e97fe6ebd91829 45 | [rapidjson] 46 | url=git@github.com:motis-project/rapidjson.git 47 | branch=master 48 | commit=8b92de0bfdcf2127e67903959c9c48d580829da7 49 | [rtree.c] 50 | url=git@github.com:triptix-tech/rtree.c.git 51 | branch=master 52 | commit=6ed73a7dc4f1184f2b5b2acd8ac1c2b28a273057 53 | [fmt] 54 | url=git@github.com:motis-project/fmt.git 55 | branch=master 56 | commit=dc10f83be70ac2873d5f8d1ce317596f1fd318a2 57 | [unordered_dense] 58 | url=git@github.com:motis-project/unordered_dense.git 59 | branch=main 60 | commit=2c7230ae7f9c30849a5b089fb4a5d11896b45dcf 61 | [expat] 62 | url=git@github.com:motis-project/expat.git 63 | branch=master 64 | commit=636c9861e8e7c119f3626d1e6c260603ab624516 65 | [net] 66 | url=git@github.com:motis-project/net.git 67 | branch=master 68 | commit=c2599102dcffa4babfdcf5320a6bd6f5c6e9b731 69 | -------------------------------------------------------------------------------- /cmake/pkg.cmake: -------------------------------------------------------------------------------- 1 | if (NOT DEFINED PROJECT_IS_TOP_LEVEL OR PROJECT_IS_TOP_LEVEL) 2 | find_program(pkg-bin pkg HINTS /opt/pkg) 3 | if (pkg-bin) 4 | message(STATUS "found pkg ${pkg-bin}") 5 | else () 6 | set(pkg-bin "${CMAKE_BINARY_DIR}/dl/pkg") 7 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL "aarch64") 8 | set(pkg-url "pkg-linux-arm64") 9 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 10 | set(pkg-url "pkg") 11 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") 12 | set(pkg-url "pkg.exe") 13 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") 14 | set(pkg-url "pkgosx") 15 | else () 16 | message(STATUS "Not downloading pkg tool. Using pkg from PATH.") 17 | set(pkg-bin "pkg") 18 | endif () 19 | 20 | if (pkg-url) 21 | if (NOT EXISTS ${pkg-bin}) 22 | message(STATUS "Downloading pkg binary from https://github.com/motis-project/pkg/releases/latest/download/${pkg-url}") 23 | file(DOWNLOAD "https://github.com/motis-project/pkg/releases/latest/download/${pkg-url}" ${pkg-bin}) 24 | if (UNIX) 25 | execute_process(COMMAND chmod +x ${pkg-bin}) 26 | endif () 27 | else () 28 | message(STATUS "Pkg binary located in project.") 29 | endif () 30 | endif () 31 | endif () 32 | 33 | if (DEFINED ENV{GITHUB_ACTIONS}) 34 | message(STATUS "${pkg-bin} -l -h -f") 35 | execute_process( 36 | COMMAND ${pkg-bin} -l -h -f 37 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 38 | RESULT_VARIABLE pkg-result 39 | ) 40 | else () 41 | message(STATUS "${pkg-bin} -l") 42 | execute_process( 43 | COMMAND ${pkg-bin} -l 44 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 45 | RESULT_VARIABLE pkg-result 46 | ) 47 | endif () 48 | 49 | if (NOT pkg-result EQUAL 0) 50 | message(FATAL_ERROR "pkg failed: ${pkg-result}") 51 | endif () 52 | 53 | if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/deps") 54 | add_subdirectory(deps) 55 | endif () 56 | 57 | set_property( 58 | DIRECTORY 59 | APPEND 60 | PROPERTY CMAKE_CONFIGURE_DEPENDS 61 | "${CMAKE_CURRENT_SOURCE_DIR}/.pkg" 62 | ) 63 | endif () 64 | -------------------------------------------------------------------------------- /src/routing/profile.cc: -------------------------------------------------------------------------------- 1 | #include "osr/routing/profile.h" 2 | 3 | #include "utl/verify.h" 4 | 5 | #include "cista/hash.h" 6 | 7 | namespace osr { 8 | 9 | search_profile to_profile(std::string_view s) { 10 | switch (cista::hash(s)) { 11 | case cista::hash("foot"): return search_profile::kFoot; 12 | case cista::hash("wheelchair"): return search_profile::kWheelchair; 13 | case cista::hash("bike"): return search_profile::kBike; 14 | case cista::hash("bike_fast"): return search_profile::kBikeFast; 15 | case cista::hash("bike_elevation_low"): 16 | return search_profile::kBikeElevationLow; 17 | case cista::hash("bike_elevation_high"): 18 | return search_profile::kBikeElevationHigh; 19 | case cista::hash("car"): return search_profile::kCar; 20 | case cista::hash("car_parking"): return search_profile::kCarParking; 21 | case cista::hash("car_parking_wheelchair"): 22 | return search_profile::kCarParkingWheelchair; 23 | case cista::hash("car_dropoff"): return search_profile::kCarDropOff; 24 | case cista::hash("car_dropoff_wheelchair"): 25 | return search_profile::kCarDropOffWheelchair; 26 | case cista::hash("bike_sharing"): return search_profile::kBikeSharing; 27 | case cista::hash("car_sharing"): return search_profile::kCarSharing; 28 | } 29 | throw utl::fail("{} is not a valid profile", s); 30 | } 31 | 32 | std::string_view to_str(search_profile const p) { 33 | switch (p) { 34 | case search_profile::kFoot: return "foot"; 35 | case search_profile::kWheelchair: return "wheelchair"; 36 | case search_profile::kCar: return "car"; 37 | case search_profile::kBike: return "bike"; 38 | case search_profile::kBikeFast: return "bike_fast"; 39 | case search_profile::kBikeElevationLow: return "bike_elevation_low"; 40 | case search_profile::kBikeElevationHigh: return "bike_elevation_high"; 41 | case search_profile::kCarParking: return "car_parking"; 42 | case search_profile::kCarParkingWheelchair: return "car_parking_wheelchair"; 43 | case search_profile::kCarDropOff: return "car_dropoff"; 44 | case search_profile::kCarDropOffWheelchair: return "car_dropoff_wheelchair"; 45 | case search_profile::kBikeSharing: return "bike_sharing"; 46 | case search_profile::kCarSharing: return "car_sharing"; 47 | } 48 | throw utl::fail("{} is not a valid profile", static_cast(p)); 49 | } 50 | 51 | bool is_rental_profile(search_profile const p) { 52 | return p == search_profile::kBikeSharing || p == search_profile::kCarSharing; 53 | } 54 | 55 | } // namespace osr 56 | -------------------------------------------------------------------------------- /cmake/buildcache.cmake: -------------------------------------------------------------------------------- 1 | option(NO_BUILDCACHE "Disable build caching using buildcache" Off) 2 | 3 | # PDB debug information is not supported by buildcache. 4 | # Store debug info in the object files. 5 | if (DEFINED ENV{GITHUB_ACTIONS}) 6 | string(REPLACE "/Zi" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 7 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") 8 | string(REPLACE "/Zi" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") 9 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") 10 | else () 11 | string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 12 | string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") 13 | string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") 14 | string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") 15 | endif () 16 | 17 | set(buildcache-bin ${CMAKE_CURRENT_BINARY_DIR}/buildcache/bin/buildcache) 18 | get_property(rule-launch-set GLOBAL PROPERTY RULE_LAUNCH_COMPILE SET) 19 | if (NO_BUILDCACHE) 20 | message(STATUS "NO_BUILDCACHE set, buildcache disabled") 21 | elseif (rule-launch-set) 22 | message(STATUS "Global property RULE_LAUNCH_COMPILE already set - skipping buildcache") 23 | else () 24 | find_program(buildcache_program buildcache HINTS ${CMAKE_CURRENT_BINARY_DIR}/buildcache/bin) 25 | if (buildcache_program) 26 | message(STATUS "Using buildcache: ${buildcache_program}") 27 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${buildcache_program}") 28 | else () 29 | message(STATUS "buildcache not found - downloading") 30 | if (APPLE) 31 | set(buildcache-archive "buildcache-macos.zip") 32 | elseif (UNIX) 33 | set(buildcache-archive "buildcache-linux.tar.gz") 34 | elseif (WIN32) 35 | set(buildcache-archive "buildcache-windows.zip") 36 | else () 37 | message(FATAL "Error: NO_BUILDCACHE was not set but buildcache was not in path and system OS detection failed") 38 | endif () 39 | 40 | set(buildcache-url "https://github.com/mbitsnbites/buildcache/releases/download/v0.28.2/${buildcache-archive}") 41 | message(STATUS "Downloading buildcache binary from ${buildcache-url}") 42 | file(DOWNLOAD "${buildcache-url}" ${CMAKE_CURRENT_BINARY_DIR}/${buildcache-archive}) 43 | execute_process( 44 | COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_CURRENT_BINARY_DIR}/${buildcache-archive} 45 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 46 | message(STATUS "using buildcache: ${buildcache-bin}") 47 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${buildcache-bin}) 48 | endif () 49 | endif () -------------------------------------------------------------------------------- /src/preprocessing/elevation/provider.cc: -------------------------------------------------------------------------------- 1 | #include "osr/preprocessing/elevation/provider.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "utl/enumerate.h" 8 | 9 | #include "osr/elevation_storage.h" 10 | #include "osr/preprocessing/elevation/dem_driver.h" 11 | #include "osr/preprocessing/elevation/hgt_driver.h" 12 | 13 | namespace osr::preprocessing::elevation { 14 | 15 | static_assert(IsDriver); 16 | static_assert(IsDriver); 17 | 18 | using driver_t = std::variant; 19 | 20 | struct provider::impl { 21 | impl() = default; 22 | 23 | void add_driver(IsDriver auto&& driver) { 24 | drivers_.emplace_back(std::move(driver)); 25 | } 26 | 27 | elevation_meters_t get(geo::latlng const& pos) const { 28 | for (auto const& driver : drivers_) { 29 | auto const meters = std::visit( 30 | [&](IsDriver auto const& d) { return d.get(pos); }, driver); 31 | if (meters != elevation_meters_t::invalid()) { 32 | return meters; 33 | } 34 | } 35 | return elevation_meters_t::invalid(); 36 | } 37 | 38 | tile_idx_t tile_idx(geo::latlng const& pos) const { 39 | for (auto const [driver_idx, driver] : utl::enumerate(drivers_)) { 40 | auto idx = std::visit( 41 | [&](IsDriver auto const& d) { return d.tile_idx(pos); }, driver); 42 | if (idx != tile_idx_t::invalid()) { 43 | idx.driver_idx_ = static_cast(driver_idx); 44 | return idx; 45 | } 46 | } 47 | return tile_idx_t::invalid(); 48 | } 49 | 50 | std::vector drivers_; 51 | }; 52 | 53 | provider::provider(std::filesystem::path const& p) 54 | : impl_{std::make_unique()} { 55 | if (std::filesystem::is_directory(p)) { 56 | auto dem = dem_driver{}; 57 | auto hgt = hgt_driver{}; 58 | for (auto const& file : std::filesystem::recursive_directory_iterator(p)) { 59 | [&]() { 60 | if (!file.is_regular_file()) { 61 | return; 62 | } 63 | auto const& path = file.path(); 64 | if (dem.add_tile(path)) { 65 | return; 66 | } 67 | if (hgt.add_tile(path)) { 68 | return; 69 | } 70 | }(); 71 | } 72 | if (dem.n_tiles() > 0U) { 73 | impl_->add_driver(std::move(dem)); 74 | } 75 | if (hgt.n_tiles() > 0U) { 76 | impl_->add_driver(std::move(hgt)); 77 | } 78 | } 79 | } 80 | 81 | provider::~provider() = default; 82 | 83 | elevation_meters_t provider::get(geo::latlng const& pos) const { 84 | return impl_->get(pos); 85 | } 86 | 87 | tile_idx_t provider::tile_idx(geo::latlng const& pos) const { 88 | return impl_->tile_idx(pos); 89 | } 90 | 91 | std::size_t provider::driver_count() const { return impl_->drivers_.size(); } 92 | 93 | resolution provider::max_resolution() const { 94 | auto res = resolution{}; 95 | for (auto const& driver : impl_->drivers_) { 96 | auto const r = std::visit( 97 | [](IsDriver auto const& d) { return d.max_resolution(); }, driver); 98 | res.update(r); 99 | } 100 | return res; 101 | } 102 | 103 | } // namespace osr::preprocessing::elevation 104 | -------------------------------------------------------------------------------- /test/restriction_test.cc: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include "windows.h" 3 | #endif 4 | 5 | #include "gtest/gtest.h" 6 | 7 | #include 8 | 9 | #include "cista/mmap.h" 10 | 11 | #include "fmt/core.h" 12 | #include "fmt/ranges.h" 13 | 14 | #include "osr/extract/extract.h" 15 | #include "osr/location.h" 16 | #include "osr/lookup.h" 17 | #include "osr/routing/profile.h" 18 | #include "osr/routing/route.h" 19 | #include "osr/types.h" 20 | #include "osr/ways.h" 21 | 22 | namespace fs = std::filesystem; 23 | using namespace osr; 24 | 25 | TEST(extract, wa) { 26 | auto p = fs::path{"/tmp/osr_test"}; 27 | auto ec = std::error_code{}; 28 | fs::remove_all(p, ec); 29 | fs::create_directories(p, ec); 30 | 31 | constexpr auto const kTestDir = "/tmp/osr_test"; 32 | osr::extract(false, "test/map.osm", kTestDir, 33 | "test/restriction_test_elevation/"); 34 | 35 | auto w = osr::ways{"/tmp/osr_test", cista::mmap::protection::READ}; 36 | auto l = osr::lookup{w, "/tmp/osr_test", cista::mmap::protection::READ}; 37 | auto const elevations = elevation_storage::try_open(kTestDir); 38 | ASSERT_TRUE(elevations); 39 | 40 | auto const n = w.find_node_idx(osm_node_idx_t{528944}); 41 | auto const rhoenring = w.find_way(osm_way_idx_t{120682496}); 42 | auto const arheilger = w.find_way(osm_way_idx_t{1068971150}); 43 | // Crossing Eckhardt / Barkhaus 44 | auto const n_dst = w.find_node_idx(osm_node_idx_t{586157}); 45 | 46 | ASSERT_TRUE(n.has_value()); 47 | ASSERT_TRUE(n_dst.has_value()); 48 | auto const from = location{w.get_node_pos(*n), kNoLevel}; 49 | auto const to = location{w.get_node_pos(*n_dst), kNoLevel}; 50 | constexpr auto const kMaxCost = cost_t{3600}; 51 | constexpr auto const kMaxMatchDistance = 100; 52 | constexpr auto const kParamsNoCosts = 53 | bike::parameters{}; 54 | constexpr auto const kParamsHighCosts = 55 | bike::parameters{}; 56 | auto const route_no_costs = 57 | route(kParamsNoCosts, w, l, search_profile::kBike, from, to, kMaxCost, 58 | direction::kForward, kMaxMatchDistance, nullptr, nullptr, 59 | elevations.get()); 60 | auto const route_high_costs = 61 | route(kParamsHighCosts, w, l, search_profile::kBikeElevationHigh, from, 62 | to, kMaxCost, direction::kForward, kMaxMatchDistance, nullptr, 63 | nullptr, elevations.get()); 64 | 65 | auto const is_restricted = w.r_->is_restricted( 66 | n.value(), w.r_->get_way_pos(n.value(), rhoenring.value()), 67 | w.r_->get_way_pos(n.value(), arheilger.value())); 68 | EXPECT_TRUE(is_restricted); 69 | 70 | constexpr auto const kShortestDistance = 163.0; 71 | ASSERT_TRUE(route_no_costs.has_value()); 72 | EXPECT_TRUE(std::abs(route_no_costs->dist_ - kShortestDistance) < 2.0); 73 | // Upper bounds for elevations on each segment 74 | EXPECT_EQ(elevation_monotonic_t{4U + 1U}, route_no_costs->elevation_.up_); 75 | EXPECT_EQ(elevation_monotonic_t{0U + 6U}, route_no_costs->elevation_.down_); 76 | 77 | ASSERT_TRUE(route_high_costs.has_value()); 78 | EXPECT_TRUE(route_high_costs->dist_ - kShortestDistance > 2.0); 79 | // Upper bounds for elevations on each segment 80 | EXPECT_EQ(elevation_monotonic_t{1U + 0U}, route_high_costs->elevation_.up_); 81 | EXPECT_EQ(elevation_monotonic_t{4U + 0U}, route_high_costs->elevation_.down_); 82 | } 83 | -------------------------------------------------------------------------------- /include/osr/routing/route.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "geo/polyline.h" 7 | 8 | #include "osr/elevation_storage.h" 9 | #include "osr/location.h" 10 | #include "osr/lookup.h" 11 | #include "osr/routing/algorithms.h" 12 | #include "osr/routing/mode.h" 13 | #include "osr/routing/parameters.h" 14 | #include "osr/routing/path.h" 15 | #include "osr/routing/profile.h" 16 | #include "osr/types.h" 17 | 18 | namespace osr { 19 | 20 | struct ways; 21 | 22 | template 23 | struct dijkstra; 24 | 25 | template 26 | struct bidirectional; 27 | 28 | struct sharing_data; 29 | 30 | template 31 | bidirectional

& get_bidirectional(); 32 | 33 | template 34 | dijkstra

& get_dijkstra(); 35 | 36 | std::vector> route( 37 | profile_parameters const&, 38 | ways const&, 39 | lookup const&, 40 | search_profile, 41 | location const& from, 42 | std::vector const& to, 43 | cost_t max, 44 | direction, 45 | double max_match_distance, 46 | bitvec const* blocked = nullptr, 47 | sharing_data const* sharing = nullptr, 48 | elevation_storage const* = nullptr, 49 | std::function const& do_reconstruct = [](path const&) { 50 | return false; 51 | }); 52 | 53 | std::optional route(profile_parameters const&, 54 | ways const&, 55 | lookup const&, 56 | search_profile, 57 | location const& from, 58 | location const& to, 59 | cost_t max, 60 | direction, 61 | double max_match_distance, 62 | bitvec const* blocked = nullptr, 63 | sharing_data const* sharing = nullptr, 64 | elevation_storage const* = nullptr, 65 | routing_algorithm = routing_algorithm::kDijkstra); 66 | 67 | std::vector> route( 68 | profile_parameters const&, 69 | ways const&, 70 | lookup const&, 71 | search_profile const, 72 | location const& from, 73 | std::vector const& to, 74 | match_view_t from_match, 75 | std::vector const& to_match, 76 | cost_t const max, 77 | direction const, 78 | bitvec const* blocked = nullptr, 79 | sharing_data const* sharing = nullptr, 80 | elevation_storage const* = nullptr, 81 | std::function const& do_reconstruct = [](path const&) { 82 | return false; 83 | }); 84 | 85 | std::optional route(profile_parameters const&, 86 | ways const& w, 87 | lookup const& l, 88 | search_profile const profile, 89 | location const& from, 90 | location const& to, 91 | match_view_t from_match, 92 | match_view_t to_match, 93 | cost_t const max, 94 | direction const dir, 95 | bitvec const* blocked = nullptr, 96 | sharing_data const* sharing = nullptr, 97 | elevation_storage const* = nullptr, 98 | routing_algorithm = routing_algorithm::kDijkstra); 99 | 100 | } // namespace osr 101 | -------------------------------------------------------------------------------- /exe/backend/src/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "boost/asio/executor_work_guard.hpp" 6 | #include "boost/asio/io_context.hpp" 7 | 8 | #include "fmt/core.h" 9 | #include "fmt/std.h" 10 | 11 | #include "conf/options_parser.h" 12 | 13 | #include "net/stop_handler.h" 14 | 15 | #include "osr/backend/http_server.h" 16 | #include "osr/elevation_storage.h" 17 | #include "osr/lookup.h" 18 | #include "osr/platforms.h" 19 | #include "osr/ways.h" 20 | 21 | namespace fs = std::filesystem; 22 | using namespace osr; 23 | using namespace osr::backend; 24 | 25 | class settings : public conf::configuration { 26 | public: 27 | explicit settings() : configuration("Options") { 28 | param(data_dir_, "data,d", "Data directory"); 29 | param(http_host_, "host,h", "HTTP host"); 30 | param(http_port_, "port,p", "HTTP port"); 31 | param(static_file_path_, "static,s", "Path to static files (ui/web)"); 32 | param(threads_, "threads,t", "Number of routing threads"); 33 | param(lock_, "lock,l", "Lock to memory"); 34 | } 35 | 36 | fs::path data_dir_{"osr"}; 37 | std::string http_host_{"0.0.0.0"}; 38 | std::string http_port_{"8000"}; 39 | std::string static_file_path_; 40 | bool lock_{true}; 41 | unsigned threads_{std::thread::hardware_concurrency()}; 42 | }; 43 | 44 | auto run(boost::asio::io_context& ioc) { 45 | return [&ioc]() { 46 | while (true) { 47 | try { 48 | ioc.run(); 49 | break; 50 | } catch (std::exception const& e) { 51 | fmt::println("unhandled error: {}", e.what()); 52 | } catch (...) { 53 | fmt::println("unhandled unknown error"); 54 | } 55 | } 56 | }; 57 | } 58 | 59 | int main(int argc, char const* argv[]) { 60 | auto opt = settings{}; 61 | auto parser = conf::options_parser({&opt}); 62 | parser.read_command_line_args(argc, argv); 63 | 64 | if (parser.help()) { 65 | parser.print_help(std::cout); 66 | return 0; 67 | } else if (parser.version()) { 68 | return 0; 69 | } 70 | 71 | parser.read_configuration_file(); 72 | parser.print_unrecognized(std::cout); 73 | parser.print_used(std::cout); 74 | 75 | if (!fs::is_directory(opt.data_dir_)) { 76 | fmt::println("directory not found: {}", opt.data_dir_); 77 | return 1; 78 | } 79 | 80 | auto const w = ways{opt.data_dir_, cista::mmap::protection::READ}; 81 | 82 | auto const platforms_check_path = opt.data_dir_ / "node_is_platform.bin"; 83 | if (!fs::exists(platforms_check_path)) { 84 | std::cout << platforms_check_path << " does not exist\n"; 85 | } 86 | auto const pl = fs::exists(platforms_check_path) 87 | ? std::make_unique( 88 | opt.data_dir_, cista::mmap::protection::READ) 89 | : nullptr; 90 | if (pl != nullptr) { 91 | pl->build_rtree(w); 92 | } 93 | auto const elevations = elevation_storage::try_open(opt.data_dir_); 94 | 95 | auto const l = lookup{w, opt.data_dir_, cista::mmap::protection::READ}; 96 | 97 | auto ioc = boost::asio::io_context{}; 98 | auto pool = boost::asio::io_context{}; 99 | auto server = http_server{ 100 | ioc, pool, w, l, pl.get(), elevations.get(), opt.static_file_path_}; 101 | 102 | auto work_guard = boost::asio::make_work_guard(pool); 103 | auto threads = std::vector(std::max(1U, opt.threads_)); 104 | for (auto& t : threads) { 105 | t = std::thread(run(pool)); 106 | } 107 | 108 | server.listen(opt.http_host_, opt.http_port_); 109 | 110 | auto const stop = net::stop_handler(ioc, [&]() { 111 | server.stop(); 112 | ioc.stop(); 113 | }); 114 | 115 | ioc.run(); 116 | pool.stop(); 117 | for (auto& t : threads) { 118 | t.join(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/restriction_test_elevation/create_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #-*- coding: utf8 -*- 3 | 4 | import sys 5 | 6 | BYTE_ORDER = 'little' 7 | 8 | def main(): 9 | BASE = int.from_bytes(b'A_', byteorder=BYTE_ORDER, signed=True) 10 | ( 11 | Tile(tl=Point(8.65617, 49.8838761, ), br=Point(8.6575625, 49.8827837, ), size=Pos(7, 5), default=BASE) 12 | # Start point 13 | .set(Pos(6, 1), BASE + 2) 14 | # Shorter path with higher elevation 15 | .set(Pos(6, 2), BASE + 2) 16 | .set(Pos(6, 3), BASE + 3) 17 | .set(Pos(6, 4), BASE + 5) 18 | .set(Pos(5, 4), BASE + 2) 19 | .set(Pos(4, 4), BASE + 0) 20 | .set(Pos(3, 4), BASE + 1) 21 | # Longer path with lower elevation 22 | .set(Pos(5, 1), BASE + 2) # Value might be skipped if steps is too low 23 | .set(Pos(4, 1), BASE + 3) 24 | .print() 25 | .save('elevations_1') 26 | ) 27 | 28 | class Point(object): 29 | 30 | x: float 31 | y: float 32 | 33 | def __init__(self, x: float, y: float): 34 | self.x = x 35 | self.y = y 36 | 37 | class Pos(object): 38 | 39 | x: int 40 | y: int 41 | 42 | def __init__(self, x: int, y: int): 43 | self.x = x 44 | self.y = y 45 | 46 | def __str__(self): 47 | return f'({self.x}, {self.y})' 48 | 49 | class Grid(object): 50 | 51 | rows: list[list[int]] 52 | 53 | def __init__(self, width: int, height: int, default: int): 54 | if not (0 < width and 0 < height): 55 | print(f'Error: Invalid grid size {pos}', file=sys.stderr) 56 | sys.exit(1) 57 | self.rows = [[default for w in range(width + 1)] for h in range(height + 1)] 58 | 59 | def set(self, pos: Pos, value: int): 60 | if not (0 <= pos.x <= self.width() and 0 <= pos.y <= self.height()): 61 | print(f'Error: Position {pos} is out of bound', file=sys.stderr) 62 | sys.exit(1) 63 | self.rows[pos.y][pos.x] = value 64 | 65 | def width(self): 66 | return len(self.rows[0]) - 1 67 | 68 | def height(self): 69 | return len(self.rows) - 1 70 | 71 | def save(self, path: str): 72 | with open(path, 'wb') as f: 73 | for row in self.rows: 74 | for value in row: 75 | f.write(value.to_bytes(length=2, byteorder=BYTE_ORDER, signed=True)) 76 | 77 | def print(self): 78 | for row in self.rows: 79 | for entry in row: 80 | print(f'{entry:>5} ', end='') 81 | print() 82 | 83 | class Tile(object): 84 | 85 | tl: Point 86 | br: Point 87 | grid: Grid 88 | 89 | def __init__(self, tl: Point, br: Point, size: Pos, default: int = 0): 90 | self.tl = tl 91 | self.br = br 92 | self.grid = Grid(width=size.x, height=size.y, default=default) 93 | 94 | def set(self, pos: Pos, value: int): 95 | self.grid.set(pos, value) 96 | return self 97 | 98 | def save(self, path: str): 99 | width = self.grid.width() 100 | height = self.grid.height() 101 | with open(path + '.hdr', 'w') as f: 102 | f.write(f''' 103 | BYTEORDER I 104 | LAYOUT BIL 105 | NROWS {height + 1} 106 | NCOLS {width + 1} 107 | NBANDS 1 108 | NBITS 16 109 | BANDROWBYTES {2 * (width + 1)} 110 | TOTALROWBYTES {2 * (width + 1)} 111 | PIXELTYPE SIGNEDINT 112 | ULXMAP {self.tl.x} 113 | ULYMAP {self.tl.y} 114 | XDIM {(self.br.x - self.tl.x) / width} 115 | YDIM {(self.tl.y - self.br.y) / height} 116 | NODATA -32767 117 | ''') 118 | self.grid.save(path + '.bil') 119 | return self 120 | 121 | def print(self): 122 | self.grid.print() 123 | return self 124 | 125 | if __name__ == '__main__': 126 | main() 127 | -------------------------------------------------------------------------------- /src/preprocessing/elevation/hgt_driver.cc: -------------------------------------------------------------------------------- 1 | #include "osr/preprocessing/elevation/hgt_driver.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "utl/verify.h" 8 | 9 | namespace fs = std::filesystem; 10 | 11 | namespace osr::preprocessing::elevation { 12 | 13 | struct grid_point { 14 | explicit grid_point(std::string const& path) { 15 | auto lat_dir = char{}; 16 | auto lng_dir = char{}; 17 | auto lat = int{}; 18 | auto lng = int{}; 19 | 20 | auto s = std::stringstream{path}; 21 | s >> lat_dir >> lat >> lng_dir >> lng; 22 | 23 | utl::verify(lat_dir == 'S' || lat_dir == 'N', "Invalid direction '{}'", 24 | lat_dir); 25 | utl::verify(lng_dir == 'W' || lng_dir == 'E', "Invalid direction '{}'", 26 | lng_dir); 27 | utl::verify(-180 <= lng && lng <= 180, "Invalid longitude '{}'", lng); 28 | utl::verify(-90 <= lat && lat <= 90, "Invalid latitude '{}'", lat); 29 | 30 | lat_ = static_cast((lat_dir == 'N') ? lat : -lat); 31 | lng_ = static_cast((lng_dir == 'E') ? lng : -lng); 32 | 33 | utl::verify(-180 <= lng_ && lng_ < 180, "Invalid longitude '{}'", lng); 34 | utl::verify(-90 <= lat_ && lat_ < 90, "Invalid latitude '{}'", lat); 35 | } 36 | 37 | std::int8_t lat_; 38 | std::int16_t lng_; 39 | }; 40 | 41 | bool hgt_driver::add_tile(fs::path const& path) { 42 | auto const ext = path.extension().string(); 43 | if (ext != ".hgt") { 44 | return false; 45 | } 46 | auto tile = open(path); 47 | if (tile.has_value()) { 48 | auto const box = 49 | std::visit([](IsTile auto const& t) { return t.get_box(); }, *tile); 50 | rtree_.insert(box.min_.lnglat_float(), box.max_.lnglat_float(), 51 | static_cast(tiles_.size())); 52 | tiles_.emplace_back(std::move(tile.value())); 53 | return true; 54 | } else { 55 | return false; 56 | } 57 | } 58 | 59 | elevation_meters_t hgt_driver::get(geo::latlng const& pos) const { 60 | auto const p = pos.lnglat_float(); 61 | auto meters = elevation_meters_t::invalid(); 62 | rtree_.search(p, p, 63 | [&](auto const&, auto const&, std::size_t const& tile_idx) { 64 | meters = std::visit( 65 | [&](IsTile auto const& t) -> elevation_meters_t { 66 | return t.get(pos); 67 | }, 68 | tiles_[tile_idx]); 69 | return meters != elevation_meters_t::invalid(); 70 | }); 71 | return meters; 72 | } 73 | 74 | tile_idx_t hgt_driver::tile_idx(geo::latlng const& pos) const { 75 | auto const p = pos.lnglat_float(); 76 | auto idx = tile_idx_t::invalid(); 77 | rtree_.search( 78 | p, p, [&](auto const&, auto const&, std::size_t const& tile_idx) { 79 | idx = std::visit( 80 | [&](IsTile auto const& t) -> tile_idx_t { return t.tile_idx(pos); }, 81 | tiles_[tile_idx]); 82 | if (idx == tile_idx_t::invalid()) { 83 | return false; 84 | } 85 | idx.tile_idx_ = static_cast(tile_idx); 86 | return true; 87 | }); 88 | return idx; 89 | } 90 | 91 | resolution hgt_driver::max_resolution() const { 92 | auto res = resolution{}; 93 | for (auto const& tile : tiles_) { 94 | auto const r = std::visit( 95 | [](IsTile auto const& t) { return t.max_resolution(); }, tile); 96 | res.update(r); 97 | } 98 | return res; 99 | } 100 | 101 | std::size_t hgt_driver::n_tiles() const { return tiles_.size(); } 102 | 103 | std::optional hgt_driver::open(fs::path const& path) { 104 | try { 105 | auto sw = grid_point{path.filename().string()}; 106 | auto const file_size = fs::file_size(path); 107 | switch (file_size) { 108 | case hgt_tile<3601>::file_size(): 109 | return hgt_tile<3601>{path.string(), sw.lat_, sw.lng_}; 110 | case hgt_tile<1201>::file_size(): 111 | return hgt_tile<1201>{path.string(), sw.lat_, sw.lng_}; 112 | default: return {}; 113 | } 114 | } catch (std::runtime_error const& e) { 115 | std::cerr << "Error opening '" << path << "': " << e.what() << "\n"; 116 | return {}; 117 | } 118 | } 119 | 120 | } // namespace osr::preprocessing::elevation 121 | -------------------------------------------------------------------------------- /include/osr/routing/dijkstra.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osr/elevation_storage.h" 4 | #include "osr/routing/additional_edge.h" 5 | #include "osr/routing/dial.h" 6 | #include "osr/routing/profile.h" 7 | #include "osr/types.h" 8 | #include "osr/ways.h" 9 | 10 | namespace osr { 11 | 12 | struct sharing_data; 13 | 14 | constexpr auto const kDebug = false; 15 | 16 | template 17 | struct dijkstra { 18 | using profile_t = P; 19 | using key = typename P::key; 20 | using label = typename P::label; 21 | using node = typename P::node; 22 | using entry = typename P::entry; 23 | using hash = typename P::hash; 24 | 25 | struct get_bucket { 26 | cost_t operator()(label const& l) { return l.cost(); } 27 | }; 28 | 29 | void reset(cost_t const max) { 30 | pq_.clear(); 31 | pq_.n_buckets(max + 1U); 32 | cost_.clear(); 33 | max_reached_ = false; 34 | } 35 | 36 | void add_start(ways const& w, label const l) { 37 | if (cost_[l.get_node().get_key()].update(l, l.get_node(), l.cost(), 38 | node::invalid())) { 39 | if constexpr (kDebug) { 40 | std::cout << "START "; 41 | l.get_node().print(std::cout, w); 42 | std::cout << "\n"; 43 | } 44 | pq_.push(l); 45 | } 46 | } 47 | 48 | cost_t get_cost(node const n) const { 49 | auto const it = cost_.find(n.get_key()); 50 | return it != end(cost_) ? it->second.cost(n) : kInfeasible; 51 | } 52 | 53 | template 54 | bool run(P::parameters const& params, 55 | ways const& w, 56 | ways::routing const& r, 57 | cost_t const max, 58 | bitvec const* blocked, 59 | sharing_data const* sharing, 60 | elevation_storage const* elevations) { 61 | while (!pq_.empty()) { 62 | auto l = pq_.pop(); 63 | if (get_cost(l.get_node()) < l.cost()) { 64 | continue; 65 | } 66 | 67 | if constexpr (kDebug) { 68 | std::cout << "EXTRACT "; 69 | l.get_node().print(std::cout, w); 70 | std::cout << "\n"; 71 | } 72 | 73 | auto const curr = l.get_node(); 74 | P::template adjacent( 75 | params, r, curr, blocked, sharing, elevations, 76 | [&](node const neighbor, std::uint32_t const cost, distance_t, 77 | way_idx_t const way, std::uint16_t, std::uint16_t, 78 | elevation_storage::elevation, bool const track) { 79 | if constexpr (kDebug) { 80 | std::cout << " NEIGHBOR "; 81 | neighbor.print(std::cout, w); 82 | } 83 | 84 | auto const total = l.cost() + cost; 85 | if (total >= max) { 86 | max_reached_ = true; 87 | return; 88 | } 89 | if (total < max && 90 | cost_[neighbor.get_key()].update( 91 | l, neighbor, static_cast(total), curr)) { 92 | auto next = label{neighbor, static_cast(total)}; 93 | next.track(l, r, way, neighbor.get_node(), track); 94 | pq_.push(std::move(next)); 95 | 96 | if constexpr (kDebug) { 97 | std::cout << " -> PUSH\n"; 98 | } 99 | } else { 100 | if constexpr (kDebug) { 101 | std::cout << " -> DOMINATED\n"; 102 | } 103 | } 104 | }); 105 | } 106 | return !max_reached_; 107 | } 108 | 109 | bool run(P::parameters const& params, 110 | ways const& w, 111 | ways::routing const& r, 112 | cost_t const max, 113 | bitvec const* blocked, 114 | sharing_data const* sharing, 115 | elevation_storage const* elevations, 116 | direction const dir) { 117 | if (blocked == nullptr) { 118 | return dir == direction::kForward 119 | ? run(params, w, r, max, blocked, 120 | sharing, elevations) 121 | : run(params, w, r, max, blocked, 122 | sharing, elevations); 123 | } else { 124 | return dir == direction::kForward 125 | ? run(params, w, r, max, blocked, 126 | sharing, elevations) 127 | : run(params, w, r, max, blocked, 128 | sharing, elevations); 129 | } 130 | } 131 | 132 | dial pq_{get_bucket{}}; 133 | ankerl::unordered_dense::map cost_; 134 | bool max_reached_; 135 | }; 136 | 137 | } // namespace osr 138 | -------------------------------------------------------------------------------- /src/lookup.cc: -------------------------------------------------------------------------------- 1 | #include "osr/lookup.h" 2 | 3 | #include "osr/routing/parameters.h" 4 | #include "osr/routing/profiles/bike.h" 5 | #include "osr/routing/profiles/bike_sharing.h" 6 | #include "osr/routing/profiles/car.h" 7 | #include "osr/routing/profiles/car_parking.h" 8 | #include "osr/routing/profiles/car_sharing.h" 9 | #include "osr/routing/profiles/foot.h" 10 | #include "osr/routing/with_profile.h" 11 | 12 | namespace osr { 13 | 14 | lookup::lookup(ways const& ways, 15 | std::filesystem::path p, 16 | cista::mmap::protection mode) 17 | : p_{std::move(p)}, 18 | mode_{mode}, 19 | rtree_{mode == cista::mmap::protection::READ 20 | ? *cista::read::meta>( 21 | p_ / "rtree_meta.bin") 22 | : cista::mm_rtree::meta{}, 23 | cista::mm_rtree::vector_t{mm("rtree_data.bin")}}, 24 | ways_{ways} {} 25 | 26 | void lookup::build_rtree() { 27 | for (auto way = way_idx_t{0U}; way != ways_.n_ways(); ++way) { 28 | auto b = geo::box{}; 29 | for (auto const& c : ways_.way_polylines_[way]) { 30 | b.extend(c); 31 | } 32 | rtree_.insert(b.min_.lnglat_float(), b.max_.lnglat_float(), way); 33 | } 34 | rtree_.write_meta(p_ / "rtree_meta.bin"); 35 | } 36 | 37 | std::vector lookup::get_raw_way_candidates( 38 | location const& query, double const max_match_distance) const { 39 | auto way_candidates = std::vector{}; 40 | auto const approx_distance_lng_degrees = 41 | geo::approx_distance_lng_degrees(query.pos_); 42 | auto const squared_max_dist = std::pow(max_match_distance, 2); 43 | find(geo::box{query.pos_, max_match_distance}, [&](way_idx_t const way) { 44 | auto const [squared_dist, best, segment_idx] = 45 | geo::approx_squared_distance_to_polyline< 46 | std::tuple>( 47 | query.pos_, ways_.way_polylines_[way], approx_distance_lng_degrees); 48 | if (squared_dist < squared_max_dist) { 49 | auto raw_wc = 50 | raw_way_candidate{static_cast(std::sqrt(squared_dist)), way}; 51 | raw_wc.left_ = 52 | find_raw_next_node(raw_wc, direction::kBackward, 53 | approx_distance_lng_degrees, best, segment_idx); 54 | raw_wc.right_ = 55 | find_raw_next_node(raw_wc, direction::kForward, 56 | approx_distance_lng_degrees, best, segment_idx); 57 | if (raw_wc.left_.valid() || raw_wc.right_.valid()) { 58 | way_candidates.emplace_back(std::move(raw_wc)); 59 | } 60 | } 61 | }); 62 | utl::sort(way_candidates); 63 | return way_candidates; 64 | } 65 | 66 | std::vector lookup::get_raw_match( 67 | location const& query, double max_match_distance) const { 68 | auto way_candidates = get_raw_way_candidates(query, max_match_distance); 69 | auto i = 0U; 70 | while (way_candidates.empty() && i++ < 4U) { 71 | max_match_distance *= 2U; 72 | way_candidates = get_raw_way_candidates(query, max_match_distance); 73 | } 74 | return way_candidates; 75 | } 76 | 77 | raw_node_candidate lookup::find_raw_next_node( 78 | raw_way_candidate const& wc, 79 | direction const dir, 80 | double approx_distance_lng_degrees, 81 | geo::latlng const best, 82 | size_t segment_idx) const { 83 | auto c = raw_node_candidate{.dist_to_node_ = wc.dist_to_way_}; 84 | auto const polyline = ways_.way_polylines_[wc.way_]; 85 | auto const osm_nodes = ways_.way_osm_nodes_[wc.way_]; 86 | 87 | auto last_path_pos = best; 88 | till_the_end(segment_idx + (dir == direction::kForward ? 1U : 0U), 89 | utl::zip(polyline, osm_nodes), dir, [&](auto&& x) { 90 | auto const& [pos, osm_node_idx] = x; 91 | 92 | auto const segment_dist = 93 | std::sqrt(geo::approx_squared_distance( 94 | last_path_pos, pos, approx_distance_lng_degrees)); 95 | c.dist_to_node_ += static_cast(segment_dist); 96 | last_path_pos = pos; 97 | 98 | auto const way_node = ways_.find_node_idx(osm_node_idx); 99 | if (way_node.has_value()) { 100 | c.node_ = *way_node; 101 | return utl::cflow::kBreak; 102 | } 103 | return utl::cflow::kContinue; 104 | }); 105 | return c; 106 | } 107 | 108 | match_t lookup::match(profile_parameters const& params, 109 | location const& query, 110 | bool const reverse, 111 | direction const search_dir, 112 | double const max_match_distance, 113 | bitvec const* blocked, 114 | search_profile const p, 115 | std::optional> 116 | raw_way_candidates) const { 117 | return with_profile(p, [&](P&&) { 118 | return match

(std::get(params), query, reverse, 119 | search_dir, max_match_distance, blocked, 120 | raw_way_candidates); 121 | }); 122 | } 123 | 124 | hash_set lookup::find_elevators(geo::box const& b) const { 125 | auto elevators = hash_set{}; 126 | find(b, [&](way_idx_t const way) { 127 | for (auto const n : ways_.r_->way_nodes_[way]) { 128 | if (ways_.r_->node_properties_[n].is_elevator()) { 129 | elevators.emplace(n); 130 | } 131 | } 132 | }); 133 | return elevators; 134 | } 135 | 136 | } // namespace osr 137 | -------------------------------------------------------------------------------- /include/osr/routing/profile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "osr/elevation_storage.h" 11 | #include "osr/routing/mode.h" 12 | #include "osr/routing/path.h" 13 | #include "osr/routing/sharing_data.h" 14 | #include "osr/types.h" 15 | #include "osr/ways.h" 16 | 17 | namespace osr { 18 | 19 | template 20 | concept IsParameters = 21 | std::is_default_constructible_v && 22 | std::is_same_v && 23 | std::is_same_v; 24 | 25 | template 26 | concept IsNode = requires { 27 | { Node::invalid() } -> std::same_as; 28 | } && requires(Node const& node, std::ostream& out, ways const& w) { 29 | { node.get_key() } -> std::same_as; 30 | { node.get_node() } -> std::same_as; 31 | { node.get_mode() } -> std::same_as; 32 | { node == node } -> std::same_as; 33 | { node.print(out, w) } -> std::same_as; 34 | }; 35 | 36 | template 37 | concept IsLabel = 38 | requires { 39 | { Label(std::declval(), std::declval()) }; 40 | } && 41 | requires(Label const& label) { 42 | { label.get_node() } -> std::same_as; 43 | { label.cost() } -> std::same_as; 44 | } && 45 | requires(Label label, 46 | Label const& l, 47 | ways::routing const r, 48 | way_idx_t const w, 49 | node_idx_t const n) { 50 | { label.track(l, r, w, n, std::declval()) } -> std::same_as; 51 | }; 52 | 53 | template 54 | concept IsEntry = 55 | requires(Entry const& entry, Node const node, path& p) { 56 | { entry.pred(node) } -> std::same_as>; 57 | { entry.cost(node) } -> std::same_as; 58 | { entry.write(node, p) } -> std::same_as; 59 | } && requires(Entry entry, 60 | Node const node, 61 | Label const& label, 62 | cost_t const cost, 63 | path& p) { 64 | { entry.update(label, node, cost, node) } -> std::same_as; 65 | }; 66 | 67 | template 68 | concept IsHash = requires(Hash const& h, NodeKey key) { 69 | { h(key) }; 70 | }; 71 | 72 | template 73 | concept Profile = 74 | IsParameters && 75 | IsNode && 76 | IsLabel && 77 | IsEntry && 78 | IsHash && 79 | requires(typename P::node const node, 80 | ways::routing const& r, 81 | way_idx_t const w, 82 | node_idx_t const node_idx, 83 | level_t const lvl, 84 | direction const dir, 85 | std::function&& f) { 86 | { 87 | P::resolve_start_node(r, w, node_idx, lvl, dir, f) 88 | } -> std::same_as; 89 | { P::resolve_all(r, node_idx, lvl, f) } -> std::same_as; 90 | } && 91 | requires(typename P::parameters const& params, 92 | typename P::node const node, 93 | ways::routing const& r, 94 | way_idx_t const w, 95 | direction const dir, 96 | node_properties const n_props, 97 | way_properties const w_props, 98 | double const dist) { 99 | { 100 | P::is_dest_reachable(params, r, node, w, dir, dir) 101 | } -> std::same_as; 102 | { 103 | P::way_cost(params, w_props, dir, std::declval()) 104 | } -> std::same_as; 105 | { P::node_cost(n_props) } -> std::same_as; 106 | { P::heuristic(params, dist) } -> std::same_as; 107 | { P::get_reverse(node) } -> std::same_as; 108 | } && 109 | requires(typename P::parameters const& params, 110 | typename P::node const n, 111 | ways::routing const& r, 112 | bitvec const* blocked, 113 | sharing_data const* sharing, 114 | elevation_storage const* elevation, 115 | std::function f) { 123 | { 124 | P::template adjacent(params, r, n, blocked, 125 | sharing, elevation, f) 126 | } -> std::same_as; 127 | }; 128 | 129 | template 130 | concept ProfileParameters = Profile; 131 | 132 | enum class search_profile : std::uint8_t { 133 | kFoot, 134 | kWheelchair, 135 | kBike, 136 | kBikeFast, 137 | kBikeElevationLow, 138 | kBikeElevationHigh, 139 | kCar, 140 | kCarDropOff, 141 | kCarDropOffWheelchair, 142 | kCarParking, 143 | kCarParkingWheelchair, 144 | kBikeSharing, 145 | kCarSharing, 146 | }; 147 | 148 | constexpr auto const kNumProfiles = 149 | static_cast>(10U); 150 | 151 | search_profile to_profile(std::string_view); 152 | 153 | std::string_view to_str(search_profile); 154 | 155 | bool is_rental_profile(search_profile); 156 | 157 | } // namespace osr 158 | -------------------------------------------------------------------------------- /include/osr/preprocessing/elevation/hgt_tile_def.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osr/preprocessing/elevation/hgt_tile.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "cista/mmap.h" 10 | 11 | #include "utl/verify.h" 12 | 13 | // SRTM HGT File Format 14 | // 15 | // https://lpdaac.usgs.gov/documents/179/SRTM_User_Guide_V3.pdf 16 | // 17 | // Usage (see User Guide 2.1.4 SRTM Topography Data Format): 18 | // 19 | // The names of individual data tiles refer to the latitude and longitude of the 20 | // south west (lower left) corner of the tile. For example, N37W105 has its 21 | // lower left corner at 37°N latitude and 105° West (W) longitude and covers 22 | // (slightly more than) the area 37-38°N and 104 -105°W. To be more exact, the 23 | // file name coordinates refer to the geometric center of the lower- left pixel, 24 | // and all edge pixels of the tile are centered on whole-degree lines of 25 | // latitude and / or longitude. The unit of elevation is meters as referenced to 26 | // the WGS84/EGM96 geoid (NGA, 1997;Lemoine, 1998) 27 | // 28 | // Height files have the extension. HGT, and the DEM is provided as two-byte 29 | // (16-bit) binary signed integer raster data. Two-byte signed integers can 30 | // range from - 32,767 to 32,767m and can encompass the range of the Earth’s 31 | // elevations. Header or trailer bytes are not embedded in the file. The data 32 | // are stored in row major order, meaning all the data for the northernmost row, 33 | // row 1, are followed by all the data for row 2, and so on . 34 | // 35 | 36 | namespace osr::preprocessing::elevation { 37 | 38 | // Void value is used in version 1.0 and 2.1 only 39 | constexpr auto const kVoidValue = std::int16_t{-32768}; 40 | 41 | template 42 | struct hgt_tile::hgt_tile::impl { 43 | 44 | constexpr static auto kStepWidth = double{1. / (RasterSize - 1U)}; 45 | constexpr static auto kCenterOffset = kStepWidth / 2.; 46 | 47 | impl(std::string const& path, std::int8_t const lat, std::int16_t const lng) 48 | : file_{cista::mmap{path.data(), cista::mmap::protection::READ}}, 49 | sw_lat_{lat}, 50 | sw_lng_{lng} { 51 | utl::verify(file_.size() == file_size(), 52 | "HGT tile '{}' ({}x{}) has incorrect file size ({} != {})", 53 | path, RasterSize, RasterSize, file_.size(), file_size()); 54 | } 55 | 56 | template 57 | std::size_t get_offset(geo::latlng const& pos) const { 58 | auto const box = get_box(); 59 | if (box.contains(pos)) { 60 | // Column: Left to right 61 | auto const column = std::clamp( 62 | static_cast( 63 | std::floor(((pos.lng_ - box.min_.lng_) * (RasterSize - 1U)) / 64 | RasterSize * UpperBound)), 65 | std::size_t{0U}, UpperBound - 1U); 66 | // Row: Top to bottom 67 | auto const row = std::clamp( 68 | static_cast( 69 | std::floor(((box.max_.lat_ - pos.lat_) * (RasterSize - 1U)) / 70 | RasterSize * UpperBound)), 71 | std::size_t{0U}, (UpperBound - 1U)); 72 | // Data in row major order 73 | return UpperBound * row + column; 74 | } 75 | return std::numeric_limits::max(); 76 | } 77 | 78 | elevation_meters_t get(geo::latlng const& pos) const { 79 | auto const offset = get_offset(pos); 80 | if (offset == std::numeric_limits::max()) { 81 | return elevation_meters_t::invalid(); 82 | } 83 | return get(kBytesPerPixel * offset); 84 | } 85 | 86 | elevation_meters_t get(std::size_t const offset) const { 87 | auto const byte_ptr = file_.data() + offset; 88 | auto const raw_value = *reinterpret_cast(byte_ptr); 89 | // Byte is stored in big-endian 90 | auto const meters = std::endian::native == std::endian::big 91 | ? raw_value 92 | : std::byteswap(raw_value); 93 | return (meters == kVoidValue) ? elevation_meters_t::invalid() 94 | : elevation_meters_t{meters}; 95 | } 96 | 97 | tile_idx_t tile_idx(geo::latlng const& pos) const { 98 | constexpr auto const kSegments = 1 << (tile_idx_t::kSubTileIdxSize / 2); 99 | auto const offset = get_offset(pos); 100 | return (offset == std::numeric_limits::max()) 101 | ? tile_idx_t::invalid() 102 | : tile_idx_t::from_sub_tile( 103 | static_cast(offset)); 104 | } 105 | 106 | geo::box get_box() const { 107 | return { 108 | { 109 | sw_lat_ - kCenterOffset, 110 | sw_lng_ - kCenterOffset, 111 | }, 112 | { 113 | sw_lat_ + 1. + kCenterOffset, 114 | sw_lng_ + 1. + kCenterOffset, 115 | }, 116 | }; 117 | } 118 | 119 | constexpr resolution max_resolution() const { 120 | return {.x_ = kStepWidth, .y_ = kStepWidth}; 121 | } 122 | 123 | cista::mmap file_{}; 124 | // south west coordinate 125 | std::int8_t sw_lat_; 126 | std::int16_t sw_lng_; 127 | }; 128 | 129 | template 130 | hgt_tile::hgt_tile(std::string const& path, 131 | std::int8_t const lat, 132 | std::int16_t const lng) 133 | : impl_{std::make_unique(path, lat, lng)} {} 134 | 135 | template 136 | hgt_tile::~hgt_tile() = default; 137 | 138 | template 139 | hgt_tile::hgt_tile(hgt_tile&& grid) noexcept = default; 140 | 141 | template 142 | elevation_meters_t hgt_tile::get(geo::latlng const& pos) const { 143 | return impl_->get(pos); 144 | } 145 | 146 | template 147 | tile_idx_t hgt_tile::tile_idx(geo::latlng const& pos) const { 148 | return impl_->tile_idx(pos); 149 | } 150 | 151 | template 152 | resolution hgt_tile::max_resolution() const { 153 | return impl_->max_resolution(); 154 | } 155 | 156 | template 157 | geo::box hgt_tile::get_box() const { 158 | return impl_->get_box(); 159 | } 160 | 161 | } // namespace osr::preprocessing::elevation 162 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(osr LANGUAGES C CXX ASM) 3 | 4 | cmake_policy(SET CMP0079 NEW) 5 | 6 | if (MSVC) 7 | # PDB debug information is not supported by buildcache. 8 | # Store debug info in the object files. 9 | option(OSR_DEBUG_SYMBOLS "generate debug symbols (debug builds)" ON) 10 | if (OSR_DEBUG_SYMBOLS) 11 | set(OSR_MSVC_DEBUG_FLAGS "/Z7") 12 | else () 13 | set(OSR_MSVC_DEBUG_FLAGS "") 14 | endif () 15 | string(REPLACE "/Zi" "${OSR_MSVC_DEBUG_FLAGS}" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 16 | string(REPLACE "/Zi" "${OSR_MSVC_DEBUG_FLAGS}" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") 17 | string(REPLACE "/Zi" "${OSR_MSVC_DEBUG_FLAGS}" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") 18 | string(REPLACE "/Zi" "${OSR_MSVC_DEBUG_FLAGS}" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") 19 | 20 | add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4996") # ignore deprecated fileno in tiles 22 | endif () 23 | 24 | option(OSR_MIMALLOC "use mimalloc" OFF) 25 | 26 | if (OSR_MIMALLOC) 27 | set(CISTA_USE_MIMALLOC ON) 28 | set(TILES_MIMALLOC ON) 29 | endif() 30 | 31 | if (NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) 32 | if (OSR_MIMALLOC) 33 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") 34 | else () 35 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 36 | endif () 37 | endif () 38 | 39 | if (OSR_MIMALLOC AND WIN32) 40 | set(MI_BUILD_SHARED ON) 41 | endif () 42 | 43 | include(cmake/buildcache.cmake) 44 | include(cmake/pkg.cmake) 45 | 46 | if (OSR_MIMALLOC) 47 | if (WIN32) 48 | set(osr-mimalloc-lib mimalloc) 49 | target_link_libraries(cista INTERFACE mimalloc) 50 | else () 51 | set(osr-mimalloc-lib mimalloc-obj) 52 | target_link_libraries(cista INTERFACE mimalloc-static) 53 | endif () 54 | target_compile_definitions(cista INTERFACE CISTA_USE_MIMALLOC=1) 55 | target_compile_definitions(boost INTERFACE BOOST_ASIO_DISABLE_STD_ALIGNED_ALLOC=1) 56 | endif () 57 | 58 | # --- LINT --- 59 | option(OSR_LINT "Run clang-tidy with the compiler." OFF) 60 | if (OSR_LINT) 61 | # clang-tidy will be run on all targets defined hereafter 62 | include(cmake/clang-tidy.cmake) 63 | endif () 64 | 65 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 66 | set(osr-compile-options 67 | -Weverything 68 | -Wno-c++98-compat 69 | -Wno-c++98-compat-pedantic 70 | -Wno-newline-eof 71 | -Wno-missing-prototypes 72 | -Wno-padded 73 | -Wno-double-promotion 74 | -Wno-undef 75 | -Wno-undefined-reinterpret-cast 76 | -Wno-float-conversion 77 | -Wno-global-constructors 78 | -Wno-exit-time-destructors 79 | -Wno-switch-enum 80 | -Wno-c99-designator 81 | -Wno-zero-as-null-pointer-constant 82 | -Wno-missing-noreturn 83 | -Wno-undefined-func-template 84 | -Wno-unsafe-buffer-usage 85 | -Wno-c++20-compat 86 | -Wno-reserved-macro-identifier 87 | -Wno-documentation-unknown-command 88 | -Wno-duplicate-enum 89 | -Wno-ctad-maybe-unsupported 90 | -Wno-unknown-pragmas 91 | -Wno-switch-default 92 | -Werror) 93 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 94 | set(osr-compile-options -Wall -Wextra -Werror -Wno-unknown-pragmas) 95 | elseif (MSVC) 96 | # Will create more than 65,279 sections in debug mode 97 | set(osr-compile-options /WX /bigobj) 98 | else () 99 | set(osr-compile-options 100 | -Wall 101 | -Wextra 102 | -Wno-changes-meaning 103 | -Wno-maybe-uninitialized) 104 | if (NOT CMAKE_CROSSCOMPILING) 105 | set(osr-compile-options ${osr-compile-options} -Werror) 106 | endif () 107 | endif () 108 | 109 | # --- LIB --- 110 | file(GLOB_RECURSE osr-src src/*.cc) 111 | add_library(osr ${osr-src}) 112 | target_include_directories(osr PUBLIC include) 113 | target_compile_features(osr PUBLIC cxx_std_23) 114 | target_compile_options(osr PRIVATE ${osr-compile-options}) 115 | target_link_libraries(osr 116 | osmium 117 | zlibstatic 118 | protozero 119 | expat 120 | geo 121 | cista 122 | utl 123 | tiles-import-library 124 | rtree 125 | unordered_dense 126 | boost-thread 127 | ) 128 | 129 | # --- MAIN --- 130 | add_executable(osr-extract exe/extract.cc) 131 | target_link_libraries(osr-extract osr) 132 | 133 | add_executable(osr-benchmark exe/benchmark.cc) 134 | target_link_libraries(osr-benchmark osr) 135 | 136 | file(GLOB_RECURSE osr-backend-src exe/backend/*.cc) 137 | add_executable(osr-backend ${osr-backend-src}) 138 | target_link_libraries(osr-backend osr web-server conf boost-json) 139 | target_include_directories(osr-backend PRIVATE exe/backend/include) 140 | 141 | # --- TEST --- 142 | configure_file( 143 | ${CMAKE_CURRENT_SOURCE_DIR}/test/test_dir.h.in 144 | ${CMAKE_CURRENT_BINARY_DIR}/generated/test_dir.h 145 | ) 146 | file(GLOB_RECURSE osr-test-files test/*cc) 147 | add_executable(osr-test ${osr-test-files}) 148 | target_include_directories(osr-test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated) 149 | target_link_libraries(osr-test gtest osr boost-json) 150 | 151 | # --- MIMALLOC --- 152 | if (OSR_MIMALLOC) 153 | target_link_libraries(osr-extract ${osr-mimalloc-lib}) 154 | target_link_libraries(osr-backend ${osr-mimalloc-lib}) 155 | if (WIN32) 156 | add_custom_command( 157 | TARGET osr-extract POST_BUILD 158 | COMMAND "${CMAKE_COMMAND}" -E copy 159 | $ 160 | $ 161 | COMMENT "Copy mimalloc.dll to output directory" 162 | ) 163 | add_custom_command( 164 | TARGET osr-extract POST_BUILD 165 | COMMAND "${CMAKE_COMMAND}" -E copy 166 | "${CMAKE_BINARY_DIR}/deps/mimalloc/mimalloc-redirect.dll" 167 | $ 168 | COMMENT "Copy mimalloc-redirect.dll to output directory" 169 | ) 170 | endif () 171 | endif () 172 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "windows-debug", 11 | "displayName": "Windows x64 Debug", 12 | "generator": "Ninja", 13 | "architecture": { 14 | "value": "x64", 15 | "strategy": "external" 16 | }, 17 | "cacheVariables": { 18 | "CMAKE_BUILD_TYPE": "Debug" 19 | }, 20 | "vendor": { 21 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 22 | "hostOS": [ "Windows" ] 23 | } 24 | } 25 | }, 26 | { 27 | "name": "windows-release", 28 | "displayName": "Windows x64 Release", 29 | "generator": "Ninja", 30 | "architecture": { 31 | "value": "x64", 32 | "strategy": "external" 33 | }, 34 | "cacheVariables": { 35 | "CMAKE_BUILD_TYPE": "Release" 36 | }, 37 | "vendor": { 38 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 39 | "hostOS": [ "Windows" ] 40 | } 41 | } 42 | }, 43 | { 44 | "name": "macos-x86_64", 45 | "displayName": "MacOS x86_64 Release", 46 | "generator": "Ninja", 47 | "binaryDir": "${sourceDir}/build/macos-x86_64-release", 48 | "cacheVariables": { 49 | "CMAKE_OSX_ARCHITECTURES": "x86_64", 50 | "CMAKE_CXX_FLAGS": "-stdlib=libc++", 51 | "CMAKE_BUILD_TYPE": "Release" 52 | } 53 | }, 54 | { 55 | "name": "macos-arm64", 56 | "displayName": "MacOS ARM64 Release", 57 | "generator": "Ninja", 58 | "binaryDir": "${sourceDir}/build/macos-arm64-release", 59 | "cacheVariables": { 60 | "CMAKE_OSX_ARCHITECTURES": "arm64", 61 | "CMAKE_CXX_FLAGS": "-stdlib=libc++", 62 | "BOOST_CONTEXT_ARCHITECTURE": "arm64", 63 | "BOOST_CONTEXT_ABI": "aapcs", 64 | "ENABLE_ASM": "OFF", 65 | "CMAKE_BUILD_TYPE": "Release" 66 | } 67 | }, 68 | { 69 | "name": "linux-amd64-release", 70 | "displayName": "Linux AMD64 Release", 71 | "generator": "Ninja", 72 | "binaryDir": "${sourceDir}/build/amd64-release", 73 | "toolchainFile": "/opt/x86_64-multilib-linux-musl/toolchain-amd64.cmake", 74 | "cacheVariables": { 75 | "CMAKE_EXE_LINKER_FLAGS": "-B/opt/mold", 76 | "CMAKE_BUILD_TYPE": "Release", 77 | "OSR_MIMALLOC": "ON" 78 | }, 79 | "environment": { 80 | "PATH": "/opt:/opt/cmake-3.26.3-linux-x86_64/bin:/opt/buildcache/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 81 | } 82 | }, 83 | { 84 | "name": "linux-arm64-release", 85 | "displayName": "Linux ARM64 Release", 86 | "generator": "Ninja", 87 | "binaryDir": "${sourceDir}/build/arm64-release", 88 | "toolchainFile": "/opt/aarch64-unknown-linux-musl/toolchain-arm64.cmake", 89 | "cacheVariables": { 90 | "CMAKE_CROSSCOMPILING_EMULATOR": "qemu-aarch64-static", 91 | "CMAKE_EXE_LINKER_FLAGS": "-B/opt/mold", 92 | "CMAKE_BUILD_TYPE": "Release", 93 | "OSR_MIMALLOC": "ON" 94 | }, 95 | "environment": { 96 | "PATH": "/opt:/opt/cmake-3.26.3-linux-x86_64/bin:/opt/buildcache/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 97 | } 98 | }, 99 | { 100 | "name": "linux-sanitizer", 101 | "displayName": "Linux Sanitizer", 102 | "generator": "Ninja", 103 | "binaryDir": "${sourceDir}/build/sanitizer", 104 | "cacheVariables": { 105 | "CMAKE_BUILD_TYPE": "Debug", 106 | "CMAKE_C_COMPILER": "clang-18", 107 | "CMAKE_CXX_COMPILER": "clang++-18", 108 | "CMAKE_C_FLAGS": "-fsanitize=address,undefined -fno-omit-frame-pointer", 109 | "CMAKE_CXX_FLAGS": "-stdlib=libc++ -fsanitize=address,undefined -fno-omit-frame-pointer", 110 | "CMAKE_EXE_LINKER_FLAGS": "-lc++abi -fuse-ld=/opt/mold/ld" 111 | }, 112 | "environment": { 113 | "PATH": "/opt:/opt/cmake-3.26.3-linux-x86_64/bin:/opt/buildcache/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 114 | "CXX": "/usr/bin/g++-13", 115 | "CC": "/usr/bin/gcc-13" 116 | } 117 | }, 118 | { 119 | "name": "linux-debug", 120 | "displayName": "Linux Debug", 121 | "generator": "Ninja", 122 | "binaryDir": "${sourceDir}/build/debug", 123 | "cacheVariables": { 124 | "CMAKE_BUILD_TYPE": "Debug" 125 | }, 126 | "environment": { 127 | "PATH": "/opt:/opt/cmake-3.26.3-linux-x86_64/bin:/opt/buildcache/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 128 | "CXX": "/usr/bin/g++-13", 129 | "CC": "/usr/bin/gcc-13" 130 | } 131 | }, 132 | { 133 | "name": "linux-relwithdebinfo", 134 | "displayName": "Linux RelWithDebInfo", 135 | "generator": "Ninja", 136 | "binaryDir": "${sourceDir}/build/relwithdebinfo", 137 | "cacheVariables": { 138 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 139 | "CMAKE_EXE_LINKER_FLAGS": "-B/opt/mold" 140 | }, 141 | "environment": { 142 | "PATH": "/opt:/opt/cmake-3.26.3-linux-x86_64/bin:/opt/buildcache/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 143 | "CXX": "/usr/bin/g++-13", 144 | "CC": "/usr/bin/gcc-13" 145 | } 146 | }, 147 | { 148 | "name": "clang-tidy", 149 | "displayName": "Clang Tidy", 150 | "generator": "Ninja", 151 | "binaryDir": "${sourceDir}/build/clang-tidy", 152 | "cacheVariables": { 153 | "CMAKE_C_COMPILER": "clang-18", 154 | "CMAKE_CXX_COMPILER": "clang++-18", 155 | "CMAKE_CXX_FLAGS": "-stdlib=libc++", 156 | "CMAKE_EXE_LINKER_FLAGS": "-lc++abi", 157 | "CMAKE_BUILD_TYPE": "Release", 158 | "OSR_LINT": "On" 159 | }, 160 | "environment": { 161 | "BUILDCACHE_LUA_PATH": "/opt/buildcache/share/lua-examples", 162 | "PATH": "/opt:/opt/cmake-3.26.3-linux-x86_64/bin:/opt/buildcache/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 163 | } 164 | } 165 | ], 166 | "buildPresets": [ 167 | { 168 | "name": "macos-x86_64", 169 | "configurePreset": "macos-x86_64" 170 | }, 171 | { 172 | "name": "macos-arm64", 173 | "configurePreset": "macos-arm64" 174 | }, 175 | { 176 | "name": "linux-amd64-release", 177 | "configurePreset": "linux-amd64-release" 178 | }, 179 | { 180 | "name": "linux-arm64-release", 181 | "configurePreset": "linux-arm64-release" 182 | }, 183 | { 184 | "name": "clang-tidy", 185 | "configurePreset": "clang-tidy" 186 | }, 187 | { 188 | "name": "linux-debug", 189 | "configurePreset": "linux-debug" 190 | }, 191 | { 192 | "name": "linux-relwithdebinfo", 193 | "configurePreset": "linux-relwithdebinfo" 194 | }, 195 | { 196 | "name": "linux-sanitizer", 197 | "configurePreset": "linux-sanitizer" 198 | } 199 | ] 200 | } 201 | -------------------------------------------------------------------------------- /include/osr/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "utl/for_each_bit_set.h" 7 | 8 | #include "ankerl/cista_adapter.h" 9 | 10 | #include "cista/containers/bitvec.h" 11 | #include "cista/containers/mmap_vec.h" 12 | #include "cista/containers/nvec.h" 13 | #include "cista/containers/paged.h" 14 | #include "cista/containers/paged_vecvec.h" 15 | #include "cista/containers/vector.h" 16 | #include "cista/containers/vecvec.h" 17 | #include "cista/strong.h" 18 | 19 | namespace osr { 20 | 21 | template > 22 | using vecvec = cista::raw::vecvec; 23 | 24 | template 25 | using vec = cista::raw::vector; 26 | 27 | template 28 | using vec_map = cista::raw::vector_map; 29 | 30 | template 31 | using bitvec = cista::basic_bitvec, K>; 32 | 33 | template 34 | using mm_vec_map = cista::basic_mmap_vec; 35 | 36 | template 37 | using mm_vec = cista::basic_mmap_vec; 38 | 39 | template 40 | using mm_vec32 = cista::basic_mmap_vec; 41 | 42 | template 43 | using mm_bitvec = cista::basic_bitvec, K>; 44 | 45 | template > 46 | using mm_vecvec = cista::basic_vecvec, mm_vec>; 47 | 48 | template 49 | using mm_nvec = 50 | cista::basic_nvec, mm_vec, N, std::uint64_t>; 51 | 52 | template 53 | struct mmap_paged_vecvec_helper { 54 | using data_t = cista::paged>; 55 | using idx_t = mm_vec; 56 | using type = cista::paged_vecvec; 57 | }; 58 | 59 | template 60 | using mm_paged_vecvec = mmap_paged_vecvec_helper::type; 61 | 62 | using bitvec64 = cista::basic_bitvec< 63 | cista::basic_vector>; 64 | 65 | template 66 | using pair = cista::pair; 67 | 68 | template 72 | using hash_map = cista::raw::ankerl_map; 73 | 74 | template 77 | using hash_set = cista::raw::ankerl_set; 78 | 79 | using string_idx_t = cista::strong; 80 | 81 | using osm_node_idx_t = cista::strong; 82 | using osm_way_idx_t = cista::strong; 83 | 84 | using way_idx_t = cista::strong; 85 | using node_idx_t = cista::strong; 86 | 87 | using component_idx_t = cista::strong; 88 | 89 | using platform_idx_t = cista::strong; 90 | 91 | using multi_level_elevator_idx_t = 92 | cista::strong; 93 | 94 | using distance_t = std::uint16_t; 95 | using elevation_monotonic_t = 96 | cista::strong; 97 | 98 | using way_pos_t = std::uint8_t; 99 | 100 | using cost_t = std::uint16_t; 101 | 102 | constexpr auto const kInfeasible = std::numeric_limits::max(); 103 | 104 | // direction 105 | enum class direction : std::uint8_t { 106 | kForward, 107 | kBackward, 108 | }; 109 | 110 | inline std::ostream& operator<<(std::ostream& out, direction const d) { 111 | return out << (d == direction::kBackward ? "bwd" : "fwd"); 112 | } 113 | 114 | constexpr direction opposite(direction const dir) { 115 | return dir == direction::kForward ? direction::kBackward 116 | : direction::kForward; 117 | } 118 | 119 | template 120 | constexpr direction flip(direction const dir) { 121 | return Dir == direction::kForward ? dir : opposite(dir); 122 | } 123 | 124 | constexpr direction flip(direction const search_dir, direction const dir) { 125 | return search_dir == direction::kForward ? dir : opposite(dir); 126 | } 127 | 128 | constexpr std::string_view to_str(direction const d) { 129 | switch (d) { 130 | case direction::kForward: return "forward"; 131 | case direction::kBackward: return "backward"; 132 | } 133 | std::unreachable(); 134 | } 135 | 136 | constexpr direction to_direction(std::string_view s) { 137 | switch (cista::hash(s)) { 138 | case cista::hash("forward"): return direction::kForward; 139 | case cista::hash("backward"): return direction::kBackward; 140 | } 141 | std::unreachable(); 142 | } 143 | 144 | // level 145 | constexpr auto const kMinLevel = -4.0F; 146 | constexpr auto const kMaxLevel = 3.5F; 147 | 148 | struct level_t { 149 | static constexpr auto kNoLevel = 0U; 150 | 151 | friend constexpr std::uint8_t to_idx(level_t l) { return l.v_; } 152 | 153 | friend std::ostream& operator<<(std::ostream& out, level_t const l) { 154 | return (l.v_ == kNoLevel) ? (out << "-") : (out << l.to_float()); 155 | } 156 | 157 | explicit constexpr level_t(std::uint8_t const x) : v_{x} {} 158 | 159 | explicit constexpr level_t(float const f) 160 | : v_{static_cast((f - kMinLevel) / 0.25F + 1U)} {} 161 | 162 | constexpr float to_float() const { 163 | return (v_ == kNoLevel) ? 0.0F : (kMinLevel + ((v_ - 1U) / 4.0F)); 164 | } 165 | 166 | constexpr level_t() = default; 167 | 168 | auto operator<=>(level_t const&) const = default; 169 | 170 | constexpr cista::hash_t hash() const { return v_; } 171 | 172 | std::uint8_t v_; 173 | }; 174 | 175 | constexpr auto const kNoLevel = level_t{std::uint8_t{0U}}; 176 | 177 | constexpr auto const kLevelBits = cista::constexpr_trailing_zeros( 178 | cista::next_power_of_two(to_idx(level_t{kMaxLevel}) + 1U)); 179 | 180 | using level_bits_t = std::uint32_t; 181 | 182 | constexpr std::tuple get_levels( 183 | bool const has_level, level_bits_t const levels) noexcept { 184 | if (!has_level) { 185 | return {level_t{kNoLevel}, level_t{kNoLevel}, false}; 186 | } 187 | auto from = kNoLevel, to = kNoLevel; 188 | utl::for_each_set_bit(levels, [&](auto&& bit) { 189 | from == kNoLevel // 190 | ? from = level_t{static_cast(bit)} 191 | : to = level_t{static_cast(bit)}; 192 | }); 193 | return {from, to == kNoLevel ? from : to, std::popcount(levels) > 2}; 194 | } 195 | 196 | static_assert(kLevelBits == 5U); 197 | 198 | // speed 199 | enum speed_limit : std::uint8_t { 200 | kmh_10, 201 | kmh_30, 202 | kmh_50, 203 | kmh_60, 204 | kmh_70, 205 | kmh_80, 206 | kmh_100, 207 | kmh_120, 208 | }; 209 | 210 | constexpr speed_limit get_speed_limit(unsigned const x) { 211 | if (x >= 120U) { 212 | return speed_limit::kmh_120; 213 | } else if (x >= 100) { 214 | return speed_limit::kmh_100; 215 | } else if (x >= 80) { 216 | return speed_limit::kmh_80; 217 | } else if (x >= 70) { 218 | return speed_limit::kmh_70; 219 | } else if (x >= 60) { 220 | return speed_limit::kmh_60; 221 | } else if (x >= 50) { 222 | return speed_limit::kmh_50; 223 | } else if (x >= 30) { 224 | return speed_limit::kmh_30; 225 | } else { 226 | return speed_limit::kmh_10; 227 | } 228 | } 229 | 230 | constexpr std::uint16_t to_kmh(speed_limit const l) { 231 | switch (l) { 232 | case speed_limit::kmh_10: return 10U; 233 | case speed_limit::kmh_30: return 30U; 234 | case speed_limit::kmh_50: return 50U; 235 | case speed_limit::kmh_60: return 60U; 236 | case speed_limit::kmh_70: return 70U; 237 | case speed_limit::kmh_80: return 80U; 238 | case speed_limit::kmh_100: return 100U; 239 | case speed_limit::kmh_120: return 120U; 240 | } 241 | std::unreachable(); 242 | } 243 | 244 | constexpr std::uint16_t to_meters_per_second(speed_limit const l) { 245 | return static_cast(to_kmh(l) / 3.6); 246 | } 247 | 248 | } // namespace osr 249 | -------------------------------------------------------------------------------- /include/osr/platforms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osmium/osm/node.hpp" 4 | #include "osmium/osm/relation.hpp" 5 | #include "osmium/osm/way.hpp" 6 | 7 | #include "rtree.h" 8 | 9 | #include "utl/overloaded.h" 10 | #include "utl/zip.h" 11 | 12 | #include "osr/types.h" 13 | #include "osr/ways.h" 14 | #include "utl/parser/cstr.h" 15 | 16 | namespace osr { 17 | 18 | using ref_t = std::variant; 19 | using ref_value_t = std::uint32_t; 20 | 21 | static_assert(sizeof(ref_value_t) >= sizeof(way_idx_t) && 22 | sizeof(ref_value_t) >= sizeof(node_idx_t)); 23 | 24 | constexpr auto const kNodeMarker = 25 | ref_value_t{1U << (sizeof(ref_value_t) * 8U - 1U)}; 26 | 27 | constexpr bool is_way(ref_value_t const v) { return v < kNodeMarker; } 28 | 29 | constexpr ref_t to_ref(ref_value_t const v) { 30 | if (is_way(v)) { 31 | return way_idx_t{v}; 32 | } else { 33 | return node_idx_t{v - kNodeMarker}; 34 | } 35 | } 36 | 37 | constexpr ref_value_t to_value(ref_t const r) { 38 | return std::visit( 39 | utl::overloaded{[](way_idx_t x) { return to_idx(x); }, 40 | [](node_idx_t x) { return to_idx(x) + kNodeMarker; }}, 41 | r); 42 | } 43 | 44 | struct platforms { 45 | platforms(std::filesystem::path p, cista::mmap::protection const mode) 46 | : p_{std::move(p)}, 47 | mode_{mode}, 48 | node_pos_{mm("node_pos.bin")}, 49 | node_is_platform_{mm_vec{mm("node_is_platform.bin")}}, 50 | way_is_platform_{mm_vec{mm("way_is_platform.bin")}}, 51 | platform_ref_{cista::paged>{ 52 | mm_vec32{mm("platform_ref_data.bin")}}, 53 | mm_vec>{ 54 | mm("platform_ref_index.bin")}}, 55 | platform_names_{{mm_vec{mm("platform_names_idx_0.bin")}, 56 | mm_vec{mm("platform_names_idx_1.bin")}}, 57 | mm_vec{mm("platform_names_data.bin")}} {} 58 | 59 | ~platforms() { 60 | if (rtree_ != nullptr) { 61 | rtree_free(rtree_); 62 | } 63 | } 64 | 65 | platform_idx_t way(way_idx_t const w, osmium::Way const& x) { 66 | assert(w != way_idx_t::invalid()); 67 | assert(to_idx(w) < kNodeMarker); 68 | auto const p = add_names(x); 69 | if (p == platform_idx_t::invalid()) { 70 | return platform_idx_t::invalid(); 71 | } 72 | way_is_platform_.resize(std::max( 73 | way_is_platform_.size(), static_cast(to_idx(w) + 1U))); 74 | way_is_platform_.set(w, true); 75 | platform_ref_.emplace_back(std::initializer_list{to_value(w)}); 76 | return p; 77 | } 78 | 79 | platform_idx_t node(node_idx_t const n, osmium::Node const& x) { 80 | assert(n != node_idx_t::invalid()); 81 | assert(to_idx(n) < kNodeMarker); 82 | auto const p = add_names(x); 83 | 84 | if (p == platform_idx_t::invalid()) { 85 | return platform_idx_t::invalid(); 86 | } 87 | 88 | node_pos_.emplace_back(n, point::from_location(x.location())); 89 | node_is_platform_.resize(std::max( 90 | node_is_platform_.size(), static_cast(to_idx(n) + 1U))); 91 | node_is_platform_.set(n, true); 92 | platform_ref_.emplace_back(std::initializer_list{to_value(n)}); 93 | return p; 94 | } 95 | 96 | platform_idx_t relation(osmium::Relation const& r) { 97 | auto const p = add_names(r); 98 | 99 | if (p == platform_idx_t::invalid()) { 100 | return platform_idx_t::invalid(); 101 | } 102 | 103 | platform_ref_.emplace_back_empty(); 104 | return p; 105 | } 106 | 107 | platform_idx_t add_names(osmium::OSMObject const& x) { 108 | strings_.clear(); 109 | for (auto const& t : x.tags()) { 110 | switch (cista::hash(std::string_view{t.key()})) { 111 | case cista::hash("ref"): 112 | case cista::hash("ref:IFOPT"): 113 | case cista::hash("ref:operator"): 114 | case cista::hash("ref_name"): 115 | case cista::hash("local_ref"): 116 | case cista::hash("name"): 117 | case cista::hash("alt_name"): [[fallthrough]]; 118 | case cista::hash("description"): 119 | utl::for_each_token(t.value(), ';', [&](auto const s) { 120 | strings_.emplace_back(s.view()); 121 | }); 122 | } 123 | } 124 | 125 | if (strings_.empty()) { 126 | return platform_idx_t::invalid(); 127 | } 128 | 129 | auto const idx = platform_idx_t{platform_names_.size()}; 130 | platform_names_.emplace_back(strings_); 131 | return idx; 132 | } 133 | 134 | level_t get_level(ways const& w, platform_idx_t const i) const { 135 | if (i == platform_idx_t::invalid()) { 136 | return level_t{0.0F}; 137 | } 138 | return std::visit( 139 | utl::overloaded{ 140 | [&](way_idx_t x) { return w.r_->way_properties_[x].from_level(); }, 141 | [&](node_idx_t x) { 142 | return w.r_->node_properties_[x].from_level(); 143 | }}, 144 | to_ref(platform_ref_[i][0])); 145 | } 146 | 147 | template 148 | void find(geo::latlng const& x, Fn&& fn) const { 149 | find({x.lat() - 0.01, x.lng() - 0.01}, {x.lat() + 0.01, x.lng() + 0.01}, 150 | std::forward(fn)); 151 | } 152 | 153 | template 154 | void find(geo::latlng const& a, geo::latlng const& b, Fn&& fn) const { 155 | auto const min = 156 | std::array{std::min(a.lng_, b.lng_), std::min(a.lat_, b.lat_)}; 157 | auto const max = 158 | std::array{std::max(a.lng_, b.lng_), std::max(a.lat_, b.lat_)}; 159 | rtree_search( 160 | rtree_, min.data(), max.data(), 161 | [](double const* /* min */, double const* /* max */, void const* item, 162 | void* udata) { 163 | (*reinterpret_cast(udata))( 164 | platform_idx_t{static_cast( 165 | reinterpret_cast(item))}); 166 | return true; 167 | }, 168 | &fn); 169 | } 170 | 171 | point get_node_pos(node_idx_t const i) const { 172 | auto const it = 173 | std::lower_bound(begin(node_pos_), end(node_pos_), i, 174 | [](auto&& a, auto&& b) { return a.first < b; }); 175 | utl::verify(it != end(node_pos_) || it->first == i, "node pos not found"); 176 | return it->second; 177 | } 178 | 179 | void build_rtree(ways const& w) { 180 | assert(rtree_ == nullptr); 181 | 182 | rtree_ = rtree_new(); 183 | 184 | auto i = platform_idx_t{0U}; 185 | if (platform_ref_.empty()) { 186 | return; 187 | } 188 | for (auto const refs : platform_ref_) { 189 | for (auto const ref : refs) { 190 | assert(ref != std::numeric_limits::max()); 191 | std::visit( 192 | utl::overloaded{ 193 | [&](node_idx_t const x) { 194 | auto const pos = get_node_pos(x).as_latlng(); 195 | auto const min_corner = std::array{pos.lng(), pos.lat()}; 196 | rtree_insert(rtree_, min_corner.data(), nullptr, 197 | reinterpret_cast( 198 | static_cast(to_idx(i)))); 199 | }, 200 | 201 | [&](way_idx_t const x) { 202 | auto b = osmium::Box{}; 203 | for (auto const& c : w.way_polylines_[x]) { 204 | b.extend(osmium::Location{c.lat_, c.lng_}); 205 | } 206 | 207 | auto const min_corner = 208 | std::array{b.bottom_left().lon(), b.bottom_left().lat()}; 209 | auto const max_corner = 210 | std::array{b.top_right().lon(), b.top_right().lat()}; 211 | 212 | rtree_insert(rtree_, min_corner.data(), max_corner.data(), 213 | reinterpret_cast( 214 | static_cast(to_idx(i)))); 215 | }}, 216 | to_ref(ref)); 217 | } 218 | ++i; 219 | } 220 | } 221 | 222 | cista::mmap mm(char const* file) { 223 | return cista::mmap{(p_ / file).generic_string().c_str(), mode_}; 224 | } 225 | 226 | std::filesystem::path p_; 227 | cista::mmap::protection mode_; 228 | mm_vec> node_pos_; 229 | mm_bitvec node_is_platform_; 230 | mm_bitvec way_is_platform_; 231 | mm_paged_vecvec platform_ref_; 232 | mm_nvec platform_names_; 233 | 234 | vecvec strings_; 235 | 236 | rtree* rtree_{nullptr}; 237 | }; 238 | 239 | } // namespace osr -------------------------------------------------------------------------------- /include/osr/routing/profiles/car.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "boost/json/object.hpp" 6 | 7 | #include "utl/helpers/algorithm.h" 8 | 9 | #include "osr/elevation_storage.h" 10 | #include "osr/routing/mode.h" 11 | #include "osr/routing/path.h" 12 | #include "osr/ways.h" 13 | 14 | namespace osr { 15 | 16 | struct sharing_data; 17 | 18 | struct car { 19 | static constexpr auto const kMaxMatchDistance = 200U; 20 | static constexpr auto const kUturnPenalty = cost_t{120U}; 21 | 22 | using key = node_idx_t; 23 | 24 | struct parameters { 25 | using profile_t = car; 26 | }; 27 | 28 | struct node { 29 | friend bool operator==(node, node) = default; 30 | 31 | static constexpr node invalid() noexcept { 32 | return node{ 33 | .n_ = node_idx_t::invalid(), .way_ = 0U, .dir_ = direction::kForward}; 34 | } 35 | 36 | boost::json::object geojson_properties(ways const&) const { 37 | return boost::json::object{{"node_id", n_.v_}, {"type", "car"}}; 38 | } 39 | 40 | constexpr node_idx_t get_node() const noexcept { return n_; } 41 | constexpr node_idx_t get_key() const noexcept { return n_; } 42 | 43 | static constexpr mode get_mode() noexcept { return mode::kCar; } 44 | 45 | std::ostream& print(std::ostream& out, ways const& w) const { 46 | return out << "(node=" << w.node_to_osm_[n_] << ", dir=" << to_str(dir_) 47 | << ", way=" << w.way_osm_idx_[w.r_->node_ways_[n_][way_]] 48 | << ")"; 49 | } 50 | 51 | node_idx_t n_; 52 | way_pos_t way_; 53 | direction dir_; 54 | }; 55 | 56 | struct label { 57 | label(node const n, cost_t const c) 58 | : n_{n.n_}, way_{n.way_}, dir_{n.dir_}, cost_{c} {} 59 | 60 | constexpr node get_node() const noexcept { return {n_, way_, dir_}; } 61 | constexpr cost_t cost() const noexcept { return cost_; } 62 | 63 | void track( 64 | label const&, ways::routing const&, way_idx_t, node_idx_t, bool) {} 65 | 66 | node_idx_t n_; 67 | way_pos_t way_; 68 | direction dir_; 69 | cost_t cost_; 70 | }; 71 | 72 | struct entry { 73 | static constexpr auto const kMaxWays = way_pos_t{16U}; 74 | static constexpr auto const kN = kMaxWays * 2U /* FWD+BWD */; 75 | 76 | entry() { utl::fill(cost_, kInfeasible); } 77 | 78 | constexpr std::optional pred(node const n) const noexcept { 79 | auto const idx = get_index(n); 80 | return pred_[idx] == node_idx_t::invalid() 81 | ? std::nullopt 82 | : std::optional{node{pred_[idx], pred_way_[idx], 83 | to_dir(pred_dir_[idx])}}; 84 | } 85 | 86 | constexpr cost_t cost(node const n) const noexcept { 87 | return cost_[get_index(n)]; 88 | } 89 | 90 | constexpr bool update(label const&, 91 | node const n, 92 | cost_t const c, 93 | node const pred) noexcept { 94 | auto const idx = get_index(n); 95 | if (c < cost_[idx]) { 96 | cost_[idx] = c; 97 | pred_[idx] = pred.n_; 98 | pred_way_[idx] = pred.way_; 99 | pred_dir_[idx] = to_bool(pred.dir_); 100 | return true; 101 | } 102 | return false; 103 | } 104 | 105 | void write(node, path&) const {} 106 | 107 | static constexpr node get_node(node_idx_t const n, 108 | std::size_t const index) { 109 | return node{n, static_cast(index % kMaxWays), 110 | to_dir((index / kMaxWays) != 0U)}; 111 | } 112 | 113 | static constexpr std::size_t get_index(node const n) { 114 | return (n.dir_ == direction::kForward ? 0U : 1U) * kMaxWays + n.way_; 115 | } 116 | 117 | static constexpr direction to_dir(bool const b) { 118 | return b == false ? direction::kForward : direction::kBackward; 119 | } 120 | 121 | static constexpr bool to_bool(direction const d) { 122 | return d == direction::kForward ? false : true; 123 | } 124 | 125 | std::array pred_; 126 | std::array pred_way_; 127 | std::bitset pred_dir_; 128 | std::array cost_; 129 | }; 130 | 131 | struct hash { 132 | using is_avalanching = void; 133 | auto operator()(key const n) const noexcept -> std::uint64_t { 134 | using namespace ankerl::unordered_dense::detail; 135 | return wyhash::hash(static_cast(to_idx(n))); 136 | } 137 | }; 138 | 139 | template 140 | static void resolve_start_node(ways::routing const& w, 141 | way_idx_t const way, 142 | node_idx_t const n, 143 | level_t, 144 | direction, 145 | Fn&& f) { 146 | auto const ways = w.node_ways_[n]; 147 | for (auto i = way_pos_t{0U}; i != ways.size(); ++i) { 148 | if (ways[i] == way) { 149 | f(node{n, i, direction::kForward}); 150 | f(node{n, i, direction::kBackward}); 151 | } 152 | } 153 | } 154 | 155 | template 156 | static void resolve_all(ways::routing const& w, 157 | node_idx_t const n, 158 | level_t, 159 | Fn&& f) { 160 | auto const ways = w.node_ways_[n]; 161 | for (auto i = way_pos_t{0U}; i != ways.size(); ++i) { 162 | f(node{n, i, direction::kForward}); 163 | f(node{n, i, direction::kBackward}); 164 | } 165 | } 166 | 167 | template 168 | static void adjacent(parameters const& params, 169 | ways::routing const& w, 170 | node const n, 171 | bitvec const* blocked, 172 | sharing_data const*, 173 | elevation_storage const*, 174 | Fn&& fn) { 175 | auto way_pos = way_pos_t{0U}; 176 | for (auto const [way, i] : 177 | utl::zip_unchecked(w.node_ways_[n.n_], w.node_in_way_idx_[n.n_])) { 178 | auto const expand = [&](direction const way_dir, std::uint16_t const from, 179 | std::uint16_t const to) { 180 | // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) 181 | auto const target_node = w.way_nodes_[way][to]; 182 | if constexpr (WithBlocked) { 183 | if (blocked->test(target_node)) { 184 | return; 185 | } 186 | } 187 | 188 | auto const target_node_prop = w.node_properties_[target_node]; 189 | if (node_cost(target_node_prop) == kInfeasible) { 190 | return; 191 | } 192 | 193 | auto const target_way_prop = w.way_properties_[way]; 194 | if (way_cost(params, target_way_prop, way_dir, 0U) == kInfeasible) { 195 | return; 196 | } 197 | 198 | if (w.is_restricted(n.n_, n.way_, way_pos)) { 199 | return; 200 | } 201 | 202 | auto const is_u_turn = way_pos == n.way_ && way_dir == opposite(n.dir_); 203 | auto const dist = w.way_node_dist_[way][std::min(from, to)]; 204 | auto const target = 205 | node{target_node, w.get_way_pos(target_node, way, to), way_dir}; 206 | auto const cost = way_cost(params, target_way_prop, way_dir, dist) + 207 | node_cost(target_node_prop) + 208 | (is_u_turn ? kUturnPenalty : 0U); 209 | fn(target, cost, dist, way, from, to, elevation_storage::elevation{}, 210 | false); 211 | }; 212 | 213 | if (i != 0U) { 214 | expand(flip(direction::kBackward), i, i - 1); 215 | } 216 | if (i != w.way_nodes_[way].size() - 1U) { 217 | expand(flip(direction::kForward), i, i + 1); 218 | } 219 | 220 | ++way_pos; 221 | } 222 | } 223 | 224 | static bool is_dest_reachable(parameters const& params, 225 | ways::routing const& w, 226 | node const n, 227 | way_idx_t const way, 228 | direction const way_dir, 229 | direction const search_dir) { 230 | auto const target_way_prop = w.way_properties_[way]; 231 | if (way_cost(params, target_way_prop, way_dir, 0U) == kInfeasible) { 232 | return false; 233 | } 234 | 235 | if (w.is_restricted(n.n_, n.way_, w.get_way_pos(n.n_, way), search_dir)) { 236 | return false; 237 | } 238 | 239 | return true; 240 | } 241 | 242 | static constexpr cost_t way_cost(parameters const&, 243 | way_properties const& e, 244 | direction const dir, 245 | std::uint16_t const dist) { 246 | if (e.is_car_accessible() && 247 | (dir == direction::kForward || !e.is_oneway_car())) { 248 | return (dist / e.max_speed_m_per_s()) * (e.is_destination() ? 5U : 1U) + 249 | (e.is_destination() ? 120U : 0U); 250 | } else { 251 | return kInfeasible; 252 | } 253 | } 254 | 255 | static constexpr cost_t node_cost(node_properties const& n) { 256 | return n.is_car_accessible() ? 0U : kInfeasible; 257 | } 258 | 259 | static constexpr double heuristic(parameters const&, double const dist) { 260 | return dist / (130U / 3.6); 261 | } 262 | static constexpr node get_reverse(node const n) { 263 | return {n.n_, n.way_, opposite(n.dir_)}; 264 | } 265 | }; 266 | 267 | } // namespace osr 268 | -------------------------------------------------------------------------------- /include/osr/routing/profiles/bike.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "osr/elevation_storage.h" 4 | #include "osr/routing/mode.h" 5 | #include "osr/routing/path.h" 6 | #include "osr/types.h" 7 | #include "osr/ways.h" 8 | 9 | namespace osr { 10 | 11 | struct sharing_data; 12 | 13 | constexpr auto const kElevationNoCost = 0U; 14 | constexpr auto const kElevationLowCost = 570U; 15 | constexpr auto const kElevationHighCost = 3700U; 16 | 17 | // Routing const configuration (cost, exp) 18 | // cost: 19 | // Higher costs will favor flatter routes, even if these take way longer. 20 | // exp: 21 | // Increase cost to more penalize ways with higher incline 22 | // Examples: 23 | // (250, 1000) // Low costs, penalize total elevation 24 | // (800, 1000) // Higher costs, penalize total elevation 25 | // (570, 2100) // Low costs, penalize ways with higher incline 26 | // (3700, 2100) // Higher costs, penalize ways with higher incline 27 | 28 | enum class bike_costing { kSafe, kFast }; 29 | 30 | template 33 | struct bike { 34 | static constexpr auto const kMaxMatchDistance = 100U; 35 | 36 | struct parameters { 37 | using profile_t = 38 | bike; 39 | float const speed_meters_per_second_{4.2F}; 40 | }; 41 | 42 | struct node { 43 | friend bool operator==(node, node) = default; 44 | 45 | static constexpr node invalid() noexcept { 46 | return {.n_ = node_idx_t::invalid(), .dir_ = direction::kForward}; 47 | } 48 | 49 | constexpr node_idx_t get_node() const noexcept { return n_; } 50 | constexpr node_idx_t get_key() const noexcept { return n_; } 51 | 52 | static constexpr mode get_mode() noexcept { return mode::kBike; } 53 | 54 | std::ostream& print(std::ostream& out, ways const& w) const { 55 | return out << "(node=" << w.node_to_osm_[n_] << ", dir=" << to_str(dir_) 56 | << ")"; 57 | } 58 | 59 | node_idx_t n_; 60 | direction dir_; 61 | }; 62 | 63 | using key = node_idx_t; 64 | 65 | struct label { 66 | label(node const n, cost_t const c) : n_{n.n_}, dir_{n.dir_}, cost_{c} {} 67 | 68 | constexpr node get_node() const noexcept { return {n_, dir_}; } 69 | constexpr cost_t cost() const noexcept { return cost_; } 70 | 71 | void track( 72 | label const&, ways::routing const&, way_idx_t, node_idx_t, bool) {} 73 | 74 | node_idx_t n_; 75 | direction dir_; 76 | cost_t cost_; 77 | }; 78 | 79 | struct hash { 80 | using is_avalanching = void; 81 | auto operator()(key const n) const noexcept -> std::uint64_t { 82 | using namespace ankerl::unordered_dense::detail; 83 | return wyhash::hash(static_cast(to_idx(n))); 84 | } 85 | }; 86 | 87 | struct entry { 88 | entry() { utl::fill(cost_, kInfeasible); } 89 | 90 | constexpr std::optional pred(node const n) const noexcept { 91 | auto const idx = get_index(n); 92 | return pred_[idx] == node_idx_t::invalid() 93 | ? std::nullopt 94 | : std::optional{node{pred_[idx], pred_dir_[idx]}}; 95 | } 96 | 97 | constexpr cost_t cost(node const n) const noexcept { 98 | return cost_[get_index(n)]; 99 | } 100 | 101 | constexpr bool update(label const&, 102 | node const n, 103 | cost_t const c, 104 | node const pred) noexcept { 105 | auto const idx = get_index(n); 106 | if (c < cost_[idx]) { 107 | cost_[idx] = c; 108 | pred_[idx] = pred.n_; 109 | pred_dir_[idx] = pred.dir_; 110 | return true; 111 | } 112 | return false; 113 | } 114 | 115 | static constexpr std::size_t get_index(node const n) { 116 | return n.dir_ == direction::kForward ? 0U : 1U; 117 | } 118 | 119 | static constexpr node get_node(node_idx_t const n, 120 | std::size_t const index) { 121 | return node{n, index == 0U ? direction::kForward : direction::kBackward}; 122 | } 123 | 124 | void write(node, path&) const {} 125 | 126 | std::array pred_; 127 | std::array pred_dir_; 128 | std::array cost_; 129 | }; 130 | 131 | template 132 | static void resolve_start_node(ways::routing const&, 133 | way_idx_t, 134 | node_idx_t const n, 135 | level_t, 136 | direction, 137 | Fn&& f) { 138 | f(node{n, direction::kForward}); 139 | f(node{n, direction::kBackward}); 140 | } 141 | 142 | template 143 | static void resolve_all(ways::routing const&, 144 | node_idx_t const n, 145 | level_t, 146 | Fn&& f) { 147 | f(node{n, direction::kForward}); 148 | f(node{n, direction::kBackward}); 149 | } 150 | 151 | static bool is_dest_reachable(parameters const& params, 152 | ways::routing const& w, 153 | node, 154 | way_idx_t const way, 155 | direction const way_dir, 156 | direction) { 157 | return way_cost(params, w.way_properties_[way], way_dir, 0U) != kInfeasible; 158 | } 159 | 160 | template 161 | static void adjacent(parameters const& params, 162 | ways::routing const& w, 163 | node const n, 164 | bitvec const* blocked, 165 | sharing_data const*, 166 | elevation_storage const* elevations, 167 | Fn&& fn) { 168 | for (auto const [way, i] : 169 | utl::zip_unchecked(w.node_ways_[n.n_], w.node_in_way_idx_[n.n_])) { 170 | auto const expand = [&](direction const way_dir, std::uint16_t const from, 171 | std::uint16_t const to) { 172 | // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) 173 | auto const target_node = w.way_nodes_[way][to]; 174 | if constexpr (WithBlocked) { 175 | if (blocked->test(target_node)) { 176 | return; 177 | } 178 | } 179 | auto const target_node_prop = w.node_properties_[target_node]; 180 | if (node_cost(target_node_prop) == kInfeasible) { 181 | return; 182 | } 183 | 184 | auto const target_way_prop = w.way_properties_[way]; 185 | if (way_cost(params, target_way_prop, way_dir, 0U) == kInfeasible) { 186 | return; 187 | } 188 | 189 | auto const dist = w.way_node_dist_[way][std::min(from, to)]; 190 | auto const elevation = [&]() { 191 | auto const e = (from < to) ? get_elevations(elevations, way, from) 192 | : get_elevations(elevations, way, to); 193 | auto const in_direction = 194 | (SearchDir == direction::kForward) == (from < to); 195 | return in_direction ? e : e.swapped(); 196 | }(); 197 | auto const elevation_cost = static_cast( 198 | ElevationUpCost > 0U && dist > 0U 199 | ? (ElevationExponentThousandth > 1000U 200 | ? ElevationUpCost * 201 | std::pow( 202 | static_cast(to_idx(elevation.up_)) / 203 | dist, 204 | ElevationExponentThousandth / 1000.0) 205 | : ElevationUpCost * to_idx(elevation.up_) / dist) 206 | : 0); 207 | auto const cost = way_cost(params, target_way_prop, way_dir, dist) + 208 | node_cost(target_node_prop) + elevation_cost; 209 | fn(node{target_node, way_dir}, static_cast(cost), dist, 210 | way, from, to, elevation, false); 211 | }; 212 | 213 | if (i != 0U) { 214 | expand(flip(direction::kBackward), i, i - 1); 215 | } 216 | if (i != w.way_nodes_[way].size() - 1U) { 217 | expand(flip(direction::kForward), i, i + 1); 218 | } 219 | } 220 | } 221 | 222 | static constexpr cost_t way_cost(parameters const& params, 223 | way_properties const e, 224 | direction const dir, 225 | std::uint16_t const dist) { 226 | if (e.is_bike_accessible() && 227 | (dir == direction::kForward || !e.is_oneway_bike())) { 228 | return static_cast( 229 | std::round(dist / (params.speed_meters_per_second_ + 230 | (Costing == bike_costing::kFast 231 | ? 0 232 | : (e.is_big_street_ ? -0.7 : 0) + 233 | (e.motor_vehicle_no_ ? 0.5 : 0.0))))); 234 | } else { 235 | return kInfeasible; 236 | } 237 | } 238 | 239 | static constexpr cost_t node_cost(node_properties const n) { 240 | return n.is_bike_accessible() ? 0U : kInfeasible; 241 | } 242 | 243 | static constexpr double heuristic(parameters const& params, 244 | double const dist) { 245 | return dist / (params.speed_meters_per_second_ + 0.5); 246 | } 247 | 248 | static constexpr node get_reverse(node const n) { 249 | return {n, opposite(n.dir_)}; 250 | } 251 | }; 252 | 253 | } // namespace osr 254 | -------------------------------------------------------------------------------- /include/osr/geojson.h: -------------------------------------------------------------------------------- 1 | #include "boost/json.hpp" 2 | 3 | #include "fmt/ranges.h" 4 | #include "fmt/std.h" 5 | 6 | #include "utl/pairwise.h" 7 | #include "utl/pipes.h" 8 | 9 | #include "osr/platforms.h" 10 | #include "osr/routing/dijkstra.h" 11 | #include "osr/routing/profiles/foot.h" 12 | #include "osr/ways.h" 13 | 14 | namespace osr { 15 | 16 | inline boost::json::array to_array(point const p) { return {p.lng(), p.lat()}; } 17 | 18 | inline boost::json::array to_array(geo::latlng const& p) { 19 | return {p.lng(), p.lat()}; 20 | } 21 | 22 | template 23 | boost::json::value to_line_string(Collection const& line) { 24 | auto x = boost::json::array{}; 25 | for (auto const& p : line) { 26 | x.emplace_back(boost::json::array{p.lng(), p.lat()}); 27 | } 28 | return {{"type", "LineString"}, {"coordinates", x}}; 29 | } 30 | 31 | template 32 | boost::json::value to_line_string(std::initializer_list&& line) { 33 | return to_line_string(line); 34 | } 35 | 36 | inline std::string to_featurecollection(ways const& w, 37 | std::optional const& p, 38 | bool const with_properties = true) { 39 | return boost::json::serialize(boost::json::object{ 40 | {"type", "FeatureCollection"}, 41 | {"metadata", with_properties ? boost::json::value{{"duration", p->cost_}, 42 | {"distance", p->dist_}} 43 | : boost::json::value{{}}}, 44 | {"features", 45 | utl::all(p->segments_) | utl::transform([&](const path::segment& s) { 46 | return boost::json::object{ 47 | {"type", "Feature"}, 48 | { 49 | "properties", 50 | {{"level", s.from_level_.to_float()}, 51 | {"osm_way_id", s.way_ == way_idx_t::invalid() 52 | ? 0U 53 | : to_idx(w.way_osm_idx_[s.way_])}, 54 | {"cost", s.cost_}, 55 | {"distance", s.dist_}}, 56 | }, 57 | {"geometry", to_line_string(s.polyline_)}}; 58 | }) | utl::emplace_back_to()}}); 59 | } 60 | 61 | inline boost::json::value to_point(point const p) { 62 | return {{"type", "Point"}, {"coordinates", to_array(p)}}; 63 | } 64 | 65 | inline std::string platform_names(platforms const& pl, platform_idx_t const i) { 66 | auto names = std::stringstream{}; 67 | for (auto j = 0U; j != pl.platform_names_[i].size(); ++j) { 68 | names << pl.platform_names_.at(i, j).view() << "\n"; 69 | } 70 | return names.str(); 71 | } 72 | 73 | struct geojson_writer { 74 | void write_platform(platform_idx_t const i) { 75 | for (auto const r : platforms_->platform_ref_[i]) { 76 | auto const geometry = std::visit( 77 | utl::overloaded{[&](node_idx_t x) { 78 | return to_point(platforms_->get_node_pos(x)); 79 | }, 80 | [&](way_idx_t x) { 81 | return to_line_string(w_.way_polylines_[x]); 82 | }}, 83 | to_ref(r)); 84 | features_.emplace_back(boost::json::value{ 85 | {"type", "Feature"}, 86 | {"properties", 87 | {{"type", is_way(r) ? "way" : "node"}, 88 | {"platform_idx", to_idx(i)}, 89 | {"level", platforms_->get_level(w_, i).to_float()}, 90 | {"names", platform_names(*platforms_, i)}}}, 91 | {"geometry", geometry}}); 92 | } 93 | } 94 | 95 | void write_way(way_idx_t const i) { 96 | auto const nodes = w_.r_->way_nodes_[i]; 97 | auto const way_nodes = utl::nwise<2>(nodes); 98 | auto const dists = w_.r_->way_node_dist_[i]; 99 | auto way_nodes_it = std::begin(way_nodes); 100 | auto dist_it = std::begin(dists); 101 | auto const p = w_.r_->way_properties_[i]; 102 | for (; dist_it != end(dists); ++way_nodes_it, ++dist_it) { 103 | auto const& [from, to] = *way_nodes_it; 104 | auto const dist = *dist_it; 105 | features_.emplace_back(boost::json::value{ 106 | {"type", "Feature"}, 107 | {"properties", 108 | {{"type", "edge"}, 109 | {"osm_way_id", to_idx(w_.way_osm_idx_[i])}, 110 | {"internal_id", to_idx(i)}, 111 | {"distance", dist}, 112 | {"car", p.is_car_accessible()}, 113 | {"bike", p.is_bike_accessible()}, 114 | {"foot", p.is_foot_accessible()}, 115 | {"is_big_street", p.is_big_street()}, 116 | {"is_destination", p.is_destination()}, 117 | {"oneway_car", p.is_oneway_car()}, 118 | {"oneway_bike", p.is_oneway_bike()}, 119 | {"max_speed", p.max_speed_km_per_h()}, 120 | {"from_level", p.from_level().to_float()}, 121 | {"to_level", p.to_level().to_float()}, 122 | {"is_elevator", p.is_elevator()}, 123 | {"sidewalk_separate", p.is_sidewalk_separate()}, 124 | {"is_steps", p.is_steps()}, 125 | {"is_parking", p.is_parking()}, 126 | {"is_ramp", p.is_ramp()}}}, 127 | {"geometry", to_line_string(std::initializer_list{ 128 | w_.get_node_pos(from), w_.get_node_pos(to)})}}); 129 | } 130 | 131 | features_.emplace_back( 132 | boost::json::value{{"type", "Feature"}, 133 | {"properties", 134 | {{"type", "geometry"}, 135 | {"osm_way_id", to_idx(w_.way_osm_idx_[i])}, 136 | {"internal_id", to_idx(i)}, 137 | {"car", p.is_car_accessible()}, 138 | {"bike", p.is_bike_accessible()}, 139 | {"foot", p.is_foot_accessible()}, 140 | {"is_destination", p.is_destination()}, 141 | {"is_big_street", p.is_big_street()}, 142 | {"oneway_car", p.is_oneway_car()}, 143 | {"oneway_bike", p.is_oneway_bike()}, 144 | {"max_speed", p.max_speed_km_per_h()}, 145 | {"from_level", p.from_level().to_float()}, 146 | {"to_level", p.to_level().to_float()}, 147 | {"is_elevator", p.is_elevator()}, 148 | {"sidewalk_separate", p.is_sidewalk_separate()}, 149 | {"is_steps", p.is_steps()}, 150 | {"is_parking", p.is_parking()}, 151 | {"is_ramp", p.is_ramp()}}}, 152 | {"geometry", to_line_string(w_.way_polylines_[i])}}); 153 | 154 | nodes_.insert(begin(nodes), end(nodes)); 155 | } 156 | 157 | template 158 | void finish(Dijkstra const* s) { 159 | for (auto const n : nodes_) { 160 | auto const p = w_.r_->node_properties_[n]; 161 | 162 | auto ss = std::stringstream{}; 163 | Dijkstra::profile_t::resolve_all(*w_.r_, n, kNoLevel, [&](auto const x) { 164 | auto const cost = s->get_cost(x); 165 | if (cost != kInfeasible) { 166 | ss << "{"; 167 | x.print(ss, w_); 168 | ss << ", " << cost << "}\n"; 169 | } 170 | }); 171 | 172 | auto levels = std::vector(); 173 | foot::for_each_elevator_level( 174 | *w_.r_, n, [&](level_t const l) { levels.push_back(l.to_float()); }); 175 | 176 | auto properties = boost::json::object{ 177 | {"osm_node_id", to_idx(w_.node_to_osm_[n])}, 178 | {"internal_id", to_idx(n)}, 179 | {"car", p.is_car_accessible()}, 180 | {"bike", p.is_bike_accessible()}, 181 | {"foot", p.is_walk_accessible()}, 182 | {"is_restricted", w_.r_->node_is_restricted_[n]}, 183 | {"is_entrance", p.is_entrance()}, 184 | {"is_elevator", p.is_elevator()}, 185 | {"is_parking", p.is_parking()}, 186 | {"multi_level", p.is_multi_level()}, 187 | {"levels", boost::json::array(levels.begin(), levels.end())}, 188 | {"ways", fmt::format("{}", w_.r_->node_ways_[n] | 189 | std::views::transform([&](auto&& w) { 190 | return w_.way_osm_idx_[w]; 191 | }))}, 192 | {"restrictions", 193 | fmt::format("{}", 194 | w_.r_->node_restrictions_[n] | 195 | std::views::transform([&](restriction const r) { 196 | return std::pair{ 197 | w_.way_osm_idx_[w_.r_->node_ways_[n][r.from_]], 198 | w_.way_osm_idx_[w_.r_->node_ways_[n][r.to_]]}; 199 | }))}, 200 | {"label", ss.str().empty() ? "unreachable" : ss.str()}}; 201 | features_.emplace_back(boost::json::value{ 202 | {"type", "Feature"}, 203 | {"properties", properties}, 204 | {"geometry", 205 | {{"type", "Point"}, 206 | {"coordinates", to_array(w_.get_node_pos(n))}}}}); 207 | } 208 | } 209 | 210 | std::string string() { 211 | return boost::json::serialize(boost::json::value{ 212 | {"type", "FeatureCollection"}, {"features", features_}}); 213 | } 214 | 215 | boost::json::value json() { 216 | return boost::json::value{{"type", "FeatureCollection"}, 217 | {"features", features_}}; 218 | } 219 | 220 | ways const& w_; 221 | platforms const* platforms_{nullptr}; 222 | boost::json::array features_{}; 223 | hash_set nodes_{}; 224 | }; 225 | 226 | } // namespace osr -------------------------------------------------------------------------------- /src/ways.cc: -------------------------------------------------------------------------------- 1 | #include "osr/ways.h" 2 | 3 | #include "utl/parallel_for.h" 4 | 5 | #include "cista/io.h" 6 | 7 | namespace osr { 8 | 9 | ways::ways(std::filesystem::path p, cista::mmap::protection const mode) 10 | : p_{std::move(p)}, 11 | mode_{mode}, 12 | r_{mode == cista::mmap::protection::READ 13 | ? routing::read(p_) 14 | : cista::wrapped{cista::raw::make_unique()}}, 15 | node_to_osm_{mm("node_to_osm.bin")}, 16 | way_osm_idx_{mm("way_osm_idx.bin")}, 17 | way_polylines_{mm_vec{mm("way_polylines_data.bin")}, 18 | mm_vec{mm("way_polylines_index.bin")}}, 19 | way_osm_nodes_{mm_vec{mm("way_osm_nodes_data.bin")}, 20 | mm_vec{mm("way_osm_nodes_index.bin")}}, 21 | strings_{mm_vec(mm("strings_data.bin")), 22 | mm_vec(mm("strings_idx.bin"))}, 23 | way_names_{mm("way_names.bin")}, 24 | way_has_conditional_access_no_{ 25 | mm_vec(mm("way_has_conditional_access_no"))}, 26 | way_conditional_access_no_{mm("way_conditional_access_no")} {} 27 | 28 | void ways::build_components() { 29 | auto q = hash_set{}; 30 | auto flood_fill = [&](way_idx_t const way_idx, component_idx_t const c) { 31 | assert(q.empty()); 32 | q.insert(way_idx); 33 | while (!q.empty()) { 34 | auto const next = *q.begin(); 35 | q.erase(q.begin()); 36 | for (auto const n : r_->way_nodes_[next]) { 37 | for (auto const w : r_->node_ways_[n]) { 38 | auto& wc = r_->way_component_[w]; 39 | if (wc == component_idx_t::invalid()) { 40 | wc = c; 41 | q.insert(w); 42 | } 43 | } 44 | } 45 | } 46 | }; 47 | 48 | auto pt = utl::get_active_progress_tracker_or_activate("osr"); 49 | pt->status("Build components").in_high(n_ways()).out_bounds(75, 90); 50 | 51 | auto next_component_idx = component_idx_t{0U}; 52 | r_->way_component_.resize(n_ways(), component_idx_t::invalid()); 53 | for (auto i = 0U; i != n_ways(); ++i) { 54 | auto const way_idx = way_idx_t{i}; 55 | auto& c = r_->way_component_[way_idx]; 56 | if (c != component_idx_t::invalid()) { 57 | continue; 58 | } 59 | c = next_component_idx++; 60 | flood_fill(way_idx, c); 61 | pt->increment(); 62 | } 63 | } 64 | 65 | void ways::add_restriction(std::vector& rs) { 66 | using it_t = std::vector::iterator; 67 | utl::sort(rs, [](auto&& a, auto&& b) { return a.via_ < b.via_; }); 68 | utl::equal_ranges_linear( 69 | begin(rs), end(rs), [](auto&& a, auto&& b) { return a.via_ == b.via_; }, 70 | [&](it_t const& lb, it_t const& ub) { 71 | auto const range = std::span{lb, ub}; 72 | r_->node_restrictions_.resize(to_idx(range.front().via_) + 1U); 73 | r_->node_is_restricted_.set(range.front().via_, true); 74 | 75 | for (auto const& x : range) { 76 | if (x.type_ == resolved_restriction::type::kNo) { 77 | r_->node_restrictions_[x.via_].push_back( 78 | restriction{r_->get_way_pos(x.via_, x.from_), 79 | r_->get_way_pos(x.via_, x.to_)}); 80 | } else /* kOnly */ { 81 | for (auto const [i, from] : 82 | utl::enumerate(r_->node_ways_[x.via_])) { 83 | for (auto const [j, to] : 84 | utl::enumerate(r_->node_ways_[x.via_])) { 85 | if (x.from_ == from && x.to_ != to) { 86 | r_->node_restrictions_[x.via_].push_back(restriction{ 87 | static_cast(i), static_cast(j)}); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | }); 94 | r_->node_restrictions_.resize(node_to_osm_.size()); 95 | } 96 | 97 | void ways::compute_big_street_neighbors() { 98 | struct state { 99 | hash_set done_; 100 | }; 101 | 102 | auto pt = utl::get_active_progress_tracker(); 103 | 104 | auto is_orig_big_street = std::vector(n_ways()); 105 | for (auto const [i, p] : utl::enumerate(r_->way_properties_)) { 106 | is_orig_big_street[i] = p.is_big_street(); 107 | } 108 | 109 | utl::parallel_for_run_threadlocal( 110 | n_ways(), [&](state& s, std::size_t const i) { 111 | auto const way = way_idx_t{i}; 112 | 113 | if (is_orig_big_street[to_idx(way)]) { 114 | pt->update_monotonic(i); 115 | return; 116 | } 117 | 118 | s.done_.clear(); 119 | 120 | auto const expand = [&](way_idx_t const x, bool const go_further, 121 | auto&& recurse) { 122 | for (auto const& n : r_->way_nodes_[x]) { 123 | for (auto const& w : r_->node_ways_[n]) { 124 | if (is_orig_big_street[to_idx(w)]) { 125 | r_->way_properties_[way].is_big_street_ = true; 126 | return true; 127 | } 128 | 129 | if (s.done_.emplace(w).second && go_further) { 130 | if (recurse(x, false, recurse)) { 131 | return true; 132 | } 133 | } 134 | } 135 | } 136 | return false; 137 | }; 138 | 139 | s.done_.emplace(way); 140 | expand(way, true, expand); 141 | pt->update_monotonic(i); 142 | }); 143 | } 144 | 145 | void ways::connect_ways() { 146 | auto pt = utl::get_active_progress_tracker_or_activate("osr"); 147 | 148 | { // Assign graph node ids to every node with >1 way. 149 | pt->status("Create graph nodes") 150 | .in_high(node_way_counter_.size()) 151 | .out_bounds(40, 50); 152 | 153 | auto node_idx = node_idx_t{0U}; 154 | node_way_counter_.multi_.for_each_set_bit([&](std::uint64_t const b_idx) { 155 | auto const i = osm_node_idx_t{b_idx}; 156 | node_to_osm_.push_back(i); 157 | ++node_idx; 158 | pt->update(b_idx); 159 | }); 160 | r_->node_is_restricted_.resize(to_idx(node_idx)); 161 | } 162 | 163 | // Build edges. 164 | { 165 | pt->status("Connect ways") 166 | .in_high(way_osm_nodes_.size()) 167 | .out_bounds(50, 75); 168 | auto node_ways = mm_paged_vecvec{ 169 | cista::paged>{ 170 | mm_vec32{mm("tmp_node_ways_data.bin")}}, 171 | mm_vec>{ 172 | mm("tmp_node_ways_index.bin")}}; 173 | auto node_in_way_idx = mm_paged_vecvec{ 174 | cista::paged>{ 175 | mm_vec32{mm("tmp_node_in_way_idx_data.bin")}}, 176 | mm_vec>{ 177 | mm("tmp_node_in_way_idx_index.bin")}}; 178 | node_ways.resize(node_to_osm_.size()); 179 | node_in_way_idx.resize(node_to_osm_.size()); 180 | for (auto const [osm_way_idx, osm_nodes, polyline] : 181 | utl::zip(way_osm_idx_, way_osm_nodes_, way_polylines_)) { 182 | auto pred_pos = std::make_optional(); 183 | auto from = node_idx_t::invalid(); 184 | auto distance = 0.0; 185 | auto i = std::uint16_t{0U}; 186 | auto way_idx = way_idx_t{r_->way_nodes_.size()}; 187 | auto dists = r_->way_node_dist_.add_back_sized(0U); 188 | auto nodes = r_->way_nodes_.add_back_sized(0U); 189 | for (auto const [osm_node_idx, pos] : utl::zip(osm_nodes, polyline)) { 190 | if (pred_pos.has_value()) { 191 | distance += geo::distance(pos, *pred_pos); 192 | } 193 | 194 | if (node_way_counter_.is_multi(to_idx(osm_node_idx))) { 195 | auto const to = get_node_idx(osm_node_idx); 196 | node_ways[to].push_back(way_idx); 197 | node_in_way_idx[to].push_back(i); 198 | nodes.push_back(to); 199 | 200 | if (from != node_idx_t::invalid()) { 201 | dists.push_back(static_cast(std::round(distance))); 202 | } 203 | 204 | distance = 0.0; 205 | from = to; 206 | 207 | if (i == std::numeric_limits::max()) { 208 | fmt::println("error: way with {} nodes", osm_way_idx); 209 | } 210 | 211 | ++i; 212 | } 213 | 214 | pred_pos = pos; 215 | } 216 | pt->increment(); 217 | } 218 | 219 | for (auto const x : node_ways) { 220 | r_->node_ways_.emplace_back(x); 221 | } 222 | for (auto const x : node_in_way_idx) { 223 | r_->node_in_way_idx_.emplace_back(x); 224 | } 225 | } 226 | 227 | auto e = std::error_code{}; 228 | std::filesystem::remove(p_ / "tmp_node_ways_data.bin", e); 229 | std::filesystem::remove(p_ / "tmp_node_ways_index.bin", e); 230 | std::filesystem::remove(p_ / "tmp_node_in_way_idx_data.bin", e); 231 | std::filesystem::remove(p_ / "tmp_node_in_way_idx_index.bin", e); 232 | } 233 | 234 | void ways::sync() { 235 | node_to_osm_.mmap_.sync(); 236 | way_osm_idx_.mmap_.sync(); 237 | way_polylines_.data_.mmap_.sync(); 238 | way_polylines_.bucket_starts_.mmap_.sync(); 239 | way_osm_nodes_.data_.mmap_.sync(); 240 | way_osm_nodes_.bucket_starts_.mmap_.sync(); 241 | strings_.data_.mmap_.sync(); 242 | strings_.bucket_starts_.mmap_.sync(); 243 | way_names_.mmap_.sync(); 244 | } 245 | 246 | std::optional ways::get_access_restriction( 247 | way_idx_t const way) const { 248 | if (!way_has_conditional_access_no_.test(way)) { 249 | return std::nullopt; 250 | } 251 | auto const it = std::lower_bound( 252 | begin(way_conditional_access_no_), end(way_conditional_access_no_), way, 253 | [](auto&& a, auto&& b) { return a.first < b; }); 254 | utl::verify( 255 | it != end(way_conditional_access_no_) && it->first == way, 256 | "access restriction for way with access restriction not found way={}", 257 | way_osm_idx_[way]); 258 | return strings_[it->second].view(); 259 | } 260 | 261 | cista::wrapped ways::routing::read( 262 | std::filesystem::path const& p) { 263 | return cista::read(p / "routing.bin"); 264 | } 265 | 266 | void ways::routing::write(std::filesystem::path const& p) const { 267 | return cista::write(p / "routing.bin", *this); 268 | } 269 | 270 | } // namespace osr -------------------------------------------------------------------------------- /test/dijkstra_astarbidir_test.cc: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include "windows.h" 3 | #endif 4 | 5 | #include "gtest/gtest.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "cista/mmap.h" 11 | 12 | #include "utl/parallel_for.h" 13 | 14 | #include "fmt/core.h" 15 | 16 | #include "osr/extract/extract.h" 17 | #include "osr/geojson.h" 18 | #include "osr/location.h" 19 | #include "osr/lookup.h" 20 | #include "osr/routing/bidirectional.h" 21 | #include "osr/routing/dijkstra.h" 22 | #include "osr/routing/profile.h" 23 | #include "osr/routing/profiles/car.h" 24 | #include "osr/routing/route.h" 25 | #include "osr/types.h" 26 | #include "osr/ways.h" 27 | 28 | namespace fs = std::filesystem; 29 | using namespace osr; 30 | 31 | constexpr auto const kUseMultithreading = true; 32 | constexpr auto const kPrintDebugGeojson = false; 33 | constexpr auto const kMaxMatchDistance = 100; 34 | constexpr auto const kMaxAllowedPathDifferenceRatio = 0.5; 35 | 36 | void load(std::string_view raw_data, std::string_view data_dir) { 37 | if (!fs::exists(data_dir)) { 38 | if (fs::exists(raw_data)) { 39 | auto const p = fs::path{data_dir}; 40 | auto ec = std::error_code{}; 41 | fs::remove_all(p, ec); 42 | fs::create_directories(p, ec); 43 | osr::extract(false, raw_data, data_dir, fs::path{}); 44 | } 45 | } 46 | } 47 | 48 | void run(ways const& w, 49 | lookup const& l, 50 | unsigned const n_samples, 51 | unsigned const max_cost, 52 | direction const dir) { 53 | 54 | auto const from_tos = [&]() { 55 | auto prng = std::mt19937{}; 56 | auto distr = 57 | std::uniform_int_distribution{0, w.n_nodes() - 1}; 58 | auto from_tos = std::vector>{}; 59 | for (auto i = 0U; i != n_samples; ++i) { 60 | from_tos.emplace_back(distr(prng), distr(prng)); 61 | } 62 | return from_tos; 63 | }(); 64 | 65 | auto n_congruent = std::atomic{0U}; 66 | auto n_empty_matches = std::atomic{0U}; 67 | auto reference_times = std::vector{}; 68 | auto experiment_times = std::vector{}; 69 | 70 | auto m = std::mutex{}; 71 | 72 | auto const single_run = [&](std::pair const from_to) { 73 | auto const from_node = from_to.first; 74 | auto const from_loc = location{w.get_node_pos(from_node)}; 75 | auto const to_node = from_to.second; 76 | auto const to_loc = location{w.get_node_pos(to_node)}; 77 | 78 | auto const node_pinned_matches = 79 | [&](location const& loc, node_idx_t const n, bool const reverse) { 80 | auto matches = l.match(car::parameters{}, loc, reverse, dir, 81 | kMaxMatchDistance, nullptr); 82 | std::erase_if(matches, [&](auto const& wc) { 83 | return wc.left_.node_ != n && wc.right_.node_ != n; 84 | }); 85 | if (matches.size() > 1) { 86 | // matches.resize(1); 87 | } 88 | return matches; 89 | }; 90 | auto const from_matches = node_pinned_matches(from_loc, from_node, false); 91 | auto const to_matches = node_pinned_matches(to_loc, to_node, true); 92 | if (from_matches.empty() || to_matches.empty()) { 93 | ++n_empty_matches; 94 | } 95 | 96 | auto const from_matches_span = 97 | std::span{begin(from_matches), end(from_matches)}; 98 | auto const to_matches_span = std::span{begin(to_matches), end(to_matches)}; 99 | 100 | auto const reference_start = std::chrono::steady_clock::now(); 101 | auto const reference = 102 | route(car::parameters{}, w, l, search_profile::kCar, from_loc, to_loc, 103 | from_matches_span, to_matches_span, max_cost, dir, nullptr, 104 | nullptr, nullptr, routing_algorithm::kDijkstra); 105 | auto const reference_time = 106 | std::chrono::steady_clock::now() - reference_start; 107 | 108 | auto const experiment_start = std::chrono::steady_clock::now(); 109 | auto const experiment = 110 | route(car::parameters{}, w, l, search_profile::kCar, from_loc, to_loc, 111 | from_matches_span, to_matches_span, max_cost, dir, nullptr, 112 | nullptr, nullptr, routing_algorithm::kAStarBi); 113 | auto const experiment_time = 114 | std::chrono::steady_clock::now() - experiment_start; 115 | 116 | if (reference.has_value() != experiment.has_value() || 117 | (reference && experiment && 118 | (reference->cost_ != experiment->cost_ /*|| 119 | std::abs(reference->dist_ - experiment->dist_) / reference->dist_ > 120 | kMaxAllowedPathDifferenceRatio*/))) { 121 | auto const print_result = [&](std::string_view name, auto const& p, 122 | auto const& t) { 123 | fmt::println( 124 | "{:10}: {:11} --> {:11} | {} | time: " 125 | "{}:{:0>3}:{:0>3} s", 126 | name, w.node_to_osm_[from_node], w.node_to_osm_[to_node], 127 | p ? fmt::format("cost: {:5} | dist: {:>10.2f}", p->cost_, p->dist_) 128 | : "no result", 129 | std::chrono::duration_cast(t).count(), 130 | std::chrono::duration_cast(t).count() % 131 | 1000, 132 | std::chrono::duration_cast(t).count() % 133 | 1000); 134 | if (p.has_value() && kPrintDebugGeojson) { 135 | fmt::println("{}\n", to_featurecollection(w, p)); 136 | } 137 | }; 138 | 139 | print_result("dijkstra", reference, reference_time); 140 | print_result("a* bidir", experiment, experiment_time); 141 | 142 | } else { 143 | ++n_congruent; 144 | } 145 | 146 | if (!from_matches.empty() && !to_matches.empty()) { 147 | auto const guard = std::lock_guard{m}; 148 | reference_times.emplace_back(reference_time); 149 | experiment_times.emplace_back(experiment_time); 150 | } 151 | }; 152 | 153 | if (kUseMultithreading) { 154 | utl::parallel_for(from_tos, single_run); 155 | } else { 156 | std::for_each(begin(from_tos), end(from_tos), single_run); 157 | } 158 | 159 | auto const non_empty_congruent = n_congruent - n_empty_matches; 160 | auto const non_empty_samples = n_samples - n_empty_matches; 161 | 162 | EXPECT_EQ(non_empty_samples, non_empty_congruent); 163 | 164 | fmt::println("congruent on non-empty: {}/{} ({:3.1f}%)", non_empty_congruent, 165 | non_empty_samples, 166 | (static_cast(non_empty_congruent) / 167 | static_cast(non_empty_samples)) * 168 | 100); 169 | if (non_empty_congruent == non_empty_samples) { 170 | fmt::println( 171 | "speedup on non-empty: {:.2f}", 172 | static_cast( 173 | std::reduce(begin(reference_times), end(reference_times)).count()) / 174 | static_cast( 175 | std::reduce(begin(experiment_times), end(experiment_times)) 176 | .count())); 177 | } 178 | } 179 | 180 | TEST(dijkstra_astarbidir, monaco_fwd) { 181 | auto const raw_data = "test/monaco.osm.pbf"; 182 | auto const data_dir = "test/monaco"; 183 | auto const num_samples = 10000U; 184 | auto const max_cost = 2 * 3600U; 185 | auto constexpr dir = direction::kForward; 186 | 187 | if (!fs::exists(raw_data) && !fs::exists(data_dir)) { 188 | GTEST_SKIP() << raw_data << " not found"; 189 | } 190 | 191 | load(raw_data, data_dir); 192 | auto const w = osr::ways{data_dir, cista::mmap::protection::READ}; 193 | auto const l = osr::lookup{w, data_dir, cista::mmap::protection::READ}; 194 | 195 | run(w, l, num_samples, max_cost, dir); 196 | } 197 | 198 | TEST(dijkstra_astarbidir, monaco_bwd) { 199 | auto const raw_data = "test/monaco.osm.pbf"; 200 | auto const data_dir = "test/monaco"; 201 | auto const num_samples = 10000U; 202 | auto const max_cost = 2 * 3600U; 203 | auto constexpr dir = direction::kBackward; 204 | 205 | if (!fs::exists(raw_data) && !fs::exists(data_dir)) { 206 | GTEST_SKIP() << raw_data << " not found"; 207 | } 208 | 209 | load(raw_data, data_dir); 210 | auto const w = osr::ways{data_dir, cista::mmap::protection::READ}; 211 | auto const l = osr::lookup{w, data_dir, cista::mmap::protection::READ}; 212 | 213 | run(w, l, num_samples, max_cost, dir); 214 | } 215 | 216 | TEST(dijkstra_astarbidir, hamburg) { 217 | auto const raw_data = "test/hamburg.osm.pbf"; 218 | auto const data_dir = "test/hamburg"; 219 | auto const num_samples = 5000U; 220 | auto const max_cost = 3 * 3600U; 221 | auto constexpr dir = direction::kForward; 222 | 223 | if (!fs::exists(raw_data) && !fs::exists(data_dir)) { 224 | GTEST_SKIP() << raw_data << " not found"; 225 | } 226 | 227 | load(raw_data, data_dir); 228 | auto const w = osr::ways{data_dir, cista::mmap::protection::READ}; 229 | auto const l = osr::lookup{w, data_dir, cista::mmap::protection::READ}; 230 | 231 | run(w, l, num_samples, max_cost, dir); 232 | } 233 | 234 | TEST(dijkstra_astarbidir, switzerland) { 235 | auto const raw_data = "test/switzerland.osm.pbf"; 236 | auto const data_dir = "test/switzerland"; 237 | auto const num_samples = 1000U; 238 | auto const max_cost = 5 * 3600U; 239 | auto constexpr dir = direction::kForward; 240 | 241 | if (!fs::exists(raw_data) && !fs::exists(data_dir)) { 242 | GTEST_SKIP() << raw_data << " not found"; 243 | } 244 | 245 | load(raw_data, data_dir); 246 | auto const w = osr::ways{data_dir, cista::mmap::protection::READ}; 247 | auto const l = osr::lookup{w, data_dir, cista::mmap::protection::READ}; 248 | 249 | run(w, l, num_samples, max_cost, dir); 250 | } 251 | 252 | TEST(dijkstra_astarbidir, DISABLED_germany) { 253 | auto const raw_data = "test/germany.osm.pbf"; 254 | auto const data_dir = "test/germany"; 255 | constexpr auto const num_samples = 50U; 256 | constexpr auto const max_cost = 12 * 3600U; 257 | auto constexpr dir = direction::kForward; 258 | 259 | if (!fs::exists(raw_data) && !fs::exists(data_dir)) { 260 | GTEST_SKIP() << raw_data << " not found"; 261 | } 262 | 263 | load(raw_data, data_dir); 264 | auto const w = osr::ways{data_dir, cista::mmap::protection::READ}; 265 | auto const l = osr::lookup{w, data_dir, cista::mmap::protection::READ}; 266 | 267 | run(w, l, num_samples, max_cost, dir); 268 | } 269 | -------------------------------------------------------------------------------- /src/preprocessing/elevation/dem_tile.cc: -------------------------------------------------------------------------------- 1 | #include "osr/preprocessing/elevation/dem_tile.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "boost/algorithm/string/case_conv.hpp" 13 | 14 | #include "cista/mmap.h" 15 | #include "cista/strong.h" 16 | 17 | #include "utl/verify.h" 18 | 19 | #include "osr/preprocessing/elevation/shared.h" 20 | 21 | // EHdr / BIL File Format: 22 | // http://www.gdal.org/frmt_various.html#EHdr 23 | // http://downloads.esri.com/support/whitepapers/other_/eximgav.pdf 24 | // http://desktop.arcgis.com/en/arcmap/10.3/manage-data/raster-and-images/bil-bip-and-bsq-raster-files.htm 25 | 26 | namespace fs = std::filesystem; 27 | 28 | namespace osr::preprocessing::elevation { 29 | 30 | constexpr auto const kNoData = std::int16_t{-32768}; 31 | 32 | using str_map = std::unordered_map; 33 | using dem_exception = std::runtime_error; 34 | 35 | str_map read_hdr_file(fs::path const& path) { 36 | str_map map; 37 | 38 | auto f = std::ifstream{path}; 39 | while (f.good()) { 40 | std::string key, value; 41 | f >> key >> value; 42 | if (f) { 43 | boost::to_upper(key); 44 | boost::to_upper(value); 45 | map[key] = value; 46 | } 47 | } 48 | 49 | return map; 50 | } 51 | 52 | std::string get_string(str_map const& map, 53 | std::string const& key, 54 | std::string const& def = "") { 55 | auto const r = map.find(key); 56 | if (r != end(map)) { 57 | auto const& val = r->second; 58 | return val.empty() ? def : val; 59 | } else { 60 | return def; 61 | } 62 | } 63 | 64 | int get_int(str_map const& map, std::string const& key, int def) { 65 | auto const str = get_string(map, key); 66 | return str.empty() ? def : std::stoi(str); 67 | } 68 | 69 | unsigned get_uint(str_map const& map, std::string const& key, unsigned def) { 70 | auto const str = get_string(map, key); 71 | return str.empty() ? def : static_cast(std::stoul(str)); 72 | } 73 | 74 | double get_double(str_map const& map, std::string const& key, double def) { 75 | auto const str = get_string(map, key); 76 | return str.empty() ? def : std::stod(str); 77 | } 78 | 79 | struct bil_header { 80 | explicit bil_header(fs::path const& bil_path) { 81 | auto const hdr_path = fs::path{bil_path.parent_path() / 82 | fs::path(bil_path.stem().string() + ".hdr")}; 83 | if (!fs::exists(hdr_path)) { 84 | throw dem_exception("Missing hdr file: " + hdr_path.string()); 85 | } 86 | auto const hdr = read_hdr_file(hdr_path); 87 | init_hdr(hdr); 88 | } 89 | 90 | void init_hdr(str_map const& hdr) { 91 | rows_ = get_uint(hdr, "NROWS", 0); 92 | cols_ = get_uint(hdr, "NCOLS", 0); 93 | if (rows_ == 0 || cols_ == 0) { 94 | throw dem_exception("Missing nrows/ncols"); 95 | } 96 | 97 | if (get_uint(hdr, "NBANDS", 1) != 1) { 98 | throw dem_exception("Unsupported nbands value"); 99 | } 100 | if (get_string(hdr, "BYTEORDER", "I") != "I") { 101 | throw dem_exception("Unsupported byte order"); 102 | } 103 | if (get_uint(hdr, "SKIPBYTES", 0) != 0) { 104 | throw dem_exception("Unsupported skipbytes"); 105 | } 106 | 107 | auto const nbits = get_uint(hdr, "NBITS", 8); 108 | auto const pixeltype = get_string(hdr, "PIXELTYPE", "UNSIGNEDINT"); 109 | if (nbits == 16 && pixeltype[0] == 'S') { 110 | pixel_type_ = pixel_type::int16; 111 | } else if (nbits == 32 && pixeltype[0] == 'F') { 112 | pixel_type_ = pixel_type::float32; 113 | } else { 114 | throw dem_exception("Unsupported pixeltype"); 115 | } 116 | pixel_size_ = nbits / 8; 117 | row_size_ = pixel_size_ * cols_; 118 | 119 | ulx_ = get_double(hdr, "ULXMAP", std::numeric_limits::min()); 120 | uly_ = get_double(hdr, "ULYMAP", std::numeric_limits::min()); 121 | if (std::equal_to<>()(ulx_, std::numeric_limits::min()) || 122 | std::equal_to<>()(uly_, std::numeric_limits::min())) { 123 | throw dem_exception("Missing ulxmap/ulymap"); 124 | } 125 | 126 | xdim_ = get_double(hdr, "XDIM", 0); 127 | ydim_ = get_double(hdr, "YDIM", 0); 128 | if (std::equal_to<>()(xdim_, 0.0) || std::equal_to<>()(ydim_, 0.0)) { 129 | throw dem_exception("Missing xdim/ydim"); 130 | } 131 | brx_ = ulx_ + cols_ * xdim_; 132 | bry_ = uly_ - rows_ * ydim_; 133 | 134 | switch (pixel_type_) { 135 | case pixel_type::int16: 136 | nodata_.int16_ = static_cast(get_int(hdr, "NODATA", 0)); 137 | break; 138 | case pixel_type::float32: 139 | nodata_.float32_ = static_cast(get_double(hdr, "NODATA", 0)); 140 | break; 141 | } 142 | 143 | auto const bandrowbytes = get_uint(hdr, "BANDROWBYTES", row_size_); 144 | auto const totalrowbytes = get_uint(hdr, "TOTALROWBYTES", row_size_); 145 | if (bandrowbytes != row_size_ || totalrowbytes != row_size_) { 146 | throw dem_exception("Unsupported bandrowbytes/totalrowbytes"); 147 | } 148 | } 149 | 150 | unsigned rows_{0}; 151 | unsigned cols_{0}; 152 | double ulx_{0}; // upper left lon 153 | double uly_{0}; // upper left lat 154 | double brx_{0}; // bottom right lon 155 | double bry_{0}; // bottom right lat 156 | double xdim_{0}; // x pixel dimension, degrees 157 | double ydim_{0}; // y pixel dimension, degrees 158 | unsigned pixel_size_{0}; // bytes per pixel 159 | unsigned row_size_{0}; // bytes per row 160 | pixel_type pixel_type_{pixel_type::int16}; 161 | pixel_value nodata_{}; 162 | }; 163 | 164 | fs::path get_bil_path(fs::path const& path) { 165 | auto const bil_path = 166 | fs::path{path.parent_path() / fs::path(path.stem().string() + ".bil")}; 167 | if (!fs::exists(bil_path)) { 168 | throw dem_exception("Missing bil file: " + bil_path.string()); 169 | } 170 | return bil_path; 171 | } 172 | 173 | struct dem_tile::impl { 174 | explicit impl(fs::path const& path) 175 | : data_file_{get_bil_path(path)}, 176 | hdr_{data_file_}, 177 | mapped_file_{cista::mmap{data_file_.string().data(), 178 | cista::mmap::protection::READ}} { 179 | auto const expected_size = hdr_.row_size_ * hdr_.rows_; 180 | utl::verify(mapped_file_.size() == expected_size, 181 | "BIL tile '{}' ({}x{}) has incorrect file size ({} != {})", 182 | data_file_.string(), hdr_.cols_, hdr_.rows_, 183 | mapped_file_.size(), expected_size); 184 | } 185 | 186 | pixel_value get(geo::latlng const& pos) const { 187 | if (!get_box().contains(pos)) { 188 | return hdr_.nodata_; 189 | } 190 | 191 | auto const pix_x = std::clamp( 192 | 0U, static_cast((pos.lng_ - hdr_.ulx_) / hdr_.xdim_), 193 | hdr_.cols_ - 1U); 194 | auto const pix_y = std::clamp( 195 | 0U, static_cast((hdr_.uly_ - pos.lat_) / hdr_.ydim_), 196 | hdr_.rows_ - 1U); 197 | auto const byte_pos = hdr_.row_size_ * pix_y + hdr_.pixel_size_ * pix_x; 198 | 199 | auto const* byte_ptr = mapped_file_.data() + byte_pos; 200 | 201 | pixel_value val{}; 202 | 203 | switch (hdr_.pixel_type_) { 204 | case pixel_type::int16: 205 | val.int16_ = *reinterpret_cast(byte_ptr); 206 | break; 207 | case pixel_type::float32: 208 | val.float32_ = *reinterpret_cast(byte_ptr); 209 | break; 210 | } 211 | 212 | return val; 213 | } 214 | 215 | geo::box get_box() const { 216 | return {{hdr_.bry_, hdr_.brx_}, {hdr_.uly_, hdr_.ulx_}}; 217 | } 218 | 219 | template 220 | std::array get_pixel(geo::latlng const& pos) { 221 | auto const pix_x = 222 | std::clamp(0U, static_cast((pos.lng_ - hdr_.ulx_) * PixelX), 223 | PixelX - 1U); 224 | auto const pix_y = 225 | std::clamp(0U, static_cast((hdr_.uly_ - pos.lat_) * PixelY), 226 | PixelY - 1U); 227 | return {pix_x, pix_y}; 228 | } 229 | 230 | fs::path data_file_; 231 | bil_header hdr_; 232 | cista::mmap mapped_file_; 233 | }; 234 | 235 | dem_tile::dem_tile(fs::path const& path) 236 | : impl_(std::make_unique(path)) {} 237 | 238 | dem_tile::dem_tile(dem_tile&& grid) noexcept : impl_(std::move(grid.impl_)) {} 239 | 240 | dem_tile::~dem_tile() = default; 241 | 242 | elevation_meters_t dem_tile::get(geo::latlng const& pos) const { 243 | auto const val = get_raw(pos); 244 | switch (impl_->hdr_.pixel_type_) { 245 | case pixel_type::int16: 246 | if (val.int16_ == impl_->hdr_.nodata_.int16_) { 247 | return elevation_meters_t::invalid(); 248 | } else { 249 | auto const value = val.int16_; 250 | return value != kNoData ? elevation_meters_t{value} 251 | : elevation_meters_t::invalid(); 252 | } 253 | case pixel_type::float32: 254 | if (std::equal_to<>()(val.float32_, impl_->hdr_.nodata_.float32_)) { 255 | return elevation_meters_t::invalid(); 256 | } else { 257 | auto const value = static_cast>( 258 | std::round(val.float32_)); 259 | return value != kNoData ? elevation_meters_t{value} 260 | : elevation_meters_t::invalid(); 261 | } 262 | } 263 | throw std::runtime_error{"dem_grid: invalid pixel type"}; 264 | } 265 | 266 | tile_idx_t dem_tile::tile_idx(geo::latlng const& pos) const { 267 | constexpr auto const kPixelSize = 1 << (tile_idx_t::kSubTileIdxSize / 2); 268 | if (impl_->get_box().contains(pos)) { 269 | auto const [pix_x, pix_y] = impl_->get_pixel(pos); 270 | return tile_idx_t::from_sub_tile(pix_x * kPixelSize + pix_y); 271 | } else { 272 | return tile_idx_t::from_sub_tile(0U); 273 | } 274 | } 275 | 276 | geo::box dem_tile::get_box() const { return impl_->get_box(); } 277 | 278 | pixel_value dem_tile::get_raw(geo::latlng const& pos) const { 279 | return impl_->get(pos); 280 | } 281 | 282 | pixel_type dem_tile::get_pixel_type() const { return impl_->hdr_.pixel_type_; } 283 | 284 | resolution dem_tile::max_resolution() const { 285 | return {.x_ = impl_->hdr_.xdim_, .y_ = impl_->hdr_.ydim_}; 286 | } 287 | 288 | } // namespace osr::preprocessing::elevation 289 | -------------------------------------------------------------------------------- /include/osr/ways.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(_WIN32) || defined(_WIN64) 4 | #include "windows.h" 5 | 6 | #include "Memoryapi.h" 7 | #define mlock(addr, size) VirtualLock((LPVOID)addr, (SIZE_T)size) 8 | #else 9 | #include 10 | #endif 11 | #include 12 | #include 13 | 14 | #include "fmt/ranges.h" 15 | #include "fmt/std.h" 16 | 17 | #include "osmium/osm/way.hpp" 18 | 19 | #include "cista/memory_holder.h" 20 | 21 | #include "utl/enumerate.h" 22 | #include "utl/equal_ranges_linear.h" 23 | #include "utl/helpers/algorithm.h" 24 | #include "utl/progress_tracker.h" 25 | #include "utl/timer.h" 26 | #include "utl/verify.h" 27 | #include "utl/zip.h" 28 | 29 | #include "osr/point.h" 30 | #include "osr/types.h" 31 | #include "osr/util/multi_counter.h" 32 | 33 | namespace osr { 34 | 35 | struct resolved_restriction { 36 | enum class type { kNo, kOnly } type_; 37 | way_idx_t from_, to_; 38 | node_idx_t via_; 39 | }; 40 | 41 | struct restriction { 42 | friend bool operator==(restriction, restriction) = default; 43 | way_pos_t from_, to_; 44 | }; 45 | 46 | struct way_properties { 47 | constexpr bool is_accessible() const { 48 | return is_car_accessible() || is_bike_accessible() || is_foot_accessible(); 49 | } 50 | constexpr bool is_car_accessible() const { return is_car_accessible_; } 51 | constexpr bool is_bike_accessible() const { return is_bike_accessible_; } 52 | constexpr bool is_foot_accessible() const { return is_foot_accessible_; } 53 | constexpr bool is_big_street() const { return is_big_street_; } 54 | constexpr bool is_destination() const { return is_destination_; } 55 | constexpr bool is_oneway_car() const { return is_oneway_car_; } 56 | constexpr bool is_oneway_bike() const { return is_oneway_bike_; } 57 | constexpr bool is_elevator() const { return is_elevator_; } 58 | constexpr bool is_steps() const { return is_steps_; } 59 | constexpr bool is_ramp() const { return is_ramp_; } 60 | constexpr bool is_parking() const { return is_parking_; } 61 | constexpr bool has_toll() const { return has_toll_; } 62 | constexpr bool is_sidewalk_separate() const { return is_sidewalk_separate_; } 63 | constexpr std::uint16_t max_speed_m_per_s() const { 64 | return to_meters_per_second(static_cast(speed_limit_)); 65 | } 66 | constexpr std::uint16_t max_speed_km_per_h() const { 67 | return to_kmh(static_cast(speed_limit_)); 68 | } 69 | constexpr level_t from_level() const { return level_t{from_level_}; } 70 | constexpr level_t to_level() const { return level_t{to_level_}; } 71 | 72 | template 73 | friend constexpr auto static_type_hash( 74 | way_properties const*, cista::hash_data h) noexcept { 75 | return h.combine(cista::hash("way_properties v1")); 76 | } 77 | 78 | template 79 | friend void serialize(Ctx&, way_properties const*, cista::offset_t) {} 80 | 81 | template 82 | friend void deserialize(Ctx const&, way_properties*) {} 83 | 84 | bool is_foot_accessible_ : 1; 85 | bool is_bike_accessible_ : 1; 86 | bool is_car_accessible_ : 1; 87 | bool is_destination_ : 1; 88 | bool is_oneway_car_ : 1; 89 | bool is_oneway_bike_ : 1; 90 | bool is_elevator_ : 1; 91 | bool is_steps_ : 1; 92 | 93 | std::uint8_t speed_limit_ : 3; 94 | 95 | std::uint8_t from_level_ : 5; 96 | std::uint8_t to_level_ : 5; 97 | 98 | std::uint8_t is_platform_ : 1; // only used during extract 99 | bool is_parking_ : 1; 100 | 101 | bool is_ramp_ : 1; 102 | bool is_sidewalk_separate_ : 1; 103 | bool motor_vehicle_no_ : 1; 104 | bool has_toll_ : 1; 105 | bool is_big_street_ : 1; 106 | }; 107 | 108 | static_assert(sizeof(way_properties) == 4); 109 | 110 | struct node_properties { 111 | constexpr bool is_car_accessible() const { return is_car_accessible_; } 112 | constexpr bool is_bike_accessible() const { return is_bike_accessible_; } 113 | constexpr bool is_walk_accessible() const { return is_foot_accessible_; } 114 | constexpr bool is_elevator() const { return is_elevator_; } 115 | constexpr bool is_multi_level() const { return is_multi_level_; } 116 | constexpr bool is_entrance() const { return is_entrance_; } 117 | constexpr bool is_parking() const { return is_parking_; } 118 | 119 | constexpr level_t from_level() const { return level_t{from_level_}; } 120 | constexpr level_t to_level() const { return level_t{to_level_}; } 121 | 122 | template 123 | friend constexpr auto static_type_hash( 124 | node_properties const*, cista::hash_data h) noexcept { 125 | return h.combine(cista::hash("node_properties v1")); 126 | } 127 | 128 | template 129 | friend void serialize(Ctx&, node_properties const*, cista::offset_t) {} 130 | 131 | template 132 | friend void deserialize(Ctx const&, node_properties*) {} 133 | 134 | std::uint8_t from_level_ : 5; 135 | 136 | bool is_foot_accessible_ : 1; 137 | bool is_bike_accessible_ : 1; 138 | bool is_car_accessible_ : 1; 139 | bool is_elevator_ : 1; 140 | bool is_entrance_ : 1; 141 | bool is_multi_level_ : 1; 142 | bool is_parking_ : 1; 143 | 144 | std::uint8_t to_level_ : 5; 145 | }; 146 | 147 | static_assert(sizeof(node_properties) == 3); 148 | 149 | struct ways { 150 | ways(std::filesystem::path, cista::mmap::protection); 151 | 152 | void add_restriction(std::vector&); 153 | void compute_big_street_neighbors(); 154 | void connect_ways(); 155 | void build_components(); 156 | 157 | std::optional find_way(osm_way_idx_t const i) { 158 | auto const it = std::lower_bound(begin(way_osm_idx_), end(way_osm_idx_), i); 159 | return it != end(way_osm_idx_) && *it == i 160 | ? std::optional{way_idx_t{ 161 | std::distance(begin(way_osm_idx_), it)}} 162 | : std::nullopt; 163 | } 164 | 165 | bool is_additional_node(osr::node_idx_t const n) const { 166 | return n != node_idx_t::invalid() && n >= n_nodes(); 167 | } 168 | 169 | std::optional find_node_idx(osm_node_idx_t const i) const { 170 | auto const it = std::lower_bound(begin(node_to_osm_), end(node_to_osm_), i, 171 | [](auto&& a, auto&& b) { return a < b; }); 172 | if (it == end(node_to_osm_) || *it != i) { 173 | return std::nullopt; 174 | } 175 | return {node_idx_t{static_cast( 176 | std::distance(begin(node_to_osm_), it))}}; 177 | } 178 | 179 | node_idx_t get_node_idx(osm_node_idx_t const i) const { 180 | auto const j = find_node_idx(i); 181 | utl::verify(j.has_value(), "osm node {} not found", i); 182 | return *j; 183 | } 184 | 185 | point get_node_pos(node_idx_t const i) const { 186 | return r_->node_positions_.at(i); 187 | } 188 | 189 | cista::mmap mm(char const* file) { 190 | return cista::mmap{(p_ / file).generic_string().c_str(), mode_}; 191 | } 192 | 193 | void sync(); 194 | 195 | way_idx_t::value_t n_ways() const { return way_osm_idx_.size(); } 196 | node_idx_t::value_t n_nodes() const { return node_to_osm_.size(); } 197 | 198 | std::optional get_access_restriction(way_idx_t) const; 199 | 200 | std::filesystem::path p_; 201 | cista::mmap::protection mode_; 202 | 203 | struct routing { 204 | static constexpr auto const kMode = 205 | cista::mode::WITH_INTEGRITY | cista::mode::WITH_STATIC_VERSION; 206 | 207 | way_pos_t get_way_pos(node_idx_t const node, way_idx_t const way) const { 208 | auto const ways = node_ways_[node]; 209 | for (auto i = way_pos_t{0U}; i != ways.size(); ++i) { 210 | if (ways[i] == way) { 211 | return i; 212 | } 213 | } 214 | return 0U; 215 | } 216 | 217 | way_pos_t get_way_pos(node_idx_t const node, 218 | way_idx_t const way, 219 | std::uint16_t const node_in_way_idx) const { 220 | auto const ways = node_ways_[node]; 221 | for (auto i = way_pos_t{0U}; i != ways.size(); ++i) { 222 | if (ways[i] == way && 223 | (i + 1U == ways.size() || ways[i] != ways[i + 1U] || 224 | node_in_way_idx_[node][i] == node_in_way_idx)) { 225 | return i; 226 | } 227 | } 228 | return 0U; 229 | } 230 | 231 | template 232 | bool is_restricted(node_idx_t const n, 233 | std::uint8_t const from, 234 | std::uint8_t const to) const { 235 | if (!node_is_restricted_[n]) { 236 | return false; 237 | } 238 | auto const r = node_restrictions_[n]; 239 | auto const needle = SearchDir == direction::kForward 240 | ? restriction{from, to} 241 | : restriction{to, from}; 242 | return utl::find(r, needle) != end(r); 243 | } 244 | 245 | bool is_restricted(node_idx_t const n, 246 | std::uint8_t const from, 247 | std::uint8_t const to, 248 | direction const search_dir) const { 249 | return search_dir == direction::kForward 250 | ? is_restricted(n, from, to) 251 | : is_restricted(n, from, to); 252 | } 253 | 254 | bool is_loop(way_idx_t const w) const { 255 | return way_nodes_[w].back() == way_nodes_[w].front(); 256 | } 257 | 258 | static cista::wrapped read(std::filesystem::path const&); 259 | void write(std::filesystem::path const&) const; 260 | 261 | vec_map node_properties_; 262 | vec_map way_properties_; 263 | 264 | vecvec way_nodes_; 265 | vecvec way_node_dist_; 266 | 267 | vecvec node_ways_; 268 | vecvec node_in_way_idx_; 269 | 270 | bitvec node_is_restricted_; 271 | vecvec node_restrictions_; 272 | 273 | vec_map node_positions_; 274 | 275 | vec> multi_level_elevators_; 276 | 277 | vec_map way_component_; 278 | }; 279 | 280 | cista::wrapped r_; 281 | 282 | mm_vec_map node_to_osm_; 283 | mm_vec_map way_osm_idx_; 284 | mm_vecvec way_polylines_; 285 | mm_vecvec way_osm_nodes_; 286 | mm_vecvec strings_; 287 | mm_vec_map way_names_; 288 | 289 | mm_bitvec way_has_conditional_access_no_; 290 | mm_vec> way_conditional_access_no_; 291 | 292 | multi_counter node_way_counter_; 293 | }; 294 | 295 | } // namespace osr -------------------------------------------------------------------------------- /test/routing_tests.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "osr/geojson.h" 3 | 4 | #include "osr/extract/extract.h" 5 | #include "osr/lookup.h" 6 | #include "osr/routing/profiles/foot.h" 7 | #include "osr/routing/route.h" 8 | #include "osr/ways.h" 9 | 10 | namespace fs = std::filesystem; 11 | 12 | std::string extract_and_route(std::string_view path, 13 | osr::location const& from, 14 | osr::location const& to) { 15 | auto const dir = fmt::format("/tmp/{}", path); 16 | auto ec = std::error_code{}; 17 | fs::remove_all(dir, ec); 18 | fs::create_directories(dir, ec); 19 | 20 | osr::extract(false, path, dir, {}); 21 | 22 | auto w = osr::ways{dir, cista::mmap::protection::READ}; 23 | auto l = osr::lookup{w, dir, cista::mmap::protection::READ}; 24 | 25 | auto const p = osr::route( 26 | osr::foot::parameters{}, w, l, 27 | osr::search_profile::kFoot, from, {to}, 900, osr::direction::kForward, 28 | 250.0, nullptr, nullptr, nullptr, osr::routing_algorithm::kDijkstra); 29 | utl::verify(p.has_value(), "{}: from={} to={} -> no route", path, 30 | fmt::streamed(from), fmt::streamed(to)); 31 | return osr::to_featurecollection(w, *p, false); 32 | } 33 | 34 | TEST(routing, island) { 35 | auto const from = osr::location{49.872715, 8.651534, osr::level_t{0.F}}; 36 | auto const to = osr::location{49.873023, 8.651523, osr::level_t{0.F}}; 37 | EXPECT_EQ( 38 | R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.651514431787469E0,4.987274386418564E1],[8.6515174E0,4.98727447E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1201551426,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[8.6515174E0,4.98727447E1],[8.6515563E0,4.98727556E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":22937760,"cost":12,"distance":12},"geometry":{"type":"LineString","coordinates":[[8.6515563E0,4.98727556E1],[8.6515406E0,4.98727706E1],[8.6514528E0,4.98728367E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":847710844,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[8.6514528E0,4.98728367E1],[8.6514754E0,4.98728959E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":23511479,"cost":10,"distance":10},"geometry":{"type":"LineString","coordinates":[[8.6514754E0,4.98728959E1],[8.651539E0,4.98729497E1],[8.6515596E0,4.98729618E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":8,"distance":9},"geometry":{"type":"LineString","coordinates":[[8.6515596E0,4.98729618E1],[8.651493334251755E0,4.987300334757231E1]]}}]})", 39 | extract_and_route("test/luisenplatz-darmstadt.osm.pbf", from, to)); 40 | } 41 | 42 | TEST(routing, ferry) { 43 | auto const from = osr::location{41.921472, 8.742216, osr::level_t{0.F}}; 44 | auto const to = osr::location{41.921436, 8.740166, osr::level_t{0.F}}; 45 | EXPECT_EQ( 46 | R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":84,"distance":101},"geometry":{"type":"LineString","coordinates":[[8.7415295E0,4.19221369E1],[8.7415321E0,4.19222147E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":147,"distance":177},"geometry":{"type":"LineString","coordinates":[[8.7415321E0,4.19222147E1],[8.7408991E0,4.19222267E1],[8.7400905E0,4.19218375E1]]}}]})", 47 | extract_and_route("test/ajaccio-ferry.osm.pbf", from, to)); 48 | } 49 | 50 | TEST(routing, corridor) { 51 | auto const from = 52 | osr::location{51.54663831994142, -0.05622849779558692, osr::level_t{0.F}}; 53 | auto const to = 54 | osr::location{51.547004658329, -0.05694437499428773, osr::level_t{0.F}}; 55 | EXPECT_EQ( 56 | R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":1,"distance":1},"geometry":{"type":"LineString","coordinates":[[-5.6227579350404025E-2,5.154663151628954E1],[-5.62413E-2,5.15466308E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1057782512,"cost":6,"distance":6},"geometry":{"type":"LineString","coordinates":[[-5.62413E-2,5.15466308E1],[-5.62459E-2,5.15466862E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103497,"cost":3,"distance":4},"geometry":{"type":"LineString","coordinates":[[-5.62459E-2,5.15466862E1],[-5.62474E-2,5.15467056E1],[-5.62483E-2,5.15467228E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103497,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[-5.62483E-2,5.15467228E1],[-5.62524E-2,5.15467815E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1079378708,"cost":5,"distance":6},"geometry":{"type":"LineString","coordinates":[[-5.62524E-2,5.15467815E1],[-5.62531E-2,5.15468061E1],[-5.62538E-2,5.15468316E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1079378709,"cost":3,"distance":4},"geometry":{"type":"LineString","coordinates":[[-5.62538E-2,5.15468316E1],[-5.62546E-2,5.15468484E1],[-5.62621E-2,5.15468663E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103498,"cost":3,"distance":4},"geometry":{"type":"LineString","coordinates":[[-5.62621E-2,5.15468663E1],[-5.62628E-2,5.15469052E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1063762150,"cost":1,"distance":1},"geometry":{"type":"LineString","coordinates":[[-5.62628E-2,5.15469052E1],[-5.6263E-2,5.15469163E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1063762149,"cost":2,"distance":3},"geometry":{"type":"LineString","coordinates":[[-5.6263E-2,5.15469163E1],[-5.62635E-2,5.1546946E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":881103496,"cost":7,"distance":9},"geometry":{"type":"LineString","coordinates":[[-5.62635E-2,5.1546946E1],[-5.62657E-2,5.15470109E1],[-5.62908E-2,5.15470107E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":41,"distance":49},"geometry":{"type":"LineString","coordinates":[[-5.62908E-2,5.15470107E1],[-5.62911E-2,5.1547031E1],[-5.66186E-2,5.15470287E1],[-5.69331E-2,5.15470277E1],[-5.694401949978242E-2,5.1547027764967275E1]]}}]})", 57 | extract_and_route("test/london-corridor.osm.pbf", from, to)); 58 | } 59 | 60 | TEST(routing, stop_area) { 61 | auto const from = 62 | osr::location{48.725296645530705, 2.2612587304760723, osr::level_t{0.F}}; 63 | auto const to = 64 | osr::location{48.725480463902784, 2.2588322597458728, osr::level_t{0.F}}; 65 | EXPECT_EQ( 66 | R"({"type":"FeatureCollection","metadata":{},"features":[{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":13,"distance":13},"geometry":{"type":"LineString","coordinates":[[2.261314500627132E0,4.8725266362834425E1],[2.2613838E0,4.87253219E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1290289280,"cost":6,"distance":6},"geometry":{"type":"LineString","coordinates":[[2.2613838E0,4.87253219E1],[2.2613206E0,4.87253586E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1290289280,"cost":5,"distance":5},"geometry":{"type":"LineString","coordinates":[[2.2613206E0,4.87253586E1],[2.2612721E0,4.87253867E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1290289280,"cost":3,"distance":3},"geometry":{"type":"LineString","coordinates":[[2.2612721E0,4.87253867E1],[2.2612448E0,4.87254022E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":892871341,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[2.2612448E0,4.87254022E1],[2.2611731E0,4.87254412E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1419149027,"cost":7,"distance":9},"geometry":{"type":"LineString","coordinates":[[2.2611731E0,4.87254412E1],[2.2610764E0,4.87254927E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1419149026,"cost":16,"distance":19},"geometry":{"type":"LineString","coordinates":[[2.2610764E0,4.87254927E1],[2.260814E0,4.87254962E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1359929257,"cost":20,"distance":24},"geometry":{"type":"LineString","coordinates":[[2.260814E0,4.87254962E1],[2.2605996E0,4.87253278E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1429030767,"cost":10,"distance":12},"geometry":{"type":"LineString","coordinates":[[2.2605996E0,4.87253278E1],[2.2604973E0,4.87252419E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1429030769,"cost":2,"distance":2},"geometry":{"type":"LineString","coordinates":[[2.2604973E0,4.87252419E1],[2.2604761E0,4.87252248E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1429030768,"cost":7,"distance":8},"geometry":{"type":"LineString","coordinates":[[2.2604761E0,4.87252248E1],[2.2604036E0,4.87251661E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1359929257,"cost":22,"distance":26},"geometry":{"type":"LineString","coordinates":[[2.2604036E0,4.87251661E1],[2.2602208E0,4.87250223E1],[2.2601787E0,4.87249893E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":25,"distance":30},"geometry":{"type":"LineString","coordinates":[[2.2601787E0,4.87249893E1],[2.2600672E0,4.87250507E1],[2.2600272E0,4.87250721E1],[2.2598576E0,4.87251608E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":11,"distance":13},"geometry":{"type":"LineString","coordinates":[[2.2598576E0,4.87251608E1],[2.2597231E0,4.87252337E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":4,"distance":5},"geometry":{"type":"LineString","coordinates":[[2.2597231E0,4.87252337E1],[2.2596673E0,4.87252638E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":93,"distance":112},"geometry":{"type":"LineString","coordinates":[[2.2596673E0,4.87252638E1],[2.2596292E0,4.8725284E1],[2.2584789E0,4.87258964E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":222040526,"cost":13,"distance":16},"geometry":{"type":"LineString","coordinates":[[2.2584789E0,4.87258964E1],[2.2583131E0,4.87259901E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642263621,"cost":8,"distance":10},"geometry":{"type":"LineString","coordinates":[[2.2583131E0,4.87259901E1],[2.2582647E0,4.87259491E1],[2.2582257E0,4.8725916E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642578765,"cost":2,"distance":3},"geometry":{"type":"LineString","coordinates":[[2.2582257E0,4.8725916E1],[2.2581937E0,4.87259329E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642263625,"cost":6,"distance":7},"geometry":{"type":"LineString","coordinates":[[2.2581937E0,4.87259329E1],[2.2581458E0,4.87258947E1],[2.2581625E0,4.87258856E1]]}},{"type":"Feature","properties":{"level":1E0,"osm_way_id":642578757,"cost":7,"distance":8},"geometry":{"type":"LineString","coordinates":[[2.2581625E0,4.87258856E1],[2.2582443E0,4.87258411E1]]}},{"type":"Feature","properties":{"level":5E-1,"osm_way_id":642263631,"cost":12,"distance":14},"geometry":{"type":"LineString","coordinates":[[2.2582443E0,4.87258411E1],[2.2581285E0,4.87257422E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1192127892,"cost":1,"distance":1},"geometry":{"type":"LineString","coordinates":[[2.2581285E0,4.87257422E1],[2.2581167E0,4.87257321E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":1192127892,"cost":2,"distance":2},"geometry":{"type":"LineString","coordinates":[[2.2581167E0,4.87257321E1],[2.2581393E0,4.87257206E1]]}},{"type":"Feature","properties":{"level":0E0,"osm_way_id":0,"cost":55,"distance":66},"geometry":{"type":"LineString","coordinates":[[2.2581393E0,4.87257206E1],[2.2582250818254357E0,4.872579309702946E1]]}}]})", 67 | extract_and_route("test/station-border.osm.pbf", from, to)); 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # Open Street Router 4 | 5 | This router is the most memory-efficient multi-profile routing for planet wide street routing (pedestrian, bike, car, etc.) on OpenStreetMap. The 6 | goal is to make it possible to import data on affordable low-end machines. This is mainly achieved by using compact data 7 | structures and [memory mapped](https://en.wikipedia.org/wiki/Memory-mapped_file) files. A planet import should not need 8 | more than 10GB of RAM. More RAM (and a fast SSD) will speed up the import. 9 | 10 | Directory with all created files after extract (only routing data has to be in-memory): 11 | 12 | ```bash 13 | # routing data 12.7G 14 | # recommended to lock to memory 15 | 22K multi_level_elevators.bin 16 | 1,2G node_in_way_idx_data.bin 17 | 1,1G node_in_way_idx_index.bin 18 | 542M node_properties.bin 19 | 34M node_restricted.bin 20 | 4,7M node_restrictions_data.bin 21 | 1,1G node_restrictions_index.bin 22 | 2,3G node_ways_data.bin 23 | 1,1G node_ways_index.bin 24 | 748M way_node_dist_data.bin 25 | 833M way_node_dist_index.bin 26 | 2,3G way_nodes_data.bin 27 | 833M way_nodes_index.bin 28 | 625M way_properties.bin 29 | 30 | # mapping to osm ids 31 | 2,2G node_to_osm.bin 32 | 1,7G way_osm_idx.bin 33 | 34 | # way osm nodes (only memory mapped) 35 | 19G way_osm_nodes_data.bin 36 | 1,7G way_osm_nodes_index.bin 37 | 38 | # way geometry (only memory mapped) 39 | 19G way_polylines_data.bin 40 | 1,7G way_polylines_index.bin 41 | ``` 42 | 43 | ## Multi-Level Indoor Routing 44 | 45 |

46 | 47 | ## Car Outdoor Routing 48 | 49 |

50 | 51 | ## Usage 52 | 53 | ```bash 54 | # --in | -i input file 55 | # --out | -o output directory (will be deleted + created) 56 | ./osr-extract -i planet-latest.osm.pbf -o osr-planet 57 | 58 | # --data | -d the output from osr-extract 59 | # --static | -s static HTML/JS/CSS assets to serve 60 | ./osr-backend -d osr-planet -s web 61 | ``` 62 | 63 | ## Data Model 64 | 65 |

66 | 67 | ### Multi Nodes 68 | 69 | The data model is not an explicit graph. Instead, the OpenStreetMap data (nodes and ways) is stored more or less as it 70 | is. For routing purposes (i.e. finding shortest paths), only nodes are relevant that are part of more than one way. 71 | Those nodes are given special IDs (`node_idx_t` in contrast to `osm_node_idx_t` which is the node index from 72 | OpenStreetMap). 73 | 74 | ### Lookup 75 | 76 | Since only a fraction of ways in OpenStreetMap are relevant for routing, we give the extracted ways internal 77 | indices (`way_idx_t` in contrast to `osm_way_idx_t` which is the way index from OpenStreetMap). To be able to map 78 | from `node_idx_t` to `osm_node_idx_t` and from `way_idx_t` to `osm_way_idx_t` and vice versa, we have a lookup table in 79 | both directions. 80 | 81 | ### Way to Node and Node to Way 82 | 83 | For routing, it's important to have a fast way to know which ways are reachable from which nodes and which nodes are 84 | reachable from which way. To achieve this, we store for each `way_idx_t` a list of `node_idx_t` and for 85 | each `node_idx_t` a list of `way_idx_t` coupled with the information which index this node has in the corresponding way. 86 | If a node is part of a way multiple times, this mapping will contain the node multiple times. The order from the source 87 | way in OpenStreetMap is maintained. 88 | 89 | ## Routing 90 | 91 | Dijkstra`s algorithm with a time-based cutoff is used currently for routing. 92 | 93 | ### Node Neighborhood 94 | 95 | For routing (here: Dijkstra's algorithm for now), it's necessary to know the neighborhood of a node. This can be looked 96 | up by iterating all `way_idx_t` this node is contained in (see above) and the corresponding index `i` this node has in 97 | the way. Neighbors are then node indices `i-1` and `i+1` in those ways. For the case that `i=0` or points to the last 98 | index, `i-1` or `i+1` do not exist. With the definition of this neighborhood, the textbook version of Dijkstra's 99 | algorithm can be applied. 100 | 101 | ### From Coordinate to Graph 102 | 103 | To be able to resolve a geo coordinate into nodes in the graph for routing, a geo index data like a quad tree or r-tree 104 | is required. As there are less `way_idx_t` than `node_idx_t`, we store the bounding boxes of all ways into the rtree. A 105 | lookup gives us all `way_idx_t` in the area. Sorting those ways by perpendicular line distance from the query coordinate 106 | to the way gives us the closes `way_idx_t` for start and destination. After that, the two closest routing nodes ("left" 107 | and "right") on that `way_idx_t` can be initialized. 108 | 109 | ### Routing Profiles 110 | 111 | All attributes of the OpenStreetMap ways that are relevant for routing are packed into a compact struct. Distances 112 | between routing nodes (i.e. `node_idx_t`) on a way are stored for fast access. 113 | The profile (see blow) function then computes the edge weights and feasibility of a way based on the stored OpenStreetMap attributes. 114 | To implement more finegrained control over path finding, it might be necessary to extract more attributes from OpenSteetMap. 115 | 116 | The current set of routing profiles can be found in the [`include/osr/routing/profiles`](https://github.com/motis-project/osr/tree/master/include/osr/routing/profiles) folder. Here's what a profile needs to provide in order to be usable by the shortest path algorithm: 117 | 118 | #### Types 119 | 120 | - `node` struct: a node defines what is considered a node for this routing profile. The most basic definition of a node would basically be just what's considered a node in the data model (`node_idx_t`, see above). However, for many profiles, this is not succifient. Let's take the foot routing profile as an example: for indoor routing, it should be possible to distinguish the same OSM node on different levels. Therefore, the level has to be part of the foot profile's node definition. Another example is the car profile: to be able to detect u-turns (just changing the direction but staying on the same way), the node has to define the current direction. In order to properly handle turn restrictions, also the last used way has to be part of the node definition. The member function `node::get_key()` returns the key (see `key`). 121 | - `label` struct: basically the same as the `node` struct but it should additionally carry the routing costs (currently tracked in seconds). The label has to provide `get_node()` and `cost()` member functions. 122 | - `key`: The shortest path algorithm (e.g. Dijkstra) tracks minimal costs to each node in a hash map. In theory, it would be sufficient to use `node` as hash map key as we need to only track one shortest path to each profile `node`. To allow for a more efficient storage, multiple nodes can share the same hash map key, therefore reducing the hash map size (which can speedup the routing significantly). Therefore, a profile can define a `key` which can be the same as `node` but doesn't have to be. The key doesn't need any member functions and can therefore just be a typedef to `node_idx_t` (in the most simple case). 123 | - `entry`: The entry is the hash map entry and is stored for each `key`. It has to store the costs and precessor. If `key` maps several `node`s to the same `entry`, then the `entry` has to store costs and predecessory for each of the `node`s. It has to provide the following member functions: 124 | - `std::optional pred(node)`: returns the predecessor for the node, or `std::nullopt` if this `node` has never been visited. 125 | - `cost_t cost(node)`: returns the shortest path costs to this `node` 126 | - `bool update(label, node, cost_t, node pred)`: updates the costs for this node if they are better than the previously stored costs and returns `true` if the costs where updated and `false` otherwise. 127 | 128 | #### Static functions 129 | 130 | - `resolve_start_node(ways::routing, way_idx_t, node_idx_t, level_t, direction, Fn&& f)`: resolves all nodes that belong to this particular (`way_idx_t`, `node_idx_t`, `level_t`, `direction`) combination. `Fn f` will be called with each `node`. It's the task of the profile to give the routing algorithm and entry point to its overlay graph. 131 | - `resolve_all(ways::routing, node_ix_t, level_t, Fn&& f)`: Same as `resolve_start_node`, just without the condition that `way_idx_t` has to match. 132 | - `adjacent(ways::routing, node, bitvec* blocked, sharing_data*, elevation_storage*, Fn&& f)`: Calls `Fn f` with each adjacent neighbor of the given `node`. This is used in the shortest path algorithm to expand a node and visit all its neighbors. This takes a runtime provided bit vector `blocked` into account where bit `i` indicates if `i` can be visited or not. This allows us to dynamically block nodes depending on the routing query. 133 | 134 | As we can see, each profile can define its own overlay graph on top of the data model. This gives us the flexibility to define a routing for anything we want from pedestrians or wheelchair users over cars, trucks, trains to ships without any additional memory overhead. Even combined profiles (e.g. walking, taking a bike, walking) can be implemented. Commonly, routing engines have to have a graph for each profile which makes it quite expensive (in terms of memory) to add a new profile on a global routing server. With our approach, a new profile doesn't come with extra costs. 135 | 136 | Profiles have to be defined at compile time. However, it would be possible to parameterize the cost calculation (or any other aspect, like restrictions, etc.) at runtime. 137 | 138 | 139 | ### Reconstruction 140 | 141 | Reconstruction works as in the textbook version of Dijkstra's algorithm except for the first and last part of the path 142 | which is not part of the routing "graph" (geo coordinate to first `node_idx_t` and last `node_idx_t` to the geo 143 | coordinate). 144 | 145 | ## Future Work 146 | 147 | This is only a first proof-of-concept. Many basic as well as advanced features can follow. 148 | 149 | Known Issues: 150 | 151 | - Routing performance can be improved 152 | - by using A* or bidirectional A* for one to one queries 153 | - explore preprocessing-based approaches: landmarks, arc flags, transit node routing, multi-level-dijkstra, etc. 154 | - If source and target are mapped to the same way, the path should not be forced to go through routing nodes 155 | - Consider the routing profile for initialization 156 | 157 | Basic: 158 | 159 | - Extract street names of ways for reconstruction. 160 | - Reconstruct description of way for navigation (including street names) 161 | - Turn restriction relations ([example](https://www.openstreetmap.org/relation/1654115), [example](https://www.openstreetmap.org/node/516914)) 162 | - Penalize u-turns 163 | 164 | Advanced: 165 | 166 | - Create a compact memory-mapped or serializable version of [rtree.c](https://github.com/tidwall/rtree.c) 167 | - Enable routing algorithm to have weights for nodes, not just edges (e.g. elevators, street crossings, etc.) 168 | - Incremental live update with OpenStreetMap change sets (should be doable as the data model is not far away from OSM) 169 | - Exclusion zones as part of the routing query (e.g. for e-scooter routing) 170 | - Time-dependent routing (consider traffic flow forecast and/or live traffic) 171 | - Add height profile information to edges (relevant e.g. for bike routing) and use it in routing profiles 172 | - Combined routing for sharing mobility (e.g. walk to e-scooter, ride e-scooter, walk to destination) 173 | - `vehicle=destination` ([example](https://www.openstreetmap.org/way/61914850)) 174 | - Start heading + destination heading 175 | - Make barriers / inaccessible nodes routing nodes (example: [way](https://www.openstreetmap.org/way/940718404), [node](https://www.openstreetmap.org/node/8712182900)) 176 | --------------------------------------------------------------------------------