Skip to content

Commit d259848

Browse files
Optimise R-tree queries in the case of map matching (#6881)
1 parent 8a82d39 commit d259848

File tree

8 files changed

+219
-86
lines changed

8 files changed

+219
-86
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- FIXED: Correctly check runtime search conditions for forcing routing steps [#6866](https://github.com/Project-OSRM/osrm-backend/pull/6866)
5050
- Map Matching:
5151
- CHANGED: Optimise path distance calculation in MLD map matching. [#6876](https://github.com/Project-OSRM/osrm-backend/pull/6876)
52+
- CHANGED: Optimise R-tree queries in the case of map matching. [#6881](https://github.com/Project-OSRM/osrm-backend/pull/6876)
5253
- Debug tiles:
5354
- FIXED: Ensure speed layer features have unique ids. [#6726](https://github.com/Project-OSRM/osrm-backend/pull/6726)
5455

include/engine/datafacade/contiguous_internalmem_datafacade.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ class ContiguousInternalMemoryDataFacadeBase : public BaseDataFacade
375375
BOOST_ASSERT(m_geospatial_query.get());
376376

377377
return m_geospatial_query->NearestPhantomNodes(
378-
input_coordinate, approach, boost::none, max_distance, bearing, use_all_edges);
378+
input_coordinate, approach, max_distance, bearing, use_all_edges);
379379
}
380380

381381
std::vector<PhantomNodeWithDistance>

include/engine/geospatial_query.hpp

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,42 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
4747
return rtree.SearchInBox(bbox);
4848
}
4949

50+
std::vector<PhantomNodeWithDistance>
51+
NearestPhantomNodes(const util::Coordinate input_coordinate,
52+
const Approach approach,
53+
const double max_distance,
54+
const boost::optional<Bearing> bearing_with_range,
55+
const boost::optional<bool> use_all_edges) const
56+
{
57+
auto results = rtree.SearchInRange(
58+
input_coordinate,
59+
max_distance,
60+
[this, approach, &input_coordinate, &bearing_with_range, &use_all_edges, max_distance](
61+
const CandidateSegment &segment)
62+
{
63+
auto invalidDistance =
64+
CheckSegmentDistance(input_coordinate, segment, max_distance);
65+
if (invalidDistance)
66+
{
67+
return std::make_pair(false, false);
68+
}
69+
auto valid = CheckSegmentExclude(segment) &&
70+
CheckApproach(input_coordinate, segment, approach) &&
71+
(use_all_edges ? HasValidEdge(segment, *use_all_edges)
72+
: HasValidEdge(segment)) &&
73+
(bearing_with_range ? CheckSegmentBearing(segment, *bearing_with_range)
74+
: std::make_pair(true, true));
75+
return valid;
76+
});
77+
return MakePhantomNodes(input_coordinate, results);
78+
}
79+
5080
// Returns max_results nearest PhantomNodes that are valid within the provided parameters.
5181
// Does not filter by small/big component!
5282
std::vector<PhantomNodeWithDistance>
5383
NearestPhantomNodes(const util::Coordinate input_coordinate,
5484
const Approach approach,
55-
const boost::optional<size_t> max_results,
85+
const size_t max_results,
5686
const boost::optional<double> max_distance,
5787
const boost::optional<Bearing> bearing_with_range,
5888
const boost::optional<bool> use_all_edges) const
@@ -70,10 +100,10 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
70100
: std::make_pair(true, true));
71101
return valid;
72102
},
73-
[this, &max_distance, &max_results, input_coordinate](const std::size_t num_results,
74-
const CandidateSegment &segment)
103+
[this, &max_distance, max_results, input_coordinate](const std::size_t num_results,
104+
const CandidateSegment &segment)
75105
{
76-
return (max_results && num_results >= *max_results) ||
106+
return (num_results >= max_results) ||
77107
(max_distance && max_distance != -1.0 &&
78108
CheckSegmentDistance(input_coordinate, segment, *max_distance));
79109
});

include/util/coordinate_calculation.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ inline double radToDeg(const double radian)
3636
}
3737
} // namespace detail
3838

39+
const constexpr static double METERS_PER_DEGREE_LAT = 110567.0;
40+
41+
inline double metersPerLngDegree(const FixedLatitude lat)
42+
{
43+
return std::cos(detail::degToRad(static_cast<double>(toFloating(lat)))) * METERS_PER_DEGREE_LAT;
44+
}
45+
3946
//! Takes the squared euclidean distance of the input coordinates. Does not return meters!
4047
std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rhs);
4148

include/util/rectangle.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
#include "util/coordinate.hpp"
55
#include "util/coordinate_calculation.hpp"
6-
76
#include <boost/assert.hpp>
87

98
#include <limits>
@@ -168,6 +167,18 @@ struct RectangleInt2D
168167
min_lat != FixedLatitude{std::numeric_limits<std::int32_t>::max()} &&
169168
max_lat != FixedLatitude{std::numeric_limits<std::int32_t>::min()};
170169
}
170+
171+
static RectangleInt2D ExpandMeters(const Coordinate &coordinate, const double meters)
172+
{
173+
const double lat_offset = meters / coordinate_calculation::METERS_PER_DEGREE_LAT;
174+
const double lon_offset =
175+
meters / coordinate_calculation::metersPerLngDegree(coordinate.lat);
176+
177+
return RectangleInt2D{coordinate.lon - toFixed(FloatLongitude{lon_offset}),
178+
coordinate.lon + toFixed(FloatLongitude{lon_offset}),
179+
coordinate.lat - toFixed(FloatLatitude{lat_offset}),
180+
coordinate.lat + toFixed(FloatLatitude{lat_offset})};
181+
}
171182
};
172183
} // namespace osrm::util
173184

include/util/static_rtree.hpp

Lines changed: 111 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#define STATIC_RTREE_HPP
33

44
#include "storage/tar_fwd.hpp"
5-
65
#include "util/bearing.hpp"
76
#include "util/coordinate_calculation.hpp"
87
#include "util/deallocating_vector.hpp"
@@ -11,6 +10,7 @@
1110
#include "util/integer_range.hpp"
1211
#include "util/mmap_file.hpp"
1312
#include "util/rectangle.hpp"
13+
#include "util/timing_util.hpp"
1414
#include "util/typedefs.hpp"
1515
#include "util/vector_view.hpp"
1616
#include "util/web_mercator.hpp"
@@ -487,70 +487,9 @@ class StaticRTree
487487
Rectangle needs to be projected!*/
488488
std::vector<EdgeDataT> SearchInBox(const Rectangle &search_rectangle) const
489489
{
490-
const Rectangle projected_rectangle{
491-
search_rectangle.min_lon,
492-
search_rectangle.max_lon,
493-
toFixed(FloatLatitude{
494-
web_mercator::latToY(toFloating(FixedLatitude(search_rectangle.min_lat)))}),
495-
toFixed(FloatLatitude{
496-
web_mercator::latToY(toFloating(FixedLatitude(search_rectangle.max_lat)))})};
497490
std::vector<EdgeDataT> results;
498-
499-
std::queue<TreeIndex> traversal_queue;
500-
traversal_queue.push(TreeIndex{});
501-
502-
while (!traversal_queue.empty())
503-
{
504-
auto const current_tree_index = traversal_queue.front();
505-
traversal_queue.pop();
506-
507-
// If we're at the bottom of the tree, we need to explore the
508-
// element array
509-
if (is_leaf(current_tree_index))
510-
{
511-
512-
// Note: irange is [start,finish), so we need to +1 to make sure we visit the
513-
// last
514-
for (const auto current_child_index : child_indexes(current_tree_index))
515-
{
516-
const auto &current_edge = m_objects[current_child_index];
517-
518-
// we don't need to project the coordinates here,
519-
// because we use the unprojected rectangle to test against
520-
const Rectangle bbox{std::min(m_coordinate_list[current_edge.u].lon,
521-
m_coordinate_list[current_edge.v].lon),
522-
std::max(m_coordinate_list[current_edge.u].lon,
523-
m_coordinate_list[current_edge.v].lon),
524-
std::min(m_coordinate_list[current_edge.u].lat,
525-
m_coordinate_list[current_edge.v].lat),
526-
std::max(m_coordinate_list[current_edge.u].lat,
527-
m_coordinate_list[current_edge.v].lat)};
528-
529-
// use the _unprojected_ input rectangle here
530-
if (bbox.Intersects(search_rectangle))
531-
{
532-
results.push_back(current_edge);
533-
}
534-
}
535-
}
536-
else
537-
{
538-
BOOST_ASSERT(current_tree_index.level + 1 < m_tree_level_starts.size());
539-
540-
for (const auto child_index : child_indexes(current_tree_index))
541-
{
542-
const auto &child_rectangle =
543-
m_search_tree[child_index].minimum_bounding_rectangle;
544-
545-
if (child_rectangle.Intersects(projected_rectangle))
546-
{
547-
traversal_queue.push(TreeIndex(
548-
current_tree_index.level + 1,
549-
child_index - m_tree_level_starts[current_tree_index.level + 1]));
550-
}
551-
}
552-
}
553-
}
491+
SearchInBox(search_rectangle,
492+
[&results](const auto &edge_data) { results.push_back(edge_data); });
554493
return results;
555494
}
556495

@@ -565,15 +504,56 @@ class StaticRTree
565504
{ return num_results >= max_results; });
566505
}
567506

507+
// NB 1: results are not guaranteed to be sorted by distance
508+
// NB 2: maxDistanceMeters is not a hard limit, it's just a way to reduce the number of edges
509+
// returned
510+
template <typename FilterT>
511+
std::vector<CandidateSegment> SearchInRange(const Coordinate input_coordinate,
512+
double maxDistanceMeters,
513+
const FilterT filter) const
514+
{
515+
auto projected_coordinate = web_mercator::fromWGS84(input_coordinate);
516+
Coordinate fixed_projected_coordinate{projected_coordinate};
517+
518+
auto bbox = Rectangle::ExpandMeters(input_coordinate, maxDistanceMeters);
519+
std::vector<CandidateSegment> results;
520+
521+
SearchInBox(
522+
bbox,
523+
[&results, &filter, fixed_projected_coordinate, this](const EdgeDataT &current_edge)
524+
{
525+
const auto projected_u = web_mercator::fromWGS84(m_coordinate_list[current_edge.u]);
526+
const auto projected_v = web_mercator::fromWGS84(m_coordinate_list[current_edge.v]);
527+
528+
auto [_, projected_nearest] = coordinate_calculation::projectPointOnSegment(
529+
projected_u, projected_v, fixed_projected_coordinate);
530+
531+
CandidateSegment current_candidate{projected_nearest, current_edge};
532+
auto use_segment = filter(current_candidate);
533+
if (!use_segment.first && !use_segment.second)
534+
{
535+
return;
536+
}
537+
current_candidate.data.forward_segment_id.enabled &= use_segment.first;
538+
current_candidate.data.reverse_segment_id.enabled &= use_segment.second;
539+
540+
results.push_back(current_candidate);
541+
});
542+
543+
return results;
544+
}
545+
568546
// Return edges in distance order with the coordinate of the closest point on the edge.
569547
template <typename FilterT, typename TerminationT>
570548
std::vector<CandidateSegment> Nearest(const Coordinate input_coordinate,
571549
const FilterT filter,
572550
const TerminationT terminate) const
573551
{
574552
std::vector<CandidateSegment> results;
553+
575554
auto projected_coordinate = web_mercator::fromWGS84(input_coordinate);
576555
Coordinate fixed_projected_coordinate{projected_coordinate};
556+
577557
// initialize queue with root element
578558
std::priority_queue<QueryCandidate> traversal_queue;
579559
traversal_queue.push(QueryCandidate{0, TreeIndex{}});
@@ -631,6 +611,73 @@ class StaticRTree
631611
}
632612

633613
private:
614+
template <typename Callback>
615+
void SearchInBox(const Rectangle &search_rectangle, Callback &&callback) const
616+
{
617+
const Rectangle projected_rectangle{
618+
search_rectangle.min_lon,
619+
search_rectangle.max_lon,
620+
toFixed(FloatLatitude{
621+
web_mercator::latToY(toFloating(FixedLatitude(search_rectangle.min_lat)))}),
622+
toFixed(FloatLatitude{
623+
web_mercator::latToY(toFloating(FixedLatitude(search_rectangle.max_lat)))})};
624+
std::queue<TreeIndex> traversal_queue;
625+
traversal_queue.push(TreeIndex{});
626+
627+
while (!traversal_queue.empty())
628+
{
629+
auto const current_tree_index = traversal_queue.front();
630+
traversal_queue.pop();
631+
632+
// If we're at the bottom of the tree, we need to explore the
633+
// element array
634+
if (is_leaf(current_tree_index))
635+
{
636+
637+
// Note: irange is [start,finish), so we need to +1 to make sure we visit the
638+
// last
639+
for (const auto current_child_index : child_indexes(current_tree_index))
640+
{
641+
const auto &current_edge = m_objects[current_child_index];
642+
643+
// we don't need to project the coordinates here,
644+
// because we use the unprojected rectangle to test against
645+
const Rectangle bbox{std::min(m_coordinate_list[current_edge.u].lon,
646+
m_coordinate_list[current_edge.v].lon),
647+
std::max(m_coordinate_list[current_edge.u].lon,
648+
m_coordinate_list[current_edge.v].lon),
649+
std::min(m_coordinate_list[current_edge.u].lat,
650+
m_coordinate_list[current_edge.v].lat),
651+
std::max(m_coordinate_list[current_edge.u].lat,
652+
m_coordinate_list[current_edge.v].lat)};
653+
654+
// use the _unprojected_ input rectangle here
655+
if (bbox.Intersects(search_rectangle))
656+
{
657+
callback(current_edge);
658+
}
659+
}
660+
}
661+
else
662+
{
663+
BOOST_ASSERT(current_tree_index.level + 1 < m_tree_level_starts.size());
664+
665+
for (const auto child_index : child_indexes(current_tree_index))
666+
{
667+
const auto &child_rectangle =
668+
m_search_tree[child_index].minimum_bounding_rectangle;
669+
670+
if (child_rectangle.Intersects(projected_rectangle))
671+
{
672+
traversal_queue.push(TreeIndex(
673+
current_tree_index.level + 1,
674+
child_index - m_tree_level_starts[current_tree_index.level + 1]));
675+
}
676+
}
677+
}
678+
}
679+
}
680+
634681
/**
635682
* Iterates over all the objects in a leaf node and inserts them into our
636683
* search priority queue. The speed of this function is very much governed

src/benchmarks/match.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "osrm/status.hpp"
1212

1313
#include <boost/assert.hpp>
14+
1415
#include <cstdlib>
1516
#include <exception>
1617
#include <iostream>

0 commit comments

Comments
 (0)