From 494a8b68f882f8e47fd838b87068d20ec677c46e Mon Sep 17 00:00:00 2001 From: Daniel Thoma Date: Sun, 14 Jul 2024 14:21:04 +0200 Subject: [PATCH 01/19] add extraction of parking properties --- include/osr/extract/tags.h | 16 +++++++++++++--- include/osr/geojson.h | 5 ++++- include/osr/ways.h | 6 +++++- src/extract.cc | 26 +++++++++++++++++++++----- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/include/osr/extract/tags.h b/include/osr/extract/tags.h index 76bd352..eb1fe0f 100644 --- a/include/osr/extract/tags.h +++ b/include/osr/extract/tags.h @@ -17,7 +17,9 @@ struct tags { for (auto const& t : o.tags()) { switch (cista::hash(std::string_view{t.key()})) { using namespace std::string_view_literals; - case cista::hash("building"): [[fallthrough]]; + case cista::hash("parking"): is_parking_ = true; break; + case cista::hash("amenity"): is_parking_ |= (t.value() == "parking"sv || t.value() == "parking_entrance"sv); break; + case cista::hash("building"): is_parking_ |= t.value() == "parking"sv; landuse_ = true; break; case cista::hash("landuse"): landuse_ = true; break; case cista::hash("railway"): landuse_ |= t.value() == "station_area"sv; @@ -31,7 +33,7 @@ struct tags { break; case cista::hash("motor_vehicle"): motor_vehicle_ = t.value(); - is_destination_ |= motor_vehicle_ == "destination"; + is_destination_ |= motor_vehicle_ == "destination"sv; break; case cista::hash("foot"): foot_ = t.value(); break; case cista::hash("bicycle"): bicycle_ = t.value(); break; @@ -158,6 +160,9 @@ struct tags { // https://wiki.openstreetmap.org/wiki/Key:entrance bool is_entrance_{false}; + // https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dparking + bool is_parking_{false}; + // https://wiki.openstreetmap.org/wiki/Key:level level_bits_t level_bits_{0U}; }; @@ -203,6 +208,11 @@ struct foot_profile { if (t.is_elevator_) { return true; } + + if (t.is_parking_) { + return true; + } + switch (cista::hash(t.highway_)) { case cista::hash("primary"): case cista::hash("primary_link"): @@ -326,7 +336,7 @@ struct car_profile { if (auto mv = get_override(t.motor_vehicle_); mv != override::kNone) { return mv; - } else if (auto mc = get_override(t.motorcar_); mv != override::kNone) { + } else if (auto mc = get_override(t.motorcar_); mc != override::kNone) { return mc; } else { return t.vehicle_; diff --git a/include/osr/geojson.h b/include/osr/geojson.h index 924769b..68544e3 100644 --- a/include/osr/geojson.h +++ b/include/osr/geojson.h @@ -57,7 +57,8 @@ struct geojson_writer { {"level", to_float(level_t{p.from_level()})}, {"to_level", to_float(level_t{p.to_level()})}, {"is_elevator", p.is_elevator()}, - {"is_steps", p.is_steps()}}}, + {"is_steps", p.is_steps()}, + {"is_parking", p.is_parking_}}}, {"geometry", to_line_string(std::initializer_list{ w_.get_node_pos(from), w_.get_node_pos(to)})}}); } @@ -113,6 +114,7 @@ struct geojson_writer { {"is_restricted", w_.r_->node_is_restricted_[n]}, {"is_entrance", p.is_entrance()}, {"is_elevator", p.is_elevator()}, + {"is_parking", p.is_parking()}, {"multi_level", p.is_multi_level()}, {"levels", levels.str()}, {"ways", fmt::format("{}", w_.r_->node_ways_[n] | @@ -128,6 +130,7 @@ struct geojson_writer { w_.way_osm_idx_[w_.r_->node_ways_[n][r.to_]]}; }))}, {"label", ss.str().empty() ? "unreachable" : ss.str()}}; + features_.emplace_back(boost::json::value{ {"type", "Feature"}, {"properties", properties}, diff --git a/include/osr/ways.h b/include/osr/ways.h index 81a10e1..d9042d5 100644 --- a/include/osr/ways.h +++ b/include/osr/ways.h @@ -94,6 +94,8 @@ struct way_properties { std::uint8_t from_level_ : 5; std::uint8_t to_level_ : 5; + + bool is_parking_: 1; }; static_assert(sizeof(way_properties) == 3); @@ -105,6 +107,7 @@ struct node_properties { constexpr bool is_elevator() const { return is_elevator_; } constexpr bool is_multi_level() const { return is_multi_level_; } constexpr bool is_entrance() const { return is_entrance_; } + constexpr bool is_parking() const { return is_parking_; } constexpr level_t from_level() const { return level_t{from_level_}; } constexpr level_t to_level() const { return level_t{to_level_}; } @@ -129,11 +132,12 @@ struct node_properties { bool is_elevator_ : 1; bool is_entrance_ : 1; bool is_multi_level_ : 1; + bool is_parking_ : 1; std::uint8_t to_level_ : 5; }; -static_assert(sizeof(node_properties) == 2); +static_assert(sizeof(node_properties) == 3); struct ways { ways(std::filesystem::path p, cista::mmap::protection const mode) diff --git a/src/extract.cc b/src/extract.cc index 1892551..125d457 100644 --- a/src/extract.cc +++ b/src/extract.cc @@ -91,6 +91,7 @@ way_properties get_way_properties(tags const& t) { p.is_oneway_bike_ = t.oneway_ && !t.not_oneway_bike_; p.is_elevator_ = t.is_elevator_; p.is_steps_ = (t.highway_ == "steps"sv); + p.is_parking_ = t.is_parking_; p.speed_limit_ = get_speed_limit(t); p.from_level_ = to_idx(from); p.to_level_ = to_idx(to); @@ -110,6 +111,7 @@ std::pair get_node_properties( p.is_elevator_ = t.is_elevator_; p.is_entrance_ = t.is_entrance_; p.is_multi_level_ = is_multi; + p.is_parking_ = t.is_parking_; p.to_level_ = to_idx(to); return {p, t.level_bits_}; } @@ -134,16 +136,30 @@ struct way_handler : public osm::handler::Handler { elevator_nodes_.emplace(first_node, t.level_bits_); } - if (!t.is_elevator_ && // elevators tagged as building would be landuse - ((it == end(rel_ways_) && t.highway_.empty() && !t.is_platform_) || - (t.highway_.empty() && !t.is_platform_ && it != end(rel_ways_) && - t.landuse_))) { + if ( + !t.is_elevator_ && // elevators tagged as building would be landuse + !t.is_parking_ && + ( + ( + it == end(rel_ways_) && + t.highway_.empty() && + !t.is_platform_ + ) || + ( + t.highway_.empty() && + !t.is_platform_ && + it != end(rel_ways_) && + t.landuse_ + ) + ) + ) { return; } - auto const p = (t.is_platform_ || !t.highway_.empty()) + auto const p = (t.is_platform_ || t.is_parking_ || !t.highway_.empty()) ? get_way_properties(t) : it->second; + if (!p.is_accessible()) { return; } From 8ffa8bb64724bc29212f2af3d2968b9d78711ec9 Mon Sep 17 00:00:00 2001 From: Daniel Thoma Date: Sun, 14 Jul 2024 14:27:09 +0200 Subject: [PATCH 02/19] update debug ui with additional feature information --- exe/backend/src/http_server.cc | 82 ++- include/osr/routing/profiles/bike.h | 5 + include/osr/routing/profiles/car.h | 7 + include/osr/routing/profiles/foot.h | 5 + include/osr/routing/route.h | 30 + web/index.html | 873 ++++++++++++++++++++-------- 6 files changed, 736 insertions(+), 266 deletions(-) diff --git a/exe/backend/src/http_server.cc b/exe/backend/src/http_server.cc index e2b9934..c1f9b3f 100644 --- a/exe/backend/src/http_server.cc +++ b/exe/backend/src/http_server.cc @@ -117,48 +117,74 @@ struct http_server::impl { max_it == q.end() ? 3600 : max_it->value().as_int64()); auto p = std::optional{}; + switch (profile) { case search_profile::kFoot: - p = route(w_, l_, get_dijkstra>(), from, to, max, - direction); + handle_routing(req, cb, get_dijkstra>(), from, to, max, direction); break; case search_profile::kWheelchair: - p = route(w_, l_, get_dijkstra>(), from, to, max, direction); + handle_routing(req, cb, get_dijkstra>(), from, to, max, direction); break; case search_profile::kBike: - p = route(w_, l_, get_dijkstra(), from, to, max, direction); + handle_routing(req, cb, get_dijkstra(), from, to, max, direction); break; case search_profile::kCar: - p = route(w_, l_, get_dijkstra(), from, to, max, direction); + handle_routing(req, cb, get_dijkstra(), from, to, max, direction); break; default: throw utl::fail("not implemented"); } + } + + template + void handle_routing( + web_server::http_req_t const& req, + web_server::http_res_cb_t const& cb, + dijkstra const& d, + location from, + location to, + cost_t max, + direction dir + ) { + auto p = route(w_, l_, get_dijkstra(), from, to, max, dir); + + if (!p.has_value()) { + cb(json_response(req, "could not find a valid path", http::status::not_found)); + return; + } - auto const response = json::serialize( - p.has_value() - ? boost::json:: - value{{"type", "FeatureCollection"}, - {"metadata", - {{"duration", p->cost_}, {"distance", p->dist_}}}, - {"features", - utl::all(p->segments_) // - | - utl::transform([&](auto&& s) { - return json::value{ - {"type", "Feature"}, - {"properties", - {{"level", to_float(s.from_level_)}, - {"way", - s.way_ == way_idx_t::invalid() - ? 0U - : to_idx(w_.way_osm_idx_[s.way_])}}}, - {"geometry", to_line_string(s.polyline_)}}; - }) // - | utl::emplace_back_to()}} - : json::value{{"error", "no path found"}}); - return cb(json_response(req, response)); + auto to_feature = [&](const path::segment& s) { + return json::object{ + {"type", "Feature"}, + {"properties", + { + {"level", to_float(s.from_level_)}, + {"osm_way_id", s.way_ == way_idx_t::invalid() + ? 0U + : to_idx(w_.way_osm_idx_[s.way_])}, + {"cost", s.cost_}, + {"distance", s.dist_}, + {"from_node", s.from_node_properties_}, + {"to_node", s.to_node_properties_} + }, + }, + {"geometry", to_line_string(s.polyline_)} + }; + }; + + auto feature_collection_meta_data = json::value{{"duration", p->cost_}, {"distance", p->dist_}}; + auto features = utl::all(p->segments_) | utl::transform(to_feature) | utl::emplace_back_to(); + + + auto feature_collection = json::object{ + {"type", "FeatureCollection"}, + {"metadata", feature_collection_meta_data}, + {"features", features} + }; + + cb(json_response(req, json::serialize(feature_collection))); } + void handle_levels(web_server::http_req_t const& req, web_server::http_res_cb_t const& cb) { auto const query = boost::json::parse(req.body()).as_object(); diff --git a/include/osr/routing/profiles/bike.h b/include/osr/routing/profiles/bike.h index 1aa18f0..1e44e61 100644 --- a/include/osr/routing/profiles/bike.h +++ b/include/osr/routing/profiles/bike.h @@ -16,6 +16,11 @@ struct bike { } constexpr node_idx_t get_node() const noexcept { return n_; } + + boost::json::object custom_geojson_properties(ways const& w) const { + return boost::json::object{{"node_id", n_.v_}, {"type", "bike"}}; + } + constexpr node get_key() const noexcept { return *this; } std::ostream& print(std::ostream& out, ways const& w) const { diff --git a/include/osr/routing/profiles/car.h b/include/osr/routing/profiles/car.h index 82e554f..95fd7fd 100644 --- a/include/osr/routing/profiles/car.h +++ b/include/osr/routing/profiles/car.h @@ -6,6 +6,8 @@ #include "osr/ways.h" +#include + namespace osr { struct car { @@ -23,6 +25,11 @@ struct car { } constexpr node_idx_t get_node() const noexcept { return n_; } + + boost::json::object custom_geojson_properties(ways const& w) const { + return boost::json::object{{"node_id", n_.v_}, {"type", "car"}}; + } + constexpr node_idx_t get_key() const noexcept { return n_; } std::ostream& print(std::ostream& out, ways const& w) const { diff --git a/include/osr/routing/profiles/foot.h b/include/osr/routing/profiles/foot.h index 46ad239..7fb6eda 100644 --- a/include/osr/routing/profiles/foot.h +++ b/include/osr/routing/profiles/foot.h @@ -16,6 +16,11 @@ struct foot { return {.n_ = node_idx_t::invalid(), .lvl_{level_t::invalid()}}; } constexpr node_idx_t get_node() const noexcept { return n_; } + + boost::json::object custom_geojson_properties(ways const& w) const { + return boost::json::object{{"node_id", n_.v_}, {"type", "foot"}}; + } + constexpr node get_key() const noexcept { return *this; } std::ostream& print(std::ostream& out, ways const& w) const { diff --git a/include/osr/routing/route.h b/include/osr/routing/route.h index 131fcd2..c6f87c8 100644 --- a/include/osr/routing/route.h +++ b/include/osr/routing/route.h @@ -28,12 +28,17 @@ search_profile to_profile(std::string_view s); std::string_view to_str(search_profile const p); + struct path { struct segment { std::vector polyline_; level_t from_level_; level_t to_level_; way_idx_t way_; + cost_t cost_{kInfeasible}; + distance_t dist_{0}; + boost::json::object from_node_properties_{}; + boost::json::object to_node_properties_{}; }; cost_t cost_{kInfeasible}; @@ -117,21 +122,46 @@ double add_path(ways const& w, direction const dir) { auto const& [way, from_idx, to_idx, is_loop, distance] = find_connecting_way(w, from, to, expected_cost, dir); + auto j = 0U; auto active = false; auto& segment = path.emplace_back(); + segment.way_ = way; + + segment.dist_ = distance; + + // we can use the expected cost here, since it gets validated in find_connecting_way + // to be the same as the actual cost. + segment.cost_ = expected_cost; + + // when going backwards we have to swap the properties, since we will be traversing + // the way in the opposite direction. + if (dir == direction::kForward) { segment.from_level_ = r.way_properties_[way].from_level(); segment.to_level_ = r.way_properties_[way].to_level(); + segment.from_node_properties_ = from.custom_geojson_properties(w); + segment.to_node_properties_ = to.custom_geojson_properties(w); + } else { + segment.from_level_ = r.way_properties_[way].to_level(); + segment.to_level_ = r.way_properties_[way].from_level(); + + segment.from_node_properties_ = to.custom_geojson_properties(w); + segment.to_node_properties_ = from.custom_geojson_properties(w); + } + for (auto const [osm_idx, coord] : infinite(reverse(utl::zip(w.way_osm_nodes_[way], w.way_polylines_[way]), (from_idx > to_idx) ^ is_loop), is_loop)) { + utl::verify(j++ != 2 * w.way_polylines_[way].size() + 1U, "infinite loop"); + if (!active && w.node_to_osm_[r.way_nodes_[way][from_idx]] == osm_idx) { active = true; } + if (active) { if (w.node_to_osm_[r.way_nodes_[way][from_idx]] == osm_idx) { // Again "from" node, then it's shorter to start from here. diff --git a/web/index.html b/web/index.html index d8de3d8..dabab50 100644 --- a/web/index.html +++ b/web/index.html @@ -9,88 +9,102 @@ -G -
- - -
- Test -
+ +
+
+ + +
+ + + +
+
+
+
+
+ + + +
+