diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b0d71ace..b743d818f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ file(GLOB CoordinateGlob DataStructures/Coordinate.cpp) file(GLOB AlgorithmGlob Algorithms/*.cpp) file(GLOB HttpGlob Server/Http/*.cpp) file(GLOB LibOSRMGlob Library/*.cpp) -file(GLOB DataStructureTestsGlob UnitTests/DataStructures/*.cpp) +file(GLOB DataStructureTestsGlob UnitTests/DataStructures/*.cpp DataStructures/HilbertValue.cpp) set( OSRMSources @@ -196,7 +196,7 @@ target_link_libraries(osrm-extract ${Boost_LIBRARIES} FINGERPRINT GITDESCRIPTION target_link_libraries(osrm-prepare ${Boost_LIBRARIES} FINGERPRINT GITDESCRIPTION COORDLIB IMPORT) target_link_libraries(osrm-routed ${Boost_LIBRARIES} ${OPTIONAL_SOCKET_LIBS} OSRM FINGERPRINT GITDESCRIPTION) target_link_libraries(osrm-datastore ${Boost_LIBRARIES} FINGERPRINT GITDESCRIPTION COORDLIB) -target_link_libraries(datastructure-tests ${Boost_LIBRARIES}) +target_link_libraries(datastructure-tests ${Boost_LIBRARIES} COORDLIB) find_package(Threads REQUIRED) target_link_libraries(osrm-extract ${CMAKE_THREAD_LIBS_INIT}) diff --git a/UnitTests/DataStructures/StaticRTreeTest.cpp b/UnitTests/DataStructures/StaticRTreeTest.cpp new file mode 100644 index 000000000..deef96b5a --- /dev/null +++ b/UnitTests/DataStructures/StaticRTreeTest.cpp @@ -0,0 +1,347 @@ +#include "../../DataStructures/StaticRTree.h" +#include "../../DataStructures/QueryNode.h" +#include "../../DataStructures/EdgeBasedNode.h" +#include "../../Include/osrm/Coordinate.h" +#include "../../typedefs.h" + +#include +#include +#include + +#include +#include + +BOOST_AUTO_TEST_SUITE(static_rtree) + +constexpr uint32_t TEST_BRANCHING_FACTOR = 8; +constexpr uint32_t TEST_LEAF_NODE_SIZE = 64; + +typedef EdgeBasedNode TestData; +typedef StaticRTree, + false, + TEST_BRANCHING_FACTOR, + TEST_LEAF_NODE_SIZE> + TestStaticRTree; + +// Choosen by a fair W20 dice roll (this value is completely arbitrary) +constexpr unsigned RANDOM_SEED = 15; +constexpr int32_t WORLD_MIN_LAT = -90*COORDINATE_PRECISION; +constexpr int32_t WORLD_MAX_LAT = 90*COORDINATE_PRECISION; +constexpr int32_t WORLD_MIN_LON = -180*COORDINATE_PRECISION; +constexpr int32_t WORLD_MAX_LON = 180*COORDINATE_PRECISION; + +class LinearSearchNN +{ +public: + LinearSearchNN(const std::shared_ptr>& coords, + const std::vector& edges) + : coords(coords) + , edges(edges) + { } + + bool LocateClosestEndPointForCoordinate(const FixedPointCoordinate &input_coordinate, + FixedPointCoordinate &result_coordinate, + const unsigned zoom_level) + { + bool ignore_tiny_components = (zoom_level <= 14); + + float min_dist = std::numeric_limits::max(); + FixedPointCoordinate min_coord; + for (const TestData& e : edges) + { + if (ignore_tiny_components && e.is_in_tiny_cc) + continue; + + const FixedPointCoordinate& start = coords->at(e.u); + const FixedPointCoordinate& end = coords->at(e.v); + float distance = FixedPointCoordinate::ApproximateEuclideanDistance( + input_coordinate.lat, + input_coordinate.lon, + start.lat, + start.lon); + if (distance < min_dist) + { + min_coord = start; + min_dist = distance; + } + + distance = FixedPointCoordinate::ApproximateEuclideanDistance( + input_coordinate.lat, + input_coordinate.lon, + end.lat, + end.lon); + if (distance < min_dist) + { + min_coord = end; + min_dist = distance; + } + } + + result_coordinate = min_coord; + return result_coordinate.isValid(); + } + + bool FindPhantomNodeForCoordinate(const FixedPointCoordinate &input_coordinate, + PhantomNode &result_phantom_node, + const unsigned zoom_level) + { + bool ignore_tiny_components = (zoom_level <= 14); + + float min_dist = std::numeric_limits::max(); + TestData nearest_edge; + for (const TestData& e : edges) + { + if (ignore_tiny_components && e.is_in_tiny_cc) + continue; + + float current_ratio = 0.; + FixedPointCoordinate nearest; + const float current_perpendicular_distance = + FixedPointCoordinate::ComputePerpendicularDistance( + coords->at(e.u), + coords->at(e.v), + input_coordinate, + nearest, + current_ratio); + + if ((current_perpendicular_distance < min_dist) && + !EpsilonCompare(current_perpendicular_distance, min_dist)) + { // found a new minimum + min_dist = current_perpendicular_distance; + result_phantom_node = { e.forward_edge_based_node_id, + e.reverse_edge_based_node_id, + e.name_id, + e.forward_weight, + e.reverse_weight, + e.forward_offset, + e.reverse_offset, + e.packed_geometry_id, + nearest, + e.fwd_segment_position}; + nearest_edge = e; + } + } + + if (result_phantom_node.location.isValid()) + { + // Hack to fix rounding errors and wandering via nodes. + if (1 == std::abs(input_coordinate.lon - result_phantom_node.location.lon)) + { + result_phantom_node.location.lon = input_coordinate.lon; + } + if (1 == std::abs(input_coordinate.lat - result_phantom_node.location.lat)) + { + result_phantom_node.location.lat = input_coordinate.lat; + } + + const float distance_1 = FixedPointCoordinate::ApproximateEuclideanDistance( + coords->at(nearest_edge.u), + result_phantom_node.location); + const float distance_2 = FixedPointCoordinate::ApproximateEuclideanDistance( + coords->at(nearest_edge.u), + coords->at(nearest_edge.v)); + const float ratio = std::min(1.f, distance_1 / distance_2); + + if (SPECIAL_NODEID != result_phantom_node.forward_node_id) + { + result_phantom_node.forward_weight *= ratio; + } + if (SPECIAL_NODEID != result_phantom_node.reverse_node_id) + { + result_phantom_node.reverse_weight *= (1. - ratio); + } + } + + return result_phantom_node.location.isValid(); + } + + template + inline bool EpsilonCompare(const FloatT d1, const FloatT d2) const + { + return (std::abs(d1 - d2) < std::numeric_limits::epsilon()); + } + +private: + const std::shared_ptr>& coords; + const std::vector& edges; +}; + + +template +struct RandomGraphFixture +{ + struct TupleHash + { + typedef std::pair argument_type; + typedef std::size_t result_type; + + result_type operator()(const argument_type & t) const + { + std::size_t val { 0 }; + boost::hash_combine(val, t.first); + boost::hash_combine(val, t.second); + return val; + } + }; + + RandomGraphFixture() + : coords(std::make_shared>()) + { + BOOST_TEST_MESSAGE("Constructing " << NUM_NODES << " nodes and " << NUM_EDGES << " edges."); + + std::mt19937 g(RANDOM_SEED); + + std::uniform_int_distribution<> lat_udist(MIN_LAT, MAX_LAT); + std::uniform_int_distribution<> lon_udist(MIN_LON, MAX_LON); + + for (unsigned i = 0; i < NUM_NODES; i++) + { + int lat = lat_udist(g); + int lon = lon_udist(g); + nodes.emplace_back(NodeInfo(lat, lon, i)); + coords->emplace_back(FixedPointCoordinate(lat, lon)); + } + + std::uniform_int_distribution<> edge_udist(0, nodes.size() - 1); + + std::unordered_set, TupleHash> used_edges; + + while(edges.size() < NUM_EDGES) + { + TestData data; + data.u = edge_udist(g); + data.v = edge_udist(g); + if (used_edges.find(std::pair(std::min(data.u, data.v), std::max(data.u, data.v))) == used_edges.end()) + { + data.is_in_tiny_cc = false; + edges.emplace_back(data); + used_edges.emplace(std::min(data.u, data.v), std::max(data.u, data.v)); + } + } + + } + + std::vector nodes; + std::shared_ptr> coords; + std::vector edges; +}; + + +typedef RandomGraphFixture TestRandomGraphFixture_LeafHalfFull; +typedef RandomGraphFixture TestRandomGraphFixture_LeafFull; +typedef RandomGraphFixture TestRandomGraphFixture_TwoLeaves; +typedef RandomGraphFixture TestRandomGraphFixture_Branch; +typedef RandomGraphFixture TestRandomGraphFixture_MultipleLevels; + +void simple_verify_rtree(TestStaticRTree& rtree, const std::shared_ptr>& coords, const std::vector& edges) +{ + BOOST_TEST_MESSAGE("Verify end points"); + for (const auto& e : edges) + { + FixedPointCoordinate result_u, result_v; + const FixedPointCoordinate& pu = coords->at(e.u); + const FixedPointCoordinate& pv = coords->at(e.v); + bool found_u = rtree.LocateClosestEndPointForCoordinate(pu, result_u, 1); + bool found_v = rtree.LocateClosestEndPointForCoordinate(pv, result_v, 1); + BOOST_CHECK(found_u && found_v); + float dist_u = FixedPointCoordinate::ApproximateEuclideanDistance( + result_u.lat, + result_u.lon, + pu.lat, + pu.lon); + BOOST_CHECK_LE(dist_u, std::numeric_limits::epsilon()); + float dist_v = FixedPointCoordinate::ApproximateEuclideanDistance( + result_v.lat, + result_v.lon, + pv.lat, + pv.lon); + BOOST_CHECK_LE(dist_v, std::numeric_limits::epsilon()); + } +} + +void sampling_verify_rtree(TestStaticRTree& rtree, LinearSearchNN& lsnn, unsigned num_samples) +{ + std::mt19937 g(RANDOM_SEED); + std::uniform_int_distribution<> lat_udist(WORLD_MIN_LAT, WORLD_MAX_LAT); + std::uniform_int_distribution<> lon_udist(WORLD_MIN_LON, WORLD_MAX_LON); + std::vector queries; + for (unsigned i = 0; i < num_samples; i++) + { + queries.emplace_back( + FixedPointCoordinate(lat_udist(g), lon_udist(g)) + ); + } + + BOOST_TEST_MESSAGE("Sampling queries"); + for (const auto& q : queries) + { + FixedPointCoordinate result_rtree; + rtree.LocateClosestEndPointForCoordinate(q, result_rtree, 1); + FixedPointCoordinate result_ln; + lsnn.LocateClosestEndPointForCoordinate(q, result_ln, 1); + BOOST_CHECK_EQUAL(result_ln, result_rtree); + + PhantomNode phantom_rtree; + rtree.FindPhantomNodeForCoordinate(q, phantom_rtree, 1); + PhantomNode phantom_ln; + lsnn.FindPhantomNodeForCoordinate(q, phantom_ln, 1); + BOOST_CHECK_EQUAL(phantom_rtree, phantom_ln); + } +} + +template +void construction_test(const std::string& prefix, FixtureT* fixture) +{ + const std::string nodes_path = prefix + ".ramIndex"; + const std::string leaves_path = prefix + ".fileIndex"; + const std::string coords_path = prefix + ".nodes"; + + boost::filesystem::ofstream node_stream(coords_path, std::ios::binary); + const unsigned num_nodes = fixture->nodes.size(); + node_stream.write((char *)&num_nodes, sizeof(unsigned)); + node_stream.write((char *)&(fixture->nodes[0]), num_nodes * sizeof(NodeInfo)); + node_stream.close(); + + TestStaticRTree::Build(fixture->edges, nodes_path, leaves_path, fixture->nodes); + TestStaticRTree rtree_querry(nodes_path, leaves_path, fixture->coords); + LinearSearchNN lsnn(fixture->coords, fixture->edges); + + simple_verify_rtree(rtree_querry, fixture->coords, fixture->edges); + sampling_verify_rtree(rtree_querry, lsnn, 100); +} + +BOOST_FIXTURE_TEST_CASE(construct_half_leaf_test, TestRandomGraphFixture_LeafHalfFull) +{ + construction_test("test_1", this); +} + +BOOST_FIXTURE_TEST_CASE(construct_full_leaf_test, TestRandomGraphFixture_LeafFull) +{ + construction_test("test_2", this); +} + +BOOST_FIXTURE_TEST_CASE(construct_two_leaves_test, TestRandomGraphFixture_TwoLeaves) +{ + construction_test("test_3", this); +} + +BOOST_FIXTURE_TEST_CASE(construct_branch_test, TestRandomGraphFixture_Branch) +{ + construction_test("test_4", this); +} + +BOOST_FIXTURE_TEST_CASE(construct_multiple_levels_test, TestRandomGraphFixture_MultipleLevels) +{ + construction_test("test_5", this); +} + + +BOOST_AUTO_TEST_SUITE_END() +