diff --git a/extensions/test/algorithms/dissolve.cpp b/extensions/test/algorithms/dissolve.cpp index 438cebdfed..4203851413 100644 --- a/extensions/test/algorithms/dissolve.cpp +++ b/extensions/test/algorithms/dissolve.cpp @@ -1,7 +1,7 @@ // Boost.Geometry (aka GGL, Generic Geometry Library) // Unit Test -// Copyright (c) 2010-2017 Barend Gehrels, Amsterdam, the Netherlands. +// Copyright (c) 2010-2025 Barend Gehrels, Amsterdam, the Netherlands. // This file was modified by Oracle on 2022. // Modifications copyright (c) 2022, Oracle and/or its affiliates. @@ -11,8 +11,11 @@ // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -//#define BOOST_GEOMETRY_DEBUG_ENRICH -//#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER + +#if defined(TEST_WITH_GEOJSON) +#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER +#define BOOST_GEOMETRY_DEBUG_IDENTIFIER +#endif #include @@ -33,276 +36,12 @@ #include #include -#include - -#include - -#include -#include - - -#if defined(TEST_WITH_SVG) -# include -# include -# include -#endif - - -namespace -{ - // Simplex - std::string const dissolve_1 = "POLYGON((0 0,0 4,1.5 2.5,2.5 1.5,4 0,0 0))"; - - // Self intersecting - std::string const dissolve_2 = "POLYGON((1 2,1 1,2 1,2 2.25,3 2.25,3 0,0 0,0 3,3 3,2.75 2,1 2))"; - - // Self intersecting in last segment - std::string const dissolve_3 = "POLYGON((0 2,2 4,2 0,4 2,0 2))"; - - // Self tangent - polygons are now included twice - std::string const dissolve_4 = "POLYGON((0 0,0 4,4 4,4 0,2 4,0 0))"; - - // Self tangent in corner - polygons are now included twice - std::string const dissolve_5 = "POLYGON((0 0,0 4,4 4,4 0,0 4,2 0,0 0))"; - - // With spike - std::string const dissolve_6 = "POLYGON((0 0,0 4,4 4,4 2,6 2,4 2,4 0,0 0))"; - - // Many intersections - std::string const dissolve_7 = "POLYGON((1 3,0 9,9 5,1 7,9 8,2 5,10 10,9 2,1 3))"; - - // Pentagram - std::string const dissolve_8 = "POLYGON((5 0,2.5 9,9.5 3.5,0.5 3.5,7.5 9,5 0))"; - // CCW pentagram - std::string const dissolve_9 = "POLYGON((5 0,7.5 9,0.5 3.5,9.5 3.5,2.5 9,5 0))"; - // CW, one keyhole - std::string const dissolve_10 = "POLYGON((2 8,8 8,8 0,0 0,0 6,4 6,4 4,2 4,2 8))"; - // CCW, one keyhole - std::string const dissolve_11 = "POLYGON((2 8,2 4,4 4,4 6,0 6,0 0,8 0,8 8,2 8))"; - - // More pentagrams - // Source: http://upload.wikimedia.org/wikipedia/commons/8/83/Acute_heptagram.svg - std::string const dissolve_12 = "POLYGON((409 5,229 793.631528,733.348792 161.198146,4.543671 512.172194,813.456329 512.172194,84.651208 161.198146,589 793.631528))"; - // Source: http://upload.wikimedia.org/wikipedia/commons/a/a7/Obtuse_heptagram.svg - std::string const dissolve_13 = "POLYGON((409 5,813.456329 512.172194,229 793.631528,84.651208 161.198146,733.348792 161.198146,589 793.631528,4.543671 512.172194))"; - - std::string const dissolve_14 = "POLYGON((0 0,0 2,2 0,4 2,4 0,2 2,0 0))"; - std::string const dissolve_15 = "POLYGON((0 0,2 2,4 0,4 2,2 0,0 2,0 0))"; - - std::string const dissolve_16 = "POLYGON((1 3,4 5,7 3,4 1,1 3),(2 2,4 4,6 2,6 4,4 2,2 4,2 2))"; - - // Contains two types of turns - std::string const dissolve_17 = "POLYGON((0 1,0 5,3 5,3 2,2 3,4 3,4 0,5 1,0 1))"; - - // Same but with one more, creating a positive turn too - std::string const dissolve_18 = "POLYGON((0 1,0 5,3 5,3 2,2 3,4 3,4 0,6 2,6 1,0 1))"; - - // Non intersection, but with duplicate - std::string const dissolve_d1 = "POLYGON((0 0,0 4,4 0,4 0,0 0))"; - - // With many duplicate points - std::string const dissolve_d2 = "POLYGON((0 0,0 1,0 1,0 1,0 2,0 2,0 3,0 3,0 3,0 3,0 4,2 4,2 4,4 4,4 0,4 0,3 0,3 0,3 0,3 0,3 0,0 0))"; - - // Case with touching reversed interior ring inside, which should be removed - std::string const dissolve_h1_a = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 2,2 4,3 2,1 2))"; - - // Case with correct interior ring inside, which should be stay - std::string const dissolve_h1_b = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 2,3 2,2 4,1 2))"; - - // Hole: interior intersecting exterior - std::string const dissolve_h2 = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 1,1 3,5 4,1 1))"; - - // Hole: two intersecting holes - std::string const dissolve_h3 = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 1,1 3,3 3,3 1,1 1),(2 2,2 3.5,3.5 3.5,3.5 2,2 2))"; - - // Hole: self-intersecting hole - std::string const dissolve_h4 = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 1,3 3,3 2.5,1 3.5,1.5 3.5,1 1))"; - - // Star having an extra thingy - std::string const dissolve_star_a = "POLYGON((20.1493053436279300 3.3291947841644287,13.9365568161010740 3.7241046428680420,18.7846546173095700 1.0315386056900024,18.7846546173095700 0.9956377148628235,17.9586830139160160 4.6934285163879395,15.1575593948364260 1.2828447818756104,20.1493053436279300 3.3291947841644287))"; - // Star with larger extra thingy - std::string const dissolve_star_b = "POLYGON((20.1493053436279300 3.3291947841644287,13.9365568161010740 3.7241046428680420,18.7846546173095700 1.0315386056900024,18.7846546173095700 0.500000000000000,17.9586830139160160 4.6934285163879395,15.1575593948364260 1.2828447818756104,20.1493053436279300 3.3291947841644287))"; - // Star without extra thingy - std::string const dissolve_star_c = "POLYGON((20.1493053436279300 3.3291947841644287,13.9365568161010740 3.7241046428680420,18.7846546173095700 1.0315386056900024,17.9586830139160160 4.6934285163879395,15.1575593948364260 1.2828447818756104,20.1493053436279300 3.3291947841644287))"; - - std::string const multi_three_triangles = "MULTIPOLYGON(((1 1,5 5,8 0,1 1)),((4 2,0 8,5 9,4 2)),((5 3,4 8,10 4,5 3)))"; - std::string const multi_simplex_two = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)))"; - std::string const multi_simplex_three = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)))"; - std::string const multi_simplex_four = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((5 5,7 7,8 4,5 5)))"; - std::string const multi_disjoint = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((1 6,2 10,5 7,1 6)),((3 4,5 6,6 2,3 4)),((6 5,8 7,9 4,6 5)))"; - std::string const multi_new_interior = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((3 1,5 4,8 4,3 1)))"; - - // Testcases sent on GGL mailing list - report Javier - 2011, March 7 - std::string const ggl_list_20110307_javier_01_a = "MULTIPOLYGON(((560 -400, 600 -400, 600 -440, 560 -440, 560 -400)), ((480 -400, 520 -400, 520 -440, 480 -440, 480 -400)), ((600 -320, 640 -320, 640 -360, 600 -360, 600 -320)), ((520 -400, 560 -400, 560 -440, 520 -440, 520 -400)))"; - std::string const ggl_list_20110307_javier_01_b = "POLYGON((0 0, 2000 0, 2000 -2000, 0 -2000, 0 0), (560 -400, 560 -440, 600 -440, 600 -400, 560 -400), (480 -400, 480 -440, 520 -440, 520 -400, 480 -400), (600 -320, 600 -360, 640 -360, 640 -320, 600 -320), (520 -400, 520 -440, 560 -440, 560 -400, 520 -400))"; - - std::string const ggl_list_denis = "POLYGON((55 10, 141 237, 249 23, 21 171, 252 169, 24 89, 266 73, 55 10))"; - - // Testcases sent by Johan Doré at September 24 / October 26 / October 30, 2017 - std::string const dissolve_mail_2017_09_24_a = "POLYGON((0 1, 1 0, 1 1, 0 0, 0 1))"; // two triangles - std::string const dissolve_mail_2017_09_24_b = "POLYGON((1 0, 0 0, 0 4, 4 4, 4 0))"; // input is not closed - std::string const dissolve_mail_2017_09_24_c = "POLYGON((0 0, 1 0, 0 -1, 0.0001 1))"; // spike and not closed - std::string const dissolve_mail_2017_09_24_d = "POLYGON((0 0, 1 0, 0 -1, 0 1))"; // spike and not closed - - std::string const dissolve_mail_2017_10_26_a = "POLYGON((0 3, 3 3, 3 1, 2 1, 2 2, 1 2, 1 1, 2 1, 2 0, 0 0, 0 3))"; // should form interior ring - std::string const dissolve_mail_2017_10_26_b = "POLYGON((0 0, 0 4, 4 4, 4 0, 1 0, 1 3, 3 3, 3 0))"; // should NOT form interior ring and (maybe) should remove two not necessary intersection points (to be decided) - std::string const dissolve_mail_2017_10_26_c = "POLYGON((0 2, 2 1, 3 1, 1 1, 2 1, 4 2, 4 0, 0 0))"; // contains cluster and should ignore count_left/count_right - - // Testcase sent by Johan Doré, giving a problem on Windows indicating that input (geometry1/geometry2) for union should NOT be reference to output collection. Fixed by copying it. - std::string const dissolve_mail_2018_08_19 = "POLYGON((13.78 -2.18, 7.04 -2.33, 7.08 -2.33,16.05 -0.55,16.46 -6.08,16.99 -5.82, 2.92 -1.99,15.14 -1.65,17.41 -4.42,13.78 -2.18))"; - - // This case needs rescaling for . For it runs fine without. - std::string const dissolve_mail_2017_10_30_a = "POLYGON((12.7069120407104490 -2.3525938987731934, 12.6983022689819340 -2.3552336692810059, 12.6984634399414060 -2.3553242683410645, 12.6980066299438480 -2.3553242683410645, 12.6983022689819340 -2.3552336692810059, 12.6911554336547850 -2.3512287139892578, 12.7025737762451170 -2.3398520946502686))"; - - // Testcases sent by Artem Pavlenko via gitter - // https://gitter.im/boostorg/geometry?at=58ef46408e4b63533dc49b48 - std::string const gitter_2013_04_a = "POLYGON((36.9121 2.03883,26.2052 54.353,60.0781 64.2202,96.2171 55.9826,71.1506 39.8365,5.72552 94.1523,4.06819 13.9054,59.7155 44.5877,60.9243 16.4597,48.8696 93.039,36.9121 2.03883))"; - std::string const gitter_2013_04_b = "POLYGON((337 176,602 377,294 372,581 166,453 449,337 176))"; - - // Large polygon with self-intersections - std::string const dissolve_mail_2017_09_24_e = "POLYGON((25.21475410461420 -1.19600892066955, 25.36818313598630 -1.20732820034027, 25.36953926086420 -1.20868659019470, 25.37180328369140 -1.20959210395812, 25.37451934814450 -1.21049761772155, 25.37632942199700 -1.21185600757598, 25.37904357910150 -1.21276152133941, 25.38085556030270 -1.21321427822113, 25.38221168518060 -1.21366703510284, 25.38311767578120 -1.21411991119384, 25.38447570800780 -1.21411991119384, 25.38447570800780 -1.21185600757598, 25.38492774963370 -1.21140325069427, 25.38492774963370 -1.21095049381256, 25.38945388793940 -1.21095049381256, 25.38990592956540 -1.21049761772155, 25.39035987854000 -1.21004486083984, 25.39081192016600 -1.21004486083984, 25.39081192016600 -1.20959210395812, 25.39126396179190 -1.20913934707641, 25.39171600341790 -1.20913934707641, 25.39262199401850 -1.20868659019470, 25.39262199401850 -1.20823383331298, 25.39352798461910 -1.20823383331298, 25.39398002624510 -1.20778107643127, 25.39443206787100 -1.20778107643127, 25.39443206787100 -1.20732820034027, 25.39488410949700 -1.20687544345855, 25.39443206787100 -1.20642268657684, 25.39420700073240 -1.20596992969512, 25.39386749267570 -1.20551717281341, 25.39352798461910 -1.20551717281341, 25.39352798461910 -1.20506441593170, 25.39386749267570 -1.20551717281341, 25.39398002624510 -1.20551717281341, 25.39420700073240 -1.20596992969512, 25.39488410949700 -1.20687544345855, 25.39533805847160 -1.20778107643127, 25.39579010009760 -1.20823383331298, 25.39579010009760 -1.20868659019470, 25.39624214172360 -1.20913934707641, 25.39669609069820 -1.20959210395812, 25.39669609069820 -1.21049761772155, 25.39714813232420 -1.21095049381256, 25.39760017395010 -1.21185600757598, 25.39805221557610 -1.21321427822113, 25.39850616455070 -1.21411991119384, 25.39895820617670 -1.21547818183898, 25.39895820617670 -1.21638369560241, 25.39986419677730 -1.21819484233856, 25.40031623840330 -1.21955311298370, 25.40076828002920 -1.22045862674713, 25.40076828002920 -1.22181701660156, 25.40122032165520 -1.22226977348327, 25.40122032165520 -1.22408080101013, 25.40212631225580 -1.22498643398284, 25.40212631225580 -1.22634470462799, 25.40257835388180 -1.22725021839141, 25.40257835388180 -1.22770309448242, 25.40303230285640 -1.22860860824584, 25.40303230285640 -1.23041963577270, 25.40348434448240 -1.23041963577270, 25.21475410461420 -1.19600892066955))"; - - // Idem but moving closing point eastwards to view self-intersections better - std::string const dissolve_mail_2017_09_24_f = "POLYGON((25.35475410461420 -1.20600892066955, 25.36818313598630 -1.20732820034027, 25.36953926086420 -1.20868659019470, 25.37180328369140 -1.20959210395812, 25.37451934814450 -1.21049761772155, 25.37632942199700 -1.21185600757598, 25.37904357910150 -1.21276152133941, 25.38085556030270 -1.21321427822113, 25.38221168518060 -1.21366703510284, 25.38311767578120 -1.21411991119384, 25.38447570800780 -1.21411991119384, 25.38447570800780 -1.21185600757598, 25.38492774963370 -1.21140325069427, 25.38492774963370 -1.21095049381256, 25.38945388793940 -1.21095049381256, 25.38990592956540 -1.21049761772155, 25.39035987854000 -1.21004486083984, 25.39081192016600 -1.21004486083984, 25.39081192016600 -1.20959210395812, 25.39126396179190 -1.20913934707641, 25.39171600341790 -1.20913934707641, 25.39262199401850 -1.20868659019470, 25.39262199401850 -1.20823383331298, 25.39352798461910 -1.20823383331298, 25.39398002624510 -1.20778107643127, 25.39443206787100 -1.20778107643127, 25.39443206787100 -1.20732820034027, 25.39488410949700 -1.20687544345855, 25.39443206787100 -1.20642268657684, 25.39420700073240 -1.20596992969512, 25.39386749267570 -1.20551717281341, 25.39352798461910 -1.20551717281341, 25.39352798461910 -1.20506441593170, 25.39386749267570 -1.20551717281341, 25.39398002624510 -1.20551717281341, 25.39420700073240 -1.20596992969512, 25.39488410949700 -1.20687544345855, 25.39533805847160 -1.20778107643127, 25.39579010009760 -1.20823383331298, 25.39579010009760 -1.20868659019470, 25.39624214172360 -1.20913934707641, 25.39669609069820 -1.20959210395812, 25.39669609069820 -1.21049761772155, 25.39714813232420 -1.21095049381256, 25.39760017395010 -1.21185600757598, 25.39805221557610 -1.21321427822113, 25.39850616455070 -1.21411991119384, 25.39895820617670 -1.21547818183898, 25.39895820617670 -1.21638369560241, 25.39986419677730 -1.21819484233856, 25.40031623840330 -1.21955311298370, 25.40076828002920 -1.22045862674713, 25.40076828002920 -1.22181701660156, 25.40122032165520 -1.22226977348327, 25.40122032165520 -1.22408080101013, 25.40212631225580 -1.22498643398284, 25.40212631225580 -1.22634470462799, 25.40257835388180 -1.22725021839141, 25.40257835388180 -1.22770309448242, 25.40303230285640 -1.22860860824584, 25.40303230285640 -1.23041963577270, 25.40348434448240 -1.23041963577270, 25.35475410461420 -1.20600892066955))"; - - // As d but with other spike - std::string const dissolve_mail_2017_09_24_g = "POLYGON((1 0, 0 -1, 0 1, 0 0,1 0))"; - std::string const dissolve_mail_2017_09_24_h = "POLYGON((0 0, 0 1, 2 1, 1 1,0 0))"; - - std::string const dissolve_ticket17 = "POLYGON ((-122.28139163 37.37319149,-122.28100699 37.37273669,-122.28002186 37.37303123,-122.27979681 37.37290072,-122.28007349 37.37240493,-122.27977334 37.37220360,-122.27819720 37.37288580,-122.27714184 37.37275161,-122.27678628 37.37253167,-122.27766437 37.37180973,-122.27804382 37.37121453,-122.27687664 37.37101354,-122.27645829 37.37203386,-122.27604423 37.37249110,-122.27632234 37.37343339,-122.27760980 37.37391082,-122.27812478 37.37800320,-122.26117222 37.39121007,-122.25572289 37.39566631,-122.25547269 37.39564971,-122.25366304 37.39552993,-122.24919976 37.39580268,-122.24417933 37.39366907,-122.24051443 37.39094143,-122.23246277 37.38100418,-122.23606766 37.38141338,-122.24001587 37.37738940,-122.23666848 37.37609347,-122.23057450 37.37882170,-122.22679803 37.37807143,-122.22525727 37.37448817,-122.22523229 37.37443000,-122.23083199 37.37609347,-122.23033486 37.37777891,-122.23169030 37.37732117,-122.23229178 37.37709687,-122.23237761 37.37631249,-122.23297776 37.37438834,-122.23872850 37.37165986,-122.24044511 37.36934068,-122.24671067 37.36865847,-122.24825570 37.36981819,-122.25151719 37.36947713,-122.25357721 37.36756706,-122.26001451 37.36579354,-122.25615213 37.36545239,-122.25486458 37.36245083,-122.25357721 37.36108651,-122.25194642 37.36013139,-122.24885652 37.35958557,-122.24911401 37.35849399,-122.25357721 37.35808470,-122.25675286 37.35897159,-122.25855539 37.35753887,-122.26181687 37.35828939,-122.26713837 37.35897159,-122.26782510 37.36108651,-122.26662339 37.36456559,-122.27288911 37.36722601,-122.27366159 37.36531602,-122.27168740 37.36470213,-122.27391900 37.36374701,-122.27074326 37.36245083,-122.27134408 37.35951742,-122.27426240 37.36135926,-122.27709482 37.36115474,-122.27966974 37.36231438,-122.27958391 37.36463382,-122.27572152 37.36463382,-122.27563569 37.36524779,-122.27700899 37.36593000,-122.27709482 37.36763529,-122.27554978 37.36838573,-122.27667254 37.36931478,-122.27677932 37.36932073,-122.27769362 37.36853987,-122.27942490 37.36830803,-122.28178776 37.36677917,-122.28509559 37.36443500,-122.28845129 37.36413744,-122.29194403 37.36695946,-122.29382577 37.36726817,-122.29600414 37.36898512,-122.29733083 37.36995398,-122.29593239 37.37141436,-122.29416649 37.37075898,-122.29325026 37.37108436,-122.29652910 37.37311697,-122.29584237 37.37374461,-122.29537583 37.37573372,-122.29487677 37.37752502,-122.30923212 37.37593011,-122.31122484 37.38230086,-122.31467994 37.38092472,-122.31715663 37.38252181,-122.32307970 37.38166978,-122.31985618 37.37667694,-122.32210304 37.37580220,-122.32581446 37.37589532,-122.32401730 37.37331839,-122.32960417 37.37189020,-122.33465527 37.37331906,-122.33425328 37.37623680,-122.33620676 37.37726132,-122.33397986 37.37822382,-122.33358918 37.38036590,-122.33202637 37.37986918,-122.33147954 37.38101784,-122.33394080 37.38198017,-122.33545239 37.38587943,-122.33478058 37.38785697,-122.33386050 37.38723721,-122.33350041 37.38571137,-122.33122003 37.38548891,-122.33140008 37.38650606,-122.33366042 37.38817490,-122.33244019 37.39157602,-122.33298157 37.39419201,-122.33164013 37.39477028,-122.33202017 37.39518351,-122.33358038 37.39499282,-122.33376050 37.39597811,-122.33550067 37.39734478,-122.33556069 37.39481797,-122.33344040 37.39292676,-122.33638094 37.38892189,-122.34240644 37.38852719,-122.34906293 37.38726898,-122.35072321 37.39338769,-122.34910291 37.39445252,-122.34796272 37.39410291,-122.34449043 37.39640534,-122.34500223 37.39729709,-122.34936291 37.39670910,-122.35098322 37.39531066,-122.35364623 37.39554510,-122.35434369 37.39612111,-122.35798429 37.39600988,-122.35768430 37.39478621,-122.36334519 37.39206871,-122.36604726 37.39203267,-122.36778592 37.39335592,-122.36518870 37.40022011,-122.36554552 37.40247752,-122.36370519 37.40331974,-122.36270506 37.40530591,-122.36320512 37.40670418,-122.36149849 37.40851392,-122.36730580 37.41054938,-122.37263720 37.41378932,-122.37161871 37.42076600,-122.36566153 37.42006292,-122.36520547 37.42742106,-122.37165953 37.43661157,-122.35943972 37.44459022,-122.35356359 37.44600810,-122.33792254 37.45796329,-122.35228518 37.47478091,-122.35127080 37.48181199,-122.34867342 37.48487322,-122.34359717 37.48801082,-122.33388431 37.48677650,-122.33142321 37.48429747,-122.32929580 37.48473149,-122.32609609 37.48291144,-122.32344850 37.48228229,-122.31924364 37.48410234,-122.31677299 37.48114051,-122.31431751 37.47848973,-122.31259201 37.47682190,-122.31515972 37.47568196,-122.31691389 37.47360309,-122.31292494 37.46960081,-122.31130153 37.46937743,-122.30889894 37.47124987,-122.30612839 37.47011613,-122.30149630 37.46568378,-122.30064277 37.46363784,-122.29283821 37.45922376,-122.28630141 37.45415497,-122.28883099 37.44629920,-122.28316717 37.44197138,-122.27554148 37.42297597,-122.25597410 37.40553692,-122.25196579 37.40129593,-122.25012043 37.40049143,-122.24823207 37.39897758,-122.24754551 37.39740941,-122.24778582 37.39621607,-122.24934787 37.39599102,-122.25005170 37.39871849,-122.25222328 37.39863668,-122.25342491 37.39737529,-122.25520162 37.39667289,-122.25528737 37.39522726,-122.27747460 37.37809616,-122.27977493 37.37858717,-122.28157729 37.37920106,-122.28322534 37.37952846,-122.28416939 37.38092656,-122.28621223 37.37984219,-122.28638389 37.37613857,-122.28382607 37.37843722,-122.27930278 37.37718220,-122.28196361 37.37652740,-122.28295058 37.37568167,-122.28216101 37.37523148,-122.28114822 37.37543608,-122.27934569 37.37528613,-122.27996369 37.37448121,-122.28104521 37.37454944,-122.28185197 37.37422883,-122.28290767 37.37474038,-122.28376597 37.37467224,-122.28428104 37.37399012,-122.28402346 37.37338989,-122.28610922 37.37364914,-122.28651264 37.37327388,-122.28672722 37.37207343,-122.28628398 37.37205448,-122.28574460 37.37166682,-122.28479711 37.37200981,-122.28327731 37.37137228,-122.28285511 37.37100700,-122.28279409 37.37125669,-122.28315527 37.37173756,-122.28321872 37.37220569,-122.28187007 37.37231918,-122.28193109 37.37294908,-122.28139163 37.37319149))"; - std::string const dissolve_reallife = "POLYGON((170718 605997,170718 605997,170776 606016,170773 606015,170786 606020,170778 606016,170787 606021,170781 606017,170795 606028,170795 606028,170829 606055,170939 606140,170933 605968,170933 605968,170932 605908,170929 605834,170920 605866,170961 605803,170739 605684,170699 605749,170691 605766,170693 605762,170686 605775,170688 605771,170673 605794,170676 605790,170668 605800,170672 605796,170651 605818,170653 605816,170639 605829,170568 605899,170662 605943,170633 605875,170603 605961,170718 605997))"; - - // https://svn.boost.org/trac/boost/ticket/10713 - std::string const dissolve_ticket10713 = "POLYGON((-0.7189743518829346 4.1308121681213379, 0.0831791982054710 4.1034231185913086, 0.1004156470298767 4.1107301712036133, 0.1044322624802589 4.1026973724365234, 0.0831791982054710 4.1034231185913086, -0.7711903452873230 3.7412264347076416, -0.7189743518829346 4.1308121681213379))"; -} - - -#if defined(TEST_WITH_SVG) -template -struct map_visitor -{ - map_visitor(Mapper& mapper) - : m_mapper(mapper) - {} - - template - void visit_turns(int phase, Turns const& turns) - { - typedef typename boost::range_value::type turn_type; - std::size_t index = 0; - for (turn_type const& turn : turns) - { - switch (phase) - { - case 2 : // after self_turns and enrich - if (turn.discarded) - { - m_mapper.map(turn.point, "fill:rgb(255,128,0);", 2); - } - else - { - m_mapper.map(turn.point, "fill:rgb(255,128,0);" - "stroke:rgb(0,0,0);stroke-width:1", 4); - } - break; - case 3 : // after enrich/traverse - label_turn(index, turn, -2, "fill:rgb(0,0,128);"); - break; - } - index++; - } - } - - template - void visit_clusters(Clusters const& , Turns const& ) {} - - template - void visit_traverse(Turns const& , Turn const& , Operation const& , char const*) - {} - - template - void visit_traverse_reject(Turns const& , Turn const& , Operation const& , - bg::detail::overlay::traverse_error_type ) - {} - - template - void visit_generated_rings(Rings const& rings) - { - typedef typename boost::range_value::type ring_type; - for (ring_type const& ring : rings) - { - double const area = bg::area(ring); - std::string const color = area < 0 ? "rgb(255,0,0)" : "rgb(0,0,255)"; - std::string const style = "stroke:" + color - + ";stroke-width:0.1;fill-opacity:0.1;fill:" + color; - m_mapper.map(ring, style); - } - } +#include "dissolve_overlay_cases.hpp" -private : - - template - bool label_operation(Turn const& turn, std::size_t index, std::ostream& os) - { - os << bg::operation_char(turn.operations[index].operation); - bool result = false; - if (! turn.discarded) - { - os << "->" << turn.operations[index].enriched.get_next_turn_index(); - result = true; - } - if (turn.operations[index].enriched.prefer_start) - { - os << "$"; - } - if (! turn.operations[index].enriched.startable) - { - os << "@"; - } - - return result; - } - - template - void label_turn(std::size_t index, Turn const& turn, int y_offset, std::string const& color) - { - std::ostringstream out; - out << index; - if (turn.cluster_id != -1) - { - out << " c=" << turn.cluster_id << " "; - } - bool lab1 = label_operation(turn, 0, out); - out << " / "; - bool lab2 = label_operation(turn, 1, out); - if (turn.discarded) - { - out << "!"; - } - - std::string font8 = "font-family:Arial;font-size:6px"; - std::string font6 = "font-family:Arial;font-size:4px"; - std::string style = color + ";" + font8; - if (turn.discarded) - { - style = "fill:rgb(92,92,92);" + font6; - } - else if (turn.cluster_id != -1) - { - style = color + ";" + font8; - } - else if (! lab1 || ! lab2) - { - style = color + ";" + font6; - } - - m_mapper.text(turn.point, out.str(), style, 5, y_offset, 6); - } - - Mapper& m_mapper; -}; - +#if defined(TEST_WITH_GEOJSON) +#include +#include "dissolve_geojson_visitor.hpp" #endif //! Unittest settings @@ -331,18 +70,31 @@ void test_dissolve(std::string const& caseid, Geometry const& geometry, double expected_area, std::size_t expected_clip_count, std::size_t expected_hole_count, - std::size_t expected_point_count, ut_settings const& settings) { - using coordinate_type = typename bg::coordinate_type::type; +#if defined(TEST_WITH_GEOJSON) + std::ostringstream filename; + // For QGis, it is usually convenient to always write to the same geojson file. + filename << "/tmp/" + // << caseid << "_" + << "dissolve.geojson"; + std::ofstream geojson_file(filename.str().c_str()); - static const bool is_line = bg::geometry_id::type::value == 2; + boost::geometry::geojson_writer writer(geojson_file); +#endif //std::cout << bg::area(geometry) << std::endl; + using coordinate_type = typename bg::coordinate_type::type; using multi_polygon = bg::model::multi_polygon; multi_polygon dissolved1; +#if defined(TEST_WITH_GEOJSON) + geojson_visitor visitor(writer); +#else + bg::detail::overlay::overlay_null_visitor visitor; +#endif + // Check dispatch::dissolve { using strategy_type = typename bg::strategies::relate::services::default_strategy @@ -350,60 +102,17 @@ void test_dissolve(std::string const& caseid, Geometry const& geometry, Geometry, Geometry >::type; - using rescale_policy_type = typename bg::rescale_policy_type - < - typename bg::point_type::type - >::type; - - rescale_policy_type robust_policy - = bg::get_rescale_policy(geometry); - - // This will optionally also create SVG with turn-debug information strategy_type strategy; -#if ! defined(TEST_WITH_SVG) - bg::detail::overlay::overlay_null_visitor visitor; -#else - std::ostringstream filename; - filename << "dissolve_" << caseid << "_" - << string_from_type::name() - << ".svg"; - - std::ofstream svg(filename.str().c_str()); - - using mapper_type = bg::svg_mapper - < - typename bg::point_type::type - >; - - mapper_type mapper(svg, 500, 500); - mapper.add(geometry); - - mapper.map(geometry, "fill-opacity:0.5;fill:rgb(153,204,0);" - "stroke:rgb(153,204,0);stroke-width:2;fill-rule:nonzero;"); - - map_visitor visitor(mapper); -#endif - bg::dispatch::dissolve < Geometry, GeometryOut, false - >::apply(geometry, robust_policy, std::back_inserter(dissolved1), + >::apply(geometry, std::back_inserter(dissolved1), strategy, visitor); - -#if defined(TEST_WITH_SVG) - for (GeometryOut& dissolved : dissolved1) - { - mapper.map(dissolved, "fill-opacity:0.1;fill:rgb(255,0,0);" - "stroke-opacity:0.4;stroke:rgb(255,0,255);stroke-width:3;" - "fill-rule:nonzero;"); } -#endif - - } if (settings.test_validity) { @@ -422,24 +131,14 @@ void test_dissolve(std::string const& caseid, Geometry const& geometry, typename bg::default_area_result::type length_or_area = 0; std::size_t holes = 0; - std::size_t count = 0; for (GeometryOut& dissolved : dissolved1) { - length_or_area += - is_line ? bg::length(dissolved) : bg::area(dissolved); + length_or_area += bg::area(dissolved); holes += bg::num_interior_rings(dissolved); - count += bg::num_points(dissolved); } - BOOST_CHECK_MESSAGE(count == expected_point_count, - "dissolve: " << caseid - << " #points expected: " << expected_point_count - << " detected: " << count - << " type: " << string_from_type::name() - ); - BOOST_CHECK_MESSAGE(dissolved1.size() == expected_clip_count, "dissolve: " << caseid << " #clips expected: " << expected_clip_count @@ -482,15 +181,25 @@ void test_dissolve(std::string const& caseid, Geometry const& geometry, BOOST_CHECK_MESSAGE(wkt1 == wkt3, caseid << " : output differs: " << wkt1 << " VERSUS " << wkt3); } } -} +#if defined(TEST_WITH_GEOJSON) + writer.feature(geometry); + writer.add_property("type", "input"); + + for (const auto& polygon : dissolved1) + { + writer.feature(polygon); + writer.add_property("type", "dissolved"); + } +#endif + +} template void test_one(std::string caseid, std::string const& wkt, double expected_area, std::size_t expected_clip_count, std::size_t expected_hole_count, - std::size_t expected_point_count, ut_settings const& settings) { Geometry geometry; @@ -502,7 +211,7 @@ void test_one(std::string caseid, std::string const& wkt, test_dissolve(caseid, geometry, expected_area, - expected_clip_count, expected_hole_count, expected_point_count, + expected_clip_count, expected_hole_count, settings); // Verify if reversed version is indeed identical (it should, because each @@ -512,7 +221,7 @@ void test_one(std::string caseid, std::string const& wkt, caseid += "_rev"; test_dissolve(caseid, geometry, expected_area, - expected_clip_count, expected_hole_count, expected_point_count, + expected_clip_count, expected_hole_count, settings); #ifdef BOOST_GEOMETRY_TEST_MULTI_PERMUTATIONS @@ -539,26 +248,26 @@ void test_one(std::string caseid, std::string const& wkt, geometry2.push_back(geometry[index]); } test_dissolve(out.str(), geometry2, expected_area, - expected_clip_count, expected_hole_count, expected_point_count, settings); + expected_clip_count, expected_hole_count, settings); } while (std::next_permutation(indices.begin(), indices.end())); #endif } -#define TEST_DISSOLVE(caseid, area, clips, holes, points) { \ +#define TEST_DISSOLVE(caseid, area, clips, holes, points_ignored) { \ ut_settings settings; \ - (test_one) ( #caseid, caseid, area, clips, holes, points, settings); } + (test_one) ( #caseid, caseid, area, clips, holes, settings); } -#define TEST_DISSOLVE_WITH(caseid, area, clips, holes, points, settings) { \ - (test_one) ( #caseid, caseid, area, clips, holes, points, settings); } +#define TEST_DISSOLVE_WITH(caseid, area, clips, holes, points_ignored, settings) { \ + (test_one) ( #caseid, caseid, area, clips, holes, settings); } -#define TEST_DISSOLVE_IGNORE(caseid, area, clips, holes, points) { \ +#define TEST_DISSOLVE_IGNORE(caseid, area, clips, holes, points_ignored) { \ ut_settings settings; settings.test_validity = false; \ - (test_one) ( #caseid, caseid, area, clips, holes, points, settings); } + (test_one) ( #caseid, caseid, area, clips, holes, settings); } -#define TEST_MULTI(caseid, area, clips, holes, points) { \ +#define TEST_MULTI(caseid, area, clips, holes, points_ignored) { \ ut_settings settings; \ - (test_one) ( #caseid, caseid, area, clips, holes, points, settings); } + (test_one) ( #caseid, caseid, area, clips, holes, settings); } template void test_all(ut_settings const& settings_for_sensitive_cases) @@ -611,8 +320,7 @@ void test_all(ut_settings const& settings_for_sensitive_cases) TEST_DISSOLVE(dissolve_mail_2017_09_24_c, 0.5, 2, 0, 8); TEST_DISSOLVE(dissolve_mail_2017_09_24_d, 0.5, 1, 0, 4); TEST_DISSOLVE(dissolve_mail_2017_09_24_e, 0.001801138128, 5, 0, 69); - TEST_DISSOLVE_WITH(dissolve_mail_2017_09_24_f, 0.000361308800, 5, 0, 69, - settings_for_sensitive_cases); + TEST_DISSOLVE(dissolve_mail_2017_09_24_f, 0.000361308800, 5, 0, 69); TEST_DISSOLVE(dissolve_mail_2017_09_24_g, 0.5, 1, 0, 4); TEST_DISSOLVE(dissolve_mail_2017_09_24_h, 0.5, 1, 0, 4); @@ -633,10 +341,8 @@ void test_all(ut_settings const& settings_for_sensitive_cases) TEST_MULTI(ggl_list_20110307_javier_01_a, 6400.0, 2, 0, 11); TEST_DISSOLVE(ggl_list_20110307_javier_01_b, 3993600.0, 1, 2, 16); - TEST_DISSOLVE_WITH(dissolve_ticket17, 0.00920834633689, 1, 1, 228, - settings_for_sensitive_cases); - TEST_DISSOLVE_WITH(dissolve_reallife, 91756.916526794434, 1, 0, 25, - settings_for_sensitive_cases); + TEST_DISSOLVE(dissolve_ticket17, 0.00920834633689, 1, 1, 228); + TEST_DISSOLVE(dissolve_reallife, 91756.916526794434, 1, 0, 25); #if defined(BOOST_GEOMETRY_TEST_FAILURES) TEST_DISSOLVE(gitter_2013_04_a, 3043.9181, 3, 0, 21); @@ -652,7 +358,6 @@ void test_all(ut_settings const& settings_for_sensitive_cases) int test_main(int, char* []) { - test_all, true >(ut_settings(0.01)); test_all, true >(ut_settings()); // Counter clockwise input does not work correctly in all cases, it is // partly a problem of the test itself diff --git a/extensions/test/algorithms/dissolve_geojson_visitor.hpp b/extensions/test/algorithms/dissolve_geojson_visitor.hpp new file mode 100644 index 0000000000..bade14a4ce --- /dev/null +++ b/extensions/test/algorithms/dissolve_geojson_visitor.hpp @@ -0,0 +1,150 @@ +// Boost.Geometry +// +// Copyright (c) 2025 Barend Gehrels, Amsterdam, the Netherlands. + +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Official repository: https://github.com/boostorg/geometry +// Documentation: http://www.boost.org/libs/geometry + +#ifndef BOOST_GEOMETRY_TEST_DISSOLVE_GEOJSON_VISITOR_HPP +#define BOOST_GEOMETRY_TEST_DISSOLVE_GEOJSON_VISITOR_HPP + +struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_visitor +{ + geojson_visitor(boost::geometry::geojson_writer& writer) + : m_writer(writer) + {} + + template + inline void visit_cluster_connections(bg::signed_size_type cluster_id, + Turns const& turns, Cluster const& cluster, Connections const& connections) + { + using point_type = typename Turns::value_type::point_type; + using ls_type = bg::model::linestring; + + auto id_as_string = [](auto const& id) + { + std::stringstream out; + out << id; + return out.str(); + }; + + if (cluster.turn_indices.empty()) + { + return; + } + + auto const& turn_point = turns[*cluster.turn_indices.begin()].point; + + for (auto const& item : connections) + { + auto const& key = item.key; + auto const& connection = item.properties; + ls_type const ls{turn_point, connection.point}; + m_writer.feature(ls); + m_writer.add_property("type", "cluster"); + m_writer.add_property("cluster_id", cluster_id); + m_writer.add_property("direction", std::string(key.connection == + bg::detail::overlay::connection_type::incoming ? "in" : "out")); + m_writer.add_property("position_code", connection.position_code); + m_writer.add_property("rank", connection.rank); + m_writer.add_property("count_left", connection.zone_count_left); + m_writer.add_property("count_right", connection.zone_count_right); + m_writer.add_property("seg_id", id_as_string(key.seg_id)); + m_writer.add_property("ring_id", id_as_string(bg::detail::overlay::ring_id_by_seg_id(key.seg_id))); + } + } + + template + void visit_turns(int phase, Turns const& turns) + { + if (phase != 3) + { + return; + } + + for (auto const& enumerated : boost::geometry::util::enumerate(turns)) + { + auto index = enumerated.index; + auto const& turn = enumerated.value; + auto const& op0 = turn.operations[0]; + auto const& op1 = turn.operations[1]; + + auto label_component = [&]() + { + std::ostringstream out; + auto const& c0 = op0.enriched.component_id; + auto const& c1 = op1.enriched.component_id; + if (c0 < 0 && c1 < 0) out << "-"; + else if (c0 == c1) out << c0; + else if (c0 < 0) out << c1; + else if (c1 < 0) out << c0; + else out << c0 << " | " << c1; + return out.str(); + }; + auto label_operation_ids = [&turn](int op_index) + { + std::ostringstream out; + out << bg::operation_char(turn.operations[op_index].operation) + << ": " << turn.operations[op_index].seg_id + << " v:" << turn.operations[op_index].enriched.travels_to_vertex_index + << "|t:" << turn.operations[op_index].enriched.travels_to_ip_index; + return out.str(); + }; + auto label_operations = [&]() + { + std::ostringstream out; + out << bg::operation_char(op0.operation) + << bg::operation_char(op1.operation); + return out.str(); + }; + auto label_preferences = [&]() + { + std::ostringstream out; + out << op0.preference_index + << "|" << op1.preference_index; + return out.str(); + }; + auto label_travel = [&]() + { + std::ostringstream out; + out << op0.enriched.travels_to_ip_index + << "|" << op1.enriched.travels_to_ip_index; + return out.str(); + }; + + m_writer.feature(turn.point); + m_writer.add_property("x", bg::get<0>(turn.point)); + m_writer.add_property("y", bg::get<1>(turn.point)); + m_writer.add_property("index", index); + m_writer.add_property("method", bg::method_char(turn.method)); + m_writer.add_property("travels_to", label_travel()); + m_writer.add_property("cluster_id", turn.cluster_id); + m_writer.add_property("discarded", turn.discarded); + m_writer.add_property("self_turn", bg::detail::overlay::is_self_turn(turn)); + m_writer.add_property("component", label_component()); + m_writer.add_property("operations", label_operations()); + m_writer.add_property("preferences", label_preferences()); + + m_writer.add_property("operation_0", label_operation_ids(0)); + m_writer.add_property("count_left_0", op0.enriched.count_left); + m_writer.add_property("count_right_0", op0.enriched.count_right); + m_writer.add_property("ahead_distance_0", op0.enriched.ahead_distance_of_side_change); + m_writer.add_property("ahead_side_0", op0.enriched.ahead_side); + + m_writer.add_property("operation_1", label_operation_ids(1)); + m_writer.add_property("count_left_1", op1.enriched.count_left); + m_writer.add_property("count_right_1", op1.enriched.count_right); + m_writer.add_property("ahead_distance_1", op1.enriched.ahead_distance_of_side_change); + m_writer.add_property("ahead_side_1", op1.enriched.ahead_side); + + } + } + + boost::geometry::geojson_writer& m_writer; + +}; + +#endif diff --git a/extensions/test/algorithms/dissolve_overlay_cases.hpp b/extensions/test/algorithms/dissolve_overlay_cases.hpp new file mode 100644 index 0000000000..7151601fa3 --- /dev/null +++ b/extensions/test/algorithms/dissolve_overlay_cases.hpp @@ -0,0 +1,141 @@ +// Boost.Geometry +// +// Copyright (c) 2025 Barend Gehrels, Amsterdam, the Netherlands. + +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Official repository: https://github.com/boostorg/geometry +// Documentation: http://www.boost.org/libs/geometry + +#ifndef BOOST_GEOMETRY_TEST_DISSOLVE_OVERLAY_CASES_HPP +#define BOOST_GEOMETRY_TEST_DISSOLVE_OVERLAY_CASES_HPP + +#include + +// Simplex +static std::string const dissolve_1 = "POLYGON((0 0,0 4,1.5 2.5,2.5 1.5,4 0,0 0))"; + +// Self intersecting +static std::string const dissolve_2 = "POLYGON((1 2,1 1,2 1,2 2.25,3 2.25,3 0,0 0,0 3,3 3,2.75 2,1 2))"; + +// Self intersecting in last segment +static std::string const dissolve_3 = "POLYGON((0 2,2 4,2 0,4 2,0 2))"; + +// Self tangent - polygons are now included twice +static std::string const dissolve_4 = "POLYGON((0 0,0 4,4 4,4 0,2 4,0 0))"; + +// Self tangent in corner - polygons are now included twice +static std::string const dissolve_5 = "POLYGON((0 0,0 4,4 4,4 0,0 4,2 0,0 0))"; + +// With spike +static std::string const dissolve_6 = "POLYGON((0 0,0 4,4 4,4 2,6 2,4 2,4 0,0 0))"; + +// Many intersections +static std::string const dissolve_7 = "POLYGON((1 3,0 9,9 5,1 7,9 8,2 5,10 10,9 2,1 3))"; + +// Pentagram +static std::string const dissolve_8 = "POLYGON((5 0,2.5 9,9.5 3.5,0.5 3.5,7.5 9,5 0))"; +// CCW pentagram +static std::string const dissolve_9 = "POLYGON((5 0,7.5 9,0.5 3.5,9.5 3.5,2.5 9,5 0))"; +// CW, one keyhole +static std::string const dissolve_10 = "POLYGON((2 8,8 8,8 0,0 0,0 6,4 6,4 4,2 4,2 8))"; +// CCW, one keyhole +static std::string const dissolve_11 = "POLYGON((2 8,2 4,4 4,4 6,0 6,0 0,8 0,8 8,2 8))"; + +// More pentagrams or heptagrams. +// Source: http://upload.wikimedia.org/wikipedia/commons/8/83/Acute_heptagram.svg +static std::string const dissolve_12 = "POLYGON((409 5,229 793.631528,733.348792 161.198146,4.543671 512.172194,813.456329 512.172194,84.651208 161.198146,589 793.631528))"; +// Source: http://upload.wikimedia.org/wikipedia/commons/a/a7/Obtuse_heptagram.svg +static std::string const dissolve_13 = "POLYGON((409 5,813.456329 512.172194,229 793.631528,84.651208 161.198146,733.348792 161.198146,589 793.631528,4.543671 512.172194))"; + +static std::string const dissolve_14 = "POLYGON((0 0,0 2,2 0,4 2,4 0,2 2,0 0))"; +static std::string const dissolve_15 = "POLYGON((0 0,2 2,4 0,4 2,2 0,0 2,0 0))"; + +static std::string const dissolve_16 = "POLYGON((1 3,4 5,7 3,4 1,1 3),(2 2,4 4,6 2,6 4,4 2,2 4,2 2))"; + +// Contains two types of turns +static std::string const dissolve_17 = "POLYGON((0 1,0 5,3 5,3 2,2 3,4 3,4 0,5 1,0 1))"; + +// Same but with one more, creating a positive turn too +static std::string const dissolve_18 = "POLYGON((0 1,0 5,3 5,3 2,2 3,4 3,4 0,6 2,6 1,0 1))"; + +// Non intersection, but with duplicate +static std::string const dissolve_d1 = "POLYGON((0 0,0 4,4 0,4 0,0 0))"; + +// With many duplicate points +static std::string const dissolve_d2 = "POLYGON((0 0,0 1,0 1,0 1,0 2,0 2,0 3,0 3,0 3,0 3,0 4,2 4,2 4,4 4,4 0,4 0,3 0,3 0,3 0,3 0,3 0,0 0))"; + +// Case with touching reversed interior ring inside, which should be removed +static std::string const dissolve_h1_a = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 2,2 4,3 2,1 2))"; + +// Case with correct interior ring inside, which should be stay +static std::string const dissolve_h1_b = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 2,3 2,2 4,1 2))"; + +// Hole: interior intersecting exterior +static std::string const dissolve_h2 = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 1,1 3,5 4,1 1))"; + +// Hole: two intersecting holes +static std::string const dissolve_h3 = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 1,1 3,3 3,3 1,1 1),(2 2,2 3.5,3.5 3.5,3.5 2,2 2))"; + +// Hole: self-intersecting hole +static std::string const dissolve_h4 = "POLYGON((0 0,0 4,4 4,4 0,0 0),(1 1,3 3,3 2.5,1 3.5,1.5 3.5,1 1))"; + +// Star having an extra thingy +static std::string const dissolve_star_a = "POLYGON((20.1493053436279300 3.3291947841644287,13.9365568161010740 3.7241046428680420,18.7846546173095700 1.0315386056900024,18.7846546173095700 0.9956377148628235,17.9586830139160160 4.6934285163879395,15.1575593948364260 1.2828447818756104,20.1493053436279300 3.3291947841644287))"; +// Star with larger extra thingy +static std::string const dissolve_star_b = "POLYGON((20.1493053436279300 3.3291947841644287,13.9365568161010740 3.7241046428680420,18.7846546173095700 1.0315386056900024,18.7846546173095700 0.500000000000000,17.9586830139160160 4.6934285163879395,15.1575593948364260 1.2828447818756104,20.1493053436279300 3.3291947841644287))"; +// Star without extra thingy +static std::string const dissolve_star_c = "POLYGON((20.1493053436279300 3.3291947841644287,13.9365568161010740 3.7241046428680420,18.7846546173095700 1.0315386056900024,17.9586830139160160 4.6934285163879395,15.1575593948364260 1.2828447818756104,20.1493053436279300 3.3291947841644287))"; + +static std::string const multi_three_triangles = "MULTIPOLYGON(((1 1,5 5,8 0,1 1)),((4 2,0 8,5 9,4 2)),((5 3,4 8,10 4,5 3)))"; +static std::string const multi_simplex_two = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)))"; +static std::string const multi_simplex_three = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)))"; +static std::string const multi_simplex_four = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((5 5,7 7,8 4,5 5)))"; +static std::string const multi_disjoint = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((1 6,2 10,5 7,1 6)),((3 4,5 6,6 2,3 4)),((6 5,8 7,9 4,6 5)))"; +static std::string const multi_new_interior = "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((3 1,5 4,8 4,3 1)))"; + +// Testcases sent on GGL mailing list - report Javier - 2011, March 7 +static std::string const ggl_list_20110307_javier_01_a = "MULTIPOLYGON(((560 -400, 600 -400, 600 -440, 560 -440, 560 -400)), ((480 -400, 520 -400, 520 -440, 480 -440, 480 -400)), ((600 -320, 640 -320, 640 -360, 600 -360, 600 -320)), ((520 -400, 560 -400, 560 -440, 520 -440, 520 -400)))"; +static std::string const ggl_list_20110307_javier_01_b = "POLYGON((0 0, 2000 0, 2000 -2000, 0 -2000, 0 0), (560 -400, 560 -440, 600 -440, 600 -400, 560 -400), (480 -400, 480 -440, 520 -440, 520 -400, 480 -400), (600 -320, 600 -360, 640 -360, 640 -320, 600 -320), (520 -400, 520 -440, 560 -440, 560 -400, 520 -400))"; + +static std::string const ggl_list_denis = "POLYGON((55 10, 141 237, 249 23, 21 171, 252 169, 24 89, 266 73, 55 10))"; + +// Testcases sent by Johan Doré at September 24 / October 26 / October 30, 2017 +static std::string const dissolve_mail_2017_09_24_a = "POLYGON((0 1, 1 0, 1 1, 0 0, 0 1))"; // two triangles +static std::string const dissolve_mail_2017_09_24_b = "POLYGON((1 0, 0 0, 0 4, 4 4, 4 0))"; // input is not closed +static std::string const dissolve_mail_2017_09_24_c = "POLYGON((0 0, 1 0, 0 -1, 0.0001 1))"; // spike and not closed +static std::string const dissolve_mail_2017_09_24_d = "POLYGON((0 0, 1 0, 0 -1, 0 1))"; // spike and not closed + +static std::string const dissolve_mail_2017_10_26_a = "POLYGON((0 3, 3 3, 3 1, 2 1, 2 2, 1 2, 1 1, 2 1, 2 0, 0 0, 0 3))"; // should form interior ring +static std::string const dissolve_mail_2017_10_26_b = "POLYGON((0 0, 0 4, 4 4, 4 0, 1 0, 1 3, 3 3, 3 0))"; // should NOT form interior ring and (maybe) should remove two not necessary intersection points (to be decided) +static std::string const dissolve_mail_2017_10_26_c = "POLYGON((0 2, 2 1, 3 1, 1 1, 2 1, 4 2, 4 0, 0 0))"; // contains cluster and should ignore count_left/count_right + +// Testcase sent by Johan Doré, giving a problem on Windows indicating that input (geometry1/geometry2) for union should NOT be reference to output collection. Fixed by copying it. +static std::string const dissolve_mail_2018_08_19 = "POLYGON((13.78 -2.18, 7.04 -2.33, 7.08 -2.33,16.05 -0.55,16.46 -6.08,16.99 -5.82, 2.92 -1.99,15.14 -1.65,17.41 -4.42,13.78 -2.18))"; + +// This case needs rescaling for . For it runs fine without. +static std::string const dissolve_mail_2017_10_30_a = "POLYGON((12.7069120407104490 -2.3525938987731934, 12.6983022689819340 -2.3552336692810059, 12.6984634399414060 -2.3553242683410645, 12.6980066299438480 -2.3553242683410645, 12.6983022689819340 -2.3552336692810059, 12.6911554336547850 -2.3512287139892578, 12.7025737762451170 -2.3398520946502686))"; + +// Testcases sent by Artem Pavlenko via gitter +// https://gitter.im/boostorg/geometry?at=58ef46408e4b63533dc49b48 +static std::string const gitter_2013_04_a = "POLYGON((36.9121 2.03883,26.2052 54.353,60.0781 64.2202,96.2171 55.9826,71.1506 39.8365,5.72552 94.1523,4.06819 13.9054,59.7155 44.5877,60.9243 16.4597,48.8696 93.039,36.9121 2.03883))"; +static std::string const gitter_2013_04_b = "POLYGON((337 176,602 377,294 372,581 166,453 449,337 176))"; + +// Large polygon with self-intersections +static std::string const dissolve_mail_2017_09_24_e = "POLYGON((25.21475410461420 -1.19600892066955, 25.36818313598630 -1.20732820034027, 25.36953926086420 -1.20868659019470, 25.37180328369140 -1.20959210395812, 25.37451934814450 -1.21049761772155, 25.37632942199700 -1.21185600757598, 25.37904357910150 -1.21276152133941, 25.38085556030270 -1.21321427822113, 25.38221168518060 -1.21366703510284, 25.38311767578120 -1.21411991119384, 25.38447570800780 -1.21411991119384, 25.38447570800780 -1.21185600757598, 25.38492774963370 -1.21140325069427, 25.38492774963370 -1.21095049381256, 25.38945388793940 -1.21095049381256, 25.38990592956540 -1.21049761772155, 25.39035987854000 -1.21004486083984, 25.39081192016600 -1.21004486083984, 25.39081192016600 -1.20959210395812, 25.39126396179190 -1.20913934707641, 25.39171600341790 -1.20913934707641, 25.39262199401850 -1.20868659019470, 25.39262199401850 -1.20823383331298, 25.39352798461910 -1.20823383331298, 25.39398002624510 -1.20778107643127, 25.39443206787100 -1.20778107643127, 25.39443206787100 -1.20732820034027, 25.39488410949700 -1.20687544345855, 25.39443206787100 -1.20642268657684, 25.39420700073240 -1.20596992969512, 25.39386749267570 -1.20551717281341, 25.39352798461910 -1.20551717281341, 25.39352798461910 -1.20506441593170, 25.39386749267570 -1.20551717281341, 25.39398002624510 -1.20551717281341, 25.39420700073240 -1.20596992969512, 25.39488410949700 -1.20687544345855, 25.39533805847160 -1.20778107643127, 25.39579010009760 -1.20823383331298, 25.39579010009760 -1.20868659019470, 25.39624214172360 -1.20913934707641, 25.39669609069820 -1.20959210395812, 25.39669609069820 -1.21049761772155, 25.39714813232420 -1.21095049381256, 25.39760017395010 -1.21185600757598, 25.39805221557610 -1.21321427822113, 25.39850616455070 -1.21411991119384, 25.39895820617670 -1.21547818183898, 25.39895820617670 -1.21638369560241, 25.39986419677730 -1.21819484233856, 25.40031623840330 -1.21955311298370, 25.40076828002920 -1.22045862674713, 25.40076828002920 -1.22181701660156, 25.40122032165520 -1.22226977348327, 25.40122032165520 -1.22408080101013, 25.40212631225580 -1.22498643398284, 25.40212631225580 -1.22634470462799, 25.40257835388180 -1.22725021839141, 25.40257835388180 -1.22770309448242, 25.40303230285640 -1.22860860824584, 25.40303230285640 -1.23041963577270, 25.40348434448240 -1.23041963577270, 25.21475410461420 -1.19600892066955))"; + +// Idem but moving closing point eastwards to view self-intersections better +static std::string const dissolve_mail_2017_09_24_f = "POLYGON((25.35475410461420 -1.20600892066955, 25.36818313598630 -1.20732820034027, 25.36953926086420 -1.20868659019470, 25.37180328369140 -1.20959210395812, 25.37451934814450 -1.21049761772155, 25.37632942199700 -1.21185600757598, 25.37904357910150 -1.21276152133941, 25.38085556030270 -1.21321427822113, 25.38221168518060 -1.21366703510284, 25.38311767578120 -1.21411991119384, 25.38447570800780 -1.21411991119384, 25.38447570800780 -1.21185600757598, 25.38492774963370 -1.21140325069427, 25.38492774963370 -1.21095049381256, 25.38945388793940 -1.21095049381256, 25.38990592956540 -1.21049761772155, 25.39035987854000 -1.21004486083984, 25.39081192016600 -1.21004486083984, 25.39081192016600 -1.20959210395812, 25.39126396179190 -1.20913934707641, 25.39171600341790 -1.20913934707641, 25.39262199401850 -1.20868659019470, 25.39262199401850 -1.20823383331298, 25.39352798461910 -1.20823383331298, 25.39398002624510 -1.20778107643127, 25.39443206787100 -1.20778107643127, 25.39443206787100 -1.20732820034027, 25.39488410949700 -1.20687544345855, 25.39443206787100 -1.20642268657684, 25.39420700073240 -1.20596992969512, 25.39386749267570 -1.20551717281341, 25.39352798461910 -1.20551717281341, 25.39352798461910 -1.20506441593170, 25.39386749267570 -1.20551717281341, 25.39398002624510 -1.20551717281341, 25.39420700073240 -1.20596992969512, 25.39488410949700 -1.20687544345855, 25.39533805847160 -1.20778107643127, 25.39579010009760 -1.20823383331298, 25.39579010009760 -1.20868659019470, 25.39624214172360 -1.20913934707641, 25.39669609069820 -1.20959210395812, 25.39669609069820 -1.21049761772155, 25.39714813232420 -1.21095049381256, 25.39760017395010 -1.21185600757598, 25.39805221557610 -1.21321427822113, 25.39850616455070 -1.21411991119384, 25.39895820617670 -1.21547818183898, 25.39895820617670 -1.21638369560241, 25.39986419677730 -1.21819484233856, 25.40031623840330 -1.21955311298370, 25.40076828002920 -1.22045862674713, 25.40076828002920 -1.22181701660156, 25.40122032165520 -1.22226977348327, 25.40122032165520 -1.22408080101013, 25.40212631225580 -1.22498643398284, 25.40212631225580 -1.22634470462799, 25.40257835388180 -1.22725021839141, 25.40257835388180 -1.22770309448242, 25.40303230285640 -1.22860860824584, 25.40303230285640 -1.23041963577270, 25.40348434448240 -1.23041963577270, 25.35475410461420 -1.20600892066955))"; + +// As d but with other spike +static std::string const dissolve_mail_2017_09_24_g = "POLYGON((1 0, 0 -1, 0 1, 0 0,1 0))"; +static std::string const dissolve_mail_2017_09_24_h = "POLYGON((0 0, 0 1, 2 1, 1 1,0 0))"; + +static std::string const dissolve_ticket17 = "POLYGON ((-122.28139163 37.37319149,-122.28100699 37.37273669,-122.28002186 37.37303123,-122.27979681 37.37290072,-122.28007349 37.37240493,-122.27977334 37.37220360,-122.27819720 37.37288580,-122.27714184 37.37275161,-122.27678628 37.37253167,-122.27766437 37.37180973,-122.27804382 37.37121453,-122.27687664 37.37101354,-122.27645829 37.37203386,-122.27604423 37.37249110,-122.27632234 37.37343339,-122.27760980 37.37391082,-122.27812478 37.37800320,-122.26117222 37.39121007,-122.25572289 37.39566631,-122.25547269 37.39564971,-122.25366304 37.39552993,-122.24919976 37.39580268,-122.24417933 37.39366907,-122.24051443 37.39094143,-122.23246277 37.38100418,-122.23606766 37.38141338,-122.24001587 37.37738940,-122.23666848 37.37609347,-122.23057450 37.37882170,-122.22679803 37.37807143,-122.22525727 37.37448817,-122.22523229 37.37443000,-122.23083199 37.37609347,-122.23033486 37.37777891,-122.23169030 37.37732117,-122.23229178 37.37709687,-122.23237761 37.37631249,-122.23297776 37.37438834,-122.23872850 37.37165986,-122.24044511 37.36934068,-122.24671067 37.36865847,-122.24825570 37.36981819,-122.25151719 37.36947713,-122.25357721 37.36756706,-122.26001451 37.36579354,-122.25615213 37.36545239,-122.25486458 37.36245083,-122.25357721 37.36108651,-122.25194642 37.36013139,-122.24885652 37.35958557,-122.24911401 37.35849399,-122.25357721 37.35808470,-122.25675286 37.35897159,-122.25855539 37.35753887,-122.26181687 37.35828939,-122.26713837 37.35897159,-122.26782510 37.36108651,-122.26662339 37.36456559,-122.27288911 37.36722601,-122.27366159 37.36531602,-122.27168740 37.36470213,-122.27391900 37.36374701,-122.27074326 37.36245083,-122.27134408 37.35951742,-122.27426240 37.36135926,-122.27709482 37.36115474,-122.27966974 37.36231438,-122.27958391 37.36463382,-122.27572152 37.36463382,-122.27563569 37.36524779,-122.27700899 37.36593000,-122.27709482 37.36763529,-122.27554978 37.36838573,-122.27667254 37.36931478,-122.27677932 37.36932073,-122.27769362 37.36853987,-122.27942490 37.36830803,-122.28178776 37.36677917,-122.28509559 37.36443500,-122.28845129 37.36413744,-122.29194403 37.36695946,-122.29382577 37.36726817,-122.29600414 37.36898512,-122.29733083 37.36995398,-122.29593239 37.37141436,-122.29416649 37.37075898,-122.29325026 37.37108436,-122.29652910 37.37311697,-122.29584237 37.37374461,-122.29537583 37.37573372,-122.29487677 37.37752502,-122.30923212 37.37593011,-122.31122484 37.38230086,-122.31467994 37.38092472,-122.31715663 37.38252181,-122.32307970 37.38166978,-122.31985618 37.37667694,-122.32210304 37.37580220,-122.32581446 37.37589532,-122.32401730 37.37331839,-122.32960417 37.37189020,-122.33465527 37.37331906,-122.33425328 37.37623680,-122.33620676 37.37726132,-122.33397986 37.37822382,-122.33358918 37.38036590,-122.33202637 37.37986918,-122.33147954 37.38101784,-122.33394080 37.38198017,-122.33545239 37.38587943,-122.33478058 37.38785697,-122.33386050 37.38723721,-122.33350041 37.38571137,-122.33122003 37.38548891,-122.33140008 37.38650606,-122.33366042 37.38817490,-122.33244019 37.39157602,-122.33298157 37.39419201,-122.33164013 37.39477028,-122.33202017 37.39518351,-122.33358038 37.39499282,-122.33376050 37.39597811,-122.33550067 37.39734478,-122.33556069 37.39481797,-122.33344040 37.39292676,-122.33638094 37.38892189,-122.34240644 37.38852719,-122.34906293 37.38726898,-122.35072321 37.39338769,-122.34910291 37.39445252,-122.34796272 37.39410291,-122.34449043 37.39640534,-122.34500223 37.39729709,-122.34936291 37.39670910,-122.35098322 37.39531066,-122.35364623 37.39554510,-122.35434369 37.39612111,-122.35798429 37.39600988,-122.35768430 37.39478621,-122.36334519 37.39206871,-122.36604726 37.39203267,-122.36778592 37.39335592,-122.36518870 37.40022011,-122.36554552 37.40247752,-122.36370519 37.40331974,-122.36270506 37.40530591,-122.36320512 37.40670418,-122.36149849 37.40851392,-122.36730580 37.41054938,-122.37263720 37.41378932,-122.37161871 37.42076600,-122.36566153 37.42006292,-122.36520547 37.42742106,-122.37165953 37.43661157,-122.35943972 37.44459022,-122.35356359 37.44600810,-122.33792254 37.45796329,-122.35228518 37.47478091,-122.35127080 37.48181199,-122.34867342 37.48487322,-122.34359717 37.48801082,-122.33388431 37.48677650,-122.33142321 37.48429747,-122.32929580 37.48473149,-122.32609609 37.48291144,-122.32344850 37.48228229,-122.31924364 37.48410234,-122.31677299 37.48114051,-122.31431751 37.47848973,-122.31259201 37.47682190,-122.31515972 37.47568196,-122.31691389 37.47360309,-122.31292494 37.46960081,-122.31130153 37.46937743,-122.30889894 37.47124987,-122.30612839 37.47011613,-122.30149630 37.46568378,-122.30064277 37.46363784,-122.29283821 37.45922376,-122.28630141 37.45415497,-122.28883099 37.44629920,-122.28316717 37.44197138,-122.27554148 37.42297597,-122.25597410 37.40553692,-122.25196579 37.40129593,-122.25012043 37.40049143,-122.24823207 37.39897758,-122.24754551 37.39740941,-122.24778582 37.39621607,-122.24934787 37.39599102,-122.25005170 37.39871849,-122.25222328 37.39863668,-122.25342491 37.39737529,-122.25520162 37.39667289,-122.25528737 37.39522726,-122.27747460 37.37809616,-122.27977493 37.37858717,-122.28157729 37.37920106,-122.28322534 37.37952846,-122.28416939 37.38092656,-122.28621223 37.37984219,-122.28638389 37.37613857,-122.28382607 37.37843722,-122.27930278 37.37718220,-122.28196361 37.37652740,-122.28295058 37.37568167,-122.28216101 37.37523148,-122.28114822 37.37543608,-122.27934569 37.37528613,-122.27996369 37.37448121,-122.28104521 37.37454944,-122.28185197 37.37422883,-122.28290767 37.37474038,-122.28376597 37.37467224,-122.28428104 37.37399012,-122.28402346 37.37338989,-122.28610922 37.37364914,-122.28651264 37.37327388,-122.28672722 37.37207343,-122.28628398 37.37205448,-122.28574460 37.37166682,-122.28479711 37.37200981,-122.28327731 37.37137228,-122.28285511 37.37100700,-122.28279409 37.37125669,-122.28315527 37.37173756,-122.28321872 37.37220569,-122.28187007 37.37231918,-122.28193109 37.37294908,-122.28139163 37.37319149))"; +static std::string const dissolve_reallife = "POLYGON((170718 605997,170718 605997,170776 606016,170773 606015,170786 606020,170778 606016,170787 606021,170781 606017,170795 606028,170795 606028,170829 606055,170939 606140,170933 605968,170933 605968,170932 605908,170929 605834,170920 605866,170961 605803,170739 605684,170699 605749,170691 605766,170693 605762,170686 605775,170688 605771,170673 605794,170676 605790,170668 605800,170672 605796,170651 605818,170653 605816,170639 605829,170568 605899,170662 605943,170633 605875,170603 605961,170718 605997))"; + +// https://svn.boost.org/trac/boost/ticket/10713 +static std::string const dissolve_ticket10713 = "POLYGON((-0.7189743518829346 4.1308121681213379, 0.0831791982054710 4.1034231185913086, 0.1004156470298767 4.1107301712036133, 0.1044322624802589 4.1026973724365234, 0.0831791982054710 4.1034231185913086, -0.7711903452873230 3.7412264347076416, -0.7189743518829346 4.1308121681213379))"; + +#endif // BOOST_GEOMETRY_TEST_OVERLAY_CASES_HPP diff --git a/extensions/test/algorithms/dissolve_using_buffer.cpp b/extensions/test/algorithms/dissolve_using_buffer.cpp new file mode 100644 index 0000000000..66fbbe6e23 --- /dev/null +++ b/extensions/test/algorithms/dissolve_using_buffer.cpp @@ -0,0 +1,299 @@ +// Boost.Geometry +// +// Copyright (c) 2025 Barend Gehrels, Amsterdam, the Netherlands. + +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Official repository: https://github.com/boostorg/geometry +// Documentation: http://www.boost.org/libs/geometry + +#if defined(TEST_WITH_GEOJSON) +#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER +#define BOOST_GEOMETRY_DEBUG_IDENTIFIER +#endif + +#include + +#include +#include + +// To check results +#include +#include +#include + +#include +#include + +#include + +#include + +#include "dissolve_overlay_cases.hpp" + +#if defined(TEST_WITH_GEOJSON) +#include +#include "dissolve_geojson_visitor.hpp" +#endif + +// Equivalent with BOOST_CHECK_CLOSE +// See also "expectation_limits.hpp" in the test directory +template +bool is_equal_within_tolerance(Settings const& settings, double const value, double const expected) +{ + double const fraction = settings.percentage / 100.0; + double const lower_limit = expected * (1.0 - fraction); + double const upper_limit = expected * (1.0 + fraction); + return value >= lower_limit && value <= upper_limit; +} + +//! Unittest settings +struct ut_settings +{ + double buffer_distance{1.0e-5}; + double percentage{0.001}; + bool test_validity{true}; +}; + +template +std::string as_wkt(Geometry const& geometry) +{ + std::ostringstream out; + out << bg::wkt(geometry); + return out.str(); +} + +template +void dissolve_alternative(GeometryOut& geometry_out, Geometry const& geometry, + double buffer_distance) +{ + using point_type = typename bg::point_type::type; + const bg::strategy::buffer::distance_symmetric distance_strategy(buffer_distance); + const bg::strategy::buffer::side_straight side_strategy; + const bg::strategy::buffer::join_miter join_strategy; + const bg::strategy::buffer::end_flat end_strategy; + const bg::strategy::buffer::point_circle point_strategy; + + // Convert the input geometry to a multi linestring. + bg::model::multi_linestring> geometry_lines; + + bg::convert(geometry, geometry_lines); + + bg::model::multi_polygon> buffered; + bg::buffer(geometry_lines, buffered, distance_strategy, + side_strategy, join_strategy, end_strategy, point_strategy); + + // Clean all interior rings. + for (auto& polygon : buffered) + { + bg::interior_rings(polygon).clear(); + } + + // Buffer again but now with a negative distance to remove the buffer + const bg::strategy::buffer::distance_symmetric deflate(-buffer_distance); + bg::buffer(buffered, geometry_out, deflate, + side_strategy, join_strategy, end_strategy, point_strategy); +} + +template +void test_dissolve(std::string const& caseid, Geometry const& geometry, + double expected_area, ut_settings const& settings) +{ +#if defined(TEST_WITH_GEOJSON) + std::ostringstream filename; + // For QGis, it is usually convenient to always write to the same geojson file. + filename << "/tmp/" + // << caseid << "_" + << "dissolve.geojson"; + std::ofstream geojson_file(filename.str().c_str()); + + boost::geometry::geojson_writer writer(geojson_file); +#endif + + using coordinate_type = typename bg::coordinate_type::type; + using multi_polygon = bg::model::multi_polygon; + multi_polygon dissolved; + + dissolve_alternative(dissolved, geometry, settings.buffer_distance); + +#if defined(TEST_WITH_GEOJSON) + geojson_visitor visitor(writer); +#else + bg::detail::overlay::overlay_null_visitor visitor; +#endif + + if (settings.test_validity) + { + std::string message; + bool const valid = bg::is_valid(dissolved, message); + BOOST_CHECK_MESSAGE(valid, + "dissolve: " << caseid + << " geometry is not valid: " << message); + } + + auto const detected_area = bg::area(dissolved); + + BOOST_CHECK_MESSAGE(is_equal_within_tolerance(settings, detected_area, expected_area), + "dissolve: " << caseid + << " #area expected: " << expected_area + << " detected: " << detected_area + ); + +#if defined(TEST_WITH_GEOJSON) + writer.feature(geometry); + writer.add_property("type", "input"); + + for (const auto& polygon : dissolved) + { + writer.feature(polygon); + writer.add_property("type", "dissolved"); + } +#endif + +} + +template +void test_one(std::string caseid, std::string const& wkt, + double expected_area, ut_settings const& settings) +{ + Geometry geometry; + bg::read_wkt(wkt, geometry); + + // If defined as closed, it should be closed. The algorithm itself + // cannot close it without making a copy. + bg::correct_closure(geometry); + + test_dissolve(caseid, geometry, + expected_area, + settings); + + // Verify if reversed version is identical + bg::reverse(geometry); + + caseid += "_rev"; + test_dissolve(caseid, geometry, + expected_area, + settings); +} + +#define TEST_DISSOLVE(caseid, area, clips_ignored, holes_ignored, points_ignored) { \ + ut_settings settings; \ + (test_one) ( #caseid, caseid, area, settings); } + +#define TEST_DISSOLVE_WITH(caseid, area, clips_ignored, holes_ignored, points_ignored, settings) { \ + (test_one) ( #caseid, caseid, area, settings); } + +#define TEST_DISSOLVE_IGNORE(caseid, area, clips_ignored, holes_ignored, points_ignored) { \ + ut_settings settings; settings.test_validity = false; \ + (test_one) ( #caseid, caseid, area, settings); } + +#define TEST_MULTI(caseid, area, clips_ignored, holes_ignored, points_ignored) { \ + ut_settings settings; \ + (test_one) ( #caseid, caseid, area, settings); } + +template +void test_all() +{ + typedef bg::model::polygon polygon; + typedef bg::model::multi_polygon multi_polygon; + + TEST_DISSOLVE(dissolve_1, 8.0, 1, 0, 4); + + // Two (potential) holes are filtered out + TEST_DISSOLVE(dissolve_2, 8.9296875, 1, 1, 12); + TEST_DISSOLVE(dissolve_3, 4.0, 2, 0, 8); + TEST_DISSOLVE(dissolve_4, 8.0, 2, 0, 8); + TEST_DISSOLVE(dissolve_5, 12.0, 2, 0, 8); + TEST_DISSOLVE(dissolve_6, 16.0, 1, 0, 5); + + TEST_DISSOLVE(dissolve_7, 50.48056402439, 1, 0, 7); + TEST_DISSOLVE(dissolve_8, 25.6158412, 1, 0, 11); + + // CCW polygons should turn CW after dissolve + TEST_DISSOLVE(dissolve_9, 25.6158412, 1, 0, 11); + TEST_DISSOLVE(dissolve_10, 60.0, 1, 0, 7); + TEST_DISSOLVE(dissolve_11, 60.0, 1, 0, 7); + + // More pentagrams + TEST_DISSOLVE(dissolve_12, 186556.84077318, 1, 0, 15); + TEST_DISSOLVE(dissolve_13, 361733.91651, 1, 0, 15); + + TEST_DISSOLVE(dissolve_14, 4.0, 3, 0, 13); + TEST_DISSOLVE(dissolve_15, 4.0, 3, 0, 13); + // Fixed by using buffer + TEST_DISSOLVE(dissolve_16, 12.1333, 8, 0, 38); + + TEST_DISSOLVE(dissolve_17, 14.5, 2, 0, 11); + TEST_DISSOLVE(dissolve_18, 15.0, 3, 0, 15); + + TEST_DISSOLVE(dissolve_d1, 8.0, 1, 0, 4); + TEST_DISSOLVE(dissolve_d2, 16.0, 1, 0, 5); + + TEST_DISSOLVE(dissolve_h1_a, 16.0, 1, 1, 9); + TEST_DISSOLVE(dissolve_h1_b, 16.0, 1, 1, 9); + TEST_DISSOLVE(dissolve_h2, 16.25, 2, 0, 13); + TEST_DISSOLVE(dissolve_h3, 16.0, 1, 1, 14); + TEST_DISSOLVE(dissolve_h4, 16.0, 1, 3, 17); + + // The default distance results in a small artefact by buffer deflate. + // It can be considered as invalid. + // That should be solved within buffer itself, or in is_valid, or both. + TEST_DISSOLVE_WITH(dissolve_star_a, 7.38821, 2, 0, 15, + ut_settings{1.0e-6}); + TEST_DISSOLVE(dissolve_star_b, 7.28259, 2, 0, 15); + TEST_DISSOLVE(dissolve_star_c, 7.399696, 1, 0, 11); + + TEST_DISSOLVE(dissolve_mail_2017_09_24_a, 0.5, 2, 0, 8); + + TEST_DISSOLVE(dissolve_mail_2017_09_24_b, 16.0, 1, 0, 5); + TEST_DISSOLVE(dissolve_mail_2017_09_24_c, 0.5, 2, 0, 8); + TEST_DISSOLVE(dissolve_mail_2017_09_24_d, 0.5, 1, 0, 4); + TEST_DISSOLVE(dissolve_mail_2017_09_24_e, 0.001801138128, 5, 0, 69); + TEST_DISSOLVE(dissolve_mail_2017_09_24_f, 0.000361308800, 5, 0, 69); + TEST_DISSOLVE(dissolve_mail_2017_09_24_g, 0.5, 1, 0, 4); + TEST_DISSOLVE(dissolve_mail_2017_09_24_h, 0.5, 1, 0, 4); + + // dissolve created an interior ring which is now removed + TEST_DISSOLVE(dissolve_mail_2017_10_26_a, 8.0, 1, 1, 12); + TEST_DISSOLVE(dissolve_mail_2017_10_26_b, 16.0, 1, 0, 5); + TEST_DISSOLVE(dissolve_mail_2017_10_26_c, 6.0, 1, 0, 6); + + TEST_DISSOLVE(dissolve_mail_2017_10_30_a, 0.0001241171, 2, 0, 9); + + TEST_DISSOLVE(dissolve_ticket10713, 0.157052766, 2, 0, 8); + + // One interior ring removed + TEST_MULTI(multi_three_triangles, 42.7807, 1, 1, 13); + TEST_MULTI(multi_simplex_two, 14.7, 1, 0, 8); + TEST_MULTI(multi_simplex_three, 16.7945, 1, 0, 14); + TEST_MULTI(multi_simplex_four, 20.7581, 1, 0, 18); + TEST_MULTI(multi_disjoint, 24.0, 4, 0, 16); + TEST_MULTI(multi_new_interior, 19.9706, 1, 1, 18); + TEST_MULTI(ggl_list_20110307_javier_01_a, 6400.0, 2, 0, 11); + + // Four interior rings removed + TEST_DISSOLVE(ggl_list_20110307_javier_01_b, 4000000.0, 1, 2, 16); + + // One interior ring removed + TEST_DISSOLVE(dissolve_ticket17, 0.00925269995, 1, 1, 228); + + // Cases using large coordinate values need other buffer. + TEST_DISSOLVE_WITH(dissolve_reallife, 91756.916526794434, 1, 0, 25, + ut_settings{1.0}); + + TEST_DISSOLVE(gitter_2013_04_a, 3224.83441, 3, 0, 21); + TEST_DISSOLVE(gitter_2013_04_b, 31210.429356259738, 1, 0, 11); + + TEST_DISSOLVE(ggl_list_denis, 22544.24890, 2, 0, 22); + + // Will now be one ring. + TEST_DISSOLVE(dissolve_mail_2018_08_19, 30.711696, 2, 1, 15); +} + + +int test_main(int, char* []) +{ + test_all, true >(); + return 0; +} diff --git a/extensions/test/algorithms/dissolve_using_correct.cpp b/extensions/test/algorithms/dissolve_using_correct.cpp new file mode 100644 index 0000000000..07e97d0db2 --- /dev/null +++ b/extensions/test/algorithms/dissolve_using_correct.cpp @@ -0,0 +1,258 @@ +// Boost.Geometry +// +// Copyright (c) 2025 Barend Gehrels, Amsterdam, the Netherlands. + +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Official repository: https://github.com/boostorg/geometry +// Documentation: http://www.boost.org/libs/geometry + +#if defined(TEST_WITH_GEOJSON) +#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER +#define BOOST_GEOMETRY_DEBUG_IDENTIFIER +#endif + +#include + +#include + +// To check results +#include +#include +#include + +#include +#include + +#include + +#include + +#include "dissolve_overlay_cases.hpp" + + +#if defined(TEST_WITH_GEOJSON) +#include +#include "dissolve_geojson_visitor.hpp" +#endif + +// Equivalent with BOOST_CHECK_CLOSE +// See also "expectation_limits.hpp" in the test directory +template +bool is_equal_within_tolerance(Settings const& settings, double const value, double const expected) +{ + double const fraction = settings.percentage / 100.0; + double const lower_limit = expected * (1.0 - fraction); + double const upper_limit = expected * (1.0 + fraction); + return value >= lower_limit && value <= upper_limit; +} + +//! Unittest settings +struct ut_settings +{ + double remove_spike_threshold{1.0e-12}; + double percentage{0.01}; + bool test_validity{true}; +}; + +template +std::string as_wkt(Geometry const& geometry) +{ + std::ostringstream out; + out << bg::wkt(geometry); + return out.str(); +} + +template +void dissolve_alternative(GeometryOut& geometry_out, Geometry const& geometry, + double remove_spike_threshold) +{ + boost::geometry::correct_non_zero(geometry, geometry_out, remove_spike_threshold); +} + +template +void test_dissolve(std::string const& caseid, Geometry const& geometry, + double expected_area, ut_settings const& settings) +{ +#if defined(TEST_WITH_GEOJSON) + std::ostringstream filename; + // For QGis, it is usually convenient to always write to the same geojson file. + filename << "/tmp/" + // << caseid << "_" + << "dissolve.geojson"; + std::ofstream geojson_file(filename.str().c_str()); + + boost::geometry::geojson_writer writer(geojson_file); +#endif + + using coordinate_type = typename bg::coordinate_type::type; + using multi_polygon = bg::model::multi_polygon; + multi_polygon dissolved; + + dissolve_alternative(dissolved, geometry, settings.remove_spike_threshold); + +#if defined(TEST_WITH_GEOJSON) + geojson_visitor visitor(writer); +#else + bg::detail::overlay::overlay_null_visitor visitor; +#endif + + if (settings.test_validity) + { + std::string message; + bool const valid = bg::is_valid(dissolved, message); + BOOST_CHECK_MESSAGE(valid, + "dissolve: " << caseid + << " geometry is not valid: " << message); + } + + auto const detected_area = bg::area(dissolved); + + BOOST_CHECK_MESSAGE(is_equal_within_tolerance(settings, detected_area, expected_area), + "dissolve: " << caseid + << " #area expected: " << expected_area + << " detected: " << detected_area + ); + +#if defined(TEST_WITH_GEOJSON) + writer.feature(geometry); + writer.add_property("type", "input"); + + for (const auto& polygon : dissolved) + { + writer.feature(polygon); + writer.add_property("type", "dissolved"); + } +#endif + +} + +template +void test_one(std::string caseid, std::string const& wkt, + double expected_area, ut_settings const& settings) +{ + Geometry geometry; + bg::read_wkt(wkt, geometry); + + // If defined as closed, it should be closed. The algorithm itself + // cannot close it without making a copy. + bg::correct_closure(geometry); + + test_dissolve(caseid, geometry, + expected_area, + settings); + + // Verify if reversed version is identical + bg::reverse(geometry); + + caseid += "_rev"; + test_dissolve(caseid, geometry, + expected_area, + settings); +} + +#define TEST_DISSOLVE(caseid, area, clips_ignored, holes_ignored, points_ignored) { \ + ut_settings settings; \ + (test_one) ( #caseid, caseid, area, settings); } + +#define TEST_DISSOLVE_WITH(caseid, area, clips_ignored, holes_ignored, points_ignored, settings) { \ + (test_one) ( #caseid, caseid, area, settings); } + +#define TEST_DISSOLVE_IGNORE(caseid, area, clips_ignored, holes_ignored, points_ignored) { \ + ut_settings settings; settings.test_validity = false; \ + (test_one) ( #caseid, caseid, area, settings); } + +#define TEST_MULTI(caseid, area, clips_ignored, holes_ignored, points_ignored) { \ + ut_settings settings; \ + (test_one) ( #caseid, caseid, area, settings); } + +template +void test_all() +{ + typedef bg::model::polygon polygon; + typedef bg::model::multi_polygon multi_polygon; + + TEST_DISSOLVE(dissolve_1, 8.0, 1, 0, 4); + TEST_DISSOLVE(dissolve_2, 7.9296875, 1, 1, 12); + TEST_DISSOLVE(dissolve_3, 4.0, 2, 0, 8); + TEST_DISSOLVE(dissolve_4, 8.0, 2, 0, 8); + TEST_DISSOLVE_IGNORE(dissolve_5, 12.0, 2, 0, 8); + TEST_DISSOLVE(dissolve_6, 16.0, 1, 0, 5); + + TEST_DISSOLVE(dissolve_7, 50.48056402439, 1, 0, 7); + TEST_DISSOLVE(dissolve_8, 25.6158412, 1, 0, 11); + + // CCW polygons should turn CW after dissolve + TEST_DISSOLVE(dissolve_9, 25.6158412, 1, 0, 11); + TEST_DISSOLVE(dissolve_10, 60.0, 1, 0, 7); + TEST_DISSOLVE(dissolve_11, 60.0, 1, 0, 7); + + // More pentagrams + TEST_DISSOLVE(dissolve_12, 186556.84077318, 1, 0, 15); + TEST_DISSOLVE(dissolve_13, 361733.91651, 1, 0, 15); + + TEST_DISSOLVE(dissolve_14, 4.0, 3, 0, 13); + TEST_DISSOLVE(dissolve_15, 4.0, 3, 0, 13); + TEST_DISSOLVE(dissolve_16, 8.1333, 8, 0, 38); + + TEST_DISSOLVE(dissolve_17, 14.5, 2, 0, 11); + TEST_DISSOLVE(dissolve_18, 15.0, 3, 0, 15); + + TEST_DISSOLVE(dissolve_d1, 8.0, 1, 0, 4); + TEST_DISSOLVE(dissolve_d2, 16.0, 1, 0, 5); + + TEST_DISSOLVE(dissolve_h1_a, 14.0, 1, 1, 9); + TEST_DISSOLVE(dissolve_h1_b, 14.0, 1, 1, 9); + TEST_DISSOLVE(dissolve_h2, 12.25, 2, 0, 13); + TEST_DISSOLVE(dissolve_h3, 10.75, 1, 1, 14); + TEST_DISSOLVE(dissolve_h4, 14.3447, 1, 3, 17); + + TEST_DISSOLVE(dissolve_star_a, 7.38821, 2, 0, 15); + TEST_DISSOLVE(dissolve_star_b, 7.28259, 2, 0, 15); + TEST_DISSOLVE(dissolve_star_c, 7.399696, 1, 0, 11); + + TEST_DISSOLVE(dissolve_mail_2017_09_24_a, 0.5, 2, 0, 8); + + TEST_DISSOLVE(dissolve_mail_2017_09_24_b, 16.0, 1, 0, 5); + TEST_DISSOLVE(dissolve_mail_2017_09_24_c, 0.5, 2, 0, 8); + TEST_DISSOLVE(dissolve_mail_2017_09_24_d, 0.5, 1, 0, 4); + TEST_DISSOLVE(dissolve_mail_2017_09_24_e, 0.001801138128, 5, 0, 69); + TEST_DISSOLVE(dissolve_mail_2017_09_24_f, 0.000361308800, 5, 0, 69); + TEST_DISSOLVE(dissolve_mail_2017_09_24_g, 0.5, 1, 0, 4); + TEST_DISSOLVE(dissolve_mail_2017_09_24_h, 0.5, 1, 0, 4); + + TEST_DISSOLVE(dissolve_mail_2017_10_26_a, 7.0, 1, 1, 12); + TEST_DISSOLVE(dissolve_mail_2017_10_26_b, 16.0, 1, 0, 5); + TEST_DISSOLVE(dissolve_mail_2017_10_26_c, 6.0, 1, 0, 6); + + TEST_DISSOLVE(dissolve_mail_2017_10_30_a, 0.0001241171, 2, 0, 9); + + TEST_DISSOLVE(dissolve_ticket10713, 0.157052766, 2, 0, 8); + + TEST_MULTI(multi_three_triangles, 42.614078674948232, 1, 1, 13); + TEST_MULTI(multi_simplex_two, 14.7, 1, 0, 8); + TEST_MULTI(multi_simplex_three, 16.7945, 1, 0, 14); + TEST_MULTI(multi_simplex_four, 20.7581, 1, 0, 18); + TEST_MULTI(multi_disjoint, 24.0, 4, 0, 16); + TEST_MULTI(multi_new_interior, 19.5206, 1, 1, 18); + TEST_MULTI(ggl_list_20110307_javier_01_a, 6400.0, 2, 0, 11); + + TEST_DISSOLVE(ggl_list_20110307_javier_01_b, 3993600.0, 1, 2, 16); + TEST_DISSOLVE(dissolve_ticket17, 0.00920834633689, 1, 1, 228); + TEST_DISSOLVE(dissolve_reallife, 91756.916526794434, 1, 0, 25); + + TEST_DISSOLVE(gitter_2013_04_a, 2829.7832, 3, 0, 21); + TEST_DISSOLVE(gitter_2013_04_b, 31210.429356259738, 1, 0, 11); + + TEST_DISSOLVE(ggl_list_denis, 21123.3281, 2, 0, 22); + + TEST_DISSOLVE(dissolve_mail_2018_08_19, 30.711696, 2, 1, 15); +} + + +int test_main(int, char* []) +{ + test_all, true >(); + return 0; +} diff --git a/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp b/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp index 191c2033ea..3a7d66fa66 100644 --- a/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp @@ -202,70 +202,6 @@ inline void enrich_assign(Operations& operations, Turns& turns) #endif } -template -inline void enrich_adapt(Operations& operations, Turns& turns) -{ - // Operations is a vector of indexed_turn_operation<> - // If it is empty, or contains one or two items, it makes no sense - if (operations.size() < 3) - { - return; - } - - bool next_phase = false; - std::size_t previous_index = operations.size() - 1; - - for (auto const& item : util::enumerate(operations)) - { - auto const& index = item.index; - auto const& indexed = item.value; - auto& turn = turns[indexed.turn_index]; - auto& op = turn.operations[indexed.operation_index]; - - std::size_t const next_index = (index + 1) % operations.size(); - auto const& next_turn = turns[operations[next_index].turn_index]; - auto const& next_op = next_turn.operations[operations[next_index].operation_index]; - - if (op.seg_id.segment_index == next_op.seg_id.segment_index) - { - auto const& prev_turn = turns[operations[previous_index].turn_index]; - auto const& prev_op = prev_turn.operations[operations[previous_index].operation_index]; - if (op.seg_id.segment_index == prev_op.seg_id.segment_index) - { - op.enriched.startable = false; - next_phase = true; - } - } - previous_index = index; - } - - if (! next_phase) - { - return; - } - - // Discard turns which are both non-startable - next_phase = false; - for (auto& turn : turns) - { - if (! turn.operations[0].enriched.startable - && ! turn.operations[1].enriched.startable) - { - turn.discarded = true; - next_phase = true; - } - } - - if (! next_phase) - { - return; - } - - // Remove discarded turns from operations to avoid having them as next turn - discarded_indexed_turn const predicate(turns); - operations.erase(std::remove_if(std::begin(operations), - std::end(operations), predicate), std::end(operations)); -} struct enriched_map_default_include_policy { @@ -420,11 +356,6 @@ inline void enrich_turns(Turns& turns, #ifdef BOOST_GEOMETRY_DEBUG_ENRICH std::cout << "ENRICH-assign Ring " << pair.first << std::endl; #endif - if BOOST_GEOMETRY_CONSTEXPR (OverlayType == overlay_dissolve) - { - enrich_adapt(pair.second, turns); - } - enrich_assign(pair.second, turns); } diff --git a/include/boost/geometry/algorithms/detail/overlay/graph/get_tois.hpp b/include/boost/geometry/algorithms/detail/overlay/graph/get_tois.hpp index 9b4be75c48..34e8826294 100644 --- a/include/boost/geometry/algorithms/detail/overlay/graph/get_tois.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/graph/get_tois.hpp @@ -40,7 +40,7 @@ void add_tois(Turns const& turns, Clusters const& clusters, turn_operation_id const toi{source_index, i}; if (is_target_operation(turns, toi)) { - result.insert(std::move(toi)); + result.insert(toi); } } } @@ -111,9 +111,9 @@ set_of_tois get_tois(Turns const& turns, Clusters const& clusters, } // Variant with multiple target nodes -template +template set_of_tois get_tois(Turns const& turns, Clusters const& clusters, - signed_size_type source_node_id, std::set const& target_node_ids) + signed_size_type source_node_id, TargetNodeIds const& target_node_ids) { set_of_tois result; for (auto const& target : target_node_ids) diff --git a/include/boost/geometry/algorithms/detail/overlay/graph/node_util.hpp b/include/boost/geometry/algorithms/detail/overlay/graph/node_util.hpp index 3649e1f9b0..76ac97bcd0 100644 --- a/include/boost/geometry/algorithms/detail/overlay/graph/node_util.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/graph/node_util.hpp @@ -16,7 +16,9 @@ #include #include +#include #include +#include #include namespace boost { namespace geometry @@ -121,6 +123,7 @@ void get_target_operations(Turns const& turns, // Get the target nodes of a specific component_id only. +// Target nodes are sorted on preference index. template auto get_target_nodes(Turns const& turns, Clusters const& clusters, Set const& turn_indices, @@ -128,7 +131,7 @@ auto get_target_nodes(Turns const& turns, Clusters const& clusters, { using is_included = is_operation_included; - std::set result; + std::vector> selected; for (auto turn_index : turn_indices) { auto const& turn = turns[turn_index]; @@ -144,10 +147,19 @@ auto get_target_nodes(Turns const& turns, Clusters const& clusters, && is_included::apply(op) && is_target_operation(turns, {turn_index, j})) { - result.insert(get_node_id(turns, op.enriched.travels_to_ip_index)); + selected.push_back({get_node_id(turns, op.enriched.travels_to_ip_index), op.preference_index}); } } } + + std::sort(selected.begin(), selected.end(), + [](auto const& a, auto const& b) { return a.second < b.second; }); + + std::vector result; + for (auto const& item : selected) + { + result.push_back(item.first); + } return result; } diff --git a/include/boost/geometry/algorithms/detail/overlay/graph/select_edge.hpp b/include/boost/geometry/algorithms/detail/overlay/graph/select_edge.hpp index 738473b892..5e5f7aa78e 100644 --- a/include/boost/geometry/algorithms/detail/overlay/graph/select_edge.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/graph/select_edge.hpp @@ -37,6 +37,28 @@ struct edge_and_side int side{0}; }; +// Sort by side and always take the right (smaller) side (for intersection, union, buffer). +template +struct compare_edges_by_side +{ + template + bool operator()(Edge1 const& a, Edge2 const& b) const + { + return a.side < b.side; + } +}; + +// For dissolve, the left (larger) side is taken. +template <> +struct compare_edges_by_side +{ + template + bool operator()(Edge1 const& a, Edge2 const& b) const + { + return a.side > b.side; + } +}; + template < bool Reverse1, @@ -154,14 +176,8 @@ struct edge_selector edge.side = side_strategy.apply(p1, p2, edge.point); } - // Sort by side (with respect to segment [p1..p2]) (TEMPORARY: and then by toi) - // Right = -1 will come first. Left = 1 will come last. - // This works for both union and intersection operations, because it should always - // take the right turn (even in uu in buffer/union). - std::sort(edges.begin(), edges.end(), [](auto const& a, auto const& b) - { - return std::tie(a.side, a.toi) < std::tie(b.side, b.toi); - }); + // Sort by side, with respect to segment [p1..p2] + std::sort(edges.begin(), edges.end(), compare_edges_by_side()); report("by side", edges, p1, p2); @@ -274,6 +290,16 @@ struct edge_selector return edges.front().toi; } + if (target_operation == operation_union + && op0.operation == operation_union + && op1.operation == operation_union + && op0.preference_index != op1.preference_index) + { + return op0.preference_index < op1.preference_index + ? edges[0].toi + : edges[1].toi; + } + if (target_operation == operation_union && turn0.is_clustered() && op0.operation == operation_union diff --git a/include/boost/geometry/algorithms/detail/overlay/graph/traverse_graph.hpp b/include/boost/geometry/algorithms/detail/overlay/graph/traverse_graph.hpp index 01cf76dfe7..b01063b21c 100644 --- a/include/boost/geometry/algorithms/detail/overlay/graph/traverse_graph.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/graph/traverse_graph.hpp @@ -178,73 +178,89 @@ struct traverse_graph bool continue_traverse(Ring& ring, signed_size_type component_id, signed_size_type start_node_id, - signed_size_type current_node_id) + signed_size_type target_node_id) { - auto const current_turn_indices = get_turn_indices_by_node_id(m_turns, m_clusters, + signed_size_type current_node_id = target_node_id; + + std::size_t iteration_count = 0; + + // Keep traversing until it finds the start (successful finish), or it is stuck, + // or it find an already visited node during traversal. + // The iteration count is a defensive check to prevent endless loops and not iterate + // more than times there are turns (this should not happen). + while (iteration_count < m_turns.size()) + { + auto const current_turn_indices = get_turn_indices_by_node_id(m_turns, m_clusters, current_node_id, allow_closed); - // Any valid node should always deliver at least one turn - BOOST_ASSERT(! current_turn_indices.empty()); + // Any valid node should always deliver at least one turn + BOOST_ASSERT(! current_turn_indices.empty()); - auto const next_target_nodes = get_target_nodes(m_turns, m_clusters, - current_turn_indices, component_id); + auto const next_target_nodes = get_target_nodes(m_turns, m_clusters, + current_turn_indices, component_id); - if (next_target_nodes.empty()) - { + if (next_target_nodes.empty()) + { #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH) - std::cout << "Stuck, start: " << start_node_id - << " stuck: " << current_node_id - << " (no targets) " << std::endl; + std::cout << "Stuck, start: " << start_node_id + << " stuck: " << current_node_id + << " (no targets) " << std::endl; #endif - return false; - } + return false; + } - auto const tois = get_tois(m_turns, m_clusters, + auto const tois = get_tois(m_turns, m_clusters, current_node_id, next_target_nodes); - if (tois.empty()) - { - return false; - } + if (tois.empty()) + { + return false; + } - auto const& turn_point = m_turns[*current_turn_indices.begin()].point; + auto const& turn_point = m_turns[*current_turn_indices.begin()].point; - auto toi = *tois.begin(); + auto toi = *tois.begin(); - if (tois.size() > 1) - { - // Select the best target edge, using the last point of the ring and the turn point - // for side calculations (if any). - toi = m_edge_selector.select_target_edge(tois, ring.back(), turn_point); - } + if (tois.size() > 1) + { + // Select the best target edge, using the last point of the ring and the turn point + // for side calculations (if any). + toi = m_edge_selector.select_target_edge(tois, ring.back(), turn_point); + } - if (m_visited_tois.count(toi) > 0 || m_finished_tois.count(toi) > 0) - { + if (m_visited_tois.count(toi) > 0 || m_finished_tois.count(toi) > 0) + { #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH) - std::cout << "ALREADY visited, turn " << toi - << " in {" << current_node_id - << " -> size " << next_target_nodes.size() << "}" << std::endl; + std::cout << "ALREADY visited, turn " << toi + << " in {" << current_node_id + << " -> size " << next_target_nodes.size() << "}" << std::endl; #endif - return false; - } + return false; + } - detail::overlay::append_no_collinear(ring, turn_point, m_strategy); + detail::overlay::append_no_collinear(ring, turn_point, m_strategy); - set_visited(toi); - use_vertices(ring, toi); + set_visited(toi); + use_vertices(ring, toi); - auto const& selected_op = m_turns[toi.turn_index].operations[toi.operation_index]; - auto const next_target_node_id = get_node_id(m_turns, - selected_op.enriched.travels_to_ip_index); - if (next_target_node_id == start_node_id) - { + auto const& selected_op = m_turns[toi.turn_index].operations[toi.operation_index]; + auto const next_target_node_id = get_node_id(m_turns, + selected_op.enriched.travels_to_ip_index); + if (next_target_node_id == start_node_id) + { #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH) - std::cout << "Finished at: " << next_target_node_id << std::endl; + std::cout << "Finished at: " << next_target_node_id << std::endl; #endif - return true; - } + return true; + } - return continue_traverse(ring, component_id, start_node_id, next_target_node_id); + current_node_id = next_target_node_id; + ++iteration_count; + } +#if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH) + std::cout << "Cancelled at: " << iteration_count << std::endl; +#endif + return false; } template @@ -256,7 +272,7 @@ struct traverse_graph // Select the first toi which is not yet visited and has the requested component. // If all tois are visited, not having the same component, it is not possible to continue, // and it returns an invalid toi. - auto select_first_toi = [&](auto const& tois) + auto select_first_toi_other = [&](auto const& tois) { for (auto const& toi : tois) { @@ -276,6 +292,40 @@ struct traverse_graph return turn_operation_id{0, -1}; }; + auto select_first_toi_of_two_in_union = [&](auto const& tois) + { + const auto& toi0 = *tois.begin(); + const auto& toi1 = *(++tois.begin()); + + if (m_finished_tois.count(toi0) == 0 + && m_finished_tois.count(toi1) == 0) + { + auto const& turn0 = m_turns[toi0.turn_index]; + auto const& turn1 = m_turns[toi1.turn_index]; + auto const& op0 = turn0.operations[toi0.operation_index]; + auto const& op1 = turn1.operations[toi1.operation_index]; + + if (op0.preference_index != op1.preference_index) + { + return op0.preference_index < op1.preference_index + ? toi0 + : toi1; + } + } + return select_first_toi_other(tois); + }; + + auto select_first_toi = [&](auto const& tois) + { + if (tois.size() == 2 + && target_operation == operation_union) + { + return select_first_toi_of_two_in_union(tois); + } + + return select_first_toi_other(tois); + }; + auto const toi = select_first_toi(get_tois(m_turns, m_clusters, start_node_id, target_node_id)); if (toi.operation_index < 0) @@ -349,25 +399,44 @@ struct traverse_graph { return; } - auto const source_node_id = get_node_id(m_turns, turn_index); - auto const turn_indices = get_turn_indices_by_node_id(m_turns, m_clusters, - source_node_id, allow_closed); + // Iterate through the turns operations which are sorted by preference. + std::vector start_operations; for (int j = 0; j < 2; j++) { auto const& op = turn.operations[j]; - if (! op.enriched.startable || ! is_included::apply(op)) - { - continue; - } - turn_operation_id const toi{turn_index, j}; - if (m_finished_tois.count(toi) > 0 - || ! is_target_operation(m_turns, toi)) + if (op.enriched.startable + && m_finished_tois.count(toi) == 0 + && is_target_operation(m_turns, toi) + && is_included::apply(op)) { - continue; + start_operations.push_back(toi); } + } + + if (start_operations.empty()) + { + return; + } + + std::sort(start_operations.begin(), start_operations.end(), + [&](auto const& a, auto const& b) + { + auto const& op_a = m_turns[a.turn_index].operations[a.operation_index]; + auto const& op_b = m_turns[b.turn_index].operations[b.operation_index]; + // Sort by preference index, then by operation index + return std::tie(op_a.preference_index, a.operation_index) + < std::tie(op_b.preference_index, b.operation_index); + }); + + auto const source_node_id = get_node_id(m_turns, turn_index); + auto const turn_indices = get_turn_indices_by_node_id(m_turns, m_clusters, + source_node_id, allow_closed); + for (const auto& toi : start_operations) + { + auto const& op = turn.operations[toi.operation_index]; auto const component_id = op.enriched.component_id; auto const target_nodes = get_target_nodes(m_turns, m_clusters, turn_indices, component_id); diff --git a/include/boost/geometry/algorithms/detail/overlay/turn_info.hpp b/include/boost/geometry/algorithms/detail/overlay/turn_info.hpp index 711e269aa0..ab5bb6baca 100644 --- a/include/boost/geometry/algorithms/detail/overlay/turn_info.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/turn_info.hpp @@ -56,6 +56,11 @@ struct turn_operation operation_type operation{operation_none}; segment_identifier seg_id; segment_ratio_type fraction; + + // Preference index can be used to sort operations which are otherwise equivalent. + // It is used by dissolve overlays, where operation is often set to operation_union + // but the preference index is used to determine which operation to use first. + std::size_t preference_index{0}; }; diff --git a/include/boost/geometry/extensions/algorithms/detail/overlay/dissolve_traverse.hpp b/include/boost/geometry/extensions/algorithms/detail/overlay/dissolve_traverse.hpp deleted file mode 100644 index 47d243dd05..0000000000 --- a/include/boost/geometry/extensions/algorithms/detail/overlay/dissolve_traverse.hpp +++ /dev/null @@ -1,82 +0,0 @@ -// Boost.Geometry (aka GGL, Generic Geometry Library) - -// Copyright (c) 2018 Barend Gehrels, Amsterdam, the Netherlands. - -// Use, modification and distribution is subject to the Boost Software License, -// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_DISSOLVE_TRAVERSE_HPP -#define BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_DISSOLVE_TRAVERSE_HPP - -#include - -#include -#include - - -namespace boost { namespace geometry -{ - -#ifndef DOXYGEN_NO_DETAIL -namespace detail { namespace dissolve -{ - - -/*! - \brief Traverses through intersection points / geometries. This version - for dissolves calls traversal_ring_creator in two phases - \ingroup dissolve - */ -template -class traverse -{ - -public : - template - < - typename Geometry, - typename IntersectionStrategy, - typename RobustPolicy, - typename Turns, - typename Rings, - typename TurnInfoMap, - typename Clusters, - typename Visitor - > - static inline void apply(Geometry const& geometry, - IntersectionStrategy const& intersection_strategy, - RobustPolicy const& robust_policy, - Turns& turns, Rings& rings, - TurnInfoMap& turn_info_map, - Clusters& clusters, - Visitor& visitor) - { - detail::overlay::traversal_ring_creator - < - Reverse, Reverse, overlay_dissolve, - Geometry, Geometry, - Turns, TurnInfoMap, Clusters, - IntersectionStrategy, - RobustPolicy, Visitor, - Backtrack - > trav(geometry, geometry, turns, turn_info_map, clusters, - intersection_strategy, robust_policy, visitor); - - std::size_t finalized_ring_size = boost::size(rings); - - typename Backtrack::state_type state; - - for (std::size_t phase = 0; phase < 2; phase++) - { - trav.iterate_with_preference(phase, rings, finalized_ring_size, state); - } - } -}; - -}} // namespace detail::dissolve -#endif // DOXYGEN_NO_DETAIL - -}} // namespace boost::geometry - -#endif // BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_DISSOLVE_TRAVERSE_HPP diff --git a/include/boost/geometry/extensions/algorithms/detail/overlay/dissolver.hpp b/include/boost/geometry/extensions/algorithms/detail/overlay/dissolver.hpp index c27c844235..dd4ff2d8af 100644 --- a/include/boost/geometry/extensions/algorithms/detail/overlay/dissolver.hpp +++ b/include/boost/geometry/extensions/algorithms/detail/overlay/dissolver.hpp @@ -36,7 +36,7 @@ #include #include -#include +#include #include @@ -105,12 +105,10 @@ class plusmin_policy < typename Geometry1, typename Geometry2, - typename RescalePolicy, typename OutputCollection, typename Strategy > static inline bool check_negative(Geometry1 a, Geometry2 b, // pass-by-value - RescalePolicy const& rescale_policy, OutputCollection& output_collection, Strategy const& strategy) { @@ -134,11 +132,7 @@ class plusmin_policy typedef typename geometry::point_type::type point_type; - typedef overlay::turn_info - < - point_type, - typename segment_ratio_type::type - > turn_info; + using turn_info = overlay::turn_info; std::deque turns; // Get (and stop on) any intersection @@ -147,7 +141,7 @@ class plusmin_policy < false, false, overlay::assign_null_policy - >(a, b, strategy, rescale_policy, turns, policy); + >(a, b, strategy, turns, policy); if (! policy.has_intersections) { @@ -187,13 +181,11 @@ class plusmin_policy typename Geometry1, typename Geometry2, typename AreaType, - typename RescalePolicy, typename OutputCollection, typename Strategy > static inline bool check(Geometry1 const& a, Geometry2 const& b, AreaType const& area_a, AreaType const& area_b, - RescalePolicy const& rescale_policy, OutputCollection& output_collection, Strategy const& strategy) { @@ -205,11 +197,11 @@ class plusmin_policy } else if (area_a > zero && area_b < zero) { - return check_negative(a, b, rescale_policy, output_collection, strategy); + return check_negative(a, b, output_collection, strategy); } else if (area_a < zero && area_b > zero) { - return check_negative(b, a, rescale_policy, output_collection, strategy); + return check_negative(b, a, output_collection, strategy); } return false; } @@ -222,12 +214,10 @@ public : < typename Geometry1, typename Geometry2, - typename RescalePolicy, typename OutputCollection, typename Strategy > static inline bool apply(Geometry1 const& a, Geometry2 const& b, - RescalePolicy const& rescale_policy, OutputCollection& output_collection, Strategy const& strategy) { @@ -254,7 +244,7 @@ public : // END DEBUG return check(a, b, geometry::area(a), geometry::area(b), - rescale_policy, output_collection, strategy); + output_collection, strategy); } }; @@ -326,7 +316,6 @@ struct dissolver_generic < typename Element, typename Geometry1, typename Geometry2, - typename RescalePolicy, typename OutputCollection, typename Strategy > @@ -337,7 +326,6 @@ struct dissolver_generic // which might change the collection itself and the address/contents of geometry1/geometry2 Geometry1 geometry1, Geometry2 geometry2, - RescalePolicy const& rescale_policy, OutputCollection& output_collection, Strategy const& strategy) { @@ -348,8 +336,7 @@ struct dissolver_generic << " (" << element2.dissolved << "," << element2.dissolved << ")" << std::endl; */ - return CombinePolicy::apply(geometry1, geometry2, - rescale_policy, output_collection, strategy); + return CombinePolicy::apply(geometry1, geometry2, output_collection, strategy); } return false; } @@ -361,7 +348,6 @@ struct dissolver_generic typename HelperVector, typename IndexVector, typename InputRange, - typename RescalePolicy, typename OutputCollection, typename Strategy, typename Box @@ -369,7 +355,6 @@ struct dissolver_generic static inline bool divide_and_conquer(HelperVector& helper_vector , IndexVector& index_vector , InputRange const& input_range - , RescalePolicy const& rescale_policy , OutputCollection& output_collection , Strategy const& strategy , Box const& total_box @@ -417,9 +402,9 @@ struct dissolver_generic // 3: recursively call function (possibly divide in other dimension) divide_and_conquer<1 - Dimension>(helper_vector, - lower_list, input_range, rescale_policy, output_collection, strategy, lower_box, changed, iteration + 1); + lower_list, input_range, output_collection, strategy, lower_box, changed, iteration + 1); divide_and_conquer<1 - Dimension>(helper_vector, - upper_list, input_range, rescale_policy, output_collection, strategy, upper_box, changed, iteration + 1); + upper_list, input_range, output_collection, strategy, upper_box, changed, iteration + 1); return changed; } @@ -452,7 +437,6 @@ struct dissolver_generic element1, element2, get_geometry::apply(input_range, element1.index), get_geometry::apply(input_range, element2.index), - rescale_policy, output_collection, strategy ) @@ -463,7 +447,6 @@ struct dissolver_generic element1, element2, get_geometry::apply(input_range, element1.index), get_geometry::apply(output_collection, element2.index), - rescale_policy, output_collection, strategy ) @@ -474,7 +457,6 @@ struct dissolver_generic element1, element2, get_geometry::apply(output_collection, element1.index), get_geometry::apply(input_range, element2.index), - rescale_policy, output_collection, strategy ) @@ -485,7 +467,6 @@ struct dissolver_generic element1, element2, get_geometry::apply(output_collection, element1.index), get_geometry::apply(output_collection, element2.index), - rescale_policy, output_collection, strategy ) @@ -528,12 +509,10 @@ struct dissolver_generic template < typename InputRange, - typename RescalePolicy, typename OutputCollection, typename Strategy > static inline void apply(InputRange const& input_range - , RescalePolicy const& rescale_policy , OutputCollection& output_collection , Strategy const& strategy ) @@ -574,7 +553,7 @@ struct dissolver_generic bool changed = false; while(divide_and_conquer<1> - (helper_vector, index_vector, input_range, rescale_policy, unioned_collection, strategy, total_box, changed) && n < 5) + (helper_vector, index_vector, input_range, unioned_collection, strategy, total_box, changed) && n < 5) { // Remove everything which is already dissolved. helper_vector.erase @@ -689,8 +668,7 @@ inline void dissolver(InputRange const& input_range, typename tag::type, typename tag::type, detail::dissolver::plusmin_policy - >::apply(input_range, detail::no_rescale_policy(), - output_collection, strategy); + >::apply(input_range, output_collection, strategy); } template diff --git a/include/boost/geometry/extensions/algorithms/dissolve.hpp b/include/boost/geometry/extensions/algorithms/dissolve.hpp index 1c54449f2a..8aa852c8cc 100644 --- a/include/boost/geometry/extensions/algorithms/dissolve.hpp +++ b/include/boost/geometry/extensions/algorithms/dissolve.hpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -46,12 +47,11 @@ #include -#include +#include -#include +#include #include -#include namespace boost { namespace geometry { @@ -75,79 +75,26 @@ struct no_interrupt_policy }; -template -class backtrack_for_dissolve -{ -public : - typedef detail::overlay::backtrack_state state_type; - - template - < - typename Operation, - typename Rings, - typename Turns, - typename IntersectionStrategy, - typename RobustPolicy, - typename Visitor - > - static inline void apply(std::size_t size_at_start, - Rings& rings, - typename boost::range_value::type& ring, - Turns& turns, - typename boost::range_value::type const& /*turn*/, - Operation& operation, - detail::overlay::traverse_error_type, - Geometry const& , - Geometry const& , - IntersectionStrategy const& , - RobustPolicy const& , - state_type& state, - Visitor const& /*visitor*/ - ) - { - state.m_good = false; - - // Make bad output clean - rings.resize(size_at_start); - ring.clear(); - - // Reject this as a starting point - operation.visited.set_rejected(); - - // And clear all visit info - clear_visit_info(turns); - } -}; - template struct dissolve_ring { + // TODO: revise / reconsider this. + // - it gives currently the best output + // - but for some cases, it should NOT be done + // - it should be adapted to the new graph algorithm template static void adapt_turns(Turns& turns) { - typedef typename boost::range_value::type turn_type; - typedef typename turn_type::turn_operation_type turn_operation_type; - - for (typename Turns::iterator it = turns.begin(); - it != turns.end(); ++it) + for (auto& turn : turns) { - turn_type& turn = *it; - for (int i = 0; i < 2; i++) + for (auto& op : turn.operations) { - turn_operation_type& op = turn.operations[i]; - - if (op.operation != detail::overlay::operation_union - && op.operation != detail::overlay::operation_continue) - { - // Only prefer union and continue turns - op.enriched.prefer_start = false; - } - if (op.operation == detail::overlay::operation_intersection) { // Make all ii->uu, iu->uu, etc, basically handle most // as if it is union op.operation = detail::overlay::operation_union; + op.preference_index++; } } } @@ -155,11 +102,10 @@ struct dissolve_ring template < - typename RescalePolicy, typename OutputIterator, + typename OutputIterator, typename Strategy, typename Visitor > static inline void apply_one(Ring const& input_ring, - RescalePolicy const& rescale_policy, OutputIterator out, Strategy const& strategy, Visitor& visitor) @@ -170,18 +116,16 @@ struct dissolve_ring using turn_info = detail::overlay::traversal_turn_info < point_type, - typename segment_ratio_type::type + typename segment_ratio_type::type >; - constexpr operation_type target_operation = operation_from_overlay::value; - std::deque turns; detail::dissolve::no_interrupt_policy policy; detail::self_get_turn_points::self_turns < Reverse, detail::overlay::assign_null_policy - >(input_ring, strategy, rescale_policy, turns, policy, 0, false); + >(input_ring, strategy, turns, policy, 0, false); adapt_turns(turns); @@ -201,34 +145,32 @@ struct dissolve_ring // Enrich/traverse the polygons // Handle colocations, gathering clusters and (below) their properties. - detail::overlay::handle_colocations - < - Reverse1, Reverse2, OverlayType, Geometry1, Geometry2 - >(turns, clusters); - - // Gather cluster properties (using even clusters with - // discarded turns - for open turns) - detail::overlay::gather_cluster_properties - < - Reverse1, - Reverse2, - OverlayType - >(clusters, turns, target_operation, geometry1, geometry2, strategy); + detail::overlay::handle_colocations(turns, clusters); enrich_intersection_points(turns, - clusters, input_ring, input_ring, rescale_policy, - strategy); + clusters, input_ring, input_ring, strategy); + + detail::overlay::colocate_clusters(clusters, turns); - visitor.visit_turns(2, turns); + detail::overlay::assign_side_counts + < + Reverse, Reverse, overlay_dissolve + >(input_ring, input_ring, turns, clusters, strategy, visitor); - visitor.visit_clusters(clusters, turns); + detail::overlay::get_properties_ahead(turns, clusters, input_ring, input_ring, strategy); std::deque rings; std::map turn_info_per_ring; - detail::dissolve::traverse > - ::apply(input_ring, strategy, rescale_policy, - turns, rings, turn_info_per_ring, clusters, visitor); + detail::overlay::traverse::apply + ( + input_ring, input_ring, + strategy, + turns, rings, + turn_info_per_ring, + clusters, + visitor + ); visitor.visit_turns(3, turns); visitor.visit_generated_rings(rings); @@ -259,18 +201,17 @@ struct dissolve_ring template < - typename RescalePolicy, typename OutputIterator, + typename OutputIterator, typename Strategy, typename Visitor > static inline OutputIterator apply(Ring const& geometry, - RescalePolicy const& rescale_policy, OutputIterator out, Strategy const& strategy, Visitor& visitor) { using multi_polygon = model::multi_polygon; multi_polygon step1; - apply_one(geometry, rescale_policy, std::back_inserter(step1), strategy, visitor); + apply_one(geometry, std::back_inserter(step1), strategy, visitor); // Step 2: remove mutual overlap { @@ -278,7 +219,7 @@ struct dissolve_ring detail::dissolver::dissolver_generic < detail::dissolver::plusmin_policy - >::apply(step1, rescale_policy, step2, strategy); + >::apply(step1, step2, strategy); for (auto it = step2.begin(); it != step2.end(); ++it) { *out++ = *it; @@ -295,11 +236,10 @@ struct dissolve_polygon template < - typename RescalePolicy, typename OutputCollection, + typename OutputCollection, typename Strategy, typename Visitor > static inline void apply_ring(ring_type const& ring, - RescalePolicy const& rescale_policy, OutputCollection& out, Strategy const& strategy, Visitor& visitor) @@ -308,14 +248,14 @@ struct dissolve_polygon if (orientation_ok) { dissolve_ring - ::apply(ring, rescale_policy, + ::apply(ring, std::back_inserter(out), strategy, visitor); } else { // Apply the whole dissolve implementation reversed dissolve_ring - ::apply(ring, rescale_policy, + ::apply(ring, std::back_inserter(out), strategy, visitor); } } @@ -323,28 +263,26 @@ struct dissolve_polygon template < typename Rings, - typename RescalePolicy, typename OutputCollection, + typename OutputCollection, typename Strategy, typename Visitor > static inline void apply_rings(Rings const& rings, - RescalePolicy const& rescale_policy, OutputCollection& out, Strategy const& strategy, Visitor& visitor) { for (auto it = boost::begin(rings); it != boost::end(rings); ++it) { - apply_ring(*it, rescale_policy, out, strategy, visitor); + apply_ring(*it, out, strategy, visitor); } } template < - typename RescalePolicy, typename OutputIterator, + typename OutputIterator, typename Strategy, typename Visitor > static inline OutputIterator apply(Polygon const& polygon, - RescalePolicy const& rescale_policy, OutputIterator out, Strategy const& strategy, Visitor& visitor) @@ -353,13 +291,13 @@ struct dissolve_polygon // Handle exterior ring multi_polygon exterior_out; - apply_ring(exterior_ring(polygon), rescale_policy, + apply_ring(exterior_ring(polygon), exterior_out, strategy, visitor); // Dissolve all the (negative) interior rings into // a (positive) mulpolygon. Do this per interior ring and combine them. multi_polygon interior_out_per_ring; - apply_rings(interior_rings(polygon), rescale_policy, + apply_rings(interior_rings(polygon), interior_out_per_ring, strategy, visitor); // Remove mutual overlap in the interior ring output @@ -367,7 +305,7 @@ struct dissolve_polygon detail::dissolver::dissolver_generic < detail::dissolver::plusmin_policy - >::apply(interior_out_per_ring, rescale_policy, interior_out, strategy); + >::apply(interior_out_per_ring, interior_out, strategy); // Subtract the interior rings from the output. Where interior rings // are partly or completely outside the polygon, sym_difference will @@ -430,11 +368,10 @@ struct dissolve { template < - typename Geometry, typename RescalePolicy, typename OutputIterator, - typename Strategy, typename Visitor + typename Geometry, typename OutputIterator, + typename Visitor > static inline OutputIterator apply(Geometry const& geometry, - RescalePolicy const& rescale_policy, OutputIterator out, Strategy const& strategy, Visitor& visitor) @@ -447,7 +384,7 @@ struct dissolve < geometry::point_order::value >::value - >::apply(geometry, rescale_policy, out, strategy, visitor); + >::apply(geometry, out, strategy, visitor); } }; @@ -456,11 +393,10 @@ struct dissolve { template < - typename Geometry, typename RescalePolicy, typename OutputIterator, - typename Strategy, typename Visitor + typename Geometry, typename OutputIterator, + typename Visitor > static inline OutputIterator apply(Geometry const& geometry, - RescalePolicy const& rescale_policy, OutputIterator out, Strategy const& strategy, Visitor& visitor) @@ -475,7 +411,7 @@ struct dissolve < geometry::point_order::value >::value - >::apply(geometry, rescale_policy, out, + >::apply(geometry, out, strategy_converter::get(strategy), visitor); } @@ -513,22 +449,12 @@ inline OutputIterator dissolve_inserter(Geometry const& geometry, concepts::check(); concepts::check(); - typedef typename geometry::rescale_policy_type - < - typename geometry::point_type::type, - typename Strategy::cs_tag - >::type rescale_policy_type; - - rescale_policy_type robust_policy - = geometry::get_rescale_policy( - geometry, strategy); - detail::overlay::overlay_null_visitor visitor; return resolve_strategy::dissolve < GeometryOut, Strategy - >::apply(geometry, robust_policy, out, strategy, visitor); + >::apply(geometry, out, strategy, visitor); } /*! @@ -577,22 +503,12 @@ inline void dissolve(Geometry const& geometry, Collection& output_collection, concepts::check(); - typedef typename geometry::rescale_policy_type - < - typename geometry::point_type::type, - typename Strategy::cs_tag - >::type rescale_policy_type; - - rescale_policy_type robust_policy - = geometry::get_rescale_policy( - geometry, strategy); - detail::overlay::overlay_null_visitor visitor; resolve_strategy::dissolve < geometry_out, Strategy - >::apply(geometry, robust_policy, + >::apply(geometry, std::back_inserter(output_collection), strategy, visitor); } diff --git a/include/boost/geometry/extensions/algorithms/dissolve_using_correct.hpp b/include/boost/geometry/extensions/algorithms/dissolve_using_correct.hpp new file mode 100644 index 0000000000..f809bc6067 --- /dev/null +++ b/include/boost/geometry/extensions/algorithms/dissolve_using_correct.hpp @@ -0,0 +1,533 @@ +// Boost.Geometry +// +// Copyright (c) 2021 Wouter van Kleunen, the Netherlands. + +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Official repository: https://github.com/boostorg/geometry +// Documentation: http://www.boost.org/libs/geometry + +#ifndef BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_DISSOLVE_USING_CORRECT_HPP +#define BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_DISSOLVE_USING_CORRECT_HPP + +// Originally licensed as: +// Used by permission of the author +// https://github.com/boostorg/geometry/issues/868 +// * ---------------------------------------------------------------------------- +// * "THE BEER-WARE LICENSE" (Revision 42): +// * As long as you retain this notice you +// * can do whatever you want with this stuff. If we meet some day, and you think +// * this stuff is worth it, you can buy me a beer in return. +// * ---------------------------------------------------------------------------- + +#include + +#include +#include + +#include +#include +#include + +namespace boost { namespace geometry { + +namespace impl { + +template +inline void result_combine(C &result, T &&new_element) +{ + result.push_back(new_element); + + for(std::size_t i = 0; i < result.size() - 1; ) { + if(!geometry::intersects(result[i], result.back())) { + ++i; + continue; + } + + std::vector union_result; + geometry::union_(result[i], result.back(), union_result); + + if(union_result.size() != 1) { + ++i; + continue; + } + + result.back() = std::move(union_result[0]); + result.erase(result.begin() + i); + } +} + +struct result_combine_multiple +{ + template + static inline void apply(GeometryOut &result, GeometryIn &new_elements) + { + for(auto &element: new_elements) + { + result_combine(result, std::move(element)); + } + } +}; + +struct pseudo_vertice_key +{ + std::size_t index_1; + double scale; + std::size_t index_2; + bool reroute; + + pseudo_vertice_key(std::size_t index_1 = 0, std::size_t index_2 = 0, double scale = 0.0, bool reroute = false) + : index_1(index_1), scale(scale), index_2(index_2), reroute(reroute) + { } +}; + +struct compare_pseudo_vertice_key +{ + bool operator()(pseudo_vertice_key const &a, pseudo_vertice_key const &b) const { + if(a.index_1 < b.index_1) return true; + if(a.index_1 > b.index_1) return false; + if(a.scale < b.scale) return true; + if(a.scale > b.scale) return false; + if(a.index_2 > b.index_2) return true; + if(a.index_2 < b.index_2) return false; + if(a.reroute && !b.reroute) return true; + if(!a.reroute && b.reroute) return false; + return false; + } +}; + +template +struct pseudo_vertice +{ + Point p; + pseudo_vertice_key link; + + pseudo_vertice(Point p, pseudo_vertice_key link = pseudo_vertice_key()) + : p(p), link(link) + { } +}; + +struct assign_policy { + static bool const include_no_turn = true; + static bool const include_degenerate = true; + static bool const include_opposite = true; + static bool const include_start_turn = true; +}; + +template +inline void dissolve_find_intersections( + Ring const &ring, + PseudoVertices &pseudo_vertices, + std::set &start_keys) +{ + if(ring.empty()) return; + + for(std::size_t i = 0; i < ring.size(); ++i) { + pseudo_vertices.emplace(pseudo_vertice_key(i, i, 0.0), ring[i]); + } + + using point_t = geometry::point_type_t; + + // Detect intersections and generate pseudo-vertices + geometry::strategies::cartesian<> strategy; + typedef geometry::detail::overlay::turn_info + < + point_t + > turn_info; + + std::vector turns; + + geometry::detail::self_get_turn_points::no_interrupt_policy policy; + geometry::self_turns + < + assign_policy + >(ring, strategy, turns, policy); + + for(auto const &turn: turns) { + auto p = turn.point; + auto i = std::min(turn.operations[0].seg_id.segment_index, turn.operations[1].seg_id.segment_index); + auto j = std::max(turn.operations[0].seg_id.segment_index, turn.operations[1].seg_id.segment_index); + + auto const offset_1 = geometry::comparable_distance(p, ring[i]); + auto const offset_2 = geometry::comparable_distance(p, ring[j]); + + auto const length = geometry::comparable_distance(ring[i], ring[j]); + if ((offset_1 > 0 && offset_1 < length) || (offset_2 > 0 && offset_2 < length)) { + pseudo_vertice_key key_j(j, i, offset_2); + pseudo_vertices.emplace(pseudo_vertice_key(i, j, offset_1, true), pseudo_vertice(p, key_j)); + pseudo_vertices.emplace(key_j, p); + start_keys.insert(key_j); + + pseudo_vertice_key key_i(i, j, offset_1); + pseudo_vertices.emplace(pseudo_vertice_key(j, i, offset_2, true), pseudo_vertice(p, key_i)); + pseudo_vertices.emplace(key_i, p); + start_keys.insert(key_i); + } + } +} + +// Remove invalid points (NaN) from ring +template +inline void correct_invalid(Ring &ring) +{ + for(auto i = ring.begin(); i != ring.end(); ) { + if(!geometry::is_valid(*i)) + i = ring.erase(i); + else + ++i; + } +} + +// Correct orientation of ring +template +inline void correct_orientation(Ring &ring, geometry::order_selector order) +{ + auto area = geometry::area(ring); + bool should_reverse = + (order == geometry::clockwise && area < 0) || + (order == geometry::counterclockwise && area > 0); + + if(should_reverse) { + std::reverse(ring.begin(), ring.end()); + } +} + +// Close ring if not closed +template +inline void correct_close(Ring &ring) +{ + // Close ring if not closed + if(!ring.empty() && !geometry::equals(ring.back(), ring.front())) + ring.push_back(ring.front()); + +} + +template +inline std::vector> dissolve_generate_rings( + PseudoVertices &pseudo_vertices, + std::set const &all_start_keys, + geometry::order_selector order, Area remove_spike_min_area) +{ + std::vector> result; + + // Generate all polygons by tracing all the intersections + // Perform union to combine all polygons into single polygon again + auto start_keys = all_start_keys; + while(!start_keys.empty()) { + Ring new_ring; + + // Store point in generated polygon + auto push_point = [&new_ring](auto const &p) { + if(new_ring.empty() || geometry::comparable_distance(new_ring.back(), p) > 0) { + new_ring.push_back(p); + } + }; + + // Store newly generated ring + auto push_ring = [&result, remove_spike_min_area](auto& new_ring) { + auto const area = geometry::area(new_ring); + if(std::abs(area) > remove_spike_min_area) { + result.push_back(std::make_pair(std::move(new_ring), area)); + } + }; + + auto i = pseudo_vertices.find(*start_keys.begin()); + + using point_t = geometry::point_type_t; + + std::vector< std::pair > start_points; + start_points.push_back(std::make_pair(i->second.p, 0)); + + // Check if the outer or inner ring is closed + auto is_closed = [&new_ring, &start_points, &push_ring](auto const& p) { + for(auto const &i: start_points) { + if(new_ring.size() > i.second+1 && geometry::comparable_distance(i.first, p) == 0) { + if(i.second == 0) return true; + + // Copy the new inner ring + Ring inner_ring(new_ring.begin() + i.second, new_ring.end()); + push_ring(inner_ring); + + // Remove the inner ring + new_ring.erase(new_ring.begin() + i.second, new_ring.end()); + } + } + return false; + }; + + do { + auto const &key = i->first; + auto const &value = i->second; + + // Store the point in output polygon + push_point(value.p); + + // Remove the key from the starting keys list + auto compare_key = [&key](pseudo_vertice_key const &i) { + return (key.index_1 == i.index_1 && key.index_2 == i.index_2 && key.scale == i.scale && key.reroute == i.reroute); + }; + + start_keys.erase(key); + + // Store possible new inner ring starting point + if(all_start_keys.find(key) != all_start_keys.end()) + start_points.push_back(std::make_pair(value.p, new_ring.size() - 1)); + + if(key.reroute) { + // Follow by-pass + i = pseudo_vertices.find(value.link); + } else { + // Continu following original polygon + ++i; + if(i == pseudo_vertices.end()) + i = pseudo_vertices.begin(); + } + + // Repeat until back at starting point + } while(!is_closed(new_ring.back())); + + // Combine with already generated polygons + push_ring(new_ring); + } + + return result; +} + +template +inline std::vector> correct(Ring const &ring, + geometry::order_selector order, Area remove_spike_min_area) +{ + constexpr std::size_t min_nodes = 3; + if(ring.size() < min_nodes) + return { }; + + using point_t = geometry::point_type_t; + std::map, compare_pseudo_vertice_key> pseudo_vertices; + std::set start_keys; + + Ring new_ring = ring; + + // Remove invalid coordinates + correct_invalid(new_ring); + + // Close ring + correct_close(new_ring); + + // Correct orientation + correct_orientation(new_ring, order); + + // Detect self-intersection points + dissolve_find_intersections(new_ring, pseudo_vertices, start_keys); + + if(start_keys.empty()) { + auto const area = geometry::area(new_ring); + if(std::abs(area) > remove_spike_min_area) + return { std::make_pair(new_ring, area) }; + else + return { }; + } + + return dissolve_generate_rings(pseudo_vertices, start_keys, order, remove_spike_min_area); +} + +template +inline void fill_normalize_polygons(MultiPolygonCollection &input) +{ + for(auto &i: input) { + for(auto &poly: i.first) { + if(i.second < 0) { + std::reverse(geometry::exterior_ring(poly).begin(), geometry::exterior_ring(poly).end()); + } + } + } +} + +template +struct fill_non_zero_winding +{ + template + inline void operator()(MultiPolygonCollection &input) const + { + auto compare = [](auto const &a, auto const &b) { return std::abs(a.second) > std::abs(b.second); }; + std::sort(input.begin(), input.end(), compare); + + std::vector scores; + for(auto const &mp: input) { + scores.push_back(mp.second > 0 ? 1 : -1); + } + + fill_normalize_polygons(input); + + for(std::size_t i = 0; i < input.size(); ++i) { + for(std::size_t j = i + 1; j < input.size(); ++j) { + if(geometry::covered_by(input[j].first, input[i].first)) { + scores[j] += scores[i]; + } + } + } + + MultiPolygon combined_outers; + MultiPolygon combined_inners; + + for(std::size_t i = 0; i < input.size(); ++i) { + if(scores[i] != 0) + result_combine_multiple::apply(combined_outers, input[i].first); + else + result_combine_multiple::apply(combined_inners, input[i].first); + } + + MultiPolygon output; + geometry::difference(combined_outers, combined_inners, output); + + input.resize(1); + input.front().first = std::move(output); + } +}; + + +template +struct fill_odd_even +{ + template + inline void operator()(MultiPolygonCollection &input) const + { + auto compare = [](auto const &a, auto const &b) { return std::abs(a.second) < std::abs(b.second); }; + std::sort(input.begin(), input.end(), compare); + + fill_normalize_polygons(input); + + while(input.size() > 1) { + std::size_t divide_i = input.size() / 2 + input.size() % 2; + for(std::size_t i = 0; i < input.size() / 2; ++i) { + std::size_t index = i + divide_i; + if(index < input.size()) { + MultiPolygon result; + geometry::sym_difference(input[index].first, input[i].first, result); + input[i].first = std::move(result); + } + } + + input.resize(divide_i); + } + } +}; + +template< + typename FillFunction, + typename CombinePolicy, + typename DifferenceFunction, + typename Polygon, + typename MultiPolygon, + typename Area +> +inline void correct(Polygon const &input, MultiPolygon &output, Area remove_spike_min_area, + FillFunction const &fill, CombinePolicy const &combine_policy, DifferenceFunction const &difference) +{ + auto const order = geometry::point_order::value; + auto outer_rings = correct(geometry::exterior_ring(input), order, remove_spike_min_area); + + // Calculate all outers + std::vector> combined_outers; + + for(auto &i: outer_rings) { + Polygon poly; + poly.outer() = std::move(i.first); + + combined_outers.push_back(std::make_pair(MultiPolygon(), i.second)); + combined_outers.back().first.push_back(std::move(poly)); + } + + // fill the collected outers and combine into single multi_polygon + fill(combined_outers); + + // Calculate all inners and combine them if possible + MultiPolygon combined_inners; + for(auto const &ring: geometry::interior_rings(input)) { + Polygon poly; + poly.outer() = std::move(ring); + + MultiPolygon new_inners; + correct(poly, new_inners, remove_spike_min_area, fill, combine_policy, difference); + combine_policy.apply(combined_inners, new_inners); + } + + // Cut out all inners from all the outers + if(!combined_outers.empty()) { + difference(combined_outers.front().first, combined_inners, output); + } +} + +template< + typename FillFunction, + typename CombinePolicy, + typename DifferenceFunction, + typename MultiPolygon, + typename Area + > +inline void correct(MultiPolygon const &input, MultiPolygon &output, Area remove_spike_min_area, FillFunction const &fill, + CombinePolicy const &combine_policy, DifferenceFunction const &difference) +{ + for(auto const &polygon: input) + { + MultiPolygon new_polygons; + correct(polygon, new_polygons, remove_spike_min_area, fill, combine_policy, difference); + combine_policy.apply(output, new_polygons); + } +} + +} // impl + +template +inline void correct_non_zero(Polygon const &input, MultiPolygon &output, Area remove_spike_min_area = 0.0) +{ + impl::correct(input, output, remove_spike_min_area, + impl::fill_non_zero_winding(), + impl::result_combine_multiple(), + geometry::difference + ); +} + +template +inline void correct_odd_even(Polygon const &input, MultiPolygon &output, Area remove_spike_min_area = 0.0) +{ + impl::correct(input, output, remove_spike_min_area, + impl::fill_odd_even(), + [](MultiPolygon &a, MultiPolygon const &b) { + MultiPolygon result; + geometry::sym_difference(a, b, result); + a = std::move(result); + }, + geometry::sym_difference + ); + +} + + +template +inline void correct_non_zero(MultiPolygon const &input, MultiPolygon &output, Area remove_spike_min_area = 0.0) +{ + impl::correct(input, output, remove_spike_min_area, + impl::fill_non_zero_winding(), + impl::result_combine_multiple(), + geometry::difference + ); +} + +template +inline void correct_odd_even(MultiPolygon const &input, MultiPolygon &output, Area remove_spike_min_area = 0.0) +{ + impl::correct(input, output, remove_spike_min_area, + impl::fill_odd_even(), + [](MultiPolygon &a, MultiPolygon const &b) { + MultiPolygon result; + geometry::sym_difference(a, b, result); + a = std::move(result); + }, + geometry::sym_difference + ); +} + +}} + +#endif diff --git a/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp b/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp index 1d552f998e..60053c46ab 100644 --- a/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp +++ b/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp @@ -10,6 +10,7 @@ #define BOOST_GEOMETRY_EXTENSIONS_GIS_IO_GEOJSON_WRITER_HPP #include +#include #include #include #include diff --git a/include/boost/geometry/extensions/multi/algorithms/dissolve.hpp b/include/boost/geometry/extensions/multi/algorithms/dissolve.hpp index c7fc3f84b0..a6440a9a30 100644 --- a/include/boost/geometry/extensions/multi/algorithms/dissolve.hpp +++ b/include/boost/geometry/extensions/multi/algorithms/dissolve.hpp @@ -42,12 +42,10 @@ struct dissolve_multi { template < - typename RescalePolicy, typename OutputIterator, - typename Strategy, typename Visitor + typename OutputIterator, typename Strategy, typename Visitor > static inline OutputIterator apply(Multi const& multi, - RescalePolicy const& rescale_policy, OutputIterator out, - Strategy const& strategy, Visitor& visitor) + OutputIterator out, Strategy const& strategy, Visitor& visitor) { typedef typename boost::range_value::type polygon_type; typedef typename boost::range_iterator::type iterator_type; @@ -63,7 +61,7 @@ struct dissolve_multi polygon_type, GeometryOut, Reverse - >::apply(*it, rescale_policy, std::back_inserter(step1), + >::apply(*it, std::back_inserter(step1), strategy, visitor); } @@ -73,7 +71,7 @@ struct dissolve_multi detail::dissolver::dissolver_generic < detail::dissolver::plusmin_policy - >::apply(step1, rescale_policy, step2, strategy); + >::apply(step1, step2, strategy); for (typename std::vector::const_iterator it = step2.begin(); it != step2.end(); ++it) {