Generalize the implementation of the Douglas Peucker algorithm to allow using a custom predicate to decide if points between the anchor and the floater should be removed.

This commit is contained in:
Lukáš Hejl 2024-06-06 17:30:58 +02:00 committed by Lukas Matena
parent b6fd7f7758
commit 225d52b084
3 changed files with 24 additions and 13 deletions

View File

@ -58,10 +58,7 @@ std::pair<std::vector<Vec2d>, std::vector<PointType>> remove_redundant_points(
const std::int64_t index{std::distance(points.begin(), iterator)}; const std::int64_t index{std::distance(points.begin(), iterator)};
if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) { if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) {
std::vector<Vec2d> simplification_result; std::vector<Vec2d> simplification_result;
douglas_peucker<double>( douglas_peucker(range_start, next(iterator), std::back_inserter(simplification_result), tolerance);
range_start, next(iterator), std::back_inserter(simplification_result), tolerance,
[](const Vec2d &point) { return point; }
);
points_result.insert( points_result.insert(
points_result.end(), simplification_result.begin(), simplification_result.end() points_result.end(), simplification_result.begin(), simplification_result.end()

View File

@ -33,8 +33,8 @@ class BoundingBox3;
// Reduces polyline in the <begin, end) range, outputs into the output iterator. // Reduces polyline in the <begin, end) range, outputs into the output iterator.
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address. // Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
template<typename SquareLengthType, typename InputIterator, typename OutputIterator, typename PointGetter> template<typename SquareLengthType, typename InputIterator, typename OutputIterator, typename TakeFloaterPredicate, typename PointGetter>
inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter) inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, TakeFloaterPredicate take_floater_predicate, PointGetter point_getter)
{ {
using InputIteratorCategory = typename std::iterator_traits<InputIterator>::iterator_category; using InputIteratorCategory = typename std::iterator_traits<InputIterator>::iterator_category;
static_assert(std::is_base_of_v<std::input_iterator_tag, InputIteratorCategory>); static_assert(std::is_base_of_v<std::input_iterator_tag, InputIteratorCategory>);
@ -50,7 +50,6 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou
// Two points input. // Two points input.
*out ++ = std::move(*next); *out ++ = std::move(*next);
} else { } else {
const auto tolerance_sq = SquareLengthType(sqr(tolerance));
InputIterator anchor = begin; InputIterator anchor = begin;
InputIterator floater = std::prev(end); InputIterator floater = std::prev(end);
std::vector<InputIterator> dpStack; std::vector<InputIterator> dpStack;
@ -108,8 +107,8 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou
assert(max_dist_sq.has_value()); assert(max_dist_sq.has_value());
// remove point if less than tolerance // Remove points between the anchor and the floater when the predicate is satisfied.
take_floater = max_dist_sq <= tolerance_sq; take_floater = take_floater_predicate(anchor, floater, *max_dist_sq);
} }
if (take_floater) { if (take_floater) {
@ -138,14 +137,29 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou
return out; return out;
} }
// Reduces polyline in the <begin, end) range, outputs into the output iterator. template<typename SquareLengthType, typename InputIterator, typename OutputIterator, typename PointGetter>
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address. inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter) {
const auto tolerance_sq = static_cast<SquareLengthType>(sqr(tolerance));
const auto take_floater_predicate = [&tolerance_sq](InputIterator, InputIterator, const SquareLengthType max_dist_sq) -> bool {
return max_dist_sq <= tolerance_sq;
};
return douglas_peucker<SquareLengthType>(begin, end, out, take_floater_predicate, point_getter);
}
template<typename OutputIterator> template<typename OutputIterator>
inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance) inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance)
{ {
return douglas_peucker<int64_t>(begin, end, out, tolerance, [](const Point &p) { return p; }); return douglas_peucker<int64_t>(begin, end, out, tolerance, [](const Point &p) { return p; });
} }
template<typename OutputIterator>
inline OutputIterator douglas_peucker(Pointfs::const_iterator begin, Pointfs::const_iterator end, OutputIterator out, const double tolerance)
{
return douglas_peucker<double>(begin, end, out, tolerance, [](const Vec2d &p) { return p; });
}
inline Points douglas_peucker(const Points &src, const double tolerance) inline Points douglas_peucker(const Points &src, const double tolerance)
{ {
Points out; Points out;

View File

@ -81,14 +81,14 @@ SCENARIO("Simplify polyne, template", "[Polyline]")
Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} }; Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} };
WHEN("simplified with Douglas-Peucker with back inserter") { WHEN("simplified with Douglas-Peucker with back inserter") {
Points out; Points out;
douglas_peucker<int64_t>(polyline.begin(), polyline.end(), std::back_inserter(out), 10, [](const Point &p) { return p; }); douglas_peucker<int64_t>(polyline.begin(), polyline.end(), std::back_inserter(out), 10., [](const Point &p) { return p; });
THEN("simplified correctly") { THEN("simplified correctly") {
REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} }); REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} });
} }
} }
WHEN("simplified with Douglas-Peucker in place") { WHEN("simplified with Douglas-Peucker in place") {
Points out{ polyline }; Points out{ polyline };
out.erase(douglas_peucker<int64_t>(out.begin(), out.end(), out.begin(), 10, [](const Point &p) { return p; }), out.end()); out.erase(douglas_peucker<int64_t>(out.begin(), out.end(), out.begin(), 10., [](const Point &p) { return p; }), out.end());
THEN("simplified correctly") { THEN("simplified correctly") {
REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} }); REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} });
} }