c++boostboost-geometry

Boost geometry buffer not behaving as expected when joining polygons


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: enter image description here

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). enter image description here

or remove it from the set altogether: enter image description here

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?


Solution

  • 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.