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