1 #include "util/static_rtree.hpp"
2 #include "extractor/edge_based_node_segment.hpp"
3 #include "engine/geospatial_query.hpp"
4 #include "util/coordinate.hpp"
5 #include "util/coordinate_calculation.hpp"
6 #include "util/exception.hpp"
7 #include "util/rectangle.hpp"
8 #include "util/typedefs.hpp"
9 
10 #include "../common/temporary_file.hpp"
11 #include "mocks/mock_datafacade.hpp"
12 
13 #include <boost/functional/hash.hpp>
14 #include <boost/test/unit_test.hpp>
15 
16 #include <cmath>
17 #include <cstdint>
18 
19 #include <algorithm>
20 #include <memory>
21 #include <random>
22 #include <string>
23 #include <unordered_set>
24 #include <utility>
25 #include <vector>
26 
27 // explicit TBB scheduler init to register resources cleanup at exit
28 #if TBB_VERSION_MAJOR == 2020
29 #include <tbb/global_control.h>
30 tbb::global_control scheduler(tbb::global_control::max_allowed_parallelism, 2);
31 #else
32 #include <tbb/task_scheduler_init.h>
33 tbb::task_scheduler_init init(2);
34 #endif
35 
36 BOOST_AUTO_TEST_SUITE(static_rtree)
37 
38 using namespace osrm;
39 using namespace osrm::util;
40 using namespace osrm::test;
41 
42 constexpr uint32_t TEST_BRANCHING_FACTOR = 8;
43 constexpr uint32_t TEST_LEAF_NODE_SIZE = 64;
44 
45 using TestData = extractor::EdgeBasedNodeSegment;
46 using TestStaticRTree = StaticRTree<TestData,
47                                     osrm::storage::Ownership::Container,
48                                     TEST_BRANCHING_FACTOR,
49                                     TEST_LEAF_NODE_SIZE>;
50 using MiniStaticRTree = StaticRTree<TestData, osrm::storage::Ownership::Container, 2, 128>;
51 using TestDataFacade = MockDataFacade<osrm::engine::routing_algorithms::ch::Algorithm>;
52 
53 // Choosen by a fair W20 dice roll (this value is completely arbitrary)
54 constexpr unsigned RANDOM_SEED = 42;
55 static const int32_t WORLD_MIN_LAT = -85 * COORDINATE_PRECISION;
56 static const int32_t WORLD_MAX_LAT = 85 * COORDINATE_PRECISION;
57 static const int32_t WORLD_MIN_LON = -180 * COORDINATE_PRECISION;
58 static const int32_t WORLD_MAX_LON = 180 * COORDINATE_PRECISION;
59 
60 template <typename DataT> class LinearSearchNN
61 {
62   public:
LinearSearchNN(const std::vector<Coordinate> & coords,const std::vector<DataT> & edges)63     LinearSearchNN(const std::vector<Coordinate> &coords, const std::vector<DataT> &edges)
64         : coords(coords), edges(edges)
65     {
66     }
67 
Nearest(const Coordinate & input_coordinate,const unsigned num_results)68     std::vector<DataT> Nearest(const Coordinate &input_coordinate, const unsigned num_results)
69     {
70         std::vector<DataT> local_edges(edges);
71 
72         auto projected_input = web_mercator::fromWGS84(input_coordinate);
73         const auto segment_comparator = [this, &projected_input](const DataT &lhs,
74                                                                  const DataT &rhs) {
75             using web_mercator::fromWGS84;
76             const auto lhs_result = coordinate_calculation::projectPointOnSegment(
77                 fromWGS84(coords[lhs.u]), fromWGS84(coords[lhs.v]), projected_input);
78             const auto rhs_result = coordinate_calculation::projectPointOnSegment(
79                 fromWGS84(coords[rhs.u]), fromWGS84(coords[rhs.v]), projected_input);
80             const auto lhs_squared_dist = coordinate_calculation::squaredEuclideanDistance(
81                 lhs_result.second, projected_input);
82             const auto rhs_squared_dist = coordinate_calculation::squaredEuclideanDistance(
83                 rhs_result.second, projected_input);
84             return lhs_squared_dist < rhs_squared_dist;
85         };
86 
87         std::nth_element(local_edges.begin(),
88                          local_edges.begin() + num_results,
89                          local_edges.end(),
90                          segment_comparator);
91         local_edges.resize(num_results);
92 
93         return local_edges;
94     }
95 
96   private:
97     const std::vector<Coordinate> &coords;
98     const std::vector<TestData> &edges;
99 };
100 
101 template <unsigned NUM_NODES, unsigned NUM_EDGES> struct RandomGraphFixture
102 {
103     struct TupleHash
104     {
105         typedef std::pair<unsigned, unsigned> argument_type;
106         typedef std::size_t result_type;
107 
operator ()RandomGraphFixture::TupleHash108         result_type operator()(const argument_type &t) const
109         {
110             std::size_t val{0};
111             boost::hash_combine(val, t.first);
112             boost::hash_combine(val, t.second);
113             return val;
114         }
115     };
116 
RandomGraphFixtureRandomGraphFixture117     RandomGraphFixture()
118     {
119         std::mt19937 g(RANDOM_SEED);
120 
121         std::uniform_int_distribution<> lat_udist(WORLD_MIN_LAT, WORLD_MAX_LAT);
122         std::uniform_int_distribution<> lon_udist(WORLD_MIN_LON, WORLD_MAX_LON);
123 
124         for (unsigned i = 0; i < NUM_NODES; i++)
125         {
126             int lon = lon_udist(g);
127             int lat = lat_udist(g);
128             coords.emplace_back(Coordinate(FixedLongitude{lon}, FixedLatitude{lat}));
129         }
130 
131         std::uniform_int_distribution<> edge_udist(0, coords.size() - 1);
132 
133         std::unordered_set<std::pair<unsigned, unsigned>, TupleHash> used_edges;
134 
135         while (edges.size() < NUM_EDGES)
136         {
137             TestData data;
138             data.u = edge_udist(g);
139             data.v = edge_udist(g);
140             data.is_startpoint = true;
141             if (used_edges.find(std::pair<unsigned, unsigned>(
142                     std::min(data.u, data.v), std::max(data.u, data.v))) == used_edges.end())
143             {
144                 edges.emplace_back(data);
145                 used_edges.emplace(std::min(data.u, data.v), std::max(data.u, data.v));
146             }
147         }
148     }
149 
150     std::vector<Coordinate> coords;
151     std::vector<TestData> edges;
152 };
153 
154 struct GraphFixture
155 {
GraphFixtureGraphFixture156     GraphFixture(const std::vector<std::pair<FloatLongitude, FloatLatitude>> &input_coords,
157                  const std::vector<std::tuple<unsigned, unsigned, bool>> &input_edges)
158     {
159 
160         for (unsigned i = 0; i < input_coords.size(); i++)
161         {
162             coords.emplace_back(input_coords[i].first, input_coords[i].second);
163         }
164 
165         for (const auto &pair : input_edges)
166         {
167             TestData d;
168             d.u = std::get<0>(pair);
169             d.v = std::get<1>(pair);
170             // We set the forward nodes to the target node-based-node IDs, just
171             // so we have something to test against.  Because this isn't a real
172             // graph, the actual values aren't important, we just need something
173             // to examine during tests.
174             d.forward_segment_id = {std::get<1>(pair), true};
175             d.reverse_segment_id = {std::get<0>(pair), true};
176             d.fwd_segment_position = 0;
177             d.is_startpoint = std::get<2>(pair);
178             edges.emplace_back(d);
179         }
180     }
181 
182     std::vector<Coordinate> coords;
183     std::vector<TestData> edges;
184 };
185 
186 typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 3, TEST_LEAF_NODE_SIZE / 2>
187     TestRandomGraphFixture_LeafHalfFull;
188 typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 5, TEST_LEAF_NODE_SIZE>
189     TestRandomGraphFixture_LeafFull;
190 typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 10, TEST_LEAF_NODE_SIZE * 2>
191     TestRandomGraphFixture_TwoLeaves;
192 typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 3,
193                            TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR>
194     TestRandomGraphFixture_Branch;
195 typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 3,
196                            TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 2>
197     TestRandomGraphFixture_MultipleLevels;
198 typedef RandomGraphFixture<10, 30> TestRandomGraphFixture_10_30;
199 
200 template <typename RTreeT>
simple_verify_rtree(RTreeT & rtree,const std::vector<Coordinate> & coords,const std::vector<TestData> & edges)201 void simple_verify_rtree(RTreeT &rtree,
202                          const std::vector<Coordinate> &coords,
203                          const std::vector<TestData> &edges)
204 {
205     for (const auto &e : edges)
206     {
207         const Coordinate &pu = coords[e.u];
208         const Coordinate &pv = coords[e.v];
209         auto result_u = rtree.Nearest(pu, 1);
210         auto result_v = rtree.Nearest(pv, 1);
211         BOOST_CHECK(result_u.size() == 1 && result_v.size() == 1);
212         BOOST_CHECK(result_u.front().u == e.u || result_u.front().v == e.u);
213         BOOST_CHECK(result_v.front().u == e.v || result_v.front().v == e.v);
214     }
215 }
216 
217 template <typename RTreeT>
sampling_verify_rtree(RTreeT & rtree,LinearSearchNN<TestData> & lsnn,const std::vector<Coordinate> & coords,unsigned num_samples)218 void sampling_verify_rtree(RTreeT &rtree,
219                            LinearSearchNN<TestData> &lsnn,
220                            const std::vector<Coordinate> &coords,
221                            unsigned num_samples)
222 {
223     std::mt19937 g(RANDOM_SEED);
224     std::uniform_int_distribution<> lat_udist(WORLD_MIN_LAT, WORLD_MAX_LAT);
225     std::uniform_int_distribution<> lon_udist(WORLD_MIN_LON, WORLD_MAX_LON);
226     std::vector<Coordinate> queries;
227     for (unsigned i = 0; i < num_samples; i++)
228     {
229         queries.emplace_back(FixedLongitude{lon_udist(g)}, FixedLatitude{lat_udist(g)});
230     }
231 
232     for (const auto &q : queries)
233     {
234         auto result_rtree = rtree.Nearest(q, 1);
235         auto result_lsnn = lsnn.Nearest(q, 1);
236         BOOST_CHECK(result_rtree.size() == 1);
237         BOOST_CHECK(result_lsnn.size() == 1);
238         auto rtree_u = result_rtree.back().u;
239         auto rtree_v = result_rtree.back().v;
240         auto lsnn_u = result_lsnn.back().u;
241         auto lsnn_v = result_lsnn.back().v;
242 
243         const double rtree_dist =
244             coordinate_calculation::perpendicularDistance(coords[rtree_u], coords[rtree_v], q);
245         const double lsnn_dist =
246             coordinate_calculation::perpendicularDistance(coords[lsnn_u], coords[lsnn_v], q);
247 
248         BOOST_CHECK_CLOSE(rtree_dist, lsnn_dist, 0.0001);
249     }
250 }
251 
252 template <typename RTreeT, typename FixtureT>
make_rtree(const boost::filesystem::path & path,FixtureT & fixture)253 auto make_rtree(const boost::filesystem::path &path, FixtureT &fixture)
254 {
255     return RTreeT(fixture.edges, fixture.coords, path);
256 }
257 
258 template <typename RTreeT = TestStaticRTree, typename FixtureT>
construction_test(const std::string & path,FixtureT & fixture)259 void construction_test(const std::string &path, FixtureT &fixture)
260 {
261     std::string leaves_path;
262     std::string nodes_path;
263     auto rtree = make_rtree<RTreeT>(path, fixture);
264     LinearSearchNN<TestData> lsnn(fixture.coords, fixture.edges);
265 
266     simple_verify_rtree(rtree, fixture.coords, fixture.edges);
267     sampling_verify_rtree(rtree, lsnn, fixture.coords, 100);
268 }
269 
BOOST_FIXTURE_TEST_CASE(construct_tiny,TestRandomGraphFixture_10_30)270 BOOST_FIXTURE_TEST_CASE(construct_tiny, TestRandomGraphFixture_10_30)
271 {
272     using TinyTestTree = StaticRTree<TestData, osrm::storage::Ownership::Container, 2, 64>;
273     construction_test<TinyTestTree>("test_tiny", *this);
274 }
275 
BOOST_FIXTURE_TEST_CASE(construct_half_leaf_test,TestRandomGraphFixture_LeafHalfFull)276 BOOST_FIXTURE_TEST_CASE(construct_half_leaf_test, TestRandomGraphFixture_LeafHalfFull)
277 {
278     construction_test("test_1", *this);
279 }
280 
BOOST_FIXTURE_TEST_CASE(construct_full_leaf_test,TestRandomGraphFixture_LeafFull)281 BOOST_FIXTURE_TEST_CASE(construct_full_leaf_test, TestRandomGraphFixture_LeafFull)
282 {
283     construction_test("test_2", *this);
284 }
285 
BOOST_FIXTURE_TEST_CASE(construct_two_leaves_test,TestRandomGraphFixture_TwoLeaves)286 BOOST_FIXTURE_TEST_CASE(construct_two_leaves_test, TestRandomGraphFixture_TwoLeaves)
287 {
288     construction_test("test_3", *this);
289 }
290 
BOOST_FIXTURE_TEST_CASE(construct_branch_test,TestRandomGraphFixture_Branch)291 BOOST_FIXTURE_TEST_CASE(construct_branch_test, TestRandomGraphFixture_Branch)
292 {
293     construction_test("test_4", *this);
294 }
295 
BOOST_FIXTURE_TEST_CASE(construct_multiple_levels_test,TestRandomGraphFixture_MultipleLevels)296 BOOST_FIXTURE_TEST_CASE(construct_multiple_levels_test, TestRandomGraphFixture_MultipleLevels)
297 {
298     construction_test("test_5", *this);
299 }
300 
301 // Bug: If you querry a point that lies between two BBs that have a gap,
302 // one BB will be pruned, even if it could contain a nearer match.
BOOST_AUTO_TEST_CASE(regression_test)303 BOOST_AUTO_TEST_CASE(regression_test)
304 {
305     using Coord = std::pair<FloatLongitude, FloatLatitude>;
306     using Edge = std::tuple<unsigned, unsigned, bool>;
307     GraphFixture fixture(
308         {
309             Coord{FloatLongitude{0.0}, FloatLatitude{40.0}},   //
310             Coord{FloatLongitude{5.0}, FloatLatitude{35.0}},   //
311             Coord{FloatLongitude{5.0}, FloatLatitude{5.0}},    //
312             Coord{FloatLongitude{10.0}, FloatLatitude{0.0}},   //
313             Coord{FloatLongitude{10.0}, FloatLatitude{20.0}},  //
314             Coord{FloatLongitude{5.0}, FloatLatitude{20.0}},   //
315             Coord{FloatLongitude{100.0}, FloatLatitude{40.0}}, //
316             Coord{FloatLongitude{105.0}, FloatLatitude{35.0}}, //
317             Coord{FloatLongitude{105.0}, FloatLatitude{5.0}},  //
318             Coord{FloatLongitude{110.0}, FloatLatitude{0.0}},  //
319         },
320         {Edge(0, 1, true), Edge(2, 3, true), Edge(4, 5, true), Edge(6, 7, true), Edge(8, 9, true)});
321 
322     TemporaryFile tmp;
323     auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
324     LinearSearchNN<TestData> lsnn(fixture.coords, fixture.edges);
325 
326     // query a node just right of the center of the gap
327     Coordinate input(FloatLongitude{55.1}, FloatLatitude{20.0});
328     auto result_rtree = rtree.Nearest(input, 1);
329     auto result_ls = lsnn.Nearest(input, 1);
330 
331     BOOST_CHECK(result_rtree.size() == 1);
332     BOOST_CHECK(result_ls.size() == 1);
333 
334     BOOST_CHECK_EQUAL(result_ls.front().u, result_rtree.front().u);
335     BOOST_CHECK_EQUAL(result_ls.front().v, result_rtree.front().v);
336 }
337 
338 // Bug: If you querry a point with a narrow radius, no result should be returned
BOOST_AUTO_TEST_CASE(radius_regression_test)339 BOOST_AUTO_TEST_CASE(radius_regression_test)
340 {
341     using Coord = std::pair<FloatLongitude, FloatLatitude>;
342     using Edge = std::tuple<unsigned, unsigned, bool>;
343     GraphFixture fixture(
344         {
345             Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
346             Coord(FloatLongitude{10.0}, FloatLatitude{10.0}),
347         },
348         {Edge(0, 1, true), Edge(1, 0, true)});
349 
350     TemporaryFile tmp;
351     auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
352     TestDataFacade mockfacade;
353     engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
354         rtree, fixture.coords, mockfacade);
355 
356     Coordinate input(FloatLongitude{5.2}, FloatLatitude{5.0});
357 
358     {
359         auto results = query.NearestPhantomNodesInRange(
360             input, 0.01, osrm::engine::Approach::UNRESTRICTED, true);
361         BOOST_CHECK_EQUAL(results.size(), 0);
362     }
363 }
364 
BOOST_AUTO_TEST_CASE(permissive_edge_snapping)365 BOOST_AUTO_TEST_CASE(permissive_edge_snapping)
366 {
367     using Coord = std::pair<FloatLongitude, FloatLatitude>;
368     using Edge = std::tuple<unsigned, unsigned, bool>;
369     GraphFixture fixture(
370         {
371             Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
372             Coord(FloatLongitude{0.001}, FloatLatitude{0.001}),
373         },
374         {Edge(0, 1, true), Edge(1, 0, false)});
375 
376     TemporaryFile tmp;
377     auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
378     TestDataFacade mockfacade;
379     engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
380         rtree, fixture.coords, mockfacade);
381 
382     Coordinate input(FloatLongitude{0.0005}, FloatLatitude{0.0005});
383 
384     {
385         auto results = query.NearestPhantomNodesInRange(
386             input, 1000, osrm::engine::Approach::UNRESTRICTED, false);
387         BOOST_CHECK_EQUAL(results.size(), 1);
388     }
389 
390     {
391         auto results = query.NearestPhantomNodesInRange(
392             input, 1000, osrm::engine::Approach::UNRESTRICTED, true);
393         BOOST_CHECK_EQUAL(results.size(), 2);
394     }
395 }
396 
BOOST_AUTO_TEST_CASE(bearing_tests)397 BOOST_AUTO_TEST_CASE(bearing_tests)
398 {
399     using Coord = std::pair<FloatLongitude, FloatLatitude>;
400     using Edge = std::tuple<unsigned, unsigned, bool>;
401     GraphFixture fixture(
402         {
403             Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
404             Coord(FloatLongitude{10.0}, FloatLatitude{10.0}),
405         },
406         {Edge(0, 1, true), Edge(1, 0, true)});
407 
408     TemporaryFile tmp;
409     auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
410     TestDataFacade mockfacade;
411     engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
412         rtree, fixture.coords, mockfacade);
413 
414     Coordinate input(FloatLongitude{5.1}, FloatLatitude{5.0});
415 
416     {
417         auto results = query.NearestPhantomNodes(input, 5, osrm::engine::Approach::UNRESTRICTED);
418         BOOST_CHECK_EQUAL(results.size(), 2);
419         BOOST_CHECK_EQUAL(results.back().phantom_node.forward_segment_id.id, 0);
420         BOOST_CHECK_EQUAL(results.back().phantom_node.reverse_segment_id.id, 1);
421     }
422 
423     {
424         auto results =
425             query.NearestPhantomNodes(input, 5, 270, 10, osrm::engine::Approach::UNRESTRICTED);
426         BOOST_CHECK_EQUAL(results.size(), 0);
427     }
428 
429     {
430         auto results =
431             query.NearestPhantomNodes(input, 5, 45, 10, osrm::engine::Approach::UNRESTRICTED);
432         BOOST_CHECK_EQUAL(results.size(), 2);
433 
434         BOOST_CHECK(results[0].phantom_node.forward_segment_id.enabled);
435         BOOST_CHECK(!results[0].phantom_node.reverse_segment_id.enabled);
436         BOOST_CHECK_EQUAL(results[0].phantom_node.forward_segment_id.id, 1);
437 
438         BOOST_CHECK(!results[1].phantom_node.forward_segment_id.enabled);
439         BOOST_CHECK(results[1].phantom_node.reverse_segment_id.enabled);
440         BOOST_CHECK_EQUAL(results[1].phantom_node.reverse_segment_id.id, 1);
441     }
442 
443     {
444         auto results = query.NearestPhantomNodesInRange(
445             input, 11000, osrm::engine::Approach::UNRESTRICTED, true);
446         BOOST_CHECK_EQUAL(results.size(), 2);
447     }
448 
449     {
450         auto results = query.NearestPhantomNodesInRange(
451             input, 11000, 270, 10, osrm::engine::Approach::UNRESTRICTED, true);
452         BOOST_CHECK_EQUAL(results.size(), 0);
453     }
454 
455     {
456         auto results = query.NearestPhantomNodesInRange(
457             input, 11000, 45, 10, osrm::engine::Approach::UNRESTRICTED, true);
458         BOOST_CHECK_EQUAL(results.size(), 2);
459 
460         BOOST_CHECK(results[0].phantom_node.forward_segment_id.enabled);
461         BOOST_CHECK(!results[0].phantom_node.reverse_segment_id.enabled);
462         BOOST_CHECK_EQUAL(results[0].phantom_node.forward_segment_id.id, 1);
463 
464         BOOST_CHECK(!results[1].phantom_node.forward_segment_id.enabled);
465         BOOST_CHECK(results[1].phantom_node.reverse_segment_id.enabled);
466         BOOST_CHECK_EQUAL(results[1].phantom_node.reverse_segment_id.id, 1);
467     }
468 }
469 
BOOST_AUTO_TEST_CASE(bbox_search_tests)470 BOOST_AUTO_TEST_CASE(bbox_search_tests)
471 {
472     using Coord = std::pair<FloatLongitude, FloatLatitude>;
473     using Edge = std::tuple<unsigned, unsigned, bool>;
474 
475     GraphFixture fixture(
476         {
477             Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
478             Coord(FloatLongitude{1.0}, FloatLatitude{1.0}),
479             Coord(FloatLongitude{2.0}, FloatLatitude{2.0}),
480             Coord(FloatLongitude{3.0}, FloatLatitude{3.0}),
481             Coord(FloatLongitude{4.0}, FloatLatitude{4.0}),
482         },
483         {Edge(0, 1, true), Edge(1, 2, true), Edge(2, 3, true), Edge(3, 4, true)});
484 
485     TemporaryFile tmp;
486     auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
487     TestDataFacade mockfacade;
488     engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
489         rtree, fixture.coords, mockfacade);
490 
491     {
492         RectangleInt2D bbox = {
493             FloatLongitude{0.5}, FloatLongitude{1.5}, FloatLatitude{0.5}, FloatLatitude{1.5}};
494         auto results = query.Search(bbox);
495         BOOST_CHECK_EQUAL(results.size(), 2);
496     }
497 
498     {
499         RectangleInt2D bbox = {
500             FloatLongitude{1.5}, FloatLongitude{3.5}, FloatLatitude{1.5}, FloatLatitude{3.5}};
501         auto results = query.Search(bbox);
502         BOOST_CHECK_EQUAL(results.size(), 3);
503     }
504 }
505 
506 BOOST_AUTO_TEST_SUITE_END()
507