Trip with Fixed Start and End points (TFSE) (#3408)
* fixed start and end trip feature to trip service
This commit is contained in:
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "engine/api/route_parameters.hpp"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
@@ -47,7 +48,33 @@ namespace api
|
||||
*/
|
||||
struct TripParameters : public RouteParameters
|
||||
{
|
||||
// bool IsValid() const; Falls back to base class
|
||||
TripParameters() = default;
|
||||
enum class SourceType
|
||||
{
|
||||
Any,
|
||||
First
|
||||
};
|
||||
enum class DestinationType
|
||||
{
|
||||
Any,
|
||||
Last
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
TripParameters(SourceType source_,
|
||||
DestinationType destination_,
|
||||
bool roundtrip_,
|
||||
Args &&... args_)
|
||||
: RouteParameters{std::forward<Args>(args_)...}, source{source_}, destination{destination_},
|
||||
roundtrip{roundtrip_}
|
||||
{
|
||||
}
|
||||
|
||||
SourceType source = SourceType::Any;
|
||||
DestinationType destination = DestinationType::Any;
|
||||
bool roundtrip = true;
|
||||
|
||||
bool IsValid() const { return RouteParameters::IsValid(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ class TripPlugin final : public BasePlugin
|
||||
|
||||
InternalRouteResult ComputeRoute(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
|
||||
const std::vector<PhantomNode> &phantom_node_list,
|
||||
const std::vector<NodeID> &trip) const;
|
||||
const std::vector<NodeID> &trip,
|
||||
const bool roundtrip) const;
|
||||
|
||||
public:
|
||||
explicit TripPlugin(const int max_locations_trip_)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -25,62 +26,76 @@ namespace trip
|
||||
EdgeWeight ReturnDistance(const util::DistTableWrapper<EdgeWeight> &dist_table,
|
||||
const std::vector<NodeID> &location_order,
|
||||
const EdgeWeight min_route_dist,
|
||||
const std::size_t component_size)
|
||||
const std::size_t number_of_locations)
|
||||
{
|
||||
EdgeWeight route_dist = 0;
|
||||
std::size_t i = 0;
|
||||
while (i < location_order.size() && (route_dist < min_route_dist))
|
||||
std::size_t current_index = 0;
|
||||
while (current_index < location_order.size() && (route_dist < min_route_dist))
|
||||
{
|
||||
route_dist += dist_table(location_order[i], location_order[(i + 1) % component_size]);
|
||||
BOOST_ASSERT_MSG(dist_table(location_order[i], location_order[(i + 1) % component_size]) !=
|
||||
|
||||
std::size_t next_index = (current_index + 1) % number_of_locations;
|
||||
auto edge_weight = dist_table(location_order[current_index], location_order[next_index]);
|
||||
|
||||
// If the edge_weight is very large (INVALID_EDGE_WEIGHT) then the algorithm will not choose
|
||||
// this edge in final minimal path. So instead of computing all the permutations after this
|
||||
// large edge, discard this edge right here and don't consider the path after this edge.
|
||||
if (edge_weight == INVALID_EDGE_WEIGHT)
|
||||
{
|
||||
return INVALID_EDGE_WEIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
route_dist += edge_weight;
|
||||
}
|
||||
|
||||
// This boost assert should not be reached if TFSE table
|
||||
BOOST_ASSERT_MSG(dist_table(location_order[current_index], location_order[next_index]) !=
|
||||
INVALID_EDGE_WEIGHT,
|
||||
"invalid route found");
|
||||
++i;
|
||||
++current_index;
|
||||
}
|
||||
|
||||
return route_dist;
|
||||
}
|
||||
|
||||
// computes the route by computing all permutations and selecting the shortest
|
||||
template <typename NodeIDIterator>
|
||||
std::vector<NodeID> BruteForceTrip(const NodeIDIterator start,
|
||||
const NodeIDIterator end,
|
||||
const std::size_t number_of_locations,
|
||||
std::vector<NodeID> BruteForceTrip(const std::size_t number_of_locations,
|
||||
const util::DistTableWrapper<EdgeWeight> &dist_table)
|
||||
{
|
||||
(void)number_of_locations; // unused
|
||||
|
||||
const auto component_size = std::distance(start, end);
|
||||
|
||||
std::vector<NodeID> perm(start, end);
|
||||
std::vector<NodeID> route = perm;
|
||||
// set initial order in which nodes are visited to 0, 1, 2, 3, ...
|
||||
std::vector<NodeID> node_order(number_of_locations);
|
||||
std::iota(std::begin(node_order), std::end(node_order), 0);
|
||||
std::vector<NodeID> route = node_order;
|
||||
|
||||
EdgeWeight min_route_dist = INVALID_EDGE_WEIGHT;
|
||||
|
||||
// check length of all possible permutation of the component ids
|
||||
|
||||
BOOST_ASSERT_MSG(perm.size() > 0, "no permutation given");
|
||||
BOOST_ASSERT_MSG(*(std::max_element(std::begin(perm), std::end(perm))) < number_of_locations,
|
||||
BOOST_ASSERT_MSG(node_order.size() > 0, "no order permutation given");
|
||||
BOOST_ASSERT_MSG(*(std::max_element(std::begin(node_order), std::end(node_order))) <
|
||||
number_of_locations,
|
||||
"invalid node id");
|
||||
BOOST_ASSERT_MSG(*(std::min_element(std::begin(node_order), std::end(node_order))) >= 0,
|
||||
"invalid node id");
|
||||
BOOST_ASSERT_MSG(*(std::min_element(std::begin(perm), std::end(perm))) >= 0, "invalid node id");
|
||||
|
||||
do
|
||||
{
|
||||
const auto new_distance = ReturnDistance(dist_table, perm, min_route_dist, component_size);
|
||||
const auto new_distance =
|
||||
ReturnDistance(dist_table, node_order, min_route_dist, number_of_locations);
|
||||
// we can use `<` instead of `<=` here, since all distances are `!=` INVALID_EDGE_WEIGHT
|
||||
// In case we really sum up to invalid edge weight for all permutations, keeping the very
|
||||
// first one is fine too.
|
||||
if (new_distance < min_route_dist)
|
||||
{
|
||||
min_route_dist = new_distance;
|
||||
route = perm;
|
||||
route = node_order;
|
||||
}
|
||||
} while (std::next_permutation(std::begin(perm), std::end(perm)));
|
||||
} while (std::next_permutation(std::begin(node_order), std::end(node_order)));
|
||||
|
||||
return route;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace trip
|
||||
} // namespace engine
|
||||
} // namespace osrm
|
||||
|
||||
#endif // TRIP_BRUTE_FORCE_HPP
|
||||
|
||||
@@ -50,8 +50,11 @@ GetShortestRoundTrip(const NodeID new_loc,
|
||||
const auto dist_to = dist_table(new_loc, *to_node);
|
||||
const auto trip_dist = dist_from + dist_to - dist_table(*from_node, *to_node);
|
||||
|
||||
BOOST_ASSERT_MSG(dist_from != INVALID_EDGE_WEIGHT, "distance has invalid edge weight");
|
||||
BOOST_ASSERT_MSG(dist_to != INVALID_EDGE_WEIGHT, "distance has invalid edge weight");
|
||||
// If the edge_weight is very large (INVALID_EDGE_WEIGHT) then the algorithm will not choose
|
||||
// this edge in final minimal path. So instead of computing all the permutations after this
|
||||
// large edge, discard this edge right here and don't consider the path after this edge.
|
||||
if (dist_from == INVALID_EDGE_WEIGHT || dist_to == INVALID_EDGE_WEIGHT)
|
||||
continue;
|
||||
// This is not neccessarily true:
|
||||
// Lets say you have an edge (u, v) with duration 100. If you place a coordinate exactly in
|
||||
// the middle of the segment yielding (u, v'), the adjusted duration will be 100 * 0.5 = 50.
|
||||
@@ -72,18 +75,14 @@ GetShortestRoundTrip(const NodeID new_loc,
|
||||
return std::make_pair(min_trip_distance, next_insert_point_candidate);
|
||||
}
|
||||
|
||||
template <typename NodeIDIterator>
|
||||
// given two initial start nodes, find a roundtrip route using the farthest insertion algorithm
|
||||
std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
|
||||
const std::size_t &component_size,
|
||||
const NodeIDIterator &start,
|
||||
const NodeIDIterator &end,
|
||||
const util::DistTableWrapper<EdgeWeight> &dist_table,
|
||||
const NodeID &start1,
|
||||
const NodeID &start2)
|
||||
{
|
||||
BOOST_ASSERT_MSG(number_of_locations >= component_size,
|
||||
"component size bigger than total number of locations");
|
||||
BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
|
||||
"number_of_locations and dist_table size do not match");
|
||||
|
||||
std::vector<NodeID> route;
|
||||
route.reserve(number_of_locations);
|
||||
@@ -96,22 +95,21 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
|
||||
route.push_back(start1);
|
||||
route.push_back(start2);
|
||||
|
||||
// add all other nodes missing (two nodes are already in the initial start trip)
|
||||
for (std::size_t j = 2; j < component_size; ++j)
|
||||
// two nodes are already in the initial start trip, so we need to add all other nodes
|
||||
for (std::size_t added_nodes = 2; added_nodes < number_of_locations; ++added_nodes)
|
||||
{
|
||||
|
||||
auto farthest_distance = std::numeric_limits<int>::min();
|
||||
auto next_node = -1;
|
||||
NodeIDIter next_insert_point;
|
||||
|
||||
// find unvisited loc i that is the farthest away from all other visited locs
|
||||
for (auto i = start; i != end; ++i)
|
||||
// find unvisited node that is the farthest away from all other visited locs
|
||||
for (std::size_t id = 0; id < number_of_locations; ++id)
|
||||
{
|
||||
// find the shortest distance from i to all visited nodes
|
||||
if (!visited[*i])
|
||||
if (!visited[id])
|
||||
{
|
||||
const auto insert_candidate =
|
||||
GetShortestRoundTrip(*i, dist_table, number_of_locations, route);
|
||||
GetShortestRoundTrip(id, dist_table, number_of_locations, route);
|
||||
|
||||
BOOST_ASSERT_MSG(insert_candidate.first != INVALID_EDGE_WEIGHT,
|
||||
"shortest round trip is invalid");
|
||||
@@ -121,7 +119,7 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
|
||||
if (insert_candidate.first > farthest_distance)
|
||||
{
|
||||
farthest_distance = insert_candidate.first;
|
||||
next_node = *i;
|
||||
next_node = id;
|
||||
next_insert_point = insert_candidate.second;
|
||||
}
|
||||
}
|
||||
@@ -136,10 +134,7 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
|
||||
return route;
|
||||
}
|
||||
|
||||
template <typename NodeIDIterator>
|
||||
std::vector<NodeID> FarthestInsertionTrip(const NodeIDIterator &start,
|
||||
const NodeIDIterator &end,
|
||||
const std::size_t number_of_locations,
|
||||
std::vector<NodeID> FarthestInsertionTrip(const std::size_t number_of_locations,
|
||||
const util::DistTableWrapper<EdgeWeight> &dist_table)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -158,57 +153,29 @@ std::vector<NodeID> FarthestInsertionTrip(const NodeIDIterator &start,
|
||||
// Guard against dist_table being empty therefore max_element returning the end iterator.
|
||||
BOOST_ASSERT(dist_table.size() > 0);
|
||||
|
||||
const auto component_size = std::distance(start, end);
|
||||
BOOST_ASSERT(component_size >= 0);
|
||||
BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
|
||||
"number_of_locations and dist_table size do not match");
|
||||
|
||||
auto max_from = -1;
|
||||
auto max_to = -1;
|
||||
|
||||
if (static_cast<std::size_t>(component_size) == number_of_locations)
|
||||
{
|
||||
// find the pair of location with the biggest distance and make the pair the initial start
|
||||
// trip. Skipping over the very first element (0,0), we make sure not to end up with the
|
||||
// same start/end in the special case where all entries are the same.
|
||||
const auto index =
|
||||
std::distance(std::begin(dist_table),
|
||||
std::max_element(std::begin(dist_table) + 1, std::end(dist_table)));
|
||||
max_from = index / number_of_locations;
|
||||
max_to = index % number_of_locations;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto max_dist = std::numeric_limits<EdgeWeight>::min();
|
||||
|
||||
for (auto x = start; x != end; ++x)
|
||||
{
|
||||
for (auto y = start; y != end; ++y)
|
||||
{
|
||||
// don't repeat coordinates
|
||||
if (*x == *y)
|
||||
continue;
|
||||
|
||||
const auto xy_dist = dist_table(*x, *y);
|
||||
// SCC decomposition done correctly?
|
||||
BOOST_ASSERT(xy_dist != INVALID_EDGE_WEIGHT);
|
||||
|
||||
if (xy_dist >= max_dist)
|
||||
{
|
||||
max_dist = xy_dist;
|
||||
max_from = *x;
|
||||
max_to = *y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// find the pair of location with the biggest distance and make the pair the initial start
|
||||
// trip. Skipping over the very first element (0,0), we make sure not to end up with the
|
||||
// same start/end in the special case where all entries are the same.
|
||||
const auto index_of_farthest_distance = std::distance(
|
||||
std::begin(dist_table), std::max_element(std::begin(dist_table) + 1, std::end(dist_table)));
|
||||
// distance table is a nxn matrix with the distance(u,v) in column u and row v
|
||||
// but the distance table is stored in an 1D array of distances
|
||||
// to get the actual (u,v), get the row by dividing and the column by computing modulo n
|
||||
NodeID max_from = index_of_farthest_distance / number_of_locations;
|
||||
NodeID max_to = index_of_farthest_distance % number_of_locations;
|
||||
|
||||
BOOST_ASSERT(max_from >= 0);
|
||||
BOOST_ASSERT(max_to >= 0);
|
||||
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_from) < number_of_locations, "start node");
|
||||
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_to) < number_of_locations, "start node");
|
||||
return FindRoute(number_of_locations, component_size, start, end, dist_table, max_from, max_to);
|
||||
}
|
||||
}
|
||||
}
|
||||
return FindRoute(number_of_locations, dist_table, max_from, max_to);
|
||||
}
|
||||
|
||||
} // namespace trip
|
||||
} // namespace engine
|
||||
} // namespace osrm
|
||||
|
||||
#endif // TRIP_FARTHEST_INSERTION_HPP
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "server/api/route_parameters_grammar.hpp"
|
||||
#include "engine/api/trip_parameters.hpp"
|
||||
|
||||
#include <boost/spirit/include/phoenix.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
|
||||
namespace osrm
|
||||
@@ -15,6 +16,7 @@ namespace api
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace ph = boost::phoenix;
|
||||
namespace qi = boost::spirit::qi;
|
||||
}
|
||||
|
||||
@@ -26,12 +28,37 @@ struct TripParametersGrammar final : public RouteParametersGrammar<Iterator, Sig
|
||||
|
||||
TripParametersGrammar() : BaseGrammar(root_rule)
|
||||
{
|
||||
roundtrip_rule =
|
||||
qi::lit("roundtrip=") >
|
||||
qi::bool_[ph::bind(&engine::api::TripParameters::roundtrip, qi::_r1) = qi::_1];
|
||||
|
||||
source_type.add("any", engine::api::TripParameters::SourceType::Any)(
|
||||
"first", engine::api::TripParameters::SourceType::First);
|
||||
|
||||
destination_type.add("any", engine::api::TripParameters::DestinationType::Any)(
|
||||
"last", engine::api::TripParameters::DestinationType::Last);
|
||||
|
||||
source_rule = qi::lit("source=") >
|
||||
source_type[ph::bind(&engine::api::TripParameters::source, qi::_r1) = qi::_1];
|
||||
|
||||
destination_rule =
|
||||
qi::lit("destination=") >
|
||||
destination_type[ph::bind(&engine::api::TripParameters::destination, qi::_r1) = qi::_1];
|
||||
|
||||
root_rule = BaseGrammar::query_rule(qi::_r1) > -qi::lit(".json") >
|
||||
-('?' > (BaseGrammar::base_rule(qi::_r1)) % '&');
|
||||
-('?' > (roundtrip_rule(qi::_r1) | source_rule(qi::_r1) |
|
||||
destination_rule(qi::_r1) | BaseGrammar::base_rule(qi::_r1)) %
|
||||
'&');
|
||||
}
|
||||
|
||||
private:
|
||||
qi::rule<Iterator, Signature> source_rule;
|
||||
qi::rule<Iterator, Signature> destination_rule;
|
||||
qi::rule<Iterator, Signature> roundtrip_rule;
|
||||
qi::rule<Iterator, Signature> root_rule;
|
||||
|
||||
qi::symbols<char, engine::api::TripParameters::SourceType> source_type;
|
||||
qi::symbols<char, engine::api::TripParameters::DestinationType> destination_type;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,18 @@ template <typename T> class DistTableWrapper
|
||||
return table_[index];
|
||||
}
|
||||
|
||||
void SetValue(NodeID from, NodeID to, EdgeWeight value)
|
||||
{
|
||||
BOOST_ASSERT_MSG(from < number_of_nodes_, "from ID is out of bound");
|
||||
BOOST_ASSERT_MSG(to < number_of_nodes_, "to ID is out of bound");
|
||||
|
||||
const auto index = from * number_of_nodes_ + to;
|
||||
|
||||
BOOST_ASSERT_MSG(index < table_.size(), "index is out of bound");
|
||||
|
||||
table_[index] = value;
|
||||
}
|
||||
|
||||
ConstIterator begin() const { return std::begin(table_); }
|
||||
|
||||
Iterator begin() { return std::begin(table_); }
|
||||
|
||||
@@ -13,8 +13,7 @@ namespace util
|
||||
{
|
||||
|
||||
// This Wrapper provides all methods that are needed for extractor::TarjanSCC, when the graph is
|
||||
// given in a
|
||||
// matrix representation (e.g. as output from a distance table call)
|
||||
// given in a matrix representation (e.g. as output from a distance table call)
|
||||
|
||||
template <typename T> class MatrixGraphWrapper
|
||||
{
|
||||
|
||||
@@ -60,6 +60,8 @@ using NameID = std::uint32_t;
|
||||
using EdgeWeight = std::int32_t;
|
||||
using TurnPenalty = std::int16_t; // turn penalty in 100ms units
|
||||
|
||||
static const std::size_t INVALID_INDEX = std::numeric_limits<std::size_t>::max();
|
||||
|
||||
using LaneID = std::uint8_t;
|
||||
static const LaneID INVALID_LANEID = std::numeric_limits<LaneID>::max();
|
||||
using LaneDataID = std::uint16_t;
|
||||
|
||||
Reference in New Issue
Block a user