Status TablePlugin::HandleRequest(const std::shared_ptr<datafacade::BaseDataFacade> facade, const api::TableParameters ¶ms, util::json::Object &result) const { BOOST_ASSERT(params.IsValid()); if (!CheckAllCoordinates(params.coordinates)) { return Error("InvalidOptions", "Coordinates are invalid", result); } if (params.bearings.size() > 0 && params.coordinates.size() != params.bearings.size()) { return Error( "InvalidOptions", "Number of bearings does not match number of coordinates", result); } // Empty sources or destinations means the user wants all of them included, respectively // The ManyToMany routing algorithm we dispatch to below already handles this perfectly. const auto num_sources = params.sources.empty() ? params.coordinates.size() : params.sources.size(); const auto num_destinations = params.destinations.empty() ? params.coordinates.size() : params.destinations.size(); if (max_locations_distance_table > 0 && ((num_sources * num_destinations) > static_cast<std::size_t>(max_locations_distance_table * max_locations_distance_table))) { return Error("TooBig", "Too many table coordinates", result); } auto snapped_phantoms = SnapPhantomNodes(GetPhantomNodes(*facade, params)); auto result_table = distance_table(*facade, snapped_phantoms, params.sources, params.destinations); if (result_table.empty()) { return Error("NoTable", "No table found", result); } api::TableAPI table_api{*facade, params}; table_api.MakeResponse(result_table, snapped_phantoms, result); return Status::Ok; }
Status NearestPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms, const api::NearestParameters ¶ms, util::json::Object &json_result) const { BOOST_ASSERT(params.IsValid()); if (!CheckAlgorithms(params, algorithms, json_result)) return Status::Error; const auto &facade = algorithms.GetFacade(); if (max_results > 0 && (boost::numeric_cast<std::int64_t>(params.number_of_results) > max_results)) { return Error("TooBig", "Number of results " + std::to_string(params.number_of_results) + " is higher than current maximum (" + std::to_string(max_results) + ")", json_result); } if (!CheckAllCoordinates(params.coordinates)) return Error("InvalidOptions", "Coordinates are invalid", json_result); if (params.coordinates.size() != 1) { return Error("InvalidOptions", "Only one input coordinate is supported", json_result); } auto phantom_nodes = GetPhantomNodes(facade, params, params.number_of_results); if (phantom_nodes.front().size() == 0) { return Error("NoSegment", "Could not find a matching segments for coordinate", json_result); } BOOST_ASSERT(phantom_nodes.front().size() > 0); api::NearestAPI nearest_api(facade, params); nearest_api.MakeResponse(phantom_nodes, json_result); return Status::Ok; }
Status ViaRoutePlugin::HandleRequest(const std::shared_ptr<datafacade::BaseDataFacade> facade, const api::RouteParameters &route_parameters, util::json::Object &json_result) { BOOST_ASSERT(route_parameters.IsValid()); if (max_locations_viaroute > 0 && (static_cast<int>(route_parameters.coordinates.size()) > max_locations_viaroute)) { return Error("TooBig", "Number of entries " + std::to_string(route_parameters.coordinates.size()) + " is higher than current maximum (" + std::to_string(max_locations_viaroute) + ")", json_result); } if (!CheckAllCoordinates(route_parameters.coordinates)) { return Error("InvalidValue", "Invalid coordinate value.", json_result); } auto phantom_node_pairs = GetPhantomNodes(*facade, route_parameters); if (phantom_node_pairs.size() != route_parameters.coordinates.size()) { return Error("NoSegment", 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() == route_parameters.coordinates.size()); auto snapped_phantoms = SnapPhantomNodes(phantom_node_pairs); const bool continue_straight_at_waypoint = route_parameters.continue_straight ? *route_parameters.continue_straight : facade->GetContinueStraightDefault(); InternalRouteResult raw_route; auto build_phantom_pairs = [&raw_route, continue_straight_at_waypoint]( const PhantomNode &first_node, const PhantomNode &second_node) { raw_route.segment_end_coordinates.push_back(PhantomNodes{first_node, second_node}); auto &last_inserted = raw_route.segment_end_coordinates.back(); // enable forward direction if possible if (last_inserted.source_phantom.forward_segment_id.id != SPECIAL_SEGMENTID) { last_inserted.source_phantom.forward_segment_id.enabled |= !continue_straight_at_waypoint; } // enable reverse direction if possible if (last_inserted.source_phantom.reverse_segment_id.id != SPECIAL_SEGMENTID) { last_inserted.source_phantom.reverse_segment_id.enabled |= !continue_straight_at_waypoint; } }; util::for_each_pair(snapped_phantoms, build_phantom_pairs); if (1 == raw_route.segment_end_coordinates.size()) { if (route_parameters.alternatives && facade->GetCoreSize() == 0) { alternative_path(*facade, raw_route.segment_end_coordinates.front(), raw_route); } else { direct_shortest_path(*facade, raw_route.segment_end_coordinates, raw_route); } } else { shortest_path(*facade, raw_route.segment_end_coordinates, route_parameters.continue_straight, raw_route); } // we can only know this after the fact, different SCC ids still // allow for connection in one direction. if (raw_route.is_valid()) { api::RouteAPI route_api{*facade, route_parameters}; route_api.MakeResponse(raw_route, json_result); } else { auto first_component_id = snapped_phantoms.front().component.id; auto not_in_same_component = std::any_of(snapped_phantoms.begin(), snapped_phantoms.end(), [first_component_id](const PhantomNode &node) { return node.component.id != first_component_id; }); if (not_in_same_component) { return Error("NoRoute", "Impossible route between points", json_result); } else { return Error("NoRoute", "No route found between points", json_result); } } return Status::Ok; }
Status TripPlugin::HandleRequest(const datafacade::ContiguousInternalMemoryDataFacadeBase &facade, const RoutingAlgorithmsInterface &algorithms, const api::TripParameters ¶meters, util::json::Object &json_result) const { if (!algorithms.HasShortestPathSearch()) { return Error("NotImplemented", "Shortest path search is not implemented for the chosen search algorithm.", json_result); } if (!algorithms.HasManyToManySearch()) { return Error("NotImplemented", "Many to many search is not implemented for the chosen search algorithm.", json_result); } BOOST_ASSERT(parameters.IsValid()); const auto number_of_locations = parameters.coordinates.size(); std::size_t source_id = INVALID_INDEX; std::size_t destination_id = INVALID_INDEX; if (parameters.source == api::TripParameters::SourceType::First) { source_id = 0; } if (parameters.destination == api::TripParameters::DestinationType::Last) { BOOST_ASSERT(number_of_locations > 0); destination_id = number_of_locations - 1; } bool fixed_start = (source_id == 0); bool fixed_end = (destination_id == number_of_locations - 1); if (!IsSupportedParameterCombination(fixed_start, fixed_end, parameters.roundtrip)) { return Error("NotImplemented", "This request is not supported", json_result); } // enforce maximum number of locations for performance reasons if (max_locations_trip > 0 && static_cast<int>(number_of_locations) > 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(facade, parameters); if (phantom_node_pairs.size() != number_of_locations) { return Error("NoSegment", 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() == number_of_locations); if (fixed_start && fixed_end && (source_id >= parameters.coordinates.size() || destination_id >= parameters.coordinates.size())) { return Error("InvalidValue", "Invalid source or destination value.", json_result); } auto snapped_phantoms = SnapPhantomNodes(phantom_node_pairs); BOOST_ASSERT(snapped_phantoms.size() == number_of_locations); // compute the duration table of all phantom nodes auto result_table = util::DistTableWrapper<EdgeWeight>( algorithms.ManyToManySearch(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"); if (!IsStronglyConnectedComponent(result_table)) { return Error("NoTrips", "No trip visiting all destinations possible.", json_result); } if (fixed_start && fixed_end) { ManipulateTableForFSE(source_id, destination_id, result_table); } std::vector<NodeID> trip; trip.reserve(number_of_locations); // get an optimized order in which the destinations should be visited if (number_of_locations < BF_MAX_FEASABLE) { trip = trip::BruteForceTrip(number_of_locations, result_table); } else { trip = trip::FarthestInsertionTrip(number_of_locations, result_table); } // rotate result such that roundtrip starts at node with index 0 // thist first if covers scenarios: !fixed_end || fixed_start || (fixed_start && fixed_end) if (!fixed_end || fixed_start) { auto desired_start_index = std::find(std::begin(trip), std::end(trip), 0); BOOST_ASSERT(desired_start_index != std::end(trip)); std::rotate(std::begin(trip), desired_start_index, std::end(trip)); } else if (fixed_end && !fixed_start && parameters.roundtrip) { auto desired_start_index = std::find(std::begin(trip), std::end(trip), destination_id); BOOST_ASSERT(desired_start_index != std::end(trip)); std::rotate(std::begin(trip), desired_start_index, std::end(trip)); } // get the route when visiting all destinations in optimized order InternalRouteResult route = ComputeRoute(algorithms, snapped_phantoms, trip, parameters.roundtrip); // get api response const std::vector<std::vector<NodeID>> trips = {trip}; const std::vector<InternalRouteResult> routes = {route}; api::TripAPI trip_api{facade, parameters}; trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result); return Status::Ok; }
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<int>(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("NoSegment", 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<EdgeWeight>( 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); std::vector<std::vector<NodeID>> 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<NodeID> scc_route; auto route_begin = std::begin(scc.component) + scc.range[k]; auto route_end = std::begin(scc.component) + scc.range[k + 1]; if (component_size > 1) { if (component_size < BF_MAX_FEASABLE) { scc_route = trip::BruteForceTrip(route_begin, route_end, number_of_locations, result_table); } else { scc_route = trip::FarthestInsertionTrip(route_begin, route_end, number_of_locations, result_table); } } else { scc_route = std::vector<NodeID>(route_begin, route_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<InternalRouteResult> routes; routes.reserve(trips.size()); for (const 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; }
Status MatchPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms, const api::MatchParameters ¶meters, util::json::Object &json_result) const { if (!algorithms.HasMapMatching()) { return Error("NotImplemented", "Map matching is not implemented for the chosen search algorithm.", json_result); } if (!CheckAlgorithms(parameters, algorithms, json_result)) return Status::Error; const auto &facade = algorithms.GetFacade(); BOOST_ASSERT(parameters.IsValid()); // enforce maximum number of locations for performance reasons if (max_locations_map_matching > 0 && static_cast<int>(parameters.coordinates.size()) > max_locations_map_matching) { return Error("TooBig", "Too many trace coordinates", json_result); } if (!CheckAllCoordinates(parameters.coordinates)) { return Error("InvalidValue", "Invalid coordinate value.", json_result); } if (max_radius_map_matching > 0 && std::any_of(parameters.radiuses.begin(), parameters.radiuses.end(), [&](const auto &radius) { if (!radius) return false; return *radius > max_radius_map_matching; })) { return Error("TooBig", "Radius search size is too large for map matching.", json_result); } // Check for same or increasing timestamps. Impl. note: Incontrast to `sort(first, // last, less_equal)` checking `greater` in reverse meets irreflexive requirements. const auto time_increases_monotonically = std::is_sorted( parameters.timestamps.rbegin(), parameters.timestamps.rend(), std::greater<>{}); if (!time_increases_monotonically) { return Error( "InvalidValue", "Timestamps need to be monotonically increasing.", json_result); } SubMatchingList sub_matchings; api::tidy::Result tidied; if (parameters.tidy) { // Transparently tidy match parameters, do map matching on tidied parameters. // Then use the mapping to restore the original <-> tidied relationship. tidied = api::tidy::tidy(parameters); } else { tidied = api::tidy::keep_all(parameters); } // Error: first and last points should be waypoints if (!parameters.waypoints.empty() && (tidied.parameters.waypoints[0] != 0 || tidied.parameters.waypoints.back() != (tidied.parameters.coordinates.size() - 1))) { return Error("InvalidValue", "First and last coordinates must be specified as waypoints.", json_result); } // assuming radius is the standard deviation of a normal distribution // that models GPS noise (in this model), x3 should give us the correct // search radius with > 99% confidence std::vector<double> search_radiuses; if (tidied.parameters.radiuses.empty()) { search_radiuses.resize(tidied.parameters.coordinates.size(), routing_algorithms::DEFAULT_GPS_PRECISION * RADIUS_MULTIPLIER); } else { search_radiuses.resize(tidied.parameters.coordinates.size()); std::transform(tidied.parameters.radiuses.begin(), tidied.parameters.radiuses.end(), search_radiuses.begin(), [](const boost::optional<double> &maybe_radius) { if (maybe_radius) { return *maybe_radius * RADIUS_MULTIPLIER; } else { return routing_algorithms::DEFAULT_GPS_PRECISION * RADIUS_MULTIPLIER; } }); } auto candidates_lists = GetPhantomNodesInRange(facade, tidied.parameters, search_radiuses); filterCandidates(tidied.parameters.coordinates, candidates_lists); if (std::all_of(candidates_lists.begin(), candidates_lists.end(), [](const std::vector<PhantomNodeWithDistance> &candidates) { return candidates.empty(); })) { return Error("NoSegment", std::string("Could not find a matching segment for any coordinate."), json_result); } // call the actual map matching sub_matchings = algorithms.MapMatching(candidates_lists, tidied.parameters.coordinates, tidied.parameters.timestamps, tidied.parameters.radiuses, parameters.gaps == api::MatchParameters::GapsType::Split); if (sub_matchings.size() == 0) { return Error("NoMatch", "Could not match the trace.", json_result); } // trace was split, we don't support the waypoints parameter across multiple match objects if (sub_matchings.size() > 1 && !parameters.waypoints.empty()) { return Error("NoMatch", "Could not match the trace with the given waypoints.", json_result); } // Error: Check if user-supplied waypoints can be found in the resulting matches { std::set<std::size_t> tidied_waypoints(tidied.parameters.waypoints.begin(), tidied.parameters.waypoints.end()); for (const auto &sm : sub_matchings) { std::for_each(sm.indices.begin(), sm.indices.end(), [&tidied_waypoints](const auto index) { tidied_waypoints.erase(index); }); } if (!tidied_waypoints.empty()) { return Error( "NoMatch", "Requested waypoint parameter could not be matched.", json_result); } } // each sub_route will correspond to a MatchObject std::vector<InternalRouteResult> sub_routes(sub_matchings.size()); for (auto index : util::irange<std::size_t>(0UL, sub_matchings.size())) { 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 PhantomNodes current_phantom_node_pair; for (unsigned i = 0; i < sub_matchings[index].nodes.size() - 1; ++i) { current_phantom_node_pair.source_phantom = sub_matchings[index].nodes[i]; current_phantom_node_pair.target_phantom = sub_matchings[index].nodes[i + 1]; BOOST_ASSERT(current_phantom_node_pair.source_phantom.IsValid()); BOOST_ASSERT(current_phantom_node_pair.target_phantom.IsValid()); sub_routes[index].segment_end_coordinates.emplace_back(current_phantom_node_pair); } // force uturns to be on // we split the phantom nodes anyway and only have bi-directional phantom nodes for // possible uturns sub_routes[index] = algorithms.ShortestPathSearch(sub_routes[index].segment_end_coordinates, {false}); BOOST_ASSERT(sub_routes[index].shortest_path_weight != INVALID_EDGE_WEIGHT); if (!tidied.parameters.waypoints.empty()) { std::vector<bool> waypoint_legs; waypoint_legs.reserve(sub_matchings[index].indices.size()); for (unsigned i = 0, j = 0; i < sub_matchings[index].indices.size(); ++i) { auto current_wp = tidied.parameters.waypoints[j]; if (current_wp == sub_matchings[index].indices[i]) { waypoint_legs.push_back(true); ++j; } else { waypoint_legs.push_back(false); } } sub_routes[index] = CollapseInternalRouteResult(sub_routes[index], waypoint_legs); } } api::MatchAPI match_api{facade, parameters, tidied}; match_api.MakeResponse(sub_matchings, sub_routes, json_result); return Status::Ok; }