diff --git a/include/engine/api/isochrone_api.hpp b/include/engine/api/isochrone_api.hpp new file mode 100644 index 00000000000..f4373dd7f5e --- /dev/null +++ b/include/engine/api/isochrone_api.hpp @@ -0,0 +1,89 @@ +#ifndef ENGINE_API_ISOCHRONE_HPP +#define ENGINE_API_ISOCHRONE_HPP + +#include "engine/api/base_api.hpp" +#include "engine/api/isochrone_parameters.hpp" +#include "engine/plugins/isochrone.hpp" +#include "engine/plugins/plugin_base.hpp" + +namespace osrm +{ +namespace engine +{ +namespace api +{ + +class IsochroneAPI final : public BaseAPI +{ + + public: + const IsochroneParameters ¶meters; + + IsochroneAPI(const datafacade::BaseDataFacade &facade_, const IsochroneParameters ¶meters_) + : BaseAPI(facade_, parameters_), parameters(parameters_) + { + } + + void MakeResponse(const std::vector isochroneNodes, + const std::vector convexhull, + const std::vector concavehull, + util::json::Object &response) const + { + util::json::Array isochroneJson; + for (auto isochrone : isochroneNodes) + { + util::json::Object object; + + util::json::Object source; + source.values["lat"] = static_cast(util::toFloating(isochrone.node.lat)); + source.values["lon"] = static_cast(util::toFloating(isochrone.node.lon)); + object.values["p1"] = std::move(source); + + util::json::Object predecessor; + predecessor.values["lat"] = + static_cast(util::toFloating(isochrone.predecessor.lat)); + predecessor.values["lon"] = + static_cast(util::toFloating(isochrone.predecessor.lon)); + object.values["p2"] = std::move(predecessor); + + util::json::Object distance; + object.values["distance_from_start"] = isochrone.distance; + + util::json::Object duration; + object.values["duration_from_start"] = isochrone.duration; + + isochroneJson.values.push_back(object); + } + response.values["isochrone"] = std::move(isochroneJson); + + if (!convexhull.empty()) + { + util::json::Array convexhullArray; + for (engine::plugins::IsochroneNode n : convexhull) + { + util::json::Object point; + point.values["lat"] = static_cast(util::toFloating(n.node.lat)); + point.values["lon"] = static_cast(util::toFloating(n.node.lon)); + convexhullArray.values.push_back(point); + } + response.values["convexhull"] = std::move(convexhullArray); + } + + if (!concavehull.empty()) + { + util::json::Array concavehullArray; + for (engine::plugins::IsochroneNode n : concavehull) + { + util::json::Object point; + point.values["lat"] = static_cast(util::toFloating(n.node.lat)); + point.values["lon"] = static_cast(util::toFloating(n.node.lon)); + concavehullArray.values.push_back(point); + } + response.values["concavehull"] = std::move(concavehullArray); + } + } +}; +} +} +} +#endif // ENGINE_API_ISOCHRONE_HPP diff --git a/include/engine/api/isochrone_parameters.hpp b/include/engine/api/isochrone_parameters.hpp new file mode 100644 index 00000000000..ef2708f6c89 --- /dev/null +++ b/include/engine/api/isochrone_parameters.hpp @@ -0,0 +1,26 @@ +#ifndef ENGINE_API_ISOCHRONE_PARAMETERS_HPP +#define ENGINE_API_ISOCHRONE_PARAMETERS_HPP + +#include "engine/api/base_parameters.hpp" + +namespace osrm +{ +namespace engine +{ +namespace api +{ + +struct IsochroneParameters : public BaseParameters +{ + unsigned int duration = 0; + double distance = 0; + bool convexhull = false; + bool concavehull = false; + double threshold = 1; // max precision + + bool IsValid() const { return BaseParameters::IsValid(); } +}; +} +} +} +#endif // ENGINE_API_ISOCHRONE_PARAMETERS_HPP diff --git a/include/engine/engine.hpp b/include/engine/engine.hpp index aece1ac1a18..87e1f054740 100644 --- a/include/engine/engine.hpp +++ b/include/engine/engine.hpp @@ -36,6 +36,7 @@ struct NearestParameters; struct TripParameters; struct MatchParameters; struct TileParameters; +struct IsochroneParameters; } namespace plugins { @@ -45,6 +46,7 @@ class NearestPlugin; class TripPlugin; class MatchPlugin; class TilePlugin; +class IsochronePlugin; } // End fwd decls @@ -70,6 +72,7 @@ class Engine final Status Trip(const api::TripParameters ¶meters, util::json::Object &result) const; Status Match(const api::MatchParameters ¶meters, util::json::Object &result) const; Status Tile(const api::TileParameters ¶meters, std::string &result) const; + Status Isochrone(const api::IsochroneParameters ¶meters, util::json::Object &result) const; private: std::unique_ptr lock; @@ -80,6 +83,7 @@ class Engine final std::unique_ptr trip_plugin; std::unique_ptr match_plugin; std::unique_ptr tile_plugin; + std::unique_ptr isochrone_plugin; std::shared_ptr query_data_facade; }; diff --git a/include/engine/engine_config.hpp b/include/engine/engine_config.hpp index e73b3748275..c545f0bf5fd 100644 --- a/include/engine/engine_config.hpp +++ b/include/engine/engine_config.hpp @@ -68,6 +68,7 @@ struct EngineConfig final int max_locations_map_matching = -1; int max_results_nearest = -1; bool use_shared_memory = true; + bool use_isochrone = true; }; } } diff --git a/include/engine/plugins/isochrone.hpp b/include/engine/plugins/isochrone.hpp new file mode 100644 index 00000000000..0698ea40039 --- /dev/null +++ b/include/engine/plugins/isochrone.hpp @@ -0,0 +1,99 @@ +#ifndef ISOCHRONE_HPP +#define ISOCHRONE_HPP + +#include "engine/api/isochrone_parameters.hpp" +#include "engine/plugins/plugin_base.hpp" +#include "osrm/json_container.hpp" +#include "util/binary_heap.hpp" +#include "util/static_graph.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace osrm +{ +namespace engine +{ +namespace plugins +{ +struct HeapData +{ + NodeID parent; + HeapData(NodeID p) : parent(p) {} +}; +struct SimpleEdgeData +{ + SimpleEdgeData() : weight(INVALID_EDGE_WEIGHT), real(false) {} + SimpleEdgeData(unsigned weight_, bool real_) : weight(weight_), real(real_) {} + unsigned weight; + bool real; +}; + +struct IsochroneNode +{ + IsochroneNode(){}; + IsochroneNode(osrm::extractor::QueryNode node, + osrm::extractor::QueryNode predecessor, + double distance, + int duration) + : node(node), predecessor(predecessor), distance(distance), duration(duration) + { + } + + osrm::extractor::QueryNode node; + osrm::extractor::QueryNode predecessor; + double distance; + int duration; + + bool operator==(const IsochroneNode &n) const + { + if (n.node.node_id == node.node_id) + return true; + else + return false; + } +}; + +using SimpleGraph = util::StaticGraph; +using SimpleEdge = SimpleGraph::InputEdge; +using QueryHeap = osrm::util:: + BinaryHeap>; +typedef std::vector IsochroneVector; + +class IsochronePlugin final : public BasePlugin +{ + private: + boost::filesystem::path base; + std::shared_ptr graph; + std::vector coordinate_list; + std::vector graph_edge_list; + std::size_t number_of_nodes; + + std::size_t loadGraph(const std::string &path, + std::vector &coordinate_list, + std::vector &graph_edge_list); + + void dijkstraByDuration(IsochroneVector &set, NodeID &source, int duration); + void dijkstraByDistance(IsochroneVector &isochroneSet, NodeID &source, double distance); + void update(IsochroneVector &s, IsochroneNode node); + + public: + explicit IsochronePlugin(const std::string base); + + Status HandleRequest(const std::shared_ptr facade, + const api::IsochroneParameters ¶ms, + util::json::Object &json_result); +}; +} +} +} + +#endif // ISOCHRONE_HPP diff --git a/include/engine/search_engine.hpp b/include/engine/search_engine.hpp new file mode 100644 index 00000000000..cab476756cb --- /dev/null +++ b/include/engine/search_engine.hpp @@ -0,0 +1,50 @@ +#ifndef SEARCH_ENGINE_HPP +#define SEARCH_ENGINE_HPP + +#include "engine/routing_algorithms/alternative_path.hpp" +#include "engine/routing_algorithms/direct_shortest_path.hpp" +#include "engine/routing_algorithms/many_to_many.hpp" +#include "engine/routing_algorithms/map_matching.hpp" +#include "engine/routing_algorithms/one_to_many.hpp" +#include "engine/routing_algorithms/shortest_path.hpp" +#include "engine/search_engine_data.hpp" + +#include + +namespace osrm +{ +namespace engine +{ + +template class SearchEngine +{ + private: + DataFacadeT *facade; + SearchEngineData engine_working_data; + + public: + routing_algorithms::ShortestPathRouting shortest_path; + routing_algorithms::DirectShortestPathRouting direct_shortest_path; + routing_algorithms::AlternativeRouting alternative_path; + routing_algorithms::ManyToManyRouting distance_table; + routing_algorithms::MapMatching map_matching; + routing_algorithms::OneToManyRouting oneToMany; + + explicit SearchEngine(DataFacadeT *facade) + : facade(facade), shortest_path(facade, engine_working_data), + direct_shortest_path(facade, engine_working_data), + alternative_path(facade, engine_working_data), + distance_table(facade, engine_working_data), map_matching(facade, engine_working_data), + oneToMany(facade, engine_working_data) + { + static_assert(!std::is_pointer::value, "don't instantiate with ptr type"); + static_assert(std::is_object::value, + "don't instantiate with void, function, or reference"); + } + + ~SearchEngine() {} +}; +} +} + +#endif // SEARCH_ENGINE_HPP diff --git a/include/osrm/isochrone_parameters.hpp b/include/osrm/isochrone_parameters.hpp new file mode 100644 index 00000000000..dd196549507 --- /dev/null +++ b/include/osrm/isochrone_parameters.hpp @@ -0,0 +1,11 @@ +#ifndef GLOBAL_ISOCHRONE_PARAMETERS_HPP +#define GLOBAL_ISOCHRONE_PARAMETERS_HPP + +#include "engine/api/isochrone_parameters.hpp" + +namespace osrm +{ +using engine::api::IsochroneParameters; +} + +#endif // GLOBAL_ISOCHRONE_PARAMETERS_HPP diff --git a/include/osrm/osrm.hpp b/include/osrm/osrm.hpp index 0bbc175d000..9334c97c19e 100644 --- a/include/osrm/osrm.hpp +++ b/include/osrm/osrm.hpp @@ -44,6 +44,7 @@ using engine::api::NearestParameters; using engine::api::TripParameters; using engine::api::MatchParameters; using engine::api::TileParameters; +using engine::api::IsochroneParameters; /** * Represents a Open Source Routing Machine with access to its services. @@ -130,6 +131,14 @@ class OSRM final */ Status Tile(const TileParameters ¶meters, std::string &result) const; + /** + * Isochrone for given distance + * \param parameters nearest query specific parameters + * \return Status indicating success for the query or failure + * \see Status, NearestParameters and json::Object + */ + Status Isochrone(const IsochroneParameters ¶meters, json::Object &result) const; + private: std::unique_ptr engine_; }; diff --git a/include/osrm/osrm_fwd.hpp b/include/osrm/osrm_fwd.hpp index f0f1f17af62..9f436b0b755 100644 --- a/include/osrm/osrm_fwd.hpp +++ b/include/osrm/osrm_fwd.hpp @@ -52,6 +52,7 @@ struct NearestParameters; struct TripParameters; struct MatchParameters; struct TileParameters; +struct IsochroneParameters; } // ns api class Engine; diff --git a/include/server/api/isochrone_parameter_grammar.hpp b/include/server/api/isochrone_parameter_grammar.hpp new file mode 100644 index 00000000000..7e67a576148 --- /dev/null +++ b/include/server/api/isochrone_parameter_grammar.hpp @@ -0,0 +1,66 @@ +#ifndef OSRM_ISOCHRONE_PARAMETER_GRAMMAR_HPP +#define OSRM_ISOCHRONE_PARAMETER_GRAMMAR_HPP + +#include "server/api/base_parameters_grammar.hpp" +#include "engine/api/isochrone_parameters.hpp" + +#include + +namespace osrm +{ +namespace server +{ +namespace api +{ + +namespace +{ +namespace ph = boost::phoenix; +namespace qi = boost::spirit::qi; +} + +template +struct IsochroneParametersGrammar final : public BaseParametersGrammar +{ + using BaseGrammar = BaseParametersGrammar; + + IsochroneParametersGrammar() : BaseGrammar(root_rule) + { + + duration_rule = + (qi::lit("duration=") > + qi::uint_)[ph::bind(&engine::api::IsochroneParameters::duration, qi::_r1) = qi::_1]; + distance_rule = + (qi::lit("distance=") > + qi::double_)[ph::bind(&engine::api::IsochroneParameters::distance, qi::_r1) = qi::_1]; + convexhull_rule = + (qi::lit("convexhull=") > + qi::bool_)[ph::bind(&engine::api::IsochroneParameters::convexhull, qi::_r1) = qi::_1]; + concavehull_rule = + (qi::lit("concavehull=") > + qi::bool_)[ph::bind(&engine::api::IsochroneParameters::concavehull, qi::_r1) = qi::_1]; + threshold_rule = + (qi::lit("threshold=") > + qi::double_)[ph::bind(&engine::api::IsochroneParameters::threshold, qi::_r1) = qi::_1]; + + root_rule = + BaseGrammar::query_rule(qi::_r1) > -qi::lit(".json") > + -('?' > (distance_rule(qi::_r1) | duration_rule(qi::_r1) | convexhull_rule(qi::_r1) | + concavehull_rule(qi::_r1) | threshold_rule(qi::_r1)) % + '&'); + } + + private: + qi::rule root_rule; + qi::rule duration_rule; + qi::rule distance_rule; + qi::rule convexhull_rule; + qi::rule concavehull_rule; + qi::rule threshold_rule; +}; +} +} +} + +#endif // OSRM_ISOCHRONE_PARAMETER_GRAMMAR_HPP diff --git a/include/server/service/isochrone_service.hpp b/include/server/service/isochrone_service.hpp new file mode 100644 index 00000000000..4774a94224f --- /dev/null +++ b/include/server/service/isochrone_service.hpp @@ -0,0 +1,33 @@ +#ifndef SERVER_SERVICE_ISOCHRONE_SERVICE_HPP +#define SERVER_SERVICE_ISOCHRONE_SERVICE_HPP + +#include "server/service/base_service.hpp" + +#include "engine/status.hpp" +#include "osrm/osrm.hpp" +#include "util/coordinate.hpp" + +#include +#include + +namespace osrm +{ +namespace server +{ +namespace service +{ + +class IsochroneService final : public BaseService +{ + public: + IsochroneService(OSRM &routing_machine) : BaseService(routing_machine) {} + + engine::Status + RunQuery(std::size_t prefix_length, std::string &query, ResultT &result) final override; + + unsigned GetVersion() final override { return 1; } +}; +} +} +} +#endif // SERVER_SERVICE_ISOCHRONE_SERVICE_HPP diff --git a/include/storage/shared_memory.hpp b/include/storage/shared_memory.hpp index 579b17edb20..2d58be9ef12 100644 --- a/include/storage/shared_memory.hpp +++ b/include/storage/shared_memory.hpp @@ -1,6 +1,5 @@ #ifndef SHARED_MEMORY_HPP #define SHARED_MEMORY_HPP - #include "util/exception.hpp" #include "util/simple_logger.hpp" diff --git a/include/storage/storage_config.hpp b/include/storage/storage_config.hpp index e68129a11cb..25b37d4cedb 100644 --- a/include/storage/storage_config.hpp +++ b/include/storage/storage_config.hpp @@ -52,6 +52,7 @@ struct StorageConfig final StorageConfig(const boost::filesystem::path &base); bool IsValid() const; + boost::filesystem::path base; boost::filesystem::path ram_index_path; boost::filesystem::path file_index_path; boost::filesystem::path hsgr_data_path; diff --git a/include/util/concave_hull.hpp b/include/util/concave_hull.hpp new file mode 100644 index 00000000000..21da7c8ddaa --- /dev/null +++ b/include/util/concave_hull.hpp @@ -0,0 +1,214 @@ +#ifndef OSRM_CONCAVE_HULL_HPP +#define OSRM_CONCAVE_HULL_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace osrm +{ +namespace util +{ + +// Compute the dot product AB ⋅ BC +double dot(const engine::plugins::IsochroneNode &A, + const engine::plugins::IsochroneNode &B, + const engine::plugins::IsochroneNode &C) +{ + const double Ax = static_cast(util::toFloating(A.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Ay = static_cast(util::toFloating(A.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Bx = static_cast(util::toFloating(B.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double By = static_cast(util::toFloating(B.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Cx = static_cast(util::toFloating(C.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Cy = static_cast(util::toFloating(C.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + + const double Axx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Ax) * std::cos(Ay); + const double Ayy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Ax) * std::sin(Ay); + const double Bxx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Bx) * std::cos(By); + const double Byy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Bx) * std::sin(By); + const double Cxx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Cx) * std::cos(Cy); + const double Cyy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Cx) * std::sin(Cy); + double AB[2]; + double BC[2]; + AB[0] = Bxx - Axx; + AB[1] = Byy - Ayy; + BC[0] = Cxx - Bxx; + BC[1] = Cyy - Byy; + const double dot = AB[0] * BC[0] + AB[1] * BC[1]; + return dot; +} +// Compute the cross product AB x AC +double cross(const engine::plugins::IsochroneNode &A, + const engine::plugins::IsochroneNode &B, + const engine::plugins::IsochroneNode &C) +{ + const double Ax = static_cast(util::toFloating(A.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Ay = static_cast(util::toFloating(A.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Bx = static_cast(util::toFloating(B.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double By = static_cast(util::toFloating(B.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Cx = static_cast(util::toFloating(C.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Cy = static_cast(util::toFloating(C.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + + const double Axx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Ax) * std::cos(Ay); + const double Ayy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Ax) * std::sin(Ay); + const double Bxx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Bx) * std::cos(By); + const double Byy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Bx) * std::sin(By); + const double Cxx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Cx) * std::cos(Cy); + const double Cyy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Cx) * std::sin(Cy); + double AB[2]; + double AC[2]; + AB[0] = Bxx - Axx; + AB[1] = Byy - Ayy; + AC[0] = Cxx - Axx; + AC[1] = Cyy - Ayy; + const double cross = AB[0] * AC[1] - AB[1] * AC[0]; + return cross; +} +// Compute the distance from A to B +double distance(const engine::plugins::IsochroneNode &A, const engine::plugins::IsochroneNode &B) +{ + const double Ax = static_cast(util::toFloating(A.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Ay = static_cast(util::toFloating(A.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double Bx = static_cast(util::toFloating(B.node.lon)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + const double By = static_cast(util::toFloating(B.node.lat)) * + util::coordinate_calculation::detail::DEGREE_TO_RAD; + + const double Axx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Ax) * std::cos(Ay); + const double Ayy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Ax) * std::sin(Ay); + const double Bxx = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Bx) * std::cos(By); + const double Byy = + util::coordinate_calculation::detail::EARTH_RADIUS * std::cos(Bx) * std::sin(By); + const double d1 = Axx - Bxx; + const double d2 = Ayy - Byy; + return std::sqrt(d1 * d1 + d2 * d2); +} + +// Compute the distance from AB to C +// if isSegment is true, AB is a segment, not a line. +double linePointDist(const engine::plugins::IsochroneNode &A, + const engine::plugins::IsochroneNode &B, + const engine::plugins::IsochroneNode &C, + bool isSegment) +{ + const double dist = cross(A, B, C) / distance(A, B); + if (isSegment) + { + const double dot1 = dot(A, B, C); + if (dot1 > 0) + { + return distance(B, C); + } + const double dot2 = dot(B, A, C); + if (dot2 > 0) + { + return distance(A, C); + } + } + return std::abs(dist); +} +double distanceToEdge(const engine::plugins::IsochroneNode &p, + const engine::plugins::IsochroneNode &e1, + const engine::plugins::IsochroneNode &e2) +{ + const std::uint64_t pX = static_cast(p.node.lon); + const std::uint64_t pY = static_cast(p.node.lat); + const std::uint64_t vX = static_cast(e1.node.lon); + const std::uint64_t vY = static_cast(e1.node.lat); + const std::uint64_t wX = static_cast(e2.node.lon); + const std::uint64_t wY = static_cast(e2.node.lat); + + const double distance = std::abs((wX - vX) * (vY - pY) - (vX - pX) * (wY - vY)) / + std::sqrt(std::pow(wX - vX, 2) + std::pow(wY - vY, 2)); + return distance; +} + +std::vector +concavehull(std::vector &convexhull, + unsigned int threshold, + std::vector &isochrone) +{ + + // std::vector concavehull(convexhull); + std::vector concavehull = convexhull; + concavehull.push_back(concavehull[0]); + + for (std::vector::iterator it = concavehull.begin(); + it != concavehull.end() - 1; + ++it) + { + // find the nearest inner point pk ∈ G from the edge (ci1, ci2); + // pk should not closer to neighbor edges of (ci1, ci2) than (ci1, ci2) + engine::plugins::IsochroneNode n1 = *it; + engine::plugins::IsochroneNode n2 = *std::next(it); + engine::plugins::IsochroneNode pk; + double d = std::numeric_limits::max(); + double edgelength = distance(n1, n2); + + for (auto iso : isochrone) + { + if (std::find(concavehull.begin(), concavehull.end(), iso) != concavehull.end()) + { + continue; + } + auto temp = linePointDist(n1, n2, iso, true); + + if (temp > edgelength) + { + continue; + } + if (d > temp) + { + d = temp; + pk = iso; + } + } + if ((edgelength / d) > threshold) + { + it = concavehull.insert(std::next(it), pk); + it = std::prev(it); + // util::SimpleLogger().Write() << "List-Size: " << concavehull.size(); + } + } + util::SimpleLogger().Write() << "Convex-Size: " << convexhull.size(); + return concavehull; +} +} +} +#endif // OSRM_CONCAVE_HULL_HPP diff --git a/include/util/graph_loader.hpp b/include/util/graph_loader.hpp index 7255270395c..6ecde286a12 100644 --- a/include/util/graph_loader.hpp +++ b/include/util/graph_loader.hpp @@ -32,8 +32,8 @@ namespace util * The since the restrictions reference nodes using their external node id, * we need to renumber it to the new internal id. */ -unsigned loadRestrictionsFromFile(std::istream &input_stream, - std::vector &restriction_list) +inline unsigned loadRestrictionsFromFile(std::istream &input_stream, + std::vector &restriction_list) { const FingerPrint fingerprint_valid = FingerPrint::GetValid(); FingerPrint fingerprint_loaded; @@ -62,10 +62,10 @@ unsigned loadRestrictionsFromFile(std::istream &input_stream, * - list of traffic lights * - nodes indexed by their internal (non-osm) id */ -NodeID loadNodesFromFile(std::istream &input_stream, - std::vector &barrier_node_list, - std::vector &traffic_light_node_list, - std::vector &node_array) +inline NodeID loadNodesFromFile(std::istream &input_stream, + std::vector &barrier_node_list, + std::vector &traffic_light_node_list, + std::vector &node_array) { const FingerPrint fingerprint_valid = FingerPrint::GetValid(); FingerPrint fingerprint_loaded; @@ -107,8 +107,8 @@ NodeID loadNodesFromFile(std::istream &input_stream, /** * Reads a .osrm file and produces the edges. */ -NodeID loadEdgesFromFile(std::istream &input_stream, - std::vector &edge_list) +inline NodeID loadEdgesFromFile(std::istream &input_stream, + std::vector &edge_list) { EdgeID m; input_stream.read(reinterpret_cast(&m), sizeof(unsigned)); diff --git a/include/util/monotone_chain.hpp b/include/util/monotone_chain.hpp new file mode 100644 index 00000000000..7efdd0e6ef1 --- /dev/null +++ b/include/util/monotone_chain.hpp @@ -0,0 +1,70 @@ +#ifndef OSRM_MONOTONE_CHAIN_HPP +#define OSRM_MONOTONE_CHAIN_HPP + +#include +#include +#include +#include + +#include +#include +#include + +namespace osrm +{ +namespace util +{ +using Node = engine::plugins::IsochroneNode; + +int ccw(Node &p, Node &q, Node &r) +{ + // Using double values to avoid integer overflows + double Q_lat = static_cast(util::toFloating(q.node.lat)); + double Q_lon = static_cast(util::toFloating(q.node.lon)); + double R_lat = static_cast(util::toFloating(r.node.lat)); + double R_lon = static_cast(util::toFloating(r.node.lon)); + double P_lat = static_cast(util::toFloating(p.node.lat)); + double P_lon = static_cast(util::toFloating(p.node.lon)); + + double val = (Q_lat - P_lat) * (R_lon - P_lon) - (Q_lon - P_lon) * (R_lat - P_lat); + if (val == 0) + { + return 0; // colinear + } + return (val < 0) ? -1 : 1; // clock or counterclock wise +} + +std::vector monotoneChain(std::vector &nodes) +{ + std::vector P(nodes); + int n = P.size(), k = 0; + std::vector H(2 * n); + + // sort Points by lat and lon + std::sort(P.begin(), P.end(), [&](const Node &a, const Node &b) { + return a.node.lat < b.node.lat || (a.node.lat == b.node.lat && a.node.lon < b.node.lon); + }); + + // Build lower hull + for (int i = 0; i < n; ++i) + { + while (k >= 2 && ccw(H[k - 2], H[k - 1], P[i]) <= 0) + k--; + H[k++] = P[i]; + } + + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; i--) + { + while (k >= t && ccw(H[k - 2], H[k - 1], P[i]) <= 0) + k--; + H[k++] = P[i]; + } + + H.resize(k - 1); + + return H; +} +} +} +#endif // OSRM_MONOTONE_CHAIN_HPP diff --git a/include/util/packed_vector.hpp b/include/util/packed_vector.hpp index 343763aa44f..705b12267f2 100644 --- a/include/util/packed_vector.hpp +++ b/include/util/packed_vector.hpp @@ -12,6 +12,27 @@ namespace osrm namespace util { +const constexpr std::size_t BITSIZE = 33; +const constexpr std::size_t ELEMSIZE = 64; +const constexpr std::size_t PACKSIZE = BITSIZE * ELEMSIZE; + +/** + * Returns the size of the packed vector datastructure with `elements` packed elements (the size of + * its underlying vector) + */ +inline std::size_t PackedVectorSize(std::size_t elements) +{ + return ceil(float(elements) / ELEMSIZE) * BITSIZE; +}; + +/** + * Returns the capacity of a packed vector with underlying vector size `vec_size` + */ +inline std::size_t PackedVectorCapacity(std::size_t vec_size) +{ + return floor(float(vec_size) / BITSIZE) * ELEMSIZE; +} + /** * Since OSM node IDs are (at the time of writing) not quite yet overflowing 32 bits, and * will predictably be containable within 33 bits for a long time, the following packs diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 429f68bb243..d43355ffb71 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3,6 +3,7 @@ #include "engine/engine_config.hpp" #include "engine/status.hpp" +#include "engine/plugins/isochrone.hpp" #include "engine/plugins/match.hpp" #include "engine/plugins/nearest.hpp" #include "engine/plugins/table.hpp" @@ -95,6 +96,10 @@ Engine::Engine(const EngineConfig &config) trip_plugin = std::make_unique(config.max_locations_trip); match_plugin = std::make_unique(config.max_locations_map_matching); tile_plugin = std::make_unique(); + if (config.use_isochrone) + { + isochrone_plugin = std::make_unique(config.storage_config.base.string()); + } } // make sure we deallocate the unique ptr at a position where we know the size of the plugins @@ -131,6 +136,10 @@ Status Engine::Tile(const api::TileParameters ¶ms, std::string &result) cons { return RunQuery(lock, query_data_facade, params, *tile_plugin, result); } +Status Engine::Isochrone(const api::IsochroneParameters ¶ms, util::json::Object &result) const +{ + return RunQuery(lock, query_data_facade, params, *isochrone_plugin, result); +} } // engine ns } // osrm ns diff --git a/src/engine/plugins/isochrone.cpp b/src/engine/plugins/isochrone.cpp new file mode 100644 index 00000000000..60b99361a87 --- /dev/null +++ b/src/engine/plugins/isochrone.cpp @@ -0,0 +1,317 @@ +#include "engine/plugins/isochrone.hpp" +#include "engine/api/isochrone_api.hpp" +#include "engine/phantom_node.hpp" +#include "util/concave_hull.hpp" +#include "util/coordinate_calculation.hpp" +#include "util/graph_loader.hpp" +#include "util/monotone_chain.hpp" +#include "util/simple_logger.hpp" +#include "util/timing_util.hpp" + +#include + +namespace osrm +{ +namespace engine +{ +namespace plugins +{ + +IsochronePlugin::IsochronePlugin(const std::string base) + : base{base} +{ + + // Loads Graph into memory + if (!base.empty()) + { + number_of_nodes = loadGraph(base, coordinate_list, graph_edge_list); + } + + tbb::parallel_sort(graph_edge_list.begin(), graph_edge_list.end()); + graph = std::make_shared(number_of_nodes, graph_edge_list); + graph_edge_list.clear(); + graph_edge_list.shrink_to_fit(); +} + +Status IsochronePlugin::HandleRequest(const std::shared_ptr facade, + const api::IsochroneParameters ¶ms, + util::json::Object &json_result) +{ + BOOST_ASSERT(params.IsValid()); + + 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); + } + + if (params.distance != 0 && params.duration != 0) + { + return Error("InvalidOptions", "Only distance or duration should be set", json_result); + } + + if (params.concavehull == true && params.convexhull == false) + { + return Error( + "InvalidOptions", "If concavehull is set, convexhull must be set too", json_result); + } + + auto phantomnodes = GetPhantomNodes(*facade, params, 1); + + if (phantomnodes.front().size() <= 0) + { + return Error("PhantomNode", "PhantomNode couldnt be found for coordinate", json_result); + } + + util::SimpleLogger().Write() << "asdasd"; + auto phantom = phantomnodes.front(); + std::vector forward_id_vector; + + auto source = (*facade).GetUncompressedReverseGeometry(phantom.front().phantom_node.packed_geometry_id).front(); + +// (*facade).GetUncompressedGeometry(phantom.front().phantom_node.reverse_packed_geometry_id, +// forward_id_vector); +// auto source = phantom.; + + IsochroneVector isochroneVector; + IsochroneVector convexhull; + IsochroneVector concavehull; + + if (params.duration != 0) + { + TIMER_START(DIJKSTRA); + dijkstraByDuration(isochroneVector, source, params.duration); + TIMER_STOP(DIJKSTRA); + util::SimpleLogger().Write() << "DijkstraByDuration took: " << TIMER_MSEC(DIJKSTRA) << "ms"; + TIMER_START(SORTING); + std::sort(isochroneVector.begin(), + isochroneVector.end(), + [&](const IsochroneNode n1, const IsochroneNode n2) { + return n1.duration < n2.duration; + }); + TIMER_STOP(SORTING); + util::SimpleLogger().Write() << "SORTING took: " << TIMER_MSEC(SORTING) << "ms"; + } + if (params.distance != 0) + { + TIMER_START(DIJKSTRA); + dijkstraByDistance(isochroneVector, source, params.distance); + TIMER_STOP(DIJKSTRA); + util::SimpleLogger().Write() << "DijkstraByDistance took: " << TIMER_MSEC(DIJKSTRA) << "ms"; + TIMER_START(SORTING); + std::sort(isochroneVector.begin(), + isochroneVector.end(), + [&](const IsochroneNode n1, const IsochroneNode n2) { + return n1.distance < n2.distance; + }); + TIMER_STOP(SORTING); + util::SimpleLogger().Write() << "SORTING took: " << TIMER_MSEC(SORTING) << "ms"; + } + + util::SimpleLogger().Write() << "Nodes Found: " << isochroneVector.size(); + // Optional param for calculating Convex Hull + if (params.convexhull) + { + + TIMER_START(CONVEXHULL); + convexhull = util::monotoneChain(isochroneVector); + TIMER_STOP(CONVEXHULL); + util::SimpleLogger().Write() << "CONVEXHULL took: " << TIMER_MSEC(CONVEXHULL) << "ms"; + } + if (params.concavehull && params.convexhull) + { + TIMER_START(CONCAVEHULL); + concavehull = util::concavehull(convexhull, params.threshold, isochroneVector); + TIMER_STOP(CONCAVEHULL); + util::SimpleLogger().Write() << "CONCAVEHULL took: " << TIMER_MSEC(CONCAVEHULL) << "ms"; + } + + TIMER_START(RESPONSE); + api::IsochroneAPI isochroneAPI{*facade, params}; + isochroneAPI.MakeResponse(isochroneVector, convexhull, concavehull, json_result); + TIMER_STOP(RESPONSE); + util::SimpleLogger().Write() << "RESPONSE took: " << TIMER_MSEC(RESPONSE) << "ms"; + isochroneVector.clear(); + isochroneVector.shrink_to_fit(); + convexhull.clear(); + convexhull.shrink_to_fit(); + concavehull.clear(); + concavehull.shrink_to_fit(); + return Status::Ok; +} + +void IsochronePlugin::dijkstraByDuration(IsochroneVector &isochroneSet, + NodeID &source, + int duration) +{ + + QueryHeap heap(number_of_nodes); + heap.Insert(source, 0, source); + + isochroneSet.emplace_back( + IsochroneNode(coordinate_list[source], coordinate_list[source], 0, 0)); + + int steps = 0; + int MAX_DURATION = duration * 60 * 10; + { + // Standard Dijkstra search, terminating when path length > MAX + while (!heap.Empty()) + { + steps++; + const NodeID source = heap.DeleteMin(); + const std::int32_t weight = heap.GetKey(source); + + for (const auto current_edge : graph->GetAdjacentEdgeRange(source)) + { + const auto target = graph->GetTarget(current_edge); + if (target != SPECIAL_NODEID) + { + const auto data = graph->GetEdgeData(current_edge); + if (data.real) + { + int to_duration = weight + data.weight; + if (to_duration > MAX_DURATION) + { + continue; + } + else if (!heap.WasInserted(target)) + { + heap.Insert(target, to_duration, source); + isochroneSet.emplace_back(IsochroneNode( + coordinate_list[target], coordinate_list[source], 0, to_duration)); + } + else if (to_duration < heap.GetKey(target)) + { + heap.GetData(target).parent = source; + heap.DecreaseKey(target, to_duration); + update(isochroneSet, + IsochroneNode(coordinate_list[target], + coordinate_list[source], + 0, + to_duration)); + } + } + } + } + } + } + util::SimpleLogger().Write() << "Steps took: " << steps; +} +void IsochronePlugin::dijkstraByDistance(IsochroneVector &isochroneSet, + NodeID &source, + double distance) +{ + QueryHeap heap(number_of_nodes); + heap.Insert(source, 0, source); + + isochroneSet.emplace_back( + IsochroneNode(coordinate_list[source], coordinate_list[source], 0, 0)); + int steps = 0; + int MAX_DISTANCE = distance; + { + // Standard Dijkstra search, terminating when path length > MAX + while (!heap.Empty()) + { + steps++; + NodeID source = heap.DeleteMin(); + std::int32_t weight = heap.GetKey(source); + + for (const auto current_edge : graph->GetAdjacentEdgeRange(source)) + { + const auto target = graph->GetTarget(current_edge); + if (target != SPECIAL_NODEID) + { + const auto data = graph->GetEdgeData(current_edge); + if (data.real) + { + Coordinate s(coordinate_list[source].lon, coordinate_list[source].lat); + Coordinate t(coordinate_list[target].lon, coordinate_list[target].lat); + // FIXME this might not be accurate enough + int to_distance = + static_cast( + util::coordinate_calculation::haversineDistance(s, t)) + + weight; + + if (to_distance > MAX_DISTANCE) + { + continue; + } + else if (!heap.WasInserted(target)) + { + heap.Insert(target, to_distance, source); + isochroneSet.emplace_back(IsochroneNode( + coordinate_list[target], coordinate_list[source], to_distance, 0)); + } + else if (to_distance < heap.GetKey(target)) + { + heap.GetData(target).parent = source; + heap.DecreaseKey(target, to_distance); + update(isochroneSet, + IsochroneNode(coordinate_list[target], + coordinate_list[source], + to_distance, + 0)); + } + } + } + } + } + } + util::SimpleLogger().Write() << "Steps took: " << steps; +} +std::size_t IsochronePlugin::loadGraph(const std::string &path, + std::vector &coordinate_list, + std::vector &graph_edge_list) +{ + + std::ifstream input_stream(path, std::ifstream::in | std::ifstream::binary); + if (!input_stream.is_open()) + { + throw util::exception("Cannot open osrm file"); + } + + // load graph data + std::vector edge_list; + std::vector traffic_light_node_list; + std::vector barrier_node_list; + + auto number_of_nodes = util::loadNodesFromFile( + input_stream, barrier_node_list, traffic_light_node_list, coordinate_list); + + util::loadEdgesFromFile(input_stream, edge_list); + traffic_light_node_list.clear(); + traffic_light_node_list.shrink_to_fit(); + + // Building an node-based graph + for (const auto &input_edge : edge_list) + { + if (input_edge.source == input_edge.target) + { + continue; + } + // forward edge + graph_edge_list.emplace_back( + input_edge.source, input_edge.target, input_edge.weight, input_edge.forward); + // backward edge + graph_edge_list.emplace_back( + input_edge.target, input_edge.source, input_edge.weight, input_edge.backward); + } + + return number_of_nodes; +} + +void IsochronePlugin::update(IsochroneVector &v, IsochroneNode n) +{ + for (auto node : v) + { + if (node.node.node_id == n.node.node_id) + { + node = n; + } + } +} +} +} +} diff --git a/src/osrm/osrm.cpp b/src/osrm/osrm.cpp index 7b0270086ea..cba5f574d32 100644 --- a/src/osrm/osrm.cpp +++ b/src/osrm/osrm.cpp @@ -1,4 +1,5 @@ #include "osrm/osrm.hpp" +#include "engine/api/isochrone_parameters.hpp" #include "engine/api/match_parameters.hpp" #include "engine/api/nearest_parameters.hpp" #include "engine/api/route_parameters.hpp" @@ -52,5 +53,9 @@ engine::Status OSRM::Tile(const engine::api::TileParameters ¶ms, std::string { return engine_->Tile(params, result); } +engine::Status OSRM::Isochrone(const engine::api::IsochroneParameters ¶ms, json::Object &result) const +{ + return engine_->Isochrone(params, result); +} } // ns osrm diff --git a/src/server/api/parameters_parser.cpp b/src/server/api/parameters_parser.cpp index e4b201e8eea..7ab495747dd 100644 --- a/src/server/api/parameters_parser.cpp +++ b/src/server/api/parameters_parser.cpp @@ -1,5 +1,6 @@ #include "server/api/parameters_parser.hpp" +#include "server/api/isochrone_parameter_grammar.hpp" #include "server/api/match_parameter_grammar.hpp" #include "server/api/nearest_parameter_grammar.hpp" #include "server/api/route_parameters_grammar.hpp" @@ -26,7 +27,8 @@ using is_grammar_t = std::is_same, T>::value || std::is_same, T>::value || std::is_same, T>::value || - std::is_same, T>::value>; + std::is_same, T>::value || + std::is_same, T>::value>; template parseParameters(std::string::iterat { return detail::parseParameters>(iter, end); } +template <> +boost::optional parseParameters(std::string::iterator &iter, + const std::string::iterator end) +{ + return detail::parseParameters>( + iter, end); +} } // ns api } // ns server diff --git a/src/server/service/isochrone_service.cpp b/src/server/service/isochrone_service.cpp new file mode 100644 index 00000000000..31d3e2ae461 --- /dev/null +++ b/src/server/service/isochrone_service.cpp @@ -0,0 +1,75 @@ +#include "server/service/isochrone_service.hpp" +#include "server/service/utils.hpp" + +#include "server/api/parameters_parser.hpp" +#include "engine/api/isochrone_parameters.hpp" + +#include "util/json_container.hpp" + +#include +#include + +namespace osrm +{ +namespace server +{ +namespace service +{ + +namespace +{ +std::string getWrongOptionHelp(const engine::api::IsochroneParameters ¶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 +IsochroneService::RunQuery(std::size_t prefix_length, std::string &query, ResultT &result) +{ + result = util::json::Object(); + auto &json_result = result.get(); + + auto query_iterator = query.begin(); + auto parameters = + api::parseParameters(query_iterator, query.end()); + if (!parameters || query_iterator != query.end()) + { + const auto position = std::distance(query.begin(), query_iterator); + json_result.values["code"] = "InvalidQuery"; + json_result.values["message"] = + "Query string malformed close to position " + std::to_string(prefix_length + position); + return engine::Status::Error; + } + BOOST_ASSERT(parameters); + + if (!parameters->IsValid()) + { + json_result.values["code"] = "InvalidOptions"; + json_result.values["message"] = getWrongOptionHelp(*parameters); + return engine::Status::Error; + } + BOOST_ASSERT(parameters->IsValid()); + + return BaseService::routing_machine.Isochrone(*parameters, json_result); +} +} +} +} diff --git a/src/server/service_handler.cpp b/src/server/service_handler.cpp index 1bba0f6cd07..065d0cc7741 100644 --- a/src/server/service_handler.cpp +++ b/src/server/service_handler.cpp @@ -1,5 +1,7 @@ #include "server/service_handler.hpp" +#include +#include "server/service/isochrone_service.hpp" #include "server/service/match_service.hpp" #include "server/service/nearest_service.hpp" #include "server/service/route_service.hpp" @@ -23,6 +25,10 @@ ServiceHandler::ServiceHandler(osrm::EngineConfig &config) : routing_machine(con service_map["trip"] = util::make_unique(routing_machine); service_map["match"] = util::make_unique(routing_machine); service_map["tile"] = util::make_unique(routing_machine); + if (config.use_isochrone) + { + service_map["isochrone"] = util::make_unique(routing_machine); + } } engine::Status ServiceHandler::RunQuery(api::ParsedURL parsed_url, diff --git a/src/storage/storage_config.cpp b/src/storage/storage_config.cpp index c1edaf6f790..8d030d9385e 100644 --- a/src/storage/storage_config.cpp +++ b/src/storage/storage_config.cpp @@ -9,10 +9,11 @@ namespace storage { StorageConfig::StorageConfig(const boost::filesystem::path &base) - : ram_index_path{base.string() + ".ramIndex"}, file_index_path{base.string() + ".fileIndex"}, - hsgr_data_path{base.string() + ".hsgr"}, nodes_data_path{base.string() + ".nodes"}, - edges_data_path{base.string() + ".edges"}, core_data_path{base.string() + ".core"}, - geometries_path{base.string() + ".geometry"}, timestamp_path{base.string() + ".timestamp"}, + : base{base.string()}, ram_index_path{base.string() + ".ramIndex"}, + file_index_path{base.string() + ".fileIndex"}, hsgr_data_path{base.string() + ".hsgr"}, + nodes_data_path{base.string() + ".nodes"}, edges_data_path{base.string() + ".edges"}, + core_data_path{base.string() + ".core"}, geometries_path{base.string() + ".geometry"}, + timestamp_path{base.string() + ".timestamp"}, datasource_names_path{base.string() + ".datasource_names"}, datasource_indexes_path{base.string() + ".datasource_indexes"}, names_data_path{base.string() + ".names"}, properties_path{base.string() + ".properties"}, @@ -23,8 +24,9 @@ StorageConfig::StorageConfig(const boost::filesystem::path &base) bool StorageConfig::IsValid() const { - const constexpr auto num_files = 13; - const boost::filesystem::path paths[num_files] = {ram_index_path, + const constexpr auto num_files = 14; + const boost::filesystem::path paths[num_files] = {base, + ram_index_path, file_index_path, hsgr_data_path, nodes_data_path, diff --git a/src/tools/components.cpp b/src/tools/components.cpp index f2e4cf73279..3cf5c3dd693 100644 --- a/src/tools/components.cpp +++ b/src/tools/components.cpp @@ -227,4 +227,7 @@ int main(int argc, char *argv[]) osrm::util::SimpleLogger().Write() << "finished component analysis"; return EXIT_SUCCESS; +} catch(...) { + } + diff --git a/src/tools/routed.cpp b/src/tools/routed.cpp index d0873025127..b22a70482f0 100644 --- a/src/tools/routed.cpp +++ b/src/tools/routed.cpp @@ -64,7 +64,8 @@ inline unsigned generateServerProgramOptions(const int argc, int &max_locations_viaroute, int &max_locations_distance_table, int &max_locations_map_matching, - int &max_results_nearest) + int &max_results_nearest, + bool &use_isochrone) { using boost::program_options::value; using boost::filesystem::path; @@ -90,6 +91,9 @@ inline unsigned generateServerProgramOptions(const int argc, ("shared-memory,s", value(&use_shared_memory)->implicit_value(true)->default_value(false), "Load data from shared memory") // + ("isochrone,I", + value(&use_isochrone)->implicit_value(true)->default_value(false), + "Load the isochrone plugin") // ("max-viaroute-size", value(&max_locations_viaroute)->default_value(500), "Max. locations supported in viaroute query") // @@ -194,7 +198,8 @@ int main(int argc, const char *argv[]) try config.max_locations_viaroute, config.max_locations_distance_table, config.max_locations_map_matching, - config.max_results_nearest); + config.max_results_nearest, + config.use_isochrone); if (init_result == INIT_OK_DO_NOT_START_ENGINE) { return EXIT_SUCCESS;