Use DouglasPeucker with squaredEuclideanDistance

This commit is contained in:
Patrick Niklaus 2016-04-09 00:39:20 +02:00
parent 67834def5f
commit 4886d46d91
3 changed files with 116 additions and 56 deletions

View File

@ -12,26 +12,47 @@ namespace engine
{ {
namespace detail namespace detail
{ {
const constexpr int DOUGLAS_PEUCKER_THRESHOLDS[19] = {
512440, // z0 // This is derived from the following formular:
256720, // z1 // x = b * (1 + lon/180) => dx = b * dlon/180
122560, // z2 // y = b * (1 - lat/180) => dy = b * dlat/180
56780, // z3 // dx^2 + dy^2 < min_pixel^2
28800, // z4 // => dlon^2 + dlat^2 < min_pixel^2 / b^2 * 180^2
14400, // z5 inline std::vector<std::uint64_t> generateThreshold(double min_pixel, unsigned number_of_zoomlevels)
7200, // z6 {
3200, // z7 std::vector<std::uint64_t> thresholds(number_of_zoomlevels);
2400, // z8 for (unsigned zoom = 0; zoom < number_of_zoomlevels; ++zoom)
1000, // z9 {
600, // z10 const double shift = (1u << zoom) * 256;
120, // z11 const double b = shift / 2.0;
60, // z12 const double pixel_to_deg = 180. / b;
45, // z13 const std::uint64_t min_deg = min_pixel * pixel_to_deg * COORDINATE_PRECISION;
36, // z14 thresholds[zoom] = min_deg * min_deg;
20, // z15 }
8, // z16
6, // z17 return thresholds;
4, // z18 }
const constexpr std::uint64_t DOUGLAS_PEUCKER_THRESHOLDS[19] = {
49438476562500, // z0
12359619140625, // z1
3089903027344, // z2
772475756836, // z3
193118939209, // z4
48279515076, // z5
12069878769, // z6
3017414761, // z7
754326225, // z8
188567824, // z9
47141956, // z10
11785489, // z11
2944656, // z12
736164, // z13
184041, // z14
45796, // z15
11449, // z16
2809, // z17
676, // z18
}; };
const constexpr auto DOUGLAS_PEUCKER_THRESHOLDS_SIZE = const constexpr auto DOUGLAS_PEUCKER_THRESHOLDS_SIZE =

View File

@ -16,6 +16,31 @@ namespace osrm
namespace engine namespace engine
{ {
struct FastPerpendicularDistance
{
FastPerpendicularDistance(const util::Coordinate start, const util::Coordinate target)
: projected_start(util::coordinate_calculation::mercator::fromWGS84(start)),
projected_target(util::coordinate_calculation::mercator::fromWGS84(target))
{
}
// Normed to the thresholds table
std::uint64_t operator()(const util::Coordinate coordinate) const
{
auto projected = util::coordinate_calculation::mercator::fromWGS84(coordinate);
util::FloatCoordinate projected_point_on_segment;
std::tie(std::ignore, projected_point_on_segment) =
util::coordinate_calculation::projectPointOnSegment(projected_start, projected_target,
projected);
auto squared_distance = util::coordinate_calculation::squaredEuclideanDistance(projected,
projected_point_on_segment);
return squared_distance;
}
const util::FloatCoordinate projected_start;
const util::FloatCoordinate projected_target;
};
std::vector<util::Coordinate> douglasPeucker(std::vector<util::Coordinate>::const_iterator begin, std::vector<util::Coordinate> douglasPeucker(std::vector<util::Coordinate>::const_iterator begin,
std::vector<util::Coordinate>::const_iterator end, std::vector<util::Coordinate>::const_iterator end,
const unsigned zoom_level) const unsigned zoom_level)
@ -51,14 +76,16 @@ std::vector<util::Coordinate> douglasPeucker(std::vector<util::Coordinate>::cons
BOOST_ASSERT_MSG(pair.second < size, "right border outside of geometry"); BOOST_ASSERT_MSG(pair.second < size, "right border outside of geometry");
BOOST_ASSERT_MSG(pair.first <= pair.second, "left border on the wrong side"); BOOST_ASSERT_MSG(pair.first <= pair.second, "left border on the wrong side");
double max_distance = 0; std::uint64_t max_distance = 0;
auto farthest_entry_index = pair.second; auto farthest_entry_index = pair.second;
FastPerpendicularDistance perpendicular_distance(begin[pair.first], begin[pair.second]);
// sweep over range to find the maximum // sweep over range to find the maximum
for (auto idx = pair.first + 1; idx != pair.second; ++idx) for (auto idx = pair.first + 1; idx != pair.second; ++idx)
{ {
using namespace util::coordinate_calculation; using namespace util::coordinate_calculation;
const auto distance = perpendicularDistance(begin[pair.first], begin[pair.second], begin[idx]); const auto distance = perpendicular_distance(begin[idx]);
// found new feasible maximum? // found new feasible maximum?
if (distance > max_distance && if (distance > max_distance &&
distance > detail::DOUGLAS_PEUCKER_THRESHOLDS[zoom_level]) distance > detail::DOUGLAS_PEUCKER_THRESHOLDS[zoom_level])

View File

@ -1,4 +1,5 @@
#include "engine/douglas_peucker.hpp" #include "engine/douglas_peucker.hpp"
#include "util/coordinate_calculation.hpp"
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <boost/test/test_case_template.hpp> #include <boost/test/test_case_template.hpp>
@ -12,6 +13,16 @@ BOOST_AUTO_TEST_SUITE(douglas_peucker_simplification)
using namespace osrm; using namespace osrm;
using namespace osrm::engine; using namespace osrm::engine;
BOOST_AUTO_TEST_CASE(calibrate_thresholds)
{
auto thresholds = detail::generateThreshold(5, 19);
auto z = 0;
for (auto t : thresholds)
{
BOOST_TEST_MESSAGE(t << ", // z" << z++);
}
}
BOOST_AUTO_TEST_CASE(removed_middle_test) BOOST_AUTO_TEST_CASE(removed_middle_test)
{ {
/* /*
@ -23,9 +34,9 @@ BOOST_AUTO_TEST_CASE(removed_middle_test)
*/ */
std::vector<util::Coordinate> coordinates = { std::vector<util::Coordinate> coordinates = {
util::Coordinate(util::FloatLongitude(5), util::FloatLatitude(5)), util::Coordinate(util::FloatLongitude(5), util::FloatLatitude(5)),
util::Coordinate(util::FloatLongitude(5.995715), util::FloatLatitude(6)), util::Coordinate(util::FloatLongitude(12.5), util::FloatLatitude(12.6096298302)),
util::Coordinate(util::FloatLongitude(10), util::FloatLatitude(10)), util::Coordinate(util::FloatLongitude(20), util::FloatLatitude(20)),
util::Coordinate(util::FloatLongitude(15), util::FloatLatitude(5))}; util::Coordinate(util::FloatLongitude(25), util::FloatLatitude(5))};
for (unsigned z = 0; z < detail::DOUGLAS_PEUCKER_THRESHOLDS_SIZE; z++) for (unsigned z = 0; z < detail::DOUGLAS_PEUCKER_THRESHOLDS_SIZE; z++)
{ {
@ -49,19 +60,19 @@ BOOST_AUTO_TEST_CASE(removed_middle_test_zoom_sensitive)
std::vector<util::Coordinate> coordinates = { std::vector<util::Coordinate> coordinates = {
util::Coordinate(util::FloatLongitude(5), util::FloatLatitude(5)), util::Coordinate(util::FloatLongitude(5), util::FloatLatitude(5)),
util::Coordinate(util::FloatLongitude(6), util::FloatLatitude(6)), util::Coordinate(util::FloatLongitude(6), util::FloatLatitude(6)),
util::Coordinate(util::FloatLongitude(10), util::FloatLatitude(10)), util::Coordinate(util::FloatLongitude(20), util::FloatLatitude(20)),
util::Coordinate(util::FloatLongitude(15), util::FloatLatitude(5))}; util::Coordinate(util::FloatLongitude(25), util::FloatLatitude(5))};
// Coordinate 6,6 should start getting included at Z12 and higher // Coordinate 6,6 should start getting included at Z9 and higher
// Note that 5,5->6,6->10,10 is *not* a straight line on the surface // Note that 5,5->6,6->10,10 is *not* a straight line on the surface
// of the earth // of the earth
for (unsigned z = 0; z < 11; z++) for (unsigned z = 0; z < 9; z++)
{ {
auto result = douglasPeucker(coordinates, z); auto result = douglasPeucker(coordinates, z);
BOOST_CHECK_EQUAL(result.size(), 3); BOOST_CHECK_EQUAL(result.size(), 3);
} }
// From 12 to max zoom, we should get all coordinates // From 9 to max zoom, we should get all coordinates
for (unsigned z = 12; z < detail::DOUGLAS_PEUCKER_THRESHOLDS_SIZE; z++) for (unsigned z = 9; z < detail::DOUGLAS_PEUCKER_THRESHOLDS_SIZE; z++)
{ {
auto result = douglasPeucker(coordinates, z); auto result = douglasPeucker(coordinates, z);
BOOST_CHECK_EQUAL(result.size(), 4); BOOST_CHECK_EQUAL(result.size(), 4);
@ -71,38 +82,39 @@ BOOST_AUTO_TEST_CASE(removed_middle_test_zoom_sensitive)
BOOST_AUTO_TEST_CASE(remove_second_node_test) BOOST_AUTO_TEST_CASE(remove_second_node_test)
{ {
for (unsigned z = 0; z < detail::DOUGLAS_PEUCKER_THRESHOLDS_SIZE; z++) // derived from the degreeToPixel function
const auto delta_pixel_to_delta_degree = [](const int pixel, const unsigned zoom) {
const double shift = (1u << zoom) * 256;
const double b = shift / 2.0;
return pixel * 180. / b;
};
// For zoom level 0 all gets reduced to a line
for (unsigned z = 1; z < detail::DOUGLAS_PEUCKER_THRESHOLDS_SIZE; z++)
{ {
/* /*
x--x x
| \ | \
x-x x x-x x
| |
x x
*/ */
std::vector<util::Coordinate> input = { std::vector<util::Coordinate> input = {
util::Coordinate(util::FixedLongitude(5 * COORDINATE_PRECISION), util::Coordinate(util::FloatLongitude(5),
util::FixedLatitude(5 * COORDINATE_PRECISION)), util::FloatLatitude(5)),
util::Coordinate(util::FixedLongitude(5 * COORDINATE_PRECISION), util::Coordinate(util::FloatLongitude(5 + delta_pixel_to_delta_degree(2, z)),
util::FixedLatitude(5 * COORDINATE_PRECISION + util::FloatLatitude(5)),
detail::DOUGLAS_PEUCKER_THRESHOLDS[z])), util::Coordinate(util::FloatLongitude(10),
util::Coordinate(util::FixedLongitude(10 * COORDINATE_PRECISION), util::FloatLatitude(10)),
util::FixedLatitude(10 * COORDINATE_PRECISION)), util::Coordinate(util::FloatLongitude(5),
util::Coordinate(util::FixedLongitude(10 * COORDINATE_PRECISION), util::FloatLatitude(15)),
util::FixedLatitude(10 + COORDINATE_PRECISION + util::Coordinate(util::FloatLongitude(5),
detail::DOUGLAS_PEUCKER_THRESHOLDS[z] * 2)), util::FloatLatitude(15 + delta_pixel_to_delta_degree(2, z)))};
util::Coordinate(util::FixedLongitude(5 * COORDINATE_PRECISION), BOOST_TEST_MESSAGE("Delta (" << z << "): " << delta_pixel_to_delta_degree(2, z));
util::FixedLatitude(15 * COORDINATE_PRECISION)),
util::Coordinate(util::FixedLongitude(5 * COORDINATE_PRECISION +
detail::DOUGLAS_PEUCKER_THRESHOLDS[z]),
util::FixedLatitude(15 * COORDINATE_PRECISION))};
BOOST_TEST_MESSAGE("Threshold (" << z << "): " << detail::DOUGLAS_PEUCKER_THRESHOLDS[z]);
auto result = douglasPeucker(input, z); auto result = douglasPeucker(input, z);
BOOST_CHECK_EQUAL(result.size(), 4); BOOST_CHECK_EQUAL(result.size(), 3);
BOOST_CHECK_EQUAL(input[0], result[0]); BOOST_CHECK_EQUAL(input[0], result[0]);
BOOST_CHECK_EQUAL(input[2], result[1]); BOOST_CHECK_EQUAL(input[2], result[1]);
BOOST_CHECK_EQUAL(input[3], result[2]); BOOST_CHECK_EQUAL(input[4], result[2]);
BOOST_CHECK_EQUAL(input[5], result[3]);
} }
} }