I have a set of polygons of which I want to find the union. In order to allow for some floating point inaccuracies for polygons that touch each other, I am using boost::geometry::buffer
with a inflation strategy via boost::geometry::strategy::buffer::distance_symmetric<double>
. For certain inputs, the buffer operation produces results that I do not understand:
There are 7 polygons to be joined. The union (via boost::geometry::union_
) behaves as expected, but in the buffered join, the last two polygons appear to be missing.
Interestingly, the buffer behaves as expected if I slightly perturbate Polygon 4 (shifted by +0.01 in x and y).
or remove it from the set altogether:
Code snippet of polygon set and buffer operation:
using Point = boost::geometry::model::d2::point_xy<double>;
using Polygon = boost::geometry::model::polygon<Point>;
double epsilon = 0.3;
boost::geometry::strategy::buffer::join_miter join_strategy;
boost::geometry::strategy::buffer::distance_symmetric<double>
inflate_strategy(epsilon);
boost::geometry::strategy::buffer::end_flat end_strategy;
boost::geometry::strategy::buffer::side_straight side_strategy;
boost::geometry::strategy::buffer::point_circle point_strategy;
boost::geometry::model::multi_polygon<Polygon> source_set, joined_inflated;
source_set.push_back(
Polygon{{Point(12.50, 36.50), Point(12.50, 30.50), Point(6.50, 30.50),
Point(6.50, 36.50), Point(12.50, 36.50)}});
source_set.push_back(
Polygon{{Point(6.50, 27.00), Point(6.50, 33.50), Point(12.50, 33.50),
Point(12.50, 27.00), Point(6.50, 27.00)}});
source_set.push_back(
Polygon{{Point(12.50, 30.00), Point(12.50, 24.00), Point(6.50, 24.00),
Point(6.50, 30.00), Point(12.50, 30.00)}});
source_set.push_back(
Polygon{{Point(24.00, 24.00), Point(9.50, 24.00), Point(9.50, 30.00),
Point(24.00, 30.00), Point(24.00, 24.00)}});
source_set.push_back(
Polygon{{Point(27.0, 30.0), Point(27.0, 24.0), Point(21.0, 24.0),
Point(21.0, 30.0), Point(27.0, 30.0)}});
source_set.push_back(
Polygon{{Point(28.10, 30.73), Point(26.90, 26.23), Point(21.10, 27.77),
Point(22.30, 32.27), Point(28.10, 30.73)}});
source_set.push_back(
Polygon{{Point(28.20, 34.50), Point(28.20, 28.50), Point(22.20, 28.50),
Point(22.20, 34.50), Point(28.20, 34.50)}});
boost::geometry::buffer(source_set, joined_inflated, inflate_strategy,
side_strategy, join_strategy, end_strategy,
point_strategy);
Why does the buffered join behave this way?
As per your Godbolt link, 1.78 fixes it: 1.77 vs. 1.78 https://godbolt.org/z/9ofqx75z1
The change history has some potentially related items:
Bugs
- 906 Invalid result of buffer on macos-11 with clang-12.
- 921 Compilation errors with c++20 (various compilers) and gcc-5.
- Various fixes in set operations and buffer.
I would assume it falls under "Various fixes in set operations and buffer" then:
git log --oneline --graph --left-right boost-1.77.0..boost-1.78.0 -- $(git ls-files | grep buffer)
> 2a7db45d0 [test] take car alternate tests are test properly in old (rescaling) and new (no rescaling) regime
> ef1b8e33f [side] make the default for no-rescaling triangle to avoid regressions when rescaling is turned off
> 915564a02 [test][buffer] Add test case that was failing on macos-11, clang-12.0.5, x86-64.
> cc21b05ab [buffer] Increase traversable turns distance threshold.
> 716c79136 [test] enhance/fix robustness tests
> e99cfde12 [intersection] use balance between distance-to-end and length-of-segments to determine to use a or b
> 03d6e82f2 [coordinate] deprecate util/promote_floating_point.hpp
> 5110ec7da Add missing headers to satisfy Boost header policy.
> 08f7e66f7 [test] Drop library dependencies in tests.
A manual bisect
shows that 03d6e82f2 was the last broken version. Since there are no other commits between 03d6e82f2..e99cfde12 that means the fix is actually to intersection
:
commit e99cfde120ba347800a1b2472cffed93729e3ad1
Author: Barend Gehrels <barend@xs4all.nl>
Date: Wed Sep 8 11:54:19 2021 +0200
[intersection] use balance between distance-to-end and length-of-segments to determine to use a or b
The code has some comments like
// When calculating the intersection, the information of "a" or "b" can be used.
// Theoretically this gives equal results, but due to floating point precision
// there might be tiny differences. These are edge cases.
But the real explanation appears in the related PR: https://github.com/boostorg/geometry/pull/897. The short summary is that it was a robustness fix uncovered by Mysql devs.