diff --git a/include/engine/api/trip_api.hpp b/include/engine/api/trip_api.hpp new file mode 100644 index 00000000000..dddd7475714 --- /dev/null +++ b/include/engine/api/trip_api.hpp @@ -0,0 +1,110 @@ +#ifndef ENGINE_API_TRIP_HPP +#define ENGINE_API_TRIP_HPP + +#include "engine/api/route_api.hpp" +#include "engine/api/trip_parameters.hpp" + +#include "engine/datafacade/datafacade_base.hpp" + +#include "engine/internal_route_result.hpp" + +#include "util/integer_range.hpp" + +namespace osrm +{ +namespace engine +{ +namespace api +{ + +class TripAPI final : public RouteAPI +{ + public: + TripAPI(const datafacade::BaseDataFacade &facade_, const TripParameters ¶meters_) + : RouteAPI(facade_, parameters_), parameters(parameters_) + { + } + + void MakeResponse(const std::vector> &sub_trips, + const std::vector &sub_routes, + const std::vector &phantoms, + util::json::Object &response) const + { + auto number_of_routes = sub_trips.size(); + util::json::Array routes; + routes.values.reserve(number_of_routes); + BOOST_ASSERT(sub_trips.size() == sub_routes.size()); + for (auto index : util::irange(0UL, sub_trips.size())) + { + auto route = MakeRoute(sub_routes[index].segment_end_coordinates, + sub_routes[index].unpacked_path_segments, + sub_routes[index].source_traversed_in_reverse, + sub_routes[index].target_traversed_in_reverse); + routes.values.push_back(std::move(route)); + } + response.values["waypoints"] = MakeWaypoints(sub_trips, phantoms); + response.values["routes"] = std::move(routes); + response.values["code"] = "ok"; + } + + protected: + // FIXME this logic is a little backwards. We should change the output format of the + // trip plugin routing algorithm to be easier to consume here. + util::json::Array MakeWaypoints(const std::vector> &sub_trips, + const std::vector &phantoms) const + { + util::json::Array waypoints; + waypoints.values.reserve(parameters.coordinates.size()); + + struct TripIndex + { + TripIndex() = default; + TripIndex(unsigned sub_trip_index_, unsigned point_index_) + : sub_trip_index(sub_trip_index_), point_index(point_index_) + { + } + + unsigned sub_trip_index = std::numeric_limits::max(); + unsigned point_index = std::numeric_limits::max(); + + bool NotUsed() + { + return sub_trip_index == std::numeric_limits::max() && + point_index == std::numeric_limits::max(); + } + }; + + std::vector input_idx_to_trip_idx(parameters.coordinates.size()); + for (auto sub_trip_index : util::irange(0u, static_cast(sub_trips.size()))) + { + for (auto point_index : + util::irange(0u, static_cast(sub_trips[sub_trip_index].size()))) + { + input_idx_to_trip_idx[sub_trips[sub_trip_index][point_index]] = + TripIndex{sub_trip_index, point_index}; + } + } + + for (auto input_index : util::irange(0UL, parameters.coordinates.size())) + { + auto trip_index = input_idx_to_trip_idx[input_index]; + BOOST_ASSERT(!trip_index.NotUsed()); + + auto waypoint = + BaseAPI::MakeWaypoint(parameters.coordinates[input_index], phantoms[input_index]); + waypoint.values["trips_index"] = trip_index.sub_trip_index; + waypoint.values["waypoint_index"] = trip_index.point_index; + waypoints.values.push_back(std::move(waypoint)); + } + + return waypoints; + } + + const TripParameters ¶meters; +}; + +} // ns api +} // ns engine +} // ns osrm + +#endif diff --git a/include/engine/api/trip_parameters.hpp b/include/engine/api/trip_parameters.hpp index 9f3dbe92956..408fbf7fd84 100644 --- a/include/engine/api/trip_parameters.hpp +++ b/include/engine/api/trip_parameters.hpp @@ -14,7 +14,7 @@ namespace api struct TripParameters : public RouteParameters { - bool IsValid() const; + //bool IsValid() const; Falls back to base class }; } diff --git a/include/engine/engine.hpp b/include/engine/engine.hpp index b6c1138bf43..374e5b5ded9 100644 --- a/include/engine/engine.hpp +++ b/include/engine/engine.hpp @@ -73,7 +73,7 @@ class Engine final std::unique_ptr route_plugin; std::unique_ptr table_plugin; std::unique_ptr nearest_plugin; - // std::unique_ptr trip_plugin; + std::unique_ptr trip_plugin; std::unique_ptr match_plugin; std::unique_ptr query_data_facade; diff --git a/include/engine/plugins/trip.hpp b/include/engine/plugins/trip.hpp index 35abce2cb14..76e1bc5cda4 100644 --- a/include/engine/plugins/trip.hpp +++ b/include/engine/plugins/trip.hpp @@ -3,16 +3,10 @@ #include "engine/plugins/plugin_base.hpp" -#include "engine/object_encoder.hpp" -#include "extractor/tarjan_scc.hpp" -#include "engine/trip/trip_nearest_neighbour.hpp" -#include "engine/trip/trip_farthest_insertion.hpp" -#include "engine/trip/trip_brute_force.hpp" -#include "engine/search_engine.hpp" -#include "util/matrix_graph_wrapper.hpp" // wrapper to use tarjan scc on dist table -#include "engine/api_response_generator.hpp" -#include "util/make_unique.hpp" -#include "util/dist_table_wrapper.hpp" // to access the dist table more easily +#include "engine/api/trip_parameters.hpp" +#include "engine/routing_algorithms/shortest_path.hpp" +#include "engine/routing_algorithms/many_to_many.hpp" + #include "osrm/json_container.hpp" #include @@ -32,335 +26,28 @@ namespace engine namespace plugins { -template class RoundTripPlugin final : public BasePlugin +class TripPlugin final : public BasePlugin { private: - std::string descriptor_string; - DataFacadeT *facade; - std::unique_ptr> search_engine_ptr; + SearchEngineData heaps; + routing_algorithms::ShortestPathRouting shortest_path; + routing_algorithms::ManyToManyRouting duration_table; int max_locations_trip; - public: - explicit RoundTripPlugin(DataFacadeT *facade, int max_locations_trip) - : descriptor_string("trip"), facade(facade), max_locations_trip(max_locations_trip) - { - search_engine_ptr = util::make_unique>(facade); - } - - const std::string GetDescriptor() const override final { return descriptor_string; } - - std::vector GetPhantomNodes(const RouteParameters &route_parameters) - { - const bool checksum_OK = (route_parameters.check_sum == facade->GetCheckSum()); - const auto &input_bearings = route_parameters.bearings; - - std::vector phantom_node_list; - phantom_node_list.reserve(route_parameters.coordinates.size()); - - // find phantom nodes for all input coords - for (const auto i : util::irange(0, route_parameters.coordinates.size())) - { - // if client hints are helpful, encode hints - if (checksum_OK && i < route_parameters.hints.size() && - !route_parameters.hints[i].empty()) - { - auto current_phantom_node = decodeBase64(route_parameters.hints[i]); - if (current_phantom_node.IsValid(facade->GetNumberOfNodes())) - { - phantom_node_list.push_back(std::move(current_phantom_node)); - continue; - } - } - const int bearing = input_bearings.size() > 0 ? input_bearings[i].first : 0; - const int range = input_bearings.size() > 0 - ? (input_bearings[i].second ? *input_bearings[i].second : 10) - : 180; - auto results = - facade->NearestPhantomNodes(route_parameters.coordinates[i], 1, bearing, range); - if (results.empty()) - { - break; - } - phantom_node_list.push_back(std::move(results.front().phantom_node)); - BOOST_ASSERT(phantom_node_list.back().IsValid(facade->GetNumberOfNodes())); - } - - return phantom_node_list; - } - - // Object to hold all strongly connected components (scc) of a graph - // to access all graphs with component ID i, get the iterators by: - // auto start = std::begin(scc_component.component) + scc_component.range[i]; - // auto end = std::begin(scc_component.component) + scc_component.range[i+1]; - struct SCC_Component - { - // in_component: all NodeIDs sorted by component ID - // in_range: index where a new component starts - // - // example: NodeID 0, 1, 2, 4, 5 are in component 0 - // NodeID 3, 6, 7, 8 are in component 1 - // => in_component = [0, 1, 2, 4, 5, 3, 6, 7, 8] - // => in_range = [0, 5] - SCC_Component(std::vector in_component_nodes, std::vector in_range) - : component(std::move(in_component_nodes)), range(std::move(in_range)) - { - BOOST_ASSERT_MSG(component.size() > 0, "there's no scc component"); - BOOST_ASSERT_MSG(*std::max_element(range.begin(), range.end()) == component.size(), - "scc component ranges are out of bound"); - BOOST_ASSERT_MSG(*std::min_element(range.begin(), range.end()) == 0, - "invalid scc component range"); - BOOST_ASSERT_MSG(std::is_sorted(std::begin(range), std::end(range)), - "invalid component ranges"); - }; - - std::size_t GetNumberOfComponents() const - { - BOOST_ASSERT_MSG(range.size() > 0, "there's no range"); - return range.size() - 1; - } - - const std::vector component; - std::vector range; - }; - - // takes the number of locations and its distance matrix, - // identifies and splits the graph in its strongly connected components (scc) - // and returns an SCC_Component - SCC_Component SplitUnaccessibleLocations(const std::size_t number_of_locations, - const util::DistTableWrapper &result_table) - { - - if (std::find(std::begin(result_table), std::end(result_table), INVALID_EDGE_WEIGHT) == - std::end(result_table)) - { - // whole graph is one scc - std::vector location_ids(number_of_locations); - std::iota(std::begin(location_ids), std::end(location_ids), 0); - std::vector range = {0, location_ids.size()}; - return SCC_Component(std::move(location_ids), std::move(range)); - } - - // Run TarjanSCC - auto wrapper = std::make_shared>( - result_table.GetTable(), number_of_locations); - auto scc = extractor::TarjanSCC>(wrapper); - scc.run(); - - const auto number_of_components = scc.get_number_of_components(); - - std::vector range_insertion; - std::vector range; - range_insertion.reserve(number_of_components); - range.reserve(number_of_components); - - std::vector components(number_of_locations, 0); - - std::size_t prefix = 0; - for (std::size_t j = 0; j < number_of_components; ++j) - { - range_insertion.push_back(prefix); - range.push_back(prefix); - prefix += scc.get_component_size(j); - } - // senitel - range.push_back(components.size()); - - for (std::size_t i = 0; i < number_of_locations; ++i) - { - components[range_insertion[scc.get_component_id(i)]] = i; - ++range_insertion[scc.get_component_id(i)]; - } - - return SCC_Component(std::move(components), std::move(range)); - } - - void SetLocPermutationOutput(const std::vector &permutation, - util::json::Object &json_result) - { - util::json::Array json_permutation; - json_permutation.values.insert(std::end(json_permutation.values), std::begin(permutation), - std::end(permutation)); - json_result.values["permutation"] = json_permutation; - } - InternalRouteResult ComputeRoute(const std::vector &phantom_node_list, - const RouteParameters &route_parameters, - const std::vector &trip) - { - InternalRouteResult min_route; - // given he final trip, compute total distance and return the route and location permutation - PhantomNodes viapoint; - const auto start = std::begin(trip); - const auto end = std::end(trip); - // computes a roundtrip from the nodes in trip - for (auto it = start; it != end; ++it) - { - const auto from_node = *it; - // if from_node is the last node, compute the route from the last to the first location - const auto to_node = std::next(it) != end ? *std::next(it) : *start; + const api::TripParameters ¶meters, + const std::vector &trip); - viapoint = PhantomNodes{phantom_node_list[from_node], phantom_node_list[to_node]}; - min_route.segment_end_coordinates.emplace_back(viapoint); - } - BOOST_ASSERT(min_route.segment_end_coordinates.size() == trip.size()); - - std::vector uturns(trip.size() + 1); - BOOST_ASSERT(route_parameters.uturns.size() > 0); - std::transform(trip.begin(), trip.end(), uturns.begin(), - [&route_parameters](const NodeID idx) - { - return route_parameters.uturns[idx]; - }); - BOOST_ASSERT(uturns.size() > 0); - uturns.back() = route_parameters.uturns[trip.front()]; - - search_engine_ptr->shortest_path(min_route.segment_end_coordinates, uturns, min_route); - - BOOST_ASSERT_MSG(min_route.shortest_path_length < INVALID_EDGE_WEIGHT, "unroutable route"); - return min_route; - } - - Status HandleRequest(const RouteParameters &route_parameters, - util::json::Object &json_result) override final + public: + explicit TripPlugin(datafacade::BaseDataFacade &facade_, const int max_locations_trip_) + : BasePlugin(facade_), shortest_path(&facade_, heaps), duration_table(&facade_, heaps), + max_locations_trip(max_locations_trip_) { - if (max_locations_trip > 0 && - (static_cast(route_parameters.coordinates.size()) > max_locations_trip)) - { - json_result.values["status_message"] = - "Number of entries " + std::to_string(route_parameters.coordinates.size()) + - " is higher than current maximum (" + std::to_string(max_locations_trip) + ")"; - return Status::Error; - } - - // check if all inputs are coordinates - if (!check_all_coordinates(route_parameters.coordinates)) - { - json_result.values["status_message"] = "Invalid coordinates"; - return Status::Error; - } - - const auto &input_bearings = route_parameters.bearings; - if (input_bearings.size() > 0 && - route_parameters.coordinates.size() != input_bearings.size()) - { - json_result.values["status_message"] = - "Number of bearings does not match number of coordinates"; - return Status::Error; - } - - // get phantom nodes - auto phantom_node_list = GetPhantomNodes(route_parameters); - if (phantom_node_list.size() != route_parameters.coordinates.size()) - { - BOOST_ASSERT(phantom_node_list.size() < route_parameters.coordinates.size()); - json_result.values["status_message"] = - std::string("Could not find a matching segment for coordinate ") + - std::to_string(phantom_node_list.size()); - return Status::NoSegment; - } - - const auto number_of_locations = phantom_node_list.size(); - - // compute the distance table of all phantom nodes - const auto result_table = util::DistTableWrapper( - *search_engine_ptr->distance_table(phantom_node_list, phantom_node_list), - number_of_locations); - - if (result_table.size() == 0) - { - return Status::Error; - } - - const constexpr std::size_t BF_MAX_FEASABLE = 10; - BOOST_ASSERT_MSG(result_table.size() == number_of_locations * number_of_locations, - "Distance Table has wrong size"); - - // get scc components - SCC_Component scc = SplitUnaccessibleLocations(number_of_locations, result_table); - - using NodeIDIterator = typename std::vector::const_iterator; - - std::vector> route_result; - route_result.reserve(scc.GetNumberOfComponents()); - // run Trip computation for every SCC - for (std::size_t k = 0; k < scc.GetNumberOfComponents(); ++k) - { - const auto component_size = scc.range[k + 1] - scc.range[k]; - - BOOST_ASSERT_MSG(component_size > 0, "invalid component size"); - - std::vector scc_route; - NodeIDIterator start = std::begin(scc.component) + scc.range[k]; - NodeIDIterator end = std::begin(scc.component) + scc.range[k + 1]; - - if (component_size > 1) - { - - if (component_size < BF_MAX_FEASABLE) - { - scc_route = trip::BruteForceTrip(start, end, number_of_locations, result_table); - } - else - { - scc_route = - trip::FarthestInsertionTrip(start, end, number_of_locations, result_table); - } - - // use this output if debugging of route is needed: - // util::SimpleLogger().Write() << "Route #" << k << ": " << [&scc_route]() - // { - // std::string s = ""; - // for (auto x : scc_route) - // { - // s += std::to_string(x) + " "; - // } - // return s; - // }(); - } - else - { - scc_route = std::vector(start, end); - } - - route_result.push_back(std::move(scc_route)); - } - - // compute all round trip routes - std::vector comp_route; - comp_route.reserve(route_result.size()); - for (auto &elem : route_result) - { - comp_route.push_back(ComputeRoute(phantom_node_list, route_parameters, elem)); - } - - // prepare JSON output - // create a json object for every trip - util::json::Array trip; - for (std::size_t i = 0; i < route_result.size(); ++i) - { - util::json::Object scc_trip; - - // annotate comp_route[i] as a json trip - auto generator = MakeApiResponseGenerator(facade); - generator.DescribeRoute(route_parameters, comp_route[i], scc_trip); - - // set permutation output - SetLocPermutationOutput(route_result[i], scc_trip); - // set viaroute output - trip.values.push_back(std::move(scc_trip)); - } - - if (trip.values.empty()) - { - json_result.values["status_message"] = "Cannot find trips"; - return Status::EmptyResult; - } - - json_result.values["trips"] = std::move(trip); - json_result.values["status_message"] = "Found trips"; - return Status::Ok; } + + Status HandleRequest(const api::TripParameters ¶meters, util::json::Object &json_result); }; + } } } diff --git a/include/engine/trip/trip_brute_force.hpp b/include/engine/trip/trip_brute_force.hpp index 351295cf824..b8fe8ecc84b 100644 --- a/include/engine/trip/trip_brute_force.hpp +++ b/include/engine/trip/trip_brute_force.hpp @@ -1,7 +1,7 @@ #ifndef TRIP_BRUTE_FORCE_HPP #define TRIP_BRUTE_FORCE_HPP -#include "engine/search_engine.hpp" +#include "util/typedefs.hpp" #include "util/dist_table_wrapper.hpp" #include "util/simple_logger.hpp" diff --git a/include/engine/trip/trip_farthest_insertion.hpp b/include/engine/trip/trip_farthest_insertion.hpp index 35a0c7fd4ae..b2c68ce0461 100644 --- a/include/engine/trip/trip_farthest_insertion.hpp +++ b/include/engine/trip/trip_farthest_insertion.hpp @@ -1,7 +1,7 @@ #ifndef TRIP_FARTHEST_INSERTION_HPP #define TRIP_FARTHEST_INSERTION_HPP -#include "engine/search_engine.hpp" +#include "util/typedefs.hpp" #include "util/dist_table_wrapper.hpp" #include "osrm/json_container.hpp" diff --git a/include/engine/trip/trip_nearest_neighbour.hpp b/include/engine/trip/trip_nearest_neighbour.hpp index 7f29217656f..df70bc4ebc2 100644 --- a/include/engine/trip/trip_nearest_neighbour.hpp +++ b/include/engine/trip/trip_nearest_neighbour.hpp @@ -1,7 +1,7 @@ #ifndef TRIP_NEAREST_NEIGHBOUR_HPP #define TRIP_NEAREST_NEIGHBOUR_HPP -#include "engine/search_engine.hpp" +#include "util/typedefs.hpp" #include "util/simple_logger.hpp" #include "util/dist_table_wrapper.hpp" diff --git a/include/server/api/trip_parameter_grammar.hpp b/include/server/api/trip_parameter_grammar.hpp index e82af0b2dc9..7a2181c8387 100644 --- a/include/server/api/trip_parameter_grammar.hpp +++ b/include/server/api/trip_parameter_grammar.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -23,16 +24,53 @@ namespace qi = boost::spirit::qi; struct TripParametersGrammar final : public BaseParametersGrammar { using Iterator = std::string::iterator; + using StepsT = bool; + using GeometriesT = engine::api::RouteParameters::GeometriesType; + using OverviewT = engine::api::RouteParameters::OverviewType; TripParametersGrammar() : BaseParametersGrammar(root_rule, parameters) { - root_rule = "TODO(daniel-j-h)"; + const auto set_geojson_type = [this]() + { + parameters.geometries = engine::api::RouteParameters::GeometriesType::GeoJSON; + }; + const auto set_polyline_type = [this]() + { + parameters.geometries = engine::api::RouteParameters::GeometriesType::Polyline; + }; + + const auto set_simplified_type = [this]() + { + parameters.overview = engine::api::RouteParameters::OverviewType::Simplified; + }; + const auto set_full_type = [this]() + { + parameters.overview = engine::api::RouteParameters::OverviewType::Full; + }; + const auto set_false_type = [this]() + { + parameters.overview = engine::api::RouteParameters::OverviewType::False; + }; + const auto set_steps = [this](const StepsT steps) + { + parameters.steps = steps; + }; + + steps_rule = qi::lit("steps=") >> qi::bool_; + geometries_rule = qi::lit("geometries=geojson")[set_geojson_type] | + qi::lit("geometries=polyline")[set_polyline_type]; + overview_rule = qi::lit("overview=simplified")[set_simplified_type] | + qi::lit("overview=full")[set_full_type] | + qi::lit("overview=false")[set_false_type]; + trip_rule = steps_rule[set_steps] | geometries_rule | overview_rule; + root_rule = -((base_rule | trip_rule) % '&'); } engine::api::TripParameters parameters; private: - qi::rule root_rule, trip_rule; + qi::rule root_rule, trip_rule, geometries_rule, overview_rule; + qi::rule steps_rule; }; } } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 67a01d905c1..39fa58e57e8 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -7,7 +7,7 @@ //#include "engine/plugins/hello_world.hpp" #include "engine/plugins/nearest.hpp" //#include "engine/plugins/timestamp.hpp" -//#include "engine/plugins/trip.hpp" +#include "engine/plugins/trip.hpp" #include "engine/plugins/viaroute.hpp" //#include "engine/plugins/tile.hpp" #include "engine/plugins/match.hpp" @@ -146,7 +146,7 @@ Engine::Engine(EngineConfig &config) route_plugin = create(*query_data_facade, config.max_locations_viaroute); table_plugin = create(*query_data_facade, config.max_locations_distance_table); nearest_plugin = create(*query_data_facade); - // trip_plugin = ceate(*query_data_facade, config.max_locations_trip); + trip_plugin = create(*query_data_facade, config.max_locations_trip); match_plugin = create(*query_data_facade, config.max_locations_map_matching); } @@ -172,8 +172,7 @@ Status Engine::Nearest(const api::NearestParameters ¶ms, util::json::Object Status Engine::Trip(const api::TripParameters ¶ms, util::json::Object &result) { - // return RunQuery(lock, *query_data_facade, params, *trip_plugin, result); - return Status::Error; + return RunQuery(lock, *query_data_facade, params, *trip_plugin, result); } Status Engine::Match(const api::MatchParameters ¶ms, util::json::Object &result) diff --git a/src/engine/plugins/match.cpp b/src/engine/plugins/match.cpp index 849b61eb4ac..6f25b93611e 100644 --- a/src/engine/plugins/match.cpp +++ b/src/engine/plugins/match.cpp @@ -163,7 +163,7 @@ Status MatchPlugin::HandleRequest(const api::MatchParameters ¶meters, std::vector sub_routes(sub_matchings.size()); for (auto index : util::irange(0UL, sub_matchings.size())) { - BOOST_ASSERT(sub.nodes.size() > 1); + BOOST_ASSERT(sub_matchings[index].nodes.size() > 1); // FIXME we only run this to obtain the geometry // The clean way would be to get this directly from the map matching plugin @@ -177,7 +177,7 @@ Status MatchPlugin::HandleRequest(const api::MatchParameters ¶meters, sub_routes[index].segment_end_coordinates.emplace_back(current_phantom_node_pair); } shortest_path(sub_routes[index].segment_end_coordinates, {}, sub_routes[index]); - BOOST_ASSERT(raw_route.shortest_path_length != INVALID_EDGE_WEIGHT); + BOOST_ASSERT(sub_routes[index].shortest_path_length != INVALID_EDGE_WEIGHT); } api::MatchAPI match_api{BasePlugin::facade, parameters}; diff --git a/src/engine/plugins/trip.cpp b/src/engine/plugins/trip.cpp new file mode 100644 index 00000000000..7725331a97d --- /dev/null +++ b/src/engine/plugins/trip.cpp @@ -0,0 +1,258 @@ +#include "engine/plugins/trip.hpp" + +#include "extractor/tarjan_scc.hpp" + +#include "engine/api/trip_api.hpp" +#include "engine/api/trip_parameters.hpp" +#include "engine/trip/trip_nearest_neighbour.hpp" +#include "engine/trip/trip_farthest_insertion.hpp" +#include "engine/trip/trip_brute_force.hpp" +#include "util/dist_table_wrapper.hpp" // to access the dist table more easily +#include "util/matrix_graph_wrapper.hpp" // wrapper to use tarjan scc on dist table +#include "util/json_container.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace osrm +{ +namespace engine +{ +namespace plugins +{ + +// Object to hold all strongly connected components (scc) of a graph +// to access all graphs with component ID i, get the iterators by: +// auto start = std::begin(scc_component.component) + scc_component.range[i]; +// auto end = std::begin(scc_component.component) + scc_component.range[i+1]; +struct SCC_Component +{ + // in_component: all NodeIDs sorted by component ID + // in_range: index where a new component starts + // + // example: NodeID 0, 1, 2, 4, 5 are in component 0 + // NodeID 3, 6, 7, 8 are in component 1 + // => in_component = [0, 1, 2, 4, 5, 3, 6, 7, 8] + // => in_range = [0, 5] + SCC_Component(std::vector in_component_nodes, std::vector in_range) + : component(std::move(in_component_nodes)), range(std::move(in_range)) + { + BOOST_ASSERT_MSG(component.size() > 0, "there's no scc component"); + BOOST_ASSERT_MSG(*std::max_element(range.begin(), range.end()) == component.size(), + "scc component ranges are out of bound"); + BOOST_ASSERT_MSG(*std::min_element(range.begin(), range.end()) == 0, + "invalid scc component range"); + BOOST_ASSERT_MSG(std::is_sorted(std::begin(range), std::end(range)), + "invalid component ranges"); + }; + + std::size_t GetNumberOfComponents() const + { + BOOST_ASSERT_MSG(range.size() > 0, "there's no range"); + return range.size() - 1; + } + + const std::vector component; + std::vector range; +}; + +// takes the number of locations and its duration matrix, +// identifies and splits the graph in its strongly connected components (scc) +// and returns an SCC_Component +SCC_Component SplitUnaccessibleLocations(const std::size_t number_of_locations, + const util::DistTableWrapper &result_table) +{ + + if (std::find(std::begin(result_table), std::end(result_table), INVALID_EDGE_WEIGHT) == + std::end(result_table)) + { + // whole graph is one scc + std::vector location_ids(number_of_locations); + std::iota(std::begin(location_ids), std::end(location_ids), 0); + std::vector range = {0, location_ids.size()}; + return SCC_Component(std::move(location_ids), std::move(range)); + } + + // Run TarjanSCC + auto wrapper = std::make_shared>(result_table.GetTable(), + number_of_locations); + auto scc = extractor::TarjanSCC>(wrapper); + scc.run(); + + const auto number_of_components = scc.get_number_of_components(); + + std::vector range_insertion; + std::vector range; + range_insertion.reserve(number_of_components); + range.reserve(number_of_components); + + std::vector components(number_of_locations, 0); + + std::size_t prefix = 0; + for (std::size_t j = 0; j < number_of_components; ++j) + { + range_insertion.push_back(prefix); + range.push_back(prefix); + prefix += scc.get_component_size(j); + } + // senitel + range.push_back(components.size()); + + for (std::size_t i = 0; i < number_of_locations; ++i) + { + components[range_insertion[scc.get_component_id(i)]] = i; + ++range_insertion[scc.get_component_id(i)]; + } + + return SCC_Component(std::move(components), std::move(range)); +} + +InternalRouteResult TripPlugin::ComputeRoute(const std::vector &snapped_phantoms, + const api::TripParameters ¶meters, + const std::vector &trip) +{ + InternalRouteResult min_route; + // given he final trip, compute total duration and return the route and location permutation + PhantomNodes viapoint; + const auto start = std::begin(trip); + const auto end = std::end(trip); + // computes a roundtrip from the nodes in trip + for (auto it = start; it != end; ++it) + { + const auto from_node = *it; + // if from_node is the last node, compute the route from the last to the first location + const auto to_node = std::next(it) != end ? *std::next(it) : *start; + + viapoint = PhantomNodes{snapped_phantoms[from_node], snapped_phantoms[to_node]}; + min_route.segment_end_coordinates.emplace_back(viapoint); + } + BOOST_ASSERT(min_route.segment_end_coordinates.size() == trip.size()); + + std::vector> uturns; + if (parameters.uturns.size() > 0) + { + uturns.resize(trip.size() + 1); + std::transform(trip.begin(), trip.end(), uturns.begin(), [¶meters](const NodeID idx) + { + return parameters.uturns[idx]; + }); + BOOST_ASSERT(uturns.size() > 0); + uturns.back() = parameters.uturns[trip.front()]; + } + + shortest_path(min_route.segment_end_coordinates, uturns, min_route); + + BOOST_ASSERT_MSG(min_route.shortest_path_length < INVALID_EDGE_WEIGHT, "unroutable route"); + return min_route; +} + +Status TripPlugin::HandleRequest(const api::TripParameters ¶meters, + util::json::Object &json_result) +{ + BOOST_ASSERT(parameters.IsValid()); + + // enforce maximum number of locations for performance reasons + if (max_locations_trip > 0 && + static_cast(parameters.coordinates.size()) > max_locations_trip) + { + return Error("TooBig", "Too many trip coordinates", json_result); + } + + if (!CheckAllCoordinates(parameters.coordinates)) + { + return Error("InvalidValue", "Invalid coordinate value.", json_result); + } + + auto phantom_node_pairs = GetPhantomNodes(parameters); + if (phantom_node_pairs.size() != parameters.coordinates.size()) + { + return Error("no-segment", + std::string("Could not find a matching segment for coordinate ") + + std::to_string(phantom_node_pairs.size()), + json_result); + } + BOOST_ASSERT(phantom_node_pairs.size() == parameters.coordinates.size()); + + auto snapped_phantoms = SnapPhantomNodes(phantom_node_pairs); + + const auto number_of_locations = snapped_phantoms.size(); + + // compute the duration table of all phantom nodes + const auto result_table = util::DistTableWrapper( + duration_table(snapped_phantoms), number_of_locations); + + if (result_table.size() == 0) + { + return Status::Error; + } + + const constexpr std::size_t BF_MAX_FEASABLE = 10; + BOOST_ASSERT_MSG(result_table.size() == number_of_locations * number_of_locations, + "Distance Table has wrong size"); + + // get scc components + SCC_Component scc = SplitUnaccessibleLocations(number_of_locations, result_table); + + using NodeIDIterator = typename std::vector::const_iterator; + + std::vector> trips; + trips.reserve(scc.GetNumberOfComponents()); + // run Trip computation for every SCC + for (std::size_t k = 0; k < scc.GetNumberOfComponents(); ++k) + { + const auto component_size = scc.range[k + 1] - scc.range[k]; + + BOOST_ASSERT_MSG(component_size > 0, "invalid component size"); + + std::vector scc_route; + NodeIDIterator start = std::begin(scc.component) + scc.range[k]; + NodeIDIterator end = std::begin(scc.component) + scc.range[k + 1]; + + if (component_size > 1) + { + + if (component_size < BF_MAX_FEASABLE) + { + scc_route = trip::BruteForceTrip(start, end, number_of_locations, result_table); + } + else + { + scc_route = + trip::FarthestInsertionTrip(start, end, number_of_locations, result_table); + } + } + else + { + scc_route = std::vector(start, end); + } + + trips.push_back(std::move(scc_route)); + } + if (trips.empty()) + { + return Error("NoTrips", "Cannot find trips", json_result); + } + + // compute all round trip routes + std::vector routes; + routes.reserve(trips.size()); + for (auto &trip : trips) + { + routes.push_back(ComputeRoute(snapped_phantoms, parameters, trip)); + } + + api::TripAPI trip_api{BasePlugin::facade, parameters}; + trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result); + + return Status::Ok; +} +} +} +} diff --git a/src/server/service/trip_service.cpp b/src/server/service/trip_service.cpp index b5c34484afc..698018cfd0d 100644 --- a/src/server/service/trip_service.cpp +++ b/src/server/service/trip_service.cpp @@ -1,4 +1,5 @@ #include "server/service/trip_service.hpp" +#include "server/service/utils.hpp" #include "engine/api/trip_parameters.hpp" #include "server/api/parameters_parser.hpp" @@ -13,14 +14,60 @@ namespace server { namespace service { +namespace +{ +std::string getWrongOptionHelp(const engine::api::TripParameters ¶meters) +{ + std::string help; + + const auto coord_size = parameters.coordinates.size(); + + const bool param_size_mismatch = constrainParamSize(PARAMETER_SIZE_MISMATCH_MSG, "hints", + parameters.hints, coord_size, help) || + constrainParamSize(PARAMETER_SIZE_MISMATCH_MSG, "bearings", + parameters.bearings, coord_size, help) || + constrainParamSize(PARAMETER_SIZE_MISMATCH_MSG, "radiuses", + parameters.radiuses, coord_size, help); + + if (!param_size_mismatch && parameters.coordinates.size() < 2) + { + help = "Number of coordinates needs to be at least two."; + } + + return help; +} +} // anon. ns engine::Status TripService::RunQuery(std::vector coordinates, - std::string &options, - util::json::Object &result) + std::string &options, + util::json::Object &result) { - // TODO(daniel-j-h) - return Status::Error; + auto options_iterator = options.begin(); + auto parameters = + api::parseParameters(options_iterator, options.end()); + if (!parameters || options_iterator != options.end()) + { + const auto position = std::distance(options.begin(), options_iterator); + result.values["code"] = "invalid-options"; + result.values["message"] = + "Options string malformed close to position " + std::to_string(position); + return engine::Status::Error; + } + + BOOST_ASSERT(parameters); + parameters->coordinates = std::move(coordinates); + + if (!parameters->IsValid()) + { + result.values["code"] = "invalid-options"; + result.values["message"] = getWrongOptionHelp(*parameters); + return engine::Status::Error; + } + BOOST_ASSERT(parameters->IsValid()); + + return BaseService::routing_machine.Trip(*parameters, result); } + } } }