osrm-backend/unit_tests/util/static_rtree.cpp

499 lines
19 KiB
C++
Raw Normal View History

2016-01-02 11:13:44 -05:00
#include "util/coordinate_calculation.hpp"
#include "engine/geospatial_query.hpp"
#include "util/static_rtree.hpp"
#include "extractor/query_node.hpp"
#include "extractor/edge_based_node.hpp"
#include "util/typedefs.hpp"
#include "util/rectangle.hpp"
#include "util/exception.hpp"
2014-06-26 19:27:45 -04:00
#include <boost/functional/hash.hpp>
2014-06-26 19:27:45 -04:00
#include <boost/test/unit_test.hpp>
#include <boost/test/test_case_template.hpp>
2014-11-24 11:57:01 -05:00
#include <osrm/coordinate.hpp>
#include <cstdint>
#include <cmath>
#include <algorithm>
#include <memory>
2014-06-26 19:27:45 -04:00
#include <random>
#include <string>
#include <utility>
2014-06-26 19:27:45 -04:00
#include <unordered_set>
#include <vector>
2014-06-26 19:27:45 -04:00
BOOST_AUTO_TEST_SUITE(static_rtree)
2016-01-05 10:51:13 -05:00
using namespace osrm;
using namespace osrm::util;
2014-06-26 19:27:45 -04:00
constexpr uint32_t TEST_BRANCHING_FACTOR = 8;
constexpr uint32_t TEST_LEAF_NODE_SIZE = 64;
2014-06-26 19:27:45 -04:00
2016-01-05 10:51:13 -05:00
using TestData = extractor::EdgeBasedNode;
using TestStaticRTree = StaticRTree<TestData,
std::vector<FixedPointCoordinate>,
false,
TEST_BRANCHING_FACTOR,
TEST_LEAF_NODE_SIZE>;
using MiniStaticRTree = StaticRTree<TestData, std::vector<FixedPointCoordinate>, false, 2, 3>;
2014-06-26 19:27:45 -04:00
// Choosen by a fair W20 dice roll (this value is completely arbitrary)
constexpr unsigned RANDOM_SEED = 42;
static const int32_t WORLD_MIN_LAT = -90 * COORDINATE_PRECISION;
static const int32_t WORLD_MAX_LAT = 90 * COORDINATE_PRECISION;
static const int32_t WORLD_MIN_LON = -180 * COORDINATE_PRECISION;
static const int32_t WORLD_MAX_LON = 180 * COORDINATE_PRECISION;
2014-06-26 19:27:45 -04:00
template <typename DataT> class LinearSearchNN
2014-06-26 19:27:45 -04:00
{
public:
LinearSearchNN(const std::shared_ptr<std::vector<FixedPointCoordinate>> &coords,
const std::vector<DataT> &edges)
: coords(coords), edges(edges)
{
}
2014-06-26 19:27:45 -04:00
std::vector<DataT> Nearest(const FixedPointCoordinate &input_coordinate,
const unsigned num_results)
2014-06-26 19:27:45 -04:00
{
std::vector<DataT> local_edges(edges);
2014-06-26 19:27:45 -04:00
std::nth_element(
local_edges.begin(), local_edges.begin() + num_results, local_edges.end(),
[this, &input_coordinate](const DataT &lhs, const DataT &rhs)
2014-06-26 19:27:45 -04:00
{
double current_ratio = 0.;
FixedPointCoordinate nearest;
2016-01-04 07:30:03 -05:00
const double lhs_dist = coordinate_calculation::perpendicularDistance(
coords->at(lhs.u), coords->at(lhs.v), input_coordinate, nearest, current_ratio);
2016-01-04 07:30:03 -05:00
const double rhs_dist = coordinate_calculation::perpendicularDistance(
coords->at(rhs.u), coords->at(rhs.v), input_coordinate, nearest, current_ratio);
return lhs_dist < rhs_dist;
});
local_edges.resize(num_results);
return local_edges;
2014-06-26 19:27:45 -04:00
}
private:
const std::shared_ptr<std::vector<FixedPointCoordinate>> &coords;
const std::vector<TestData> &edges;
2014-06-26 19:27:45 -04:00
};
template <unsigned NUM_NODES, unsigned NUM_EDGES> struct RandomGraphFixture
2014-06-26 19:27:45 -04:00
{
struct TupleHash
{
typedef std::pair<unsigned, unsigned> argument_type;
typedef std::size_t result_type;
2014-06-26 19:27:45 -04:00
result_type operator()(const argument_type &t) const
2014-06-26 19:27:45 -04:00
{
std::size_t val{0};
2014-06-26 19:27:45 -04:00
boost::hash_combine(val, t.first);
boost::hash_combine(val, t.second);
return val;
}
};
RandomGraphFixture() : coords(std::make_shared<std::vector<FixedPointCoordinate>>())
2014-06-26 19:27:45 -04:00
{
BOOST_TEST_MESSAGE("Constructing " << NUM_NODES << " nodes and " << NUM_EDGES << " edges.");
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);
2014-06-26 19:27:45 -04:00
for (unsigned i = 0; i < NUM_NODES; i++)
{
int lat = lat_udist(g);
int lon = lon_udist(g);
2016-01-05 10:51:13 -05:00
nodes.emplace_back(extractor::QueryNode(lat, lon, OSMNodeID(i)));
2014-06-26 19:27:45 -04:00
coords->emplace_back(FixedPointCoordinate(lat, lon));
}
std::uniform_int_distribution<> edge_udist(0, nodes.size() - 1);
std::unordered_set<std::pair<unsigned, unsigned>, TupleHash> used_edges;
while (edges.size() < NUM_EDGES)
2014-06-26 19:27:45 -04:00
{
TestData data;
data.u = edge_udist(g);
data.v = edge_udist(g);
if (used_edges.find(std::pair<unsigned, unsigned>(
std::min(data.u, data.v), std::max(data.u, data.v))) == used_edges.end())
2014-06-26 19:27:45 -04:00
{
2015-11-18 17:23:00 -05:00
data.component.id = 0;
2014-06-26 19:27:45 -04:00
edges.emplace_back(data);
used_edges.emplace(std::min(data.u, data.v), std::max(data.u, data.v));
}
}
}
2016-01-05 10:51:13 -05:00
std::vector<extractor::QueryNode> nodes;
2014-06-26 19:27:45 -04:00
std::shared_ptr<std::vector<FixedPointCoordinate>> coords;
std::vector<TestData> edges;
};
struct GraphFixture
{
GraphFixture(const std::vector<std::pair<double, double>> &input_coords,
const std::vector<std::pair<unsigned, unsigned>> &input_edges)
: coords(std::make_shared<std::vector<FixedPointCoordinate>>())
{
for (unsigned i = 0; i < input_coords.size(); i++)
{
FixedPointCoordinate c(input_coords[i].first * COORDINATE_PRECISION,
input_coords[i].second * COORDINATE_PRECISION);
coords->emplace_back(c);
2016-01-05 10:51:13 -05:00
nodes.emplace_back(extractor::QueryNode(c.lat, c.lon, OSMNodeID(i)));
}
for (const auto &pair : input_edges)
{
TestData d;
d.u = pair.first;
d.v = pair.second;
// We set the forward nodes to the target node-based-node IDs, just
// so we have something to test against. Because this isn't a real
// graph, the actual values aren't important, we just need something
// to examine during tests.
d.forward_edge_based_node_id = pair.second;
d.reverse_edge_based_node_id = pair.first;
edges.emplace_back(d);
}
}
2016-01-05 10:51:13 -05:00
std::vector<extractor::QueryNode> nodes;
std::shared_ptr<std::vector<FixedPointCoordinate>> coords;
std::vector<TestData> edges;
};
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 3, TEST_LEAF_NODE_SIZE / 2>
TestRandomGraphFixture_LeafHalfFull;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 5, TEST_LEAF_NODE_SIZE>
TestRandomGraphFixture_LeafFull;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 10, TEST_LEAF_NODE_SIZE * 2>
TestRandomGraphFixture_TwoLeaves;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 3,
TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR>
TestRandomGraphFixture_Branch;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 3,
TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 2>
TestRandomGraphFixture_MultipleLevels;
template <typename RTreeT>
void simple_verify_rtree(RTreeT &rtree,
const std::shared_ptr<std::vector<FixedPointCoordinate>> &coords,
const std::vector<TestData> &edges)
2014-06-26 19:27:45 -04:00
{
BOOST_TEST_MESSAGE("Verify end points");
for (const auto &e : edges)
2014-06-26 19:27:45 -04:00
{
const FixedPointCoordinate &pu = coords->at(e.u);
const FixedPointCoordinate &pv = coords->at(e.v);
auto result_u = rtree.Nearest(pu, 1);
auto result_v = rtree.Nearest(pv, 1);
BOOST_CHECK(result_u.size() == 1 && result_v.size() == 1);
BOOST_CHECK(result_u.front().u == e.u || result_u.front().v == e.u);
BOOST_CHECK(result_v.front().u == e.v || result_v.front().v == e.v);
2014-06-26 19:27:45 -04:00
}
}
template <typename RTreeT>
2016-01-05 06:04:04 -05:00
void sampling_verify_rtree(RTreeT &rtree,
LinearSearchNN<TestData> &lsnn,
const std::vector<FixedPointCoordinate> &coords,
unsigned num_samples)
2014-06-26 19:27:45 -04:00
{
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<FixedPointCoordinate> queries;
for (unsigned i = 0; i < num_samples; i++)
{
queries.emplace_back(FixedPointCoordinate(lat_udist(g), lon_udist(g)));
2014-06-26 19:27:45 -04:00
}
BOOST_TEST_MESSAGE("Sampling queries");
for (const auto &q : queries)
2014-06-26 19:27:45 -04:00
{
auto result_rtree = rtree.Nearest(q, 1);
auto result_lsnn = lsnn.Nearest(q, 1);
BOOST_CHECK(result_rtree.size() == 1);
BOOST_CHECK(result_lsnn.size() == 1);
auto rtree_u = result_rtree.back().u;
auto rtree_v = result_rtree.back().v;
auto lsnn_u = result_lsnn.back().u;
auto lsnn_v = result_lsnn.back().v;
double current_ratio = 0.;
FixedPointCoordinate nearest;
2016-01-04 07:30:03 -05:00
const double rtree_dist = coordinate_calculation::perpendicularDistance(
coords[rtree_u], coords[rtree_v], q, nearest, current_ratio);
2016-01-04 07:30:03 -05:00
const double lsnn_dist = coordinate_calculation::perpendicularDistance(
coords[lsnn_u], coords[lsnn_v], q, nearest, current_ratio);
BOOST_CHECK_LE(std::abs(rtree_dist - lsnn_dist), std::numeric_limits<double>::epsilon());
2014-06-26 19:27:45 -04:00
}
}
template <typename FixtureT, typename RTreeT = TestStaticRTree>
void build_rtree(const std::string &prefix,
FixtureT *fixture,
std::string &leaves_path,
std::string &nodes_path)
2014-06-26 19:27:45 -04:00
{
nodes_path = prefix + ".ramIndex";
leaves_path = prefix + ".fileIndex";
2014-06-26 19:27:45 -04:00
const std::string coords_path = prefix + ".nodes";
boost::filesystem::ofstream node_stream(coords_path, std::ios::binary);
2015-01-06 09:29:51 -05:00
const auto num_nodes = static_cast<unsigned>(fixture->nodes.size());
2014-06-26 19:27:45 -04:00
node_stream.write((char *)&num_nodes, sizeof(unsigned));
2016-01-05 10:51:13 -05:00
node_stream.write((char *)&(fixture->nodes[0]), num_nodes * sizeof(extractor::QueryNode));
2014-06-26 19:27:45 -04:00
node_stream.close();
RTreeT r(fixture->edges, nodes_path, leaves_path, fixture->nodes);
}
template <typename FixtureT, typename RTreeT = TestStaticRTree>
void construction_test(const std::string &prefix, FixtureT *fixture)
{
std::string leaves_path;
std::string nodes_path;
build_rtree<FixtureT, RTreeT>(prefix, fixture, leaves_path, nodes_path);
RTreeT rtree(nodes_path, leaves_path, fixture->coords);
LinearSearchNN<TestData> lsnn(fixture->coords, fixture->edges);
2014-06-26 19:27:45 -04:00
simple_verify_rtree(rtree, fixture->coords, fixture->edges);
sampling_verify_rtree(rtree, lsnn, *fixture->coords, 100);
2014-06-26 19:27:45 -04:00
}
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);
}
// Bug: If you querry a point that lies between two BBs that have a gap,
// one BB will be pruned, even if it could contain a nearer match.
BOOST_AUTO_TEST_CASE(regression_test)
{
using Coord = std::pair<double, double>;
using Edge = std::pair<unsigned, unsigned>;
GraphFixture fixture(
{
Coord(40.0, 0.0), Coord(35.0, 5.0),
Coord(5.0, 5.0), Coord(0.0, 10.0),
Coord(20.0, 10.0), Coord(20.0, 5.0),
Coord(40.0, 100.0), Coord(35.0, 105.0),
Coord(5.0, 105.0), Coord(0.0, 110.0),
},
{Edge(0, 1), Edge(2, 3), Edge(4, 5), Edge(6, 7), Edge(8, 9)});
std::string leaves_path;
std::string nodes_path;
build_rtree<GraphFixture, MiniStaticRTree>("test_regression", &fixture, leaves_path,
nodes_path);
MiniStaticRTree rtree(nodes_path, leaves_path, fixture.coords);
LinearSearchNN<TestData> lsnn(fixture.coords, fixture.edges);
// query a node just right of the center of the gap
FixedPointCoordinate input(20.0 * COORDINATE_PRECISION, 55.1 * COORDINATE_PRECISION);
auto result_rtree = rtree.Nearest(input, 1);
auto result_ls = lsnn.Nearest(input, 1);
BOOST_CHECK(result_rtree.size() == 1);
BOOST_CHECK(result_ls.size() == 1);
BOOST_CHECK_EQUAL(result_ls.front().u, result_rtree.front().u);
BOOST_CHECK_EQUAL(result_ls.front().v, result_rtree.front().v);
}
2014-06-26 19:27:45 -04:00
2014-07-04 18:59:31 -04:00
void TestRectangle(double width, double height, double center_lat, double center_lon)
{
FixedPointCoordinate center(center_lat * COORDINATE_PRECISION,
center_lon * COORDINATE_PRECISION);
2014-07-04 18:59:31 -04:00
TestStaticRTree::Rectangle rect;
rect.min_lat = center.lat - height / 2.0 * COORDINATE_PRECISION;
rect.max_lat = center.lat + height / 2.0 * COORDINATE_PRECISION;
rect.min_lon = center.lon - width / 2.0 * COORDINATE_PRECISION;
rect.max_lon = center.lon + width / 2.0 * COORDINATE_PRECISION;
2014-07-04 18:59:31 -04:00
unsigned offset = 5 * COORDINATE_PRECISION;
2014-07-04 18:59:31 -04:00
FixedPointCoordinate north(rect.max_lat + offset, center.lon);
FixedPointCoordinate south(rect.min_lat - offset, center.lon);
FixedPointCoordinate west(center.lat, rect.min_lon - offset);
FixedPointCoordinate east(center.lat, rect.max_lon + offset);
FixedPointCoordinate north_east(rect.max_lat + offset, rect.max_lon + offset);
FixedPointCoordinate north_west(rect.max_lat + offset, rect.min_lon - offset);
FixedPointCoordinate south_east(rect.min_lat - offset, rect.max_lon + offset);
FixedPointCoordinate south_west(rect.min_lat - offset, rect.min_lon - offset);
/* Distance to line segments of rectangle */
BOOST_CHECK_EQUAL(rect.GetMinDist(north),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
north, FixedPointCoordinate(rect.max_lat, north.lon)));
BOOST_CHECK_EQUAL(rect.GetMinDist(south),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
south, FixedPointCoordinate(rect.min_lat, south.lon)));
BOOST_CHECK_EQUAL(rect.GetMinDist(west),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
west, FixedPointCoordinate(west.lat, rect.min_lon)));
BOOST_CHECK_EQUAL(rect.GetMinDist(east),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
east, FixedPointCoordinate(east.lat, rect.max_lon)));
2014-07-04 18:59:31 -04:00
/* Distance to corner points */
BOOST_CHECK_EQUAL(rect.GetMinDist(north_east),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
north_east, FixedPointCoordinate(rect.max_lat, rect.max_lon)));
BOOST_CHECK_EQUAL(rect.GetMinDist(north_west),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
north_west, FixedPointCoordinate(rect.max_lat, rect.min_lon)));
BOOST_CHECK_EQUAL(rect.GetMinDist(south_east),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
south_east, FixedPointCoordinate(rect.min_lat, rect.max_lon)));
BOOST_CHECK_EQUAL(rect.GetMinDist(south_west),
2016-01-04 07:30:03 -05:00
coordinate_calculation::greatCircleDistance(
south_west, FixedPointCoordinate(rect.min_lat, rect.min_lon)));
2014-07-04 18:59:31 -04:00
}
BOOST_AUTO_TEST_CASE(rectangle_test)
{
TestRectangle(10, 10, 5, 5);
TestRectangle(10, 10, -5, 5);
TestRectangle(10, 10, 5, -5);
TestRectangle(10, 10, -5, -5);
TestRectangle(10, 10, 0, 0);
}
BOOST_AUTO_TEST_CASE(bearing_tests)
{
using Coord = std::pair<double, double>;
using Edge = std::pair<unsigned, unsigned>;
GraphFixture fixture(
{
Coord(0.0, 0.0), Coord(10.0, 10.0),
},
{Edge(0, 1), Edge(1, 0)});
std::string leaves_path;
std::string nodes_path;
build_rtree<GraphFixture, MiniStaticRTree>("test_bearing", &fixture, leaves_path, nodes_path);
MiniStaticRTree rtree(nodes_path, leaves_path, fixture.coords);
2016-01-05 10:51:13 -05:00
engine::GeospatialQuery<MiniStaticRTree> query(rtree, fixture.coords);
FixedPointCoordinate input(5.0 * COORDINATE_PRECISION, 5.1 * COORDINATE_PRECISION);
{
auto results = query.NearestPhantomNodes(input, 5);
BOOST_CHECK_EQUAL(results.size(), 2);
2015-12-09 17:01:28 -05:00
BOOST_CHECK_EQUAL(results.back().phantom_node.forward_node_id, 0);
BOOST_CHECK_EQUAL(results.back().phantom_node.reverse_node_id, 1);
}
{
auto results = query.NearestPhantomNodes(input, 5, 270, 10);
BOOST_CHECK_EQUAL(results.size(), 0);
}
{
auto results = query.NearestPhantomNodes(input, 5, 45, 10);
BOOST_CHECK_EQUAL(results.size(), 2);
2015-12-09 17:01:28 -05:00
BOOST_CHECK_EQUAL(results[0].phantom_node.forward_node_id, 1);
BOOST_CHECK_EQUAL(results[0].phantom_node.reverse_node_id, SPECIAL_NODEID);
BOOST_CHECK_EQUAL(results[1].phantom_node.forward_node_id, SPECIAL_NODEID);
BOOST_CHECK_EQUAL(results[1].phantom_node.reverse_node_id, 1);
}
{
auto results = query.NearestPhantomNodesInRange(input, 11000);
BOOST_CHECK_EQUAL(results.size(), 2);
}
{
auto results = query.NearestPhantomNodesInRange(input, 11000, 270, 10);
BOOST_CHECK_EQUAL(results.size(), 0);
}
{
auto results = query.NearestPhantomNodesInRange(input, 11000, 45, 10);
BOOST_CHECK_EQUAL(results.size(), 2);
2015-12-09 17:01:28 -05:00
BOOST_CHECK_EQUAL(results[0].phantom_node.forward_node_id, 1);
BOOST_CHECK_EQUAL(results[0].phantom_node.reverse_node_id, SPECIAL_NODEID);
BOOST_CHECK_EQUAL(results[1].phantom_node.forward_node_id, SPECIAL_NODEID);
BOOST_CHECK_EQUAL(results[1].phantom_node.reverse_node_id, 1);
}
}
BOOST_AUTO_TEST_CASE(bbox_search_tests)
{
using Coord = std::pair<double, double>;
using Edge = std::pair<unsigned, unsigned>;
GraphFixture fixture(
{
Coord(0.0,0.0),
Coord(1.0,1.0),
Coord(2.0,2.0),
Coord(3.0,3.0),
Coord(4.0,4.0),
},
{Edge(0,1), Edge(1,2), Edge(2,3), Edge(3,4)});
std::string leaves_path;
std::string nodes_path;
build_rtree<GraphFixture, MiniStaticRTree>("test_bbox", &fixture, leaves_path, nodes_path);
MiniStaticRTree rtree(nodes_path, leaves_path, fixture.coords);
engine::GeospatialQuery<MiniStaticRTree> query(rtree, fixture.coords);
{
RectangleInt2D bbox = {static_cast<uint32_t>(0.5 * COORDINATE_PRECISION), static_cast<uint32_t>(1.5 * COORDINATE_PRECISION),
static_cast<uint32_t>(0.5 * COORDINATE_PRECISION), static_cast<uint32_t>(1.5 * COORDINATE_PRECISION)};
auto results = query.Search(bbox);
BOOST_CHECK_EQUAL(results.size(), 2);
}
{
RectangleInt2D bbox = {static_cast<uint32_t>(1.5 * COORDINATE_PRECISION), static_cast<uint32_t>(3.5 * COORDINATE_PRECISION),
static_cast<uint32_t>(1.5 * COORDINATE_PRECISION), static_cast<uint32_t>(3.5 * COORDINATE_PRECISION)};
auto results = query.Search(bbox);
BOOST_CHECK_EQUAL(results.size(), 3);
}
}
2014-06-26 19:27:45 -04:00
BOOST_AUTO_TEST_SUITE_END()