#ifndef TRIP_HPP #define TRIP_HPP #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 "osrm/json_container.hpp" #include #include #include #include #include #include #include #include namespace osrm { namespace engine { namespace plugins { template class RoundTripPlugin final : public BasePlugin { private: std::string descriptor_string; DataFacadeT *facade; std::unique_ptr> search_engine_ptr; 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()) { PhantomNode current_phantom_node; ObjectEncoder::DecodeFromBase64(route_parameters.hints[i], current_phantom_node); if (current_phantom_node.is_valid(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().is_valid(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; 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 { 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; } }; } } } #endif // TRIP_HPP