From e325023621247a9bb6f13a3d3f7a0a29fe39f090 Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Fri, 6 Dec 2024 19:28:33 +0100 Subject: [PATCH 1/5] feat: prepare dsv for geojson --- include/boost/geometry/io/dsv/write.hpp | 39 ++++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/include/boost/geometry/io/dsv/write.hpp b/include/boost/geometry/io/dsv/write.hpp index 60a6e9af7f..e509c853f5 100644 --- a/include/boost/geometry/io/dsv/write.hpp +++ b/include/boost/geometry/io/dsv/write.hpp @@ -37,6 +37,8 @@ #include +#include + namespace boost { namespace geometry { @@ -53,6 +55,7 @@ struct dsv_settings std::string list_open; std::string list_close; std::string list_separator; + bool close_rings{false}; dsv_settings(std::string const& sep , std::string const& open @@ -61,6 +64,7 @@ struct dsv_settings , std::string const& lopen , std::string const& lclose , std::string const& lsep + , bool cr = false ) : coordinate_separator(sep) , point_open(open) @@ -69,6 +73,7 @@ struct dsv_settings , list_open(lopen) , list_close(lclose) , list_separator(lsep) + , close_rings(cr) {} }; @@ -161,7 +166,7 @@ struct dsv_point \brief Stream ranges as DSV \note policy is used to stream prefix/postfix, enabling derived classes to override this */ -template +template struct dsv_range { template @@ -173,20 +178,31 @@ struct dsv_range os << settings.list_open; - for (auto it = boost::begin(range); it != boost::end(range); ++it) + auto stream_point = [&os, &settings](std::string const& sep, auto const& point) { - os << (first ? "" : settings.point_separator) - << settings.point_open; - + os << sep << settings.point_open; stream_coordinate < point_type, 0, dimension::type::value - >::apply(os, *it, settings); + >::apply(os, point, settings); os << settings.point_close; + }; + for (auto it = boost::begin(range); it != boost::end(range); ++it) + { + stream_point(first ? "" : settings.point_separator, *it); first = false; } + if (BOOST_GEOMETRY_CONDITION(Areal)) + { + if (settings.close_rings && boost::size(range) > 0) + { + // Close it explicitly + stream_point(settings.point_separator, *boost::begin(range)); + } + } + os << settings.list_close; } @@ -211,13 +227,13 @@ struct dsv_poly os << settings.list_open; - dsv_range::apply(os, exterior_ring(poly), settings); + dsv_range::apply(os, exterior_ring(poly), settings); auto const& rings = interior_rings(poly); for (auto it = boost::begin(rings); it != boost::end(rings); ++it) { os << settings.list_separator; - dsv_range::apply(os, *it, settings); + dsv_range::apply(os, *it, settings); } os << settings.list_close; } @@ -277,7 +293,7 @@ struct dsv template struct dsv - : detail::dsv::dsv_range + : detail::dsv::dsv_range {}; template @@ -292,7 +308,7 @@ struct dsv template struct dsv - : detail::dsv::dsv_range + : detail::dsv::dsv_range {}; template @@ -404,6 +420,7 @@ inline detail::dsv::dsv_manipulator dsv(Geometry const& geometry , std::string const& list_open = "(" , std::string const& list_close = ")" , std::string const& list_separator = ", " + , bool close_rings = false ) { concepts::check(); @@ -411,7 +428,7 @@ inline detail::dsv::dsv_manipulator dsv(Geometry const& geometry return detail::dsv::dsv_manipulator(geometry, detail::dsv::dsv_settings(coordinate_separator, point_open, point_close, point_separator, - list_open, list_close, list_separator)); + list_open, list_close, list_separator, close_rings)); } }} // namespace boost::geometry From 7039aba4f7f984e44a9c8a3dcffde92406047318 Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Sat, 14 Dec 2024 12:00:01 +0100 Subject: [PATCH 2/5] test: add test cases --- .../overlay/multi_overlay_cases.hpp | 191 +++++++++++++++++- .../difference/difference_multi.cpp | 1 - .../intersection/intersection_multi.cpp | 1 - .../set_operations/set_ops_areal_areal.cpp | 111 ++++++++-- .../set_operations/union/union_multi.cpp | 1 - 5 files changed, 278 insertions(+), 27 deletions(-) diff --git a/test/algorithms/overlay/multi_overlay_cases.hpp b/test/algorithms/overlay/multi_overlay_cases.hpp index 2fac8f0db5..1f2cd53343 100644 --- a/test/algorithms/overlay/multi_overlay_cases.hpp +++ b/test/algorithms/overlay/multi_overlay_cases.hpp @@ -653,8 +653,157 @@ static std::string case_140_multi[2] = static std::string case_141_multi[2] = { // Version to test more isolation/validity cases - "MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0),(2 3,1 2,2 1,3 2,2 3),(2 7,3 8,2 9,1 8,2 7),(10 3,9 4,8 3,9 2,10 3),(7 10,6 9,7 8,8 9,7 10),(10 7,9 8,8 7,9 6,10 7)))", - "MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0),(0 5,2 3,4 5,2 7),(3 2,4 1,5 2,4 3,3 2),(3 8,4 7,5 8,4 9,3 8),(7 2,8 1,9 2,8 2,8 3,7 2),(8 7,7 8,6 7,7 6,8 7)))" + R""""( + MULTIPOLYGON( + ((0 0,0 10,10 10,10 0,0 0), + (2 3,1 2,2 1,3 2,2 3), + (2 7,3 8,2 9,1 8,2 7), + (10 3,9 4,8 3,9 2,10 3), + (7 10,6 9,7 8,8 9,7 10), + (10 7,9 8,8 7,9 6,10 7))) + )"""", + R""""( + MULTIPOLYGON( + ((0 0,0 10,10 10,10 0,0 0), + (0 5,2 3,4 5,2 7), + (3 2,4 1,5 2,4 3,3 2), + (3 8,4 7,5 8,4 9,3 8), + (7 2,8 1,9 2,8 2,8 3,7 2), + (8 7,7 8,6 7,7 6,8 7))) + )"""" +}; + +static std::string case_142_multi[2] = +{ + // Simpler version of 141 with one isolated interior (in intersection), + // no clusters, no self-turns. Only cc, ii and ui turns. + R""""( + MULTIPOLYGON( + ((0 0,0 10,0 10,10 10,10 0,0 0), + (2 7,4 5,6 5,8 7,2 7)) + ) + )"""", + R""""( + MULTIPOLYGON( + ((0 0,0 10,4 10,5 11,6 10,10 10,10 0,0 0), + (5 10,2 7,5 4,8 7,5 10)) + ) + )"""" +}; + +static std::string case_143_multi[2] = +{ + // Same but with one extra inteior + R""""( + MULTIPOLYGON( + ((0 0,0 10,10 10,10 8,10 6,10 4,10 2,10 0,0 0), + (2 7,4 5,6 5,8 7,2 7), + (5 4,4 3,5 2,6 3,5 4) + ) + ) + )"""", + R""""( + MULTIPOLYGON( + ((0 0,0 10,4 10,5 11,6 10,10 10,10 9,10 7,10 5,10 3,10 1,10 0,0 0), + (5 10,2 7,5 4,8 7,5 10)) + ) + )"""" +}; + +static std::string case_144_multi[2] = +{ + // Simple case resulting in two separate exterior rings. + R""""( + MULTIPOLYGON( + ((0 0,0 10,10 10,10 0,0 0), + (2 7,4 5,6 5,8 7,2 7), + (5 4,4 3,5 2,6 3,5 4) + ) + ) + )"""", + R""""( + MULTIPOLYGON( + ((0 0,0 10,4 10,5 11,6 10,10 10,10 9,10 1,10 0,6 0,5 -1,4 0,0 0), + (5 10,2 7,5 4,8 7,5 10), + (4 1,5 0,6 1,5 2,4 1) + ) + ) + )"""" +}; + +static std::string case_145_multi[2] = +{ + // Simpler version of 141 with two isolated interiors, and two variations in the lower corners. + // This gives mainly cc, some ii, and some iu cases. + R""""( + MULTIPOLYGON( + ((0 2,0 9,0 10,2 10,6 10,10 10,10 4,10 0,7 0,2 0,0 2), + (10 7,9 8,8 7,9 6,10 7), + (3 8,5 6,7 8,3 8), + (5 0,6 1,5 2,4 1,5 0)) + ) + )"""", + R""""( + MULTIPOLYGON( + ((0 0,0 1,0 10,4 10,5 11,6 10,10 10,10 4,10 2,8 0,3 0,0 0), + (5 10,3 8,5 6,7 8,5 10), + (0 5,2 3,4 5,2 7)) + ) + )"""" +}; + +static std::string case_146_multi[2] = +{ + // As 141, with an interior ring and a separate island, + // but 1: attached is another isolated interior ring + // and 2: these combined shapes are copied to the left, in reversed order. + R""""( + MULTIPOLYGON( + ((0 0,0 10,10 10,10 0,0 0), + (10 3,9 4,8 3,9 2,10 3), + (2 2,1 3,0 2,1 1,2 2), + (5 3,4 4,3 3,4 2,5 3), + (7 2,6 3,5 2,6 1,7 2), + (7 10,6 9,7 8,8 9,7 10), + (10 7,9 8,8 7,9 6,10 7)) + ) + )"""", + R""""( + MULTIPOLYGON( + ((0 0,0 10,10 10,10 0,0 0), + (7 2,8 1,9 2,8 2,8 3,7 2), + (2 2,3 1,4 2,3 2,3 3,2 2), + (8 7,7 8,6 7,7 6,8 7)) + ) + )"""" +}; + +static std::string case_147_multi[2] = +{ + // Version with touching islands in holes in various ways. + R""""( + MULTIPOLYGON( + ((10 0,0 10,10 20,20 10,10 0), + (5 5,7 5,7 7,5 7,5 5), + (15 5,15 9,11 9,11 5,15 5), + (5 15,5 11,7 11,6 12,7 13,7 15,5 15), + (15 15,11 15,11 11,15 11,15 15)), + ((15 9,14 6,11 5,12 8,15 9)), + ((15 15,14 12,11 11,12 14,15 15)) + ) + )"""", + R""""( + MULTIPOLYGON( + ((10 0,0 10,10 20,20 10,10 0), + (7 7,9 7,9 9,7 9,7 7), + (15 5,15 9,11 9,11 5,15 5), + (7 15,8 14,7 13,7 11,9 11,9 15,7 15), + (15 15,11 15,11 11,15 11,15 15)), + ((15 9,14 6,11 5,12 8,15 9)), + ((15 15,14 12,11 11,12 14,15 15)), + ((7 9,8.5 8.5,9 7,7.5 7.5,7 9)) + ) + )"""" }; static std::string case_recursive_boxes_1[2] = @@ -1528,6 +1677,13 @@ static std::string issue_869_a[2] = "MULTIPOLYGON(((50 50,50 70,70 70,70 50,50 50)),((50 50,50 30,30 30,30 50,50 50)))" }; +// To use intersection for difference research (made valid in tests) +static std::string issue_869_a_inverse[2] = +{ + "MULTIPOLYGON(((70 50,70 10,10 10,10 70,50 70,50 90,70 90,70 70,90 70,90 50,70 50)))", + "MULTIPOLYGON(((0 0,0 100,100 100,100 0,0 0),(50 50,50 70,70 70,70 50,50 50),(50 50,50 30,30 30,30 50,50 50)))" +}; + // For union static std::string issue_869_b[2] = { @@ -1580,12 +1736,24 @@ static std::string issue_1109[2] = "MULTIPOLYGON(((0 -88,0 -115.40000152587890625,-10 -88,0 -88)))" }; +static std::string issue_1221[2] = +{ + "MULTIPOLYGON(((1 2,1 3,0 3,0 4,1 4,1 5,3 5,3 0,0 0,0 2,1 2),(1 2,1 1,2 1,2 2,1 2)))", + "POLYGON((1 3,1 4,2 4,2 3,1 3))" +}; + static std::string issue_1222[2] = { "MULTIPOLYGON(((2 4,2 2,0 2,0 4,2 4)),((6 4,4 4,2 4,2 6,0 6,0 10,6 10,6 4)))", "MULTIPOLYGON(((4 4,4 2,2 2,2 4,4 4)),((4 8,4 6,2 6,2 8,4 8)))" }; +static std::string issue_1241[2] = +{ + "POLYGON((-124.19999999845255 51.901455507812500, -124.19999999460376 51.935823093966235, -123.99999999648789 51.935823093966235, -123.99999999317222 51.902564290309876, -124.19999999845255 51.901455507812500))", + "POLYGON((-123.99999999367975 51.907655109375000, -123.99999999291659 51.900000006653443, -124.19999999861555 51.900000005468293, -124.19999999792353 51.906179355468751, -123.99999999367975 51.907655109375000))" +}; + static std::string issue_1288[3] = { // Issue with differences in behaviour for multi polygon vs polygon @@ -1600,12 +1768,31 @@ static std::string issue_1299[2] = "MULTIPOLYGON(((-0.87500000000000000 -0.84999999999999998, -0.87500000000000000 -0.070000000000000201, -1.2549999999999999 -0.070000000000000201, -1.2549999999999999 -0.84999999999999998)))" }; +static std::string issue_1349[2] = +{ + "MULTIPOLYGON(((0 0,0 1178,2002 1178,2002 0,0 0),(1000.99879999999996 124.985299999999995,1011.01279999999997 134.999300000000005,1011.01400000000001 325.002599999999973,1001 335.016599999999983,990.98599999999999 325.002599999999973,990.98479999999995 134.999300000000005,1000.99879999999996 124.985299999999995)))", + "MULTIPOLYGON(((1592.0101999999999407 1149.8399999999999181, 1586.9901999999999589 1149.8399999999999181, 1586.9901999999999589 1178.0000000000000000, 1592.0101999999999407 1178.0000000000000000, 1592.0101999999999407 1149.8399999999999181)), ((2001.9988000000000739 230.0014999999999930, 2001.9988000000000739 0.0015000000000000, 1000.9238000000000284 0.0000000000000000, 1000.9238000000000284 230.0765000000000100, 2001.9988000000000739 230.0014999999999930)))" +}; + +// To use intersection for difference research (made valid in tests) +static std::string issue_1349_inverse[2] = +{ + "MULTIPOLYGON(((0 0,0 1178,2002 1178,2002 0,0 0),(1000.99879999999996 124.985299999999995,1011.01279999999997 134.999300000000005,1011.01400000000001 325.002599999999973,1001 335.016599999999983,990.98599999999999 325.002599999999973,990.98479999999995 134.999300000000005,1000.99879999999996 124.985299999999995)))", + "MULTIPOLYGON(((-1000 -1000,-1000 3000,3000 3000,3000 -1000,-1000 -1000),(1592.0101999999999407 1149.8399999999999181, 1586.9901999999999589 1149.8399999999999181, 1586.9901999999999589 1178.0000000000000000, 1592.0101999999999407 1178.0000000000000000, 1592.0101999999999407 1149.8399999999999181),(2001.9988000000000739 230.0014999999999930, 2001.9988000000000739 0.0015000000000000, 1000.9238000000000284 0.0000000000000000, 1000.9238000000000284 230.0765000000000100, 2001.9988000000000739 230.0014999999999930)))" +}; + static std::string issue_1350_comment[2] = { "MULTIPOLYGON(((2 10,2 8,0 8,0 10,2 10)),((10 8,10 2,8 2,8 0,0 0,0 4,2 4,2 8,4 8,4 10,6 10,6 8,6 6,8 6,8 8,10 8),(8 2,8 4,4 4,4 2,8 2)))", "MULTIPOLYGON(((2 6,2 4,2 2,2 0,0 0,0 6,2 6)),((2 10,2 8,0 8,0 10,2 10)),((6 8,6 6,2 6,2 8,6 8)),((8 4,8 2,6 2,6 4,8 4)),((10 8,10 6,8 6,8 8,10 8)),((8 10,8 8,6 8,6 10,8 10)))" }; +static std::string issue_1354[2] = +{ + "MULTIPOLYGON(((295.199999999999989 -295.199999999999989,295.199999999999989 295.199999999999989,-295.199999999999989 295.199999999999989,-295.199999999999989 -295.199999999999989,295.199999999999989 -295.199999999999989),(-179.997681584096682 -159.068022953381728,-179.997941555125578 -158.950077048237887,-180 -158.832148820362704,-180 -14.5241121286848625,-179.998787156680379 -14.4546283814396119,-179.999195199119924 -14.3851352478099308,-179.995132365653177 -14.2452455437218966,-179.992689924229154 -14.1053181682548523,-179.989052871933012 -14.0359190762519734,-179.987035390416196 -13.9664540354863789,-179.978092951651348 -13.8267913392705939,-179.970768603117904 -13.6870344437553584,-179.96471177303323 -13.6178045590987882,-179.960271225550514 -13.5484522436487858,-179.899302518930739 -12.8010583222347041,-179.862001646775752 -12.4192286301278614,-179.812514069087342 -12.0387864551623842,-179.756717919995367 -11.6575893141408002,-179.69354553378804 -11.2702990769800557,-179.617743949785876 -10.8852814175903685,-179.449050736785438 -10.0966985448598905,-179.370713374286112 -9.75497969251792263,-179.282427103876586 -9.41569507620525137,-179.18426728065765 -9.07913428598321026,-178.935424198586816 -8.27008653807789607,-178.812412221610401 -7.89249729706248626,-178.67697357776251 -7.51918515533267851,-178.52206744438692 -7.11329246102736157,-178.372718942212316 -6.74078412878685995,-178.210997411857903 -6.37347828985937515,-177.83796586524096 -5.56325522558086671,-177.683643331306087 -5.24079944287654875,-177.519784336166936 -4.92308352260890025,-177.346534296916872 -4.61038942260158002,-176.888642811189385 -3.8122348268109878,-176.682925174829364 -3.46716074752500347,-176.4657719066781 -3.12916618508967126,-176.203261097574938 -2.73523344331954199,-175.97364845346371 -2.40292746742542196,-175.732982121071331 -2.078537355980135,-175.143129804051284 -1.310831533374758,-174.920901565835123 -1.03035395323309942,-174.690409107515819 -0.756627283942919782,-174.451857396213086 -0.489894938965182547,-173.780108917183412 0.239037812837519681,-173.533488205359163 0.498765442860035435,-173.279226152669509 0.751017352386726955,-173.017549264610096 0.995568826627545533,-172.318055071282714 1.62999550300293983,-172.067822666567167 1.85068903895734671,-171.811551390238662 2.06434045156852441,-171.549439357600335 2.27078457361611186,-171.281689199261223 2.46986180961050472,-171.031950157793972 2.65021087534550936,-170.768065071279523 2.835433324854856,-170.499299340935181 3.01349982052276921,-170.225846968387685 3.18428182963124229,-169.947905338195852 3.34765607758145833,-169.680262243237394 3.50017672557883053,-169.398031980415396 3.65602528487362832,-169.111716805616084 3.80423566024079918,-168.821523388081118 3.94470086983453072,-168.527661196461878 4.07731952245764262,-168.245202320314831 4.20025185193289374,-167.947883471472693 4.32492822022349799,-167.64732257736847 4.44157230975317141,-167.343736590149689 4.55009992398729324,-167.037344645549581 4.6504327250600852,-166.743329216418374 4.74235824805159822,-166.434352475578294 4.83442381331231275,-166.123013965408205 4.91815568760162414,-165.809538417600521 4.99349343122062095,-165.494152106413281 5.06038266355761301,-165.191972700917376 5.12024067253396975,-164.874903279844006 5.17863311131773951,-164.556379617287462 5.2284866074688674,-164.23663163135754 5.26976517552396029,-163.915890123910316 5.3024390196108131,-163.609033520920434 5.32953882673178825,-163.287530010959983 5.35358436207679489,-162.965496567522251 5.36898423202898734,-162.643165642162018 5.375727320588501,-162.320769901163743 5.37380876043037148,-162.012776848660081 5.36783766723553946,-161.690549005093743 5.35725884322304502,-161.368721650836875 5.33802739140809557,-161.047527088684063 5.31015719351933235,-160.727197164665256 5.27366836694196373,-160.421621523858249 5.23469527250653854,-160.102387459886472 5.18961415576110952,-159.784479686856571 5.13597328949175136,-159.468127678314318 5.07381139297753059,-159.153559784816935 5.00317333618393612,-158.853927522957889 4.93164794210813451,-158.541370807245158 4.85258471329888774,-158.231050880339666 4.7651533822312695,-157.923191738695721 4.66941705896188797,-157.618015602513111 4.56544484829227759,-157.327784107566345 4.46219246969767802,-157.025511260386196 4.35005942129268952,-156.726359890085519 4.22984647550804382,-156.430545931383392 4.1016404049654156,-156.138282909974265 3.96553375191790813,-155.860801096610373 3.83174579432549756,-155.572299975036202 3.68783680385857515,-155.287769000407764 3.53622935297387997,-155.007413554087719 3.37703287560877818,-154.731436003441075 3.21036228364411969,-154.46990567067823 3.04758249561918326,-154.198505222998278 2.87355809593285194,-153.931877781715087 2.69230550356012799,-153.670215804986441 2.50395555102093859,-153.413708166777326 2.30864419388203324,-153.171147053156858 2.11875085407343189,-152.87864945418977 1.88227751953715927,-152.593706706865078 1.63675335759936136,-152.316598756515816 1.38241958634308926,-152.047597851087176 1.11952607895504297,-151.758492641328019 0.827987423984159143,-151.497863063903992 0.556792463263721338,-151.245860873059797 0.277562487870442354,-151.002733651293767 -0.00942816944216584574,-150.768720261787024 -0.303897551347931838,-150.580866676220865 -0.54804178071738896,-150.387712159765329 -0.806177457173726353,-150.201562005690334 -1.06940894241931783,-150.022550581694702 -1.33754622957983571,-149.850807102568638 -1.61039577065407258,-149.690225111389282 -1.87328129413026989,-149.633025334764199 -1.97070420726289952,-149.574266655031067 -2.06719491150401513,-149.526997232690093 -2.15129174077182661,-149.478152856477351 -2.23448369888000897,-149.424387935046411 -2.33384350802175922)))", + "MULTIPOLYGON(((-161.64096882920154 -24.7006892825620064,-161.289685158351034 -24.4725629993453921,-160.946576972490334 -24.2323160611842638,-160.612062296092091 -23.9802411719655595,-160.286548683761538 -23.7166454461126293,-159.970432723693705 -23.4418500344136262,-159.664099554492026 -23.1561897327487074,-159.367922395936176 -22.8600125741928615,-159.082262094271272 -22.5536794049911933,-158.807466682572255 -22.2375634449233637,-158.543870956719331 -21.9120498325927926,-158.291796067500627 -21.5775351561945712,-158.051549129339492 -21.2344269703338533,-157.823422846122895 -20.8831432994833506,-157.607695154586736 -20.5241121286848909,-157.404628885692887 -20.1577708821155781,-157.21447144440998 -19.7845658901538215,-157.037454508288789 -19.4049518455944927,-156.873793745198554 -19.0193912496758344,-156.723688550569108 -18.6283538485929157,-156.587321804458156 -18.2323160611842603,-156.464859648740173 -17.8317603984888819,-156.356451284688035 -17.4271748758809046,-156.262228791194332 -17.0190524184980028,-156.182306963853506 -16.6078902606880554,-156.116783175101148 -16.1941893402056749,-156.065737255580729 -15.7784536878967323,-156.029231396882096 -15.361189813614395,-156.007310075770846 -14.9429060891149028,-156 -14.5241121286848909,-156 -0.659861765609321083,-156 -0.659861765609321083,-156.007310075770874 -0.24106780517901305,-156.029231396882125 0.177215919320473647,-156.065737255580757 0.594479793602806117,-156.116783175101205 1.01021544591174406,-156.182306963853563 1.42391636639411612,-156.262228791194389 1.8350785242040577,-156.35645128468812 2.24320098158695203,-156.464859648740259 2.64778650419492223,-156.587321804458242 3.04834216689029436,-156.723688550569193 3.44437995429894173,-156.87379374519864 3.83541735538185424,-157.037454508288874 4.22097795130050457,-157.214471444410094 4.6005919958598227,-157.404628885693 4.97379698782157575,-157.60769515458685 5.34013823439087876,-157.823422846123009 5.69916940518932957,-158.051549129339634 6.0504530760398243,-158.291796067500769 6.39356126190053153,-158.543870956719473 6.72807593829874495,-158.807466682572397 7.05358955062930804,-159.082262094271414 7.36970551069712698,-159.367922395936318 7.67603867989878808,-159.664099554492168 7.97221583845462689,-159.970432723693847 8.25787614011953508,-160.28654868376168 8.53267155181853276,-160.612062296092233 8.79626727767145233,-160.946576972490448 9.04834216689014958,-161.289685158351176 9.28858910505127255,-161.640968829201682 9.51671538826787611,-162.000000000000142 9.73244307980402112,-162.366341246569448 9.93550934869787383,-162.739546238531204 10.1256667899807482,-163.119160283090537 10.3026837261019484,-163.504720879009199 10.4663444891921795,-163.89575828009211 10.6164496838216245,-164.291796067500741 10.7528164299325617,-164.692351730196123 10.8752785856505412,-165.096937252804111 10.9836869497026655,-165.505059710186998 11.0779094431963721,-165.916221867996938 11.1578312705371943,-166.329922788479308 11.2233550592895384,-166.745658440788247 11.2744009788099682,-167.162922315070603 11.3109068375085773,-167.581206039570077 11.3328281586198312,-168.000000000000085 11.3401382343906789,-168.418793960430094 11.3328281586198258,-168.837077684929568 11.3109068375085648,-169.254341559211923 11.2744009788099522,-169.670077211520862 11.2233550592895135,-170.083778132003232 11.1578312705371641,-170.494940289813172 11.077909443196333,-170.90306274719606 10.9836869497026228,-171.307648269804048 10.8752785856504914,-171.708203932499401 10.7528164299325084,-172.10424171990806 10.6164496838215658,-172.495279120990972 10.4663444891921138,-172.880839716909634 10.3026837261018773,-173.260453761468966 10.1256667899806718,-173.633658753430694 9.93550934869779212,-174 9.73244307980393408,-174.35903117079846 9.51671538826778551,-174.710314841648966 9.28858910505117663,-175.053423027509666 9.04834216689004656,-175.387937703907909 8.79626727767134398,-175.713451316238462 8.53267155181842085,-176.029567276306295 8.25787614011941784,-176.335900445507946 7.97221583845450077,-176.632077604063795 7.67603867989865307,-176.917737905728728 7.36970551069698487,-177.192533317427717 7.05358955062915793,-177.456129043280669 6.72807593829858508,-177.708203932499373 6.39356126190036278,-177.948450870660508 6.05045307603964844,-178.176577153877105 5.69916940518914394,-178.392304845413264 5.34013823439068602,-178.595371114307113 4.97379698782137414,-178.785528555589991 4.60059199585961309,-178.962545491711211 4.22097795130028608,-179.126206254801446 3.83541735538162776,-179.276311449430892 3.44437995429870725,-179.412678195541844 3.04834216689005189,-179.535140351259827 2.64778650419467221,-179.643548715311965 2.24320098158669401,-179.737771208805668 1.83507852420379303,-179.817693036146494 1.42391636639384433,-179.883216824898852 1.01021544591146517,-179.934262744419271 0.59447979360252079,-179.970768603117904 0.177215919320182214,-179.992689924229154 -0.241067805179310257,-180 -0.659861765609321083,-180 -14.5241121286848909,-179.992689924229154 -14.942906089114862,-179.970768603117904 -15.3611898136143541,-179.9342627444193 -15.7784536878966932,-179.883216824898852 -16.1941893402056394,-179.817693036146494 -16.6078902606880163,-179.737771208805668 -17.0190524184979672,-179.643548715311965 -17.4271748758808691,-179.535140351259827 -17.8317603984888464,-179.412678195541844 -18.2323160611842248,-179.276311449430921 -18.6283538485928801,-179.126206254801474 -19.0193912496758024,-178.962545491711239 -19.4049518455944607,-178.78552855559002 -19.7845658901537895,-178.595371114307142 -20.1577708821155497,-178.392304845413292 -20.5241121286848625,-178.176577153877133 -20.8831432994833222,-177.948450870660508 -21.2344269703338284,-177.708203932499373 -21.5775351561945428,-177.456129043280669 -21.9120498325927642,-177.192533317427745 -22.2375634449233388,-176.917737905728757 -22.5536794049911684,-176.632077604063824 -22.8600125741928366,-176.335900445507974 -23.1561897327486861,-176.029567276306324 -23.4418500344136049,-175.713451316238491 -23.7166454461126079,-175.387937703907909 -23.9802411719655382,-175.053423027509695 -24.2323160611842425,-174.710314841648994 -24.4725629993453779,-174.359031170798488 -24.7006892825619886,-174.000000000000028 -24.9164169740981407,-173.633658753430723 -25.1194832429920041,-173.260453761468938 -25.3096406842748856,-172.880839716909634 -25.4866576203960911,-172.495279120990972 -25.6503183834863329,-172.104241719908032 -25.8004235781157831,-171.708203932499401 -25.9367903242267275,-171.307648269804019 -26.0592524799447105,-170.903062747196032 -26.1676608439968419,-170.494940289813144 -26.2618833374905556,-170.083778132003175 -26.3418051648313849,-169.670077211520805 -26.4073289535837326,-169.254341559211866 -26.4583748731041695,-168.837077684929511 -26.4948807318027804,-168.418793960430037 -26.5168020529140378,-168.000000000000028 -26.5241121286848909,-167.581206039569992 -26.5168020529140378,-167.162922315070517 -26.4948807318027804,-166.745658440788162 -26.4583748731041695,-166.329922788479223 -26.4073289535837361,-165.916221867996853 -26.341805164831392,-165.505059710186913 -26.2618833374905591,-165.096937252803997 -26.167660843996849,-164.692351730196009 -26.0592524799447212,-164.291796067500627 -25.9367903242267381,-163.895758280091997 -25.8004235781157938,-163.504720879009056 -25.6503183834863435,-163.119160283090395 -25.4866576203961053,-162.739546238531091 -25.3096406842748962,-162.366341246569306 -25.1194832429920183,-162 -24.9164169740981585,-161.64096882920154 -24.7006892825620064)))" +}; + static std::string bug_21155501[2] = { "MULTIPOLYGON(((-8.3935546875 27.449790329784214,4.9658203125 18.729501999072138,11.8212890625 23.563987128451217,9.7119140625 25.48295117535531,9.8876953125 31.728167146023935,8.3056640625 32.99023555965106,8.5693359375 37.16031654673677,-1.8896484375 35.60371874069731,-0.5712890625 32.02670629333614,-8.9208984375 29.458731185355344,-8.3935546875 27.449790329784214)))", diff --git a/test/algorithms/set_operations/difference/difference_multi.cpp b/test/algorithms/set_operations/difference/difference_multi.cpp index 734ba09b2d..e98cc17891 100644 --- a/test/algorithms/set_operations/difference/difference_multi.cpp +++ b/test/algorithms/set_operations/difference/difference_multi.cpp @@ -201,7 +201,6 @@ void test_areal() TEST_DIFFERENCE(case_138_multi, 5, 16.6, 3, 8.225, 8); TEST_DIFFERENCE(case_139_multi, 4, 16.328125, 3, 8.078125, 7); TEST_DIFFERENCE(case_140_multi, 4, 16.328125, 3, 8.078125, 7); - TEST_DIFFERENCE(case_141_multi, 5, 15.5, 5, 10.0, 10); // Areas correspond with POSTGIS, // #clips in PostGIS is 11,11,5 but should most probably be be 12,12,6 diff --git a/test/algorithms/set_operations/intersection/intersection_multi.cpp b/test/algorithms/set_operations/intersection/intersection_multi.cpp index ec45ce3fd1..7a2298f37b 100644 --- a/test/algorithms/set_operations/intersection/intersection_multi.cpp +++ b/test/algorithms/set_operations/intersection/intersection_multi.cpp @@ -156,7 +156,6 @@ void test_areal() TEST_INTERSECTION(case_138_multi, 2, 23, 40.4); TEST_INTERSECTION(case_139_multi, 2, 23, 40.546875); TEST_INTERSECTION(case_140_multi, 2, 23, 40.546875); - TEST_INTERSECTION(case_141_multi, 3, -1, 74.5); TEST_INTERSECTION(case_recursive_boxes_1, 10, 89, 47.0); diff --git a/test/algorithms/set_operations/set_ops_areal_areal.cpp b/test/algorithms/set_operations/set_ops_areal_areal.cpp index 6a2b9a835a..f4157119b5 100644 --- a/test/algorithms/set_operations/set_ops_areal_areal.cpp +++ b/test/algorithms/set_operations/set_ops_areal_areal.cpp @@ -17,40 +17,71 @@ // Use another alias, on purpose it is not "bg::" here namespace bgeo = boost::geometry; +// For convenience and to test current failures +#if defined(BOOST_GEOMETRY_TEST_FAILURES) +#define BG_IF_TEST_FAILURES true +#else +#define BG_IF_TEST_FAILURES false +#endif + struct ut_settings { explicit ut_settings() {} + inline ut_settings& ignore_reverse() + { + test_reverse = BG_IF_TEST_FAILURES; + return *this; + } + + inline ut_settings& ignore_diff() + { + test_difference = BG_IF_TEST_FAILURES; + return *this; + } + inline ut_settings& ignore_validity() { - test_validity = false; + test_validity = BG_IF_TEST_FAILURES; + return *this; + } + + inline ut_settings& ignore_validity_union() + { + test_validity_union = BG_IF_TEST_FAILURES; + return *this; + } + + inline ut_settings& ignore_validity_intersection() + { + test_validity_intersection = BG_IF_TEST_FAILURES; return *this; } inline ut_settings& ignore_validity_diff() { - test_validity_diff1 = false; - test_validity_diff2 = false; - test_validity_diff_sym = false; + test_validity_diff1 = BG_IF_TEST_FAILURES; + test_validity_diff2 = BG_IF_TEST_FAILURES; + test_validity_diff_sym = BG_IF_TEST_FAILURES; return *this; } inline ut_settings& ignore_validity_diff1() { - test_validity_diff1 = false; + test_validity_diff1 = BG_IF_TEST_FAILURES; return *this; } inline ut_settings& ignore_validity_diff2() { - test_validity_diff2 = false; + test_validity_diff2 = BG_IF_TEST_FAILURES; return *this; } inline ut_settings& ignore_validity_diff_sym() { - test_validity_diff_sym = false; + test_validity_diff_sym = BG_IF_TEST_FAILURES; return *this; } @@ -60,7 +91,12 @@ struct ut_settings return *this; } + bool test_reverse{true}; + bool test_difference{true}; + bool test_validity{true}; + bool test_validity_union{true}; + bool test_validity_intersection{true}; bool test_validity_diff1{true}; bool test_validity_diff2{true}; bool test_validity_diff_sym{true}; @@ -112,17 +148,23 @@ void test_detail(std::string const& name, std::string const& wkt1, std::string c BOOST_CHECK_MESSAGE(bgeo::math::abs(balance) < eps, "Case: " << name << " wrong union or intersection " << balance); - BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_d1) < eps, - "Case: " << name << " wrong difference (a-b) " << balance_d1); - BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_d2) < eps, - "Case: " << name << " wrong difference (b-a) " << balance_d2); - BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_sym) < eps, - "Case: " << name << " wrong symmetric difference " << balance_sym); - - BOOST_CHECK_MESSAGE(bgeo::math::abs(area_union - bgeo::area(result_union_rev)) < eps, - "Case: " << name << " wrong union reversed: " << area_union << " != " << bgeo::area(result_union_rev)); - BOOST_CHECK_MESSAGE(bgeo::math::abs(area_intersection - bgeo::area(result_intersection_rev)) < eps, - "Case: " << name << " wrong intersection reversed: " << area_intersection << " != " << bgeo::area(result_intersection_rev)); + if (settings.test_difference) + { + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_d1) < eps, + "Case: " << name << " wrong difference (a-b) " << balance_d1); + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_d2) < eps, + "Case: " << name << " wrong difference (b-a) " << balance_d2); + BOOST_CHECK_MESSAGE(bgeo::math::abs(balance_sym) < eps, + "Case: " << name << " wrong symmetric difference " << balance_sym); + } + + if (settings.test_reverse) + { + BOOST_CHECK_MESSAGE(bgeo::math::abs(area_union - bgeo::area(result_union_rev)) < eps, + "Case: " << name << " wrong union reversed: " << area_union << " != " << bgeo::area(result_union_rev)); + BOOST_CHECK_MESSAGE(bgeo::math::abs(area_intersection - bgeo::area(result_intersection_rev)) < eps, + "Case: " << name << " wrong intersection reversed: " << area_intersection << " != " << bgeo::area(result_intersection_rev)); + } if (settings.test_validity) { @@ -130,10 +172,16 @@ void test_detail(std::string const& name, std::string const& wkt1, std::string c "Case: " << name << " geometry1 is not valid"); BOOST_CHECK_MESSAGE(bgeo::is_valid(geometry2), "Case: " << name << " geometry2 is not valid"); - BOOST_CHECK_MESSAGE(bgeo::is_valid(result_union), - "Case: " << name << " union is not valid"); - BOOST_CHECK_MESSAGE(bgeo::is_valid(result_intersection), - "Case: " << name << " intersection is not valid"); + if (settings.test_validity_union) + { + BOOST_CHECK_MESSAGE(bgeo::is_valid(result_union), + "Case: " << name << " union is not valid"); + } + if (settings.test_validity_intersection) + { + BOOST_CHECK_MESSAGE(bgeo::is_valid(result_intersection), + "Case: " << name << " intersection is not valid"); + } if (settings.test_validity_diff1) { BOOST_CHECK_MESSAGE(bgeo::is_valid(diff1), @@ -193,9 +241,20 @@ void test_all(std::string const& name, std::string const& wkt1, std::string cons int test_main(int, char* []) { + TEST_CASE_WITH(case_141_multi, 0, 1, ut_settings().ignore_reverse()); + TEST_CASE(case_142_multi); + TEST_CASE(case_143_multi); + TEST_CASE(case_144_multi); + TEST_CASE(case_145_multi); + TEST_CASE_WITH(case_146_multi, 0, 1, ut_settings().ignore_validity_intersection()); + TEST_CASE(case_147_multi); + + TEST_CASE_WITH(issue_1221, 0, 1, ut_settings().ignore_validity_diff()); + TEST_CASE(issue_1222); TEST_CASE_WITH(issue_1226, 0, 1, ut_settings().ignore_validity_diff()); TEST_CASE(issue_1231); + TEST_CASE_WITH(issue_1241, 0, 1, ut_settings().ignore_diff()); TEST_CASE(issue_1244); TEST_CASE_WITH(issue_1288, 0, 1, ut_settings().ignore_validity_diff()); TEST_CASE_WITH(issue_1288, 0, 2, ut_settings()); @@ -212,7 +271,15 @@ int test_main(int, char* []) TEST_CASE_WITH(issue_1345_a, 1, 0, ut_settings()); TEST_CASE_WITH(issue_1345_b, 1, 0, ut_settings()); + TEST_CASE_WITH(issue_1349, 0, 1, ut_settings().ignore_diff()); + TEST_CASE(issue_1349_inverse); + TEST_CASE(issue_1350_comment); +#if defined(BOOST_GEOMETRY_TEST_FAILURES) + // Fails in union or intersection, and in difference. Also the union is invalid. + TEST_CASE_WITH(issue_1354, 0, 1, ut_settings().ignore_validity_union().ignore_diff()); +#endif + return 0; } \ No newline at end of file diff --git a/test/algorithms/set_operations/union/union_multi.cpp b/test/algorithms/set_operations/union/union_multi.cpp index fc3307d16a..8f12698474 100644 --- a/test/algorithms/set_operations/union/union_multi.cpp +++ b/test/algorithms/set_operations/union/union_multi.cpp @@ -199,7 +199,6 @@ void test_areal() TEST_UNION(case_138_multi, 2, 1, -1, 65.225); TEST_UNION(case_139_multi, 2, 1, -1, 64.953); TEST_UNION(case_140_multi, 2, 1, -1, 64.953); - TEST_UNION(case_141_multi, 1, 0, -1, 100.0); TEST_UNION(case_multi_rectangular, 1, 1, -1, 33125); TEST_UNION(case_multi_diagonal, 1, 2, -1, 5350); From 2a1da1db8917e6cf8fe9449689812d9470af1ac0 Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Fri, 6 Dec 2024 12:26:07 +0100 Subject: [PATCH 3/5] extension: add geojson writer --- .../gis/io/geojson/geojson_writer.hpp | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp diff --git a/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp b/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp new file mode 100644 index 0000000000..51414afc88 --- /dev/null +++ b/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp @@ -0,0 +1,179 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_GIS_IO_GEOJSON_WRITER_HPP +#define BOOST_GEOMETRY_EXTENSIONS_GIS_IO_GEOJSON_WRITER_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DISPATCH +namespace dispatch +{ + +template +struct geojson_feature_type +{ + BOOST_GEOMETRY_STATIC_ASSERT_FALSE("Not or not yet implemented for this Geometry type.", + GeometryTag); +}; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Point"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "LineString"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Polygon"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "LineString"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Polygon"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "MultiPolygon"; } }; + +} // namespace dispatch +#endif + +/*! +\brief Helper class to create geojson output +*/ +struct geojson_writer +{ + + private: + std::ostream& m_out; + + std::size_t feature_count = 0; + std::size_t property_count = 0; + + template + void stream_quoted(T const& entry) + { + m_out << '"' << entry << '"'; + } + + void start_feature() + { + end_properties(); + end_feature(); + + m_out << (feature_count > 0 ? ",\n" : "") << R"({"type": "Feature")"; + feature_count++; + } + + void start_property() + { + // Write a comma, either after the "geometry" tag, or after the previous property + // If there are no properties yet, start the list of properties + m_out << "," << (property_count == 0 ? R"("properties": {)" : "") << '\n'; + property_count++; + } + + void end_properties() + { + if (property_count > 0) + { + m_out << "}\n"; + property_count = 0; + } + } + void end_feature() + { + if (feature_count > 0) + { + m_out << "}\n"; + } + } + + public: + explicit geojson_writer(std::ostream& out) : m_out(out) + { + m_out << R"({"type": "FeatureCollection", "features": [)" << '\n'; + } + + ~geojson_writer() + { + end_properties(); + end_feature(); + + m_out << "]}"; + } + + // Set a property. It is expected that a feature is already started. + template + void add_property(std::string const& name, T const& value) + { + constexpr bool needs_quotes + = std::is_same::value + || std::is_same::type, char>::value; + + start_property(); + stream_quoted(name); + m_out << ": "; + if BOOST_GEOMETRY_CONSTEXPR(needs_quotes) + { + stream_quoted(value); + } + else + { + m_out << std::boolalpha << value; + } + } + + // The method "feature" should be called to start a feature. + // After that, properties can be added, until a new "feature" is called, + // or the instance is destructed + template + void feature(Geometry const& geometry) + { + using tag_t = typename tag::type; + + start_feature(); + + // Write the comma after either the "Feature" tag + m_out << ",\n"; + + // Write the geometry + m_out << R"("geometry": {"type":)"; + stream_quoted(dispatch::geojson_feature_type::apply()); + m_out << R"(, "coordinates": )"; + + // A ring is modelled as a geojson polygon, and therefore the opening and closing + // list marks should be duplicated to indicate empty interior rings. + constexpr bool dup = std::is_same::value; + const char* list_open = dup ? "[[" : "["; + const char* list_close = dup ? "]]" : "]"; + + // Indicate that dsv should close any ring automatically if its model is open + constexpr bool close = geometry::closure::value == geometry::open; + + m_out << geometry::dsv(geometry, ", ", "[", "]", ", ", list_open, + list_close, ",", close) << "}\n"; + } + +}; + +}} // namespace boost::geometry + +#endif From e7ae131802529ce260a7af897a396b83bcf20a9a Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Fri, 6 Dec 2024 12:49:16 +0100 Subject: [PATCH 4/5] test: use geojson in overlay test --- test/algorithms/overlay/overlay.cpp | 408 +++++++--------------------- 1 file changed, 93 insertions(+), 315 deletions(-) diff --git a/test/algorithms/overlay/overlay.cpp b/test/algorithms/overlay/overlay.cpp index 9a51a7cf3a..dd7b760648 100644 --- a/test/algorithms/overlay/overlay.cpp +++ b/test/algorithms/overlay/overlay.cpp @@ -1,7 +1,7 @@ // Boost.Geometry (aka GGL, Generic Geometry Library) // Unit Test -// Copyright (c) 2015 Barend Gehrels, Amsterdam, the Netherlands. +// Copyright (c) 2015-2024 Barend Gehrels, Amsterdam, the Netherlands. // This file was modified by Oracle on 2017-2024. // Modifications copyright (c) 2017-2024, Oracle and/or its affiliates. @@ -12,6 +12,9 @@ // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) +#if defined(TEST_WITH_GEOJSON) +#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER +#endif #include #include @@ -25,314 +28,89 @@ #include #include #include +#include #include #include +#include -#if defined(TEST_WITH_SVG) -# include +#if defined(TEST_WITH_GEOJSON) +#include #endif #include "multi_overlay_cases.hpp" -#if defined(TEST_WITH_SVG) -template -struct map_visitor +#if defined(TEST_WITH_GEOJSON) +struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_visitor { - map_visitor(Mapper& mapper) - : m_mapper(mapper) - , m_traverse_seq(0) - , m_do_output(true) - {} - - void print(char const* header) + geojson_visitor(boost::geometry::geojson_writer& writer) + : m_writer(writer) {} - template - void print(char const* header, Turns const& turns, int turn_index) - { - std::string style = "fill:rgb(0,0,0);font-family:Arial;font-size:6px"; - stream(turns, turns[turn_index], turns[turn_index].operations[0], header, style); - } - - template - void print(char const* header, Turns const& turns, int turn_index, int op_index) - { - std::string style = "fill:rgb(0,0,0);font-family:Arial;font-size:6px"; - stream(turns, turns[turn_index], turns[turn_index].operations[op_index], header, style); - } - template void visit_turns(int phase, Turns const& turns) { - int index = 0; - for (auto const& turn : turns) - { - switch (phase) - { - case 1 : - m_mapper.map(turn.point, "fill:rgb(255,128,0);" - "stroke:rgb(0,0,0);stroke-width:1", 3); - break; - case 11 : - m_mapper.map(turn.point, "fill:rgb(92,255,0);" // Greenish - "stroke:rgb(0,0,0);stroke-width:1", 3); - break; - case 21 : - m_mapper.map(turn.point, "fill:rgb(0,128,255);" // Blueish - "stroke:rgb(0,0,0);stroke-width:1", 3); - break; - case 3 : - label_turn(index, turn); - break; - } - index++; - } - } - - template - std::string stream_turn_index(Turns const& turns, Turn const& turn, Operation const& op) - { - std::ostringstream out; - - if (turn.cluster_id >= 0) - { - out << "cl=" << turn.cluster_id << " "; - } - - // Because turn index is unknown here, and still useful for debugging, - std::size_t index = 0; - for (typename Turns::const_iterator it = turns.begin(); - it != turns.end(); ++it, ++index) - { - Turn const& t = *it; - if (&t == &turn) - { - out << index; - break; - } - } - - if (&op == &turn.operations[0]) { out << "[0]"; } - if (&op == &turn.operations[1]) { out << "[1]"; } - return out.str(); - } - - template - void visit_clusters(Clusters const& clusters, Turns const& turns) - { - int index = 0; - for (auto const& turn : turns) - { - if (turn.cluster_id >= 0) - { - std::cout << " TURN: " << index << " part of cluster " << turn.cluster_id << std::endl; - } - index++; - } - - for (typename Clusters::const_iterator it = clusters.begin(); it != clusters.end(); ++it) - { - std::cout << " CLUSTER " << it->first << ": "; - for (typename std::set::const_iterator sit - = it->second.turn_indices.begin(); - sit != it->second.turn_indices.end(); ++sit) - { - std::cout << " " << *sit; - } - std::cout << std::endl; - } - - std::cout << std::endl; - - } - - template - void visit_traverse(Turns const& turns, Turn const& turn, Operation const& op, const std::string& header) - { - if (! m_do_output) + if (phase != 3) { return; } - std::cout << "Visit turn " << stream_turn_index(turns, turn, op) - << " " << bg::operation_char(turn.operations[0].operation) - << bg::operation_char(turn.operations[1].operation) - << " (" << bg::operation_char(op.operation) << ")" - << " " << header << std::endl; - - // Uncomment for more detailed debug info in SVG on traversal - std::string style - = header == "Visit" ? "fill:rgb(80,80,80)" : "fill:rgb(0,0,0)"; - - style += ";font-family:Arial;font-size:6px"; - - stream(turns, turn, op, header.substr(0, 1), style); - } - - template - void visit_traverse_reject(Turns const& turns, Turn const& turn, Operation const& op, - bg::detail::overlay::traverse_error_type error) - { - if (! m_do_output) + for (auto const& enumerated : boost::geometry::util::enumerate(turns)) { - return; - } - std::cout << "Reject turn " << stream_turn_index(turns, turn, op) - << bg::operation_char(turn.operations[0].operation) - << bg::operation_char(turn.operations[1].operation) - << " (" << bg::operation_char(op.operation) << ")" - << " " << bg::detail::overlay::traverse_error_string(error) << std::endl; - //return; - - std::string style = "fill:rgb(255,0,0);font-family:Arial;font-size:7px"; - stream(turns, turn, op, bg::detail::overlay::traverse_error_string(error), style); - - m_do_output = false; - } - - template - void visit_traverse_select_turn_from_cluster(Turns const& turns, Turn const& turn, Operation const& op) - { - std::cout << "Visit turn from cluster " << stream_turn_index(turns, turn, op) - << " " << bg::operation_char(turn.operations[0].operation) - << bg::operation_char(turn.operations[1].operation) - << " (" << bg::operation_char(op.operation) << ")" - << std::endl; - return; - } - - template - void stream(Turns const& turns, Turn const& turn, Operation const& op, const std::string& header, const std::string& style) - { - std::ostringstream out; - out << m_traverse_seq++ << " " << header - << " " << stream_turn_index(turns, turn, op); - - out << " " << bg::visited_char(op.visited); - - add_text(turn, out.str(), style); - } - - template - bool label_operation(Turn const& turn, int index, std::ostream& os) - { - os << bg::operation_char(turn.operations[index].operation); - bool result = false; - if (! turn.discarded) - { - if (turn.operations[index].enriched.next_ip_index != -1) + auto index = enumerated.index; + auto const& turn = enumerated.value; + auto label_enriched = [&turn](int i) { - os << "->" << turn.operations[index].enriched.next_ip_index; - if (turn.operations[index].enriched.next_ip_index != -1) - { - result = true; - } - } - else + auto const& op = turn.operations[i].enriched; + std::ostringstream out; + out //<< " l:" << op.count_left << " r:" << op.count_right + //<< " rank:" << op.rank + // << " z:" << op.zone + << " region:" << op.region_id + << (op.isolated ? " ISOLATED" : ""); + return out.str(); + }; + auto label_operation_ids = [&turn](int op_index) { - os << "->" << turn.operations[index].enriched.travels_to_ip_index; - if (turn.operations[index].enriched.travels_to_ip_index != -1) - { - result = true; - } - } - - os << " {" << turn.operations[index].enriched.region_id - << (turn.operations[index].enriched.isolated ? " ISO" : "") - << "}"; - - if (! turn.operations[index].enriched.startable) + std::ostringstream out; + out << bg::operation_char(turn.operations[op_index].operation) + << ": " << turn.operations[op_index].seg_id + << " " << turn.operations[op_index].enriched.next_ip_index + << "|" << turn.operations[op_index].enriched.travels_to_ip_index; + return out.str(); + }; + auto label_operations = [&turn]() { - os << "$"; - } - } - - return result; - } - - template - void label_turn(int index, Turn const& turn) - { - 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 << "!"; - } - if (turn.has_colocated_both) - { - out << "+"; - } - bool const self_turn = bg::detail::overlay::is_self_turn(turn); - if (self_turn) - { - out << "@"; - } - - std::string font8 = "font-family:Arial;font-size:6px"; - std::string font6 = "font-family:Arial;font-size:4px"; - std::string style = "fill:rgb(0,0,255);" + font8; - if (self_turn) - { - if (turn.discarded) - { - style = "fill:rgb(128,28,128);" + font6; - } - else + std::ostringstream out; + out << bg::operation_char(turn.operations[0].operation) + << bg::operation_char(turn.operations[1].operation); + return out.str(); + }; + auto label_travel = [&turn]() { - style = "fill:rgb(255,0,255);" + font8; - } - } - else if (turn.discarded) - { - style = "fill:rgb(92,92,92);" + font6; - } - else if (turn.cluster_id != -1) - { - style = "fill:rgb(0,0,255);" + font8; + std::ostringstream out; + out << turn.operations[0].enriched.travels_to_ip_index + << "|" << turn.operations[1].enriched.travels_to_ip_index; + return out.str(); + }; + + m_writer.feature(turn.point); + m_writer.add_property("index", index); + m_writer.add_property("method", bg::method_char(turn.method)); + m_writer.add_property("operations", label_operations()); + 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("has_colocated_both", turn.has_colocated_both); + m_writer.add_property("self_turn", bg::detail::overlay::is_self_turn(turn)); + m_writer.add_property("operation_0", label_operation_ids(0)); + m_writer.add_property("operation_1", label_operation_ids(1)); + m_writer.add_property("enriched_0", label_enriched(0)); + m_writer.add_property("enriched_1", label_enriched(1)); } - else if (! lab1 || ! lab2) - { - style = "fill:rgb(0,0,255);" + font6; - } - - add_text(turn, out.str(), style); - } - - template - void add_text(Turn const& turn, std::string const& text, std::string const& style) - { - int const margin = 5; - int const lineheight = 6; - double const half = 0.5; - double const ten = 10; - - // Map characteristics - // Create a rounded off point - std::pair p - = std::make_pair( - util::numeric_cast(half - + ten * bg::get<0>(turn.point)), - util::numeric_cast(half - + ten * bg::get<1>(turn.point)) - ); - m_mapper.text(turn.point, text, style, margin, m_offsets[p], lineheight); - m_offsets[p] += lineheight; } - - Mapper& m_mapper; - std::map, int> m_offsets; - int m_traverse_seq; - bool m_do_output; + boost::geometry::geojson_writer& m_writer; }; #endif @@ -350,35 +128,22 @@ void test_overlay(std::string const& caseid, Geometry g2; bg::read_wkt(wkt2, g2); - // Reverse if necessary bg::correct(g1); bg::correct(g2); -#if defined(TEST_WITH_SVG) - bool const ccw = bg::point_order::value == bg::counterclockwise; - bool const open = bg::closure::value == bg::open; - +#if defined(TEST_WITH_GEOJSON) std::ostringstream filename; - filename << "overlay" - << "_" << caseid - << "_" << string_from_type::type>::name() - << (ccw ? "_ccw" : "") - << (open ? "_open" : "") - << ".svg"; - - std::ofstream svg(filename.str().c_str()); - - using svg_mapper = bg::svg_mapper::type>; - - svg_mapper mapper(svg, 500, 500); - mapper.add(g1); - mapper.add(g2); - - // Input shapes in green (src=0) / blue (src=1) - mapper.map(g1, "fill-opacity:0.5;fill:rgb(153,204,0);" - "stroke:rgb(153,204,0);stroke-width:3"); - mapper.map(g2, "fill-opacity:0.3;fill:rgb(51,51,153);" - "stroke:rgb(51,51,153);stroke-width:3"); + // For QGis, it is usually convenient to always write to the same geojson file. + filename << "/tmp/" + // << caseid << "_" + << "overlay.geojson"; + std::ofstream geojson_file(filename.str().c_str()); + + boost::geometry::geojson_writer writer(geojson_file); + writer.feature(g1); + writer.add_property("type", "input1"); + writer.feature(g2); + writer.add_property("type", "input2"); #endif @@ -402,15 +167,14 @@ void test_overlay(std::string const& caseid, strategy_type strategy; -#if defined(TEST_WITH_SVG) - map_visitor visitor(mapper); +#if defined(TEST_WITH_GEOJSON) + geojson_visitor visitor(writer); #else bg::detail::overlay::overlay_null_visitor visitor; #endif Geometry result; - overlay::apply(g1, g2, std::back_inserter(result), - strategy, visitor); + overlay::apply(g1, g2, std::back_inserter(result), strategy, visitor); std::string message; bool const valid = check_validity::apply(result, caseid, g1, g2, message); @@ -428,10 +192,24 @@ void test_overlay(std::string const& caseid, << " clip count: detected: " << result.size() << " expected: " << expected_clip_count); -#if defined(TEST_WITH_SVG) - mapper.map(result, "fill-opacity:0.2;stroke-opacity:0.4;fill:rgb(255,0,0);" - "stroke:rgb(255,0,255);stroke-width:8"); +#if defined(TEST_WITH_GEOJSON) + std::size_t result_index = 0; + for (auto const& p : result) + { + writer.feature(p); + writer.add_property("type", "result"); + writer.add_property("index", result_index++); + } + for (auto const& p : result) + { + for (const auto& ring : bg::interior_rings(p)) + { + writer.feature(ring); + writer.add_property("type", "hole"); + writer.add_property("index", result_index++); + } + } #endif } From 61b002b5929e52746a81e31dbbbc0c76b050a5da Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Sat, 21 Dec 2024 17:56:07 +0100 Subject: [PATCH 5/5] feat: add biconnected_components instead of isolated --- CMakeLists.txt | 7 +- .../overlay/detect_biconnected_components.hpp | 227 +++++++++++ .../detail/overlay/enrichment_info.hpp | 4 +- .../algorithms/detail/overlay/graph_util.hpp | 61 +++ .../algorithms/detail/overlay/node_util.hpp | 146 +++++++ .../algorithms/detail/overlay/traversal.hpp | 14 +- .../overlay/traversal_switch_detector.hpp | 385 +----------------- .../algorithms/detail/overlay/traverse.hpp | 11 +- .../overlay/multi_overlay_cases.hpp | 32 +- test/algorithms/overlay/overlay.cpp | 43 +- .../intersection/intersection_multi.cpp | 2 +- .../set_operations/set_ops_areal_areal.cpp | 16 +- 12 files changed, 548 insertions(+), 400 deletions(-) create mode 100644 include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp create mode 100644 include/boost/geometry/algorithms/detail/overlay/graph_util.hpp create mode 100644 include/boost/geometry/algorithms/detail/overlay/node_util.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 750938c0a5..46009fe144 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ target_link_libraries(boost_geometry Boost::type_traits Boost::utility Boost::variant + + Boost::graph ) # Required for Boost.Geometry Index @@ -112,7 +114,10 @@ if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") serialization tokenizer variant - test) + test + + graph + ) if (BOOST_SRC_DIR_IS_VALID) set(BOOST_EXCLUDE_LIBRARIES ${PROJECT_NAME}) diff --git a/include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp b/include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp new file mode 100644 index 0000000000..33d44f58f2 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp @@ -0,0 +1,227 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_ALGORITHMS_DETAIL_OVERLAY_DETECT_ARTICULATION_POINTS_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DETECT_ARTICULATION_POINTS_HPP + +#include +#include + +#include +#include +#include + +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ + + +struct vertex_info +{ + signed_size_type node_id{0}; + std::set target_vertex_indices; + + bool is_extra{false}; + + // For extra nodes, also store the original node + signed_size_type original_node_id{0}; +}; + +struct state_type +{ + // Maps from vertex to vertex info + std::map vertex_map; + + // Reverse mapping. Every node (turn or cluster) has only ONE vertex, + // but there might be additional vertices, not associated with a node. + std::map node_to_vertex_index; + + // Keeps track of vertex index, which must, for Boost.Graph, + // be consecutive. The turn index is not (because of discarded and clusters). + std::size_t vertex_index{0}; + + // For some cases (returning to itself) extra nodes are inserted. + // They are numbered from turn.size() and up. + std::size_t extra_node_id{0}; +}; + +void add_target_node(signed_size_type source_node_id, signed_size_type target_node_id, + state_type& state, bool allow_returning) +{ + // Insert the source and target node (turn or cluster) + auto it_source = state.node_to_vertex_index.find(source_node_id); + if (it_source == state.node_to_vertex_index.end()) + { + it_source = state.node_to_vertex_index.insert({source_node_id, state.vertex_index++}).first; + } + + auto it_target = state.node_to_vertex_index.find(target_node_id); + if (it_target == state.node_to_vertex_index.end()) + { + it_target = state.node_to_vertex_index.insert({target_node_id, state.vertex_index++}).first; + } + + // Get the accompanying vertex into (might be a new record) + auto& vertex_info = state.vertex_map[it_source->second]; + vertex_info.node_id = source_node_id; + + if (target_node_id == source_node_id && allow_returning) + { + std::size_t const extra_node_id = state.extra_node_id++; + + // Add a brand new vertex, and add the target to it + // To keep the index right, first this brand new vertex as the target + vertex_info.target_vertex_indices.insert(state.vertex_index); + state.node_to_vertex_index.insert({extra_node_id, state.vertex_index}); + auto& extra_vertex_info = state.vertex_map[state.vertex_index++]; + extra_vertex_info.node_id = extra_node_id; + extra_vertex_info.is_extra = true; + extra_vertex_info.original_node_id = source_node_id; + extra_vertex_info.target_vertex_indices.insert(it_target->second); + } + else + { + vertex_info.target_vertex_indices.insert(it_target->second); + } +} + + +template +void fill_vertex_map(Turns const& turns, Clusters const& clusters, state_type& state) +{ + std::set visited_turns; + for (std::size_t i = 0; i < turns.size(); i++) + { + auto const & turn = turns[i]; + if (turn.discarded) + { + continue; + } + if (visited_turns.count(i) > 0) + { + continue; + } + + auto const source_node_id = get_node_id(turns, i); + auto const turn_indices = get_turn_indices_by_node_id(turns, clusters, source_node_id); + + visited_turns.insert(turn_indices.begin(), turn_indices.end()); + + auto const target_nodes = get_target_nodes(turns, clusters, turn_indices); + + for (auto const& target_node_id : target_nodes) + { + add_target_node(source_node_id, target_node_id, state, true); + } + } +} + +// Assigns biconnected components to turns +template +void assign_biconnected_component_ids(Turns& turns, Clusters const& clusters, + Graph const& graph, Components const& component, state_type const& state) +{ + auto node_id_from_it = [&state](auto const& it) + { + return it->second.is_extra + ? it->second.original_node_id + : it->second.node_id; + }; + + typename graph_traits::edge_iterator ei, ei_end; + for (boost::tie(ei, ei_end) = edges(graph); ei != ei_end; ++ei) + { + auto it_source = state.vertex_map.find(source(*ei, graph)); + auto it_target = state.vertex_map.find(target(*ei, graph)); + if (it_source == state.vertex_map.end() || it_target == state.vertex_map.end()) + { + continue; + } + + auto const source_node_id = node_id_from_it(it_source); + auto const target_node_id = node_id_from_it(it_target); + + auto const source_turn_indices = get_turn_indices_by_node_id(turns, clusters, source_node_id); + + // Assign the component to all the operations + // going from the source node to the target node. + for (auto const& turn_index : source_turn_indices) + { + auto& source_turn = turns[turn_index]; + for (std::size_t j = 0; j < 2; j++) + { + auto& op = source_turn.operations[j]; + if (op.enriched.travels_to_ip_index < 0) + { + continue; + } + auto const travels_to_node_id = get_node_id(turns, op.enriched.travels_to_ip_index); + if (travels_to_node_id == target_node_id) + { + op.enriched.component_id = static_cast(component[*ei]); + } + } + } + } +} + +template +void detect_biconnected_components(Turns& turns, Clusters const& clusters) +{ + using graph_t = boost::adjacency_list + < + boost::vecS, + boost::vecS, + boost::undirectedS, + boost::no_property, + boost::property + >; + using vertex_t = boost::graph_traits ::vertex_descriptor; + + // Mapping to add turns to vertices, count them, and then build the graph. + // (It is convenient if the vertex index is the same as the turn index. + // Therefore the default mapping is made like that, extra vertices + // are added later) + + state_type state; + state.extra_node_id = static_cast(turns.size()); + + fill_vertex_map(turns, clusters, state); + + // Build the graph from the vertices + graph_t graph(state.vertex_map.size()); + for (auto const& key_value : state.vertex_map) + { + auto const vertex_index = key_value.first; + for (auto const& target_vertex_index : key_value.second.target_vertex_indices) + { + add_edge(vertex_index, target_vertex_index, graph); + } + } + + edge_component ec; + auto component = get(ec, graph); + biconnected_components(graph, component); + fix_components(component, graph); + + assign_biconnected_component_ids(turns, clusters, graph, component, state); +} + + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DETECT_ARTICULATION_POINTS_HPP diff --git a/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp b/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp index e01c13f749..f40804d27b 100644 --- a/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp @@ -41,7 +41,6 @@ struct enrichment_info , rank(-1) , zone(-1) , region_id(-1) - , isolated(false) {} inline signed_size_type get_next_turn_index() const @@ -69,7 +68,8 @@ struct enrichment_info signed_size_type rank; // in cluster signed_size_type zone; // open zone, in cluster signed_size_type region_id; - bool isolated; + + signed_size_type component_id{-1}; }; diff --git a/include/boost/geometry/algorithms/detail/overlay/graph_util.hpp b/include/boost/geometry/algorithms/detail/overlay/graph_util.hpp new file mode 100644 index 0000000000..924de19d43 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/graph_util.hpp @@ -0,0 +1,61 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_ALGORITHMS_DETAIL_OVERLAY_GRAPH_UTIL_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GRAPH_UTIL_HPP + +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ + +struct edge_component +{ + using kind = edge_property_tag; +}; + +// It appears that in an undirected graph, the components for two edges are sometimes different. +// Fix that. To be found out why this is. +template +void fix_components(Components& components, Graph const& g) +{ + typename graph_traits::edge_iterator ei, ei_end; + for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) + { + auto& component = components[*ei]; + + auto const source_vertex = source(*ei, g); + auto const target_vertex = target(*ei, g); + + // Get the reverse edge and its component + auto const reverse_edge_pair = edge(target_vertex, source_vertex, g); + if (! reverse_edge_pair.second) + { + continue; + } + + auto& reverse_component = components[reverse_edge_pair.first]; + + if (component != reverse_component) + { + component = reverse_component; + } + } +} + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GRAPH_UTIL_HPP diff --git a/include/boost/geometry/algorithms/detail/overlay/node_util.hpp b/include/boost/geometry/algorithms/detail/overlay/node_util.hpp new file mode 100644 index 0000000000..391382da62 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/node_util.hpp @@ -0,0 +1,146 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_ALGORITHMS_DETAIL_OVERLAY_NODE_UTIL_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_NODE_UTIL_HPP + +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ +template +std::set get_turn_indices_by_cluster_id(Turns const& turns, Clusters const& clusters, signed_size_type cluster_id) +{ + std::set result; + auto it = clusters.find(cluster_id); + if (it != clusters.end()) + { + result.insert(it->second.turn_indices.begin(), it->second.turn_indices.end()); + } + return result; +} + +// Returns the node id of the turn: +// - if it is clustered, the negative cluster_id +// - if it is not clustered, the turn index +template +signed_size_type get_node_id(Turns const& turns, std::size_t turn_index) +{ + auto const& turn = turns[turn_index]; + return turn.is_clustered() ? -turn.cluster_id : turn_index; +} + +template +std::set get_turn_indices_by_node_id(Turns const& turns, Clusters const& clusters, signed_size_type node_id) +{ + if (node_id < 0) + { + return get_turn_indices_by_cluster_id(turns, clusters, -node_id); + } + if (node_id >= turns.size()) + { + // It is 'allowed' to have node_ids larger than the largest turn index (for example extra + // nodes in a graph). But they are not related to turns. + return {}; + } + + auto const turn_index = static_cast(node_id); + auto const& turn = turns[turn_index]; + if (turn.is_clustered()) + { + return get_turn_indices_by_cluster_id(turns, clusters, turn.cluster_id); + } + return {turn_index}; +} + +// TODO: later union too +template +bool is_target_operation(Operation const& op, std::size_t turn_count, int op_index) +{ + return + op.enriched.travels_to_ip_index >= 0 + && op.enriched.travels_to_ip_index < static_cast(turn_count) + && (op.operation == TargetOperation || (op_index == 0 && op.operation == operation_continue)); +} + +template +auto get_blocked_nodes(Turns const& turns, Clusters const& clusters, + std::set const& turn_indices) +{ + std::set blocked_nodes; + for (std::size_t turn_index : turn_indices) + { + auto const& turn = turns[turn_index]; + + if (turn.discarded) + { + continue; + } + if (! turn.combination(TargetOperation, operation_blocked)) + { + continue; + } + + for (std::size_t j = 0; j < 2; j++) + { + auto const& op = turn.operations[j]; + if (op.operation == operation_blocked) + { + blocked_nodes.insert(get_node_id(turns, op.enriched.travels_to_ip_index)); + } + } + } + return blocked_nodes; +} + +template +auto get_target_nodes(Turns const& turns, Clusters const& clusters, + std::set const& turn_indices) +{ + auto const blocked_nodes = get_blocked_nodes(turns, clusters, turn_indices); + + std::set target_nodes; + for (std::size_t turn_index : turn_indices) + { + auto const& turn = turns[turn_index]; + if (turn.discarded) + { + continue; + } + + bool const is_clustered_self = turn.is_self() && turn.is_clustered(); + for (std::size_t j = 0; j < 2; j++) + { + auto const& op = turn.operations[j]; + + if (is_target_operation(op, turns.size(), j)) + { + auto const target_node_id = get_node_id(turns, op.enriched.travels_to_ip_index); + if (is_clustered_self && blocked_nodes.count(target_node_id) > 0) + { + continue; + } + target_nodes.insert(target_node_id); + } + } + } + + return target_nodes; +} + + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_NODE_UTIL_HPP diff --git a/include/boost/geometry/algorithms/detail/overlay/traversal.hpp b/include/boost/geometry/algorithms/detail/overlay/traversal.hpp index 653f1930fe..f980fac8e3 100644 --- a/include/boost/geometry/algorithms/detail/overlay/traversal.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/traversal.hpp @@ -609,8 +609,14 @@ public : inline sort_by_side::rank_type select_rank(sbs_type const& sbs) const { - static bool const is_intersection - = target_operation == operation_intersection; + auto is_isolated = [](turn_operation_type const& op1, turn_operation_type const& op2) -> bool + { + return //(op1.seg_id.ring_index >=0 || op2.seg_id.ring_index >= 0) && + op1.enriched.component_id >= 0 && op2.enriched.component_id >= 0 + && op1.enriched.component_id != op2.enriched.component_id; + }; + + static bool const is_intersection = target_operation == operation_intersection; // Take the first outgoing rank corresponding to incoming region, // or take another region if it is not isolated @@ -631,8 +637,8 @@ public : continue; } - if (in_op.enriched.region_id == out_op.enriched.region_id - || (is_intersection && ! out_op.enriched.isolated)) + if ((in_op.enriched.region_id == out_op.enriched.region_id) + || (is_intersection && ! is_isolated(in_op, out_op))) { // Region corresponds to incoming region, or (for intersection) // there is a non-isolated other region which should be taken diff --git a/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp b/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp index 0756034afa..9f70a8f9c2 100644 --- a/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp @@ -74,12 +74,9 @@ namespace detail { namespace overlay // Switching using region_id is only relevant for UU or II turns. // In all T turns it will follow "u" for union or "i" for intersection, // and in C turns it will follow either direction (they are the same). -// There is also "isolated", making it more complex, and documented below. template < - bool Reverse1, - bool Reverse2, - overlay_type OverlayType, + operation_type TargetOperation, typename Geometry1, typename Geometry2, typename Turns, @@ -88,16 +85,6 @@ template > struct traversal_switch_detector { - static const operation_type target_operation - = operation_from_overlay::value; - - enum isolation_type - { - isolation_no = 0, - isolation_yes = 1, - isolation_multiple = 2 - }; - using turn_type = typename boost::range_value::type; using set_type= std::set; @@ -109,24 +96,11 @@ struct traversal_switch_detector set_type turn_indices; }; - struct connection_properties - { - std::size_t count = 0; - // Set with turn-index OR (if clustered) the negative cluster_id - set_type unique_turn_ids; - }; - - // Maps region_id -> properties - using connection_map = std::map; - // Per region, a set of properties is maintained, including its connections // to other regions struct region_properties { signed_size_type region_id = -1; - isolation_type isolated = isolation_no; - set_type unique_turn_ids; - connection_map connected_region_counts; }; // Maps ring -> properties @@ -147,292 +121,6 @@ struct traversal_switch_detector { } - bool one_connection_to_another_region(region_properties const& region) const - { - // For example: - // +----------------------+ - // | __ | - // | / \| - // | | x - // | \__/| - // | | - // +----------------------+ - - if (region.connected_region_counts.size() == 1) - { - auto const& cprop = region.connected_region_counts.begin()->second; - return cprop.count <= 1; - } - return region.connected_region_counts.empty(); - } - - // TODO: might be combined with previous - bool multiple_connections_to_one_region(region_properties const& region) const - { - // For example: - // +----------------------+ - // | __ | - // | / \| - // | | x - // | \ /| - // | / \| - // | | x - // | \__/| - // | | - // +----------------------+ - - if (region.connected_region_counts.size() == 1) - { - auto const& cprop = region.connected_region_counts.begin()->second; - return cprop.count > 1; - } - return false; - } - - bool one_connection_to_multiple_regions(region_properties const& region) const - { - // For example: - // +----------------------+ - // | __ | __ - // | / \|/ | - // | | x | - // | \__/|\__| - // | | - // +----------------------+ - - bool first = true; - signed_size_type first_turn_id = 0; - for (auto const& key_val : region.connected_region_counts) - { - auto const& cprop = key_val.second; - - if (cprop.count != 1) - { - return false; - } - auto const unique_turn_id = *cprop.unique_turn_ids.begin(); - if (first) - { - first_turn_id = unique_turn_id; - first = false; - } - else if (first_turn_id != unique_turn_id) - { - return false; - } - } - return true; - } - - bool ii_turn_connects_two_regions(region_properties const& region, - region_properties const& connected_region, - signed_size_type turn_index) const - { - turn_type const& turn = m_turns[turn_index]; - if (! turn.both(operation_intersection)) - { - return false; - } - - signed_size_type const id0 = turn.operations[0].enriched.region_id; - signed_size_type const id1 = turn.operations[1].enriched.region_id; - - return (id0 == region.region_id && id1 == connected_region.region_id) - || (id1 == region.region_id && id0 == connected_region.region_id); - } - - - bool isolated_multiple_connection(region_properties const& region, - region_properties const& connected_region) const - { - if (connected_region.isolated != isolation_multiple) - { - return false; - } - - // First step: compare turns of regions with turns of connected region - set_type turn_ids = region.unique_turn_ids; - for (auto turn_id : connected_region.unique_turn_ids) - { - turn_ids.erase(turn_id); - } - - // There should be one connection (turn or cluster) left - if (turn_ids.size() != 1) - { - return false; - } - - for (auto id_or_index : connected_region.unique_turn_ids) - { - if (id_or_index >= 0) - { - if (! ii_turn_connects_two_regions(region, connected_region, id_or_index)) - { - return false; - } - } - else - { - signed_size_type const cluster_id = -id_or_index; - auto it = m_clusters.find(cluster_id); - if (it != m_clusters.end()) - { - cluster_info const& cinfo = it->second; - for (auto turn_index : cinfo.turn_indices) - { - if (! ii_turn_connects_two_regions(region, connected_region, turn_index)) - { - return false; - } - } - } - } - } - - return true; - } - - bool has_only_isolated_children(region_properties const& region) const - { - bool first_with_turn = true; - signed_size_type first_turn_id = 0; - - for (auto const& key_val : region.connected_region_counts) - { - signed_size_type const region_id = key_val.first; - connection_properties const& cprop = key_val.second; - - auto mit = m_connected_regions.find(region_id); - if (mit == m_connected_regions.end()) - { - // Should not occur - return false; - } - - region_properties const& connected_region = mit->second; - - if (cprop.count != 1) - { - // If there are more connections, check their isolation - if (! isolated_multiple_connection(region, connected_region)) - { - return false; - } - } - - if (connected_region.isolated != isolation_yes - && connected_region.isolated != isolation_multiple) - { - signed_size_type const unique_turn_id = *cprop.unique_turn_ids.begin(); - if (first_with_turn) - { - first_turn_id = unique_turn_id; - first_with_turn = false; - } - else if (first_turn_id != unique_turn_id) - { - return false; - } - } - } - - // If there is only one connection (with a 'parent'), and all other - // connections are itself isolated, it is isolated - return true; - } - - void get_isolated_regions() - { - // First time: check regions isolated (one connection only), - // semi-isolated (multiple connections between same region), - // and complex isolated (connection with multiple rings but all - // at same point) - for (auto& key_val : m_connected_regions) - { - region_properties& properties = key_val.second; - if (one_connection_to_another_region(properties)) - { - properties.isolated = isolation_yes; - } - else if (multiple_connections_to_one_region(properties)) - { - properties.isolated = isolation_multiple; - } - else if (one_connection_to_multiple_regions(properties)) - { - properties.isolated = isolation_yes; - } - } - - // Propagate isolation to next level - // TODO: should be optimized - std::size_t defensive_check = 0; - bool changed = true; - while (changed && defensive_check++ < m_connected_regions.size()) - { - changed = false; - for (auto& key_val : m_connected_regions) - { - region_properties& properties = key_val.second; - - if (properties.isolated == isolation_no - && has_only_isolated_children(properties)) - { - properties.isolated = isolation_yes; - changed = true; - } - } - } - } - - void assign_isolation_to_enriched() - { - for (turn_type& turn : m_turns) - { - constexpr auto order1 = geometry::point_order::value; - constexpr bool reverse1 = (order1 == boost::geometry::counterclockwise) - ? ! Reverse1 : Reverse1; - - constexpr auto order2 = geometry::point_order::value; - constexpr bool reverse2 = (order2 == boost::geometry::counterclockwise) - ? ! Reverse2 : Reverse2; - - // For difference, for the input walked through in reverse, - // the meaning is reversed: what is isolated is actually not, - // and vice versa. - bool const reverseMeaningInTurn - = (reverse1 || reverse2) - && ! turn.is_self() - && ! turn.is_clustered() - && uu_or_ii(turn) - && turn.operations[0].enriched.region_id - != turn.operations[1].enriched.region_id; - - for (auto& op : turn.operations) - { - auto mit = m_connected_regions.find(op.enriched.region_id); - if (mit != m_connected_regions.end()) - { - bool const reverseMeaningInOp - = reverseMeaningInTurn - && ((op.seg_id.source_index == 0 && reverse1) - || (op.seg_id.source_index == 1 && reverse2)); - - // It is assigned to isolated if it's property is "Yes", - // (one connected interior, or chained). - // "Multiple" doesn't count for isolation, - // neither for intersection, neither for difference. - region_properties const& prop = mit->second; - op.enriched.isolated - = reverseMeaningInOp - ? false - : prop.isolated == isolation_yes; - } - } - } - } - void assign_region_ids_to_enriched() { for (auto const& key_val : m_turns_per_ring) @@ -477,31 +165,10 @@ struct traversal_switch_detector if (id0 != -1) { m_connected_regions[id0].region_id = id0; - m_connected_regions[id0].unique_turn_ids.insert(unique_turn_id); } if (id1 != -1 && id0 != id1) { m_connected_regions[id1].region_id = id1; - m_connected_regions[id1].unique_turn_ids.insert(unique_turn_id); - } - - if (id0 != id1 && id0 != -1 && id1 != -1) - { - // Assign connections - connection_properties& prop0 = m_connected_regions[id0].connected_region_counts[id1]; - connection_properties& prop1 = m_connected_regions[id1].connected_region_counts[id0]; - - // Reference this turn or cluster to later check uniqueness on ring - if (prop0.unique_turn_ids.count(unique_turn_id) == 0) - { - prop0.count++; - prop0.unique_turn_ids.insert(unique_turn_id); - } - if (prop1.unique_turn_ids.count(unique_turn_id) == 0) - { - prop1.count++; - prop1.unique_turn_ids.insert(unique_turn_id); - } } } } @@ -532,7 +199,7 @@ struct traversal_switch_detector return ! uu_or_ii(turn); } - if BOOST_GEOMETRY_CONSTEXPR (target_operation == operation_union) + if BOOST_GEOMETRY_CONSTEXPR (TargetOperation == operation_union) { // It is a cluster, check zones // (assigned by sort_by_side/handle colocations) of both operations @@ -606,41 +273,11 @@ struct traversal_switch_detector #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSAL_SWITCH_DETECTOR) void debug_show_results() - { - auto isolation_to_string = [](isolation_type const& iso) -> std::string - { - switch(iso) - { - case isolation_no : return "no"; - case isolation_yes : return "yes"; - case isolation_multiple : return "multiple"; - } - return "error"; - }; - auto set_to_string = [](auto const& s) -> std::string - { - std::ostringstream result; - for (auto item : s) { result << " " << item; } - return result.str(); - }; - + { for (auto const& kv : m_connected_regions) { auto const& prop = kv.second; - - std::ostringstream sub; - sub << "[turns" << set_to_string(prop.unique_turn_ids) - << "] regions"; - for (auto const& kvs : prop.connected_region_counts) - { - sub << " { " << kvs.first - << " : via [" << set_to_string(kvs.second.unique_turn_ids) - << " ] }"; - } - std::cout << "REGION " << prop.region_id - << " " << isolation_to_string(prop.isolated) - << " " << sub.str() << std::endl; } @@ -654,13 +291,10 @@ struct traversal_switch_detector << " " << turn_index << " (" << geometry::get<0>(turn.point) << ", " << geometry::get<1>(turn.point) << ")" - << " -> " << std::boolalpha - << " [" << turn.operations[0].seg_id.source_index + << " -> [" << turn.operations[0].seg_id.source_index << "/" << turn.operations[1].seg_id.source_index << "] " << "(" << turn.operations[0].enriched.region_id - << " " << turn.operations[0].enriched.isolated - << ") / (" << turn.operations[1].enriched.region_id - << " " << turn.operations[1].enriched.isolated << ")" + << ") / (" << turn.operations[1].enriched.region_id << ")" << std::endl; } } @@ -677,10 +311,7 @@ struct traversal_switch_detector void iterate() { #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSAL_SWITCH_DETECTOR) - std::cout << "BEGIN SWITCH DETECTOR (region_ids and isolation)" - << (Reverse1 ? " REVERSE_1" : "") - << (Reverse2 ? " REVERSE_2" : "") - << std::endl; + std::cout << "BEGIN SWITCH DETECTOR (region_ids and isolation)" << std::endl; #endif // Collect turns per ring @@ -691,7 +322,7 @@ struct traversal_switch_detector { turn_type const& turn = m_turns[turn_index]; - if BOOST_GEOMETRY_CONSTEXPR (target_operation == operation_intersection) + if BOOST_GEOMETRY_CONSTEXPR (TargetOperation == operation_intersection) { if (turn.discarded) { @@ -716,8 +347,6 @@ struct traversal_switch_detector assign_region_ids_to_enriched(); assign_connected_regions(); - get_isolated_regions(); - assign_isolation_to_enriched(); } #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSAL_SWITCH_DETECTOR) diff --git a/include/boost/geometry/algorithms/detail/overlay/traverse.hpp b/include/boost/geometry/algorithms/detail/overlay/traverse.hpp index 6c0b4488e8..79589b3f6e 100644 --- a/include/boost/geometry/algorithms/detail/overlay/traverse.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/traverse.hpp @@ -18,8 +18,10 @@ #include #include +#include #include #include +#include namespace boost { namespace geometry @@ -76,9 +78,16 @@ public : Clusters& clusters, Visitor& visitor) { + constexpr operation_type target_operation = operation_from_overlay::value; + + if BOOST_GEOMETRY_CONSTEXPR (target_operation == operation_intersection) + { + detect_biconnected_components(turns, clusters); + } + traversal_switch_detector < - Reverse1, Reverse2, OverlayType, + target_operation, Geometry1, Geometry2, Turns, Clusters, Visitor diff --git a/test/algorithms/overlay/multi_overlay_cases.hpp b/test/algorithms/overlay/multi_overlay_cases.hpp index 1f2cd53343..32783a6e16 100644 --- a/test/algorithms/overlay/multi_overlay_cases.hpp +++ b/test/algorithms/overlay/multi_overlay_cases.hpp @@ -1463,6 +1463,32 @@ static std::string case_recursive_boxes_88[2] = "MULTIPOLYGON(((4 0,4 1,5 1,5 0,4 0)),((3 4,3 3,1 3,1 5,3 5,2.5 4.5,3 4)),((3 4,3 5,4 5,4 4,3 4)),((1 1,2 0,1 0,1 1)),((1 1,1 2,2 2,2 1,1 1)),((1 2,0 2,0 3,1 3,1 2)),((4 4,5 4,5 2,4 2,4 4)))" }; +static std::string case_recursive_boxes_89[2] = +{ + "MULTIPOLYGON(((1 3,2 3,2 2,1 2,1 3)),((1 1,2 2,2 1,1 1)))", + "MULTIPOLYGON(((1 0,1 3,3 3,3 2,2 2,3 1,2 1,1 0)))" +}; + +static std::string case_recursive_boxes_90[2] = +{ + "MULTIPOLYGON(((5 3,5 4,6 4,5 3)),((7 1,6 1,6 2,7 1)),((5 1,5 2,6 2,5 1)))", + "MULTIPOLYGON(((7 1,4 1,5 2,5 3,6 3,6 2,7 2,7 1)),((4 3,5 2,4 2,4 3)),((7 5,6 4,6 5,7 5)))" +}; + +static std::string case_bitset_1[2] = +{ + "MULTIPOLYGON(((2 10,2 8,0 8,0 10,2 10)),((10 8,10 2,8 2,8 0,0 0,0 4,2 4,2 8,4 8,4 10,6 10,6 8,6 6,8 6,8 8,10 8),(8 2,8 4,4 4,4 2,8 2)))", + "MULTIPOLYGON(((2 6,2 4,2 2,2 0,0 0,0 6,2 6)),((2 10,2 8,0 8,0 10,2 10)),((6 8,6 6,2 6,2 8,6 8)),((8 4,8 2,6 2,6 4,8 4)),((10 8,10 6,8 6,8 8,10 8)),((8 10,8 8,6 8,6 10,8 10)))" +}; + +static std::string case_bitset_2[3] = +{ + // (The last one is an invalid version of the first one) + "MULTIPOLYGON(((12 2,12 0,6 0,6 2,8 2,8 4,10 4,10 8,12 8,12 6,14 6,14 2,12 2)),((4 2,4 0,0 0,0 2,2 2,2 4,0 4,0 10,2 10,2 14,6 14,6 12,8 12,8 14,10 14,10 12,12 12,12 10,8 10,8 8,8 6,4 6,4 4,6 4,6 2,4 2),(4 8,2 8,2 6,4 6,4 8),(6 8,6 10,4 10,4 8,6 8)),((14 10,14 8,12 8,12 10,14 10)),((12 12,12 14,14 14,14 12,12 12)))", + "MULTIPOLYGON(((6 2,6 0,0 0,0 2,2 2,2 4,4 4,4 2,6 2)),((10 2,10 0,8 0,8 2,10 2)),((14 4,14 2,12 2,12 4,14 4)))", + "MULTIPOLYGON(((14 10,14 8,12 8,12 6,14 6,14 2,12 2,12 0,6 0,6 2,4 2,4 0,0 0,0 2,2 2,2 4,0 4,0 10,2 10,2 14,6 14,6 12,8 12,8 14,10 14,10 12,12 12,12 10,14 10),(12 10,8 10,8 8,8 6,4 6,4 4,6 4,6 2,8 2,8 4,10 4,10 8,12 8,12 10),(4 6,4 8,2 8,2 6,4 6),(4 8,6 8,6 10,4 10,4 8)),((14 12,12 12,12 14,14 14,14 12)))" +}; + static std::string pie_21_7_21_0_3[2] = { "MULTIPOLYGON(((2500 2500,2500 3875,2855 3828,3187 3690,3472 3472,3690 3187,3828 2855,3875 2500,3828 2144,3690 1812,3472 1527,3187 1309,2855 1171,2499 1125,2144 1171,1812 1309,1527 1527,1309 1812,1171 2144,1125 2499,1171 2855,1309 3187,2500 2500)))", @@ -1781,12 +1807,6 @@ static std::string issue_1349_inverse[2] = "MULTIPOLYGON(((-1000 -1000,-1000 3000,3000 3000,3000 -1000,-1000 -1000),(1592.0101999999999407 1149.8399999999999181, 1586.9901999999999589 1149.8399999999999181, 1586.9901999999999589 1178.0000000000000000, 1592.0101999999999407 1178.0000000000000000, 1592.0101999999999407 1149.8399999999999181),(2001.9988000000000739 230.0014999999999930, 2001.9988000000000739 0.0015000000000000, 1000.9238000000000284 0.0000000000000000, 1000.9238000000000284 230.0765000000000100, 2001.9988000000000739 230.0014999999999930)))" }; -static std::string issue_1350_comment[2] = -{ - "MULTIPOLYGON(((2 10,2 8,0 8,0 10,2 10)),((10 8,10 2,8 2,8 0,0 0,0 4,2 4,2 8,4 8,4 10,6 10,6 8,6 6,8 6,8 8,10 8),(8 2,8 4,4 4,4 2,8 2)))", - "MULTIPOLYGON(((2 6,2 4,2 2,2 0,0 0,0 6,2 6)),((2 10,2 8,0 8,0 10,2 10)),((6 8,6 6,2 6,2 8,6 8)),((8 4,8 2,6 2,6 4,8 4)),((10 8,10 6,8 6,8 8,10 8)),((8 10,8 8,6 8,6 10,8 10)))" -}; - static std::string issue_1354[2] = { "MULTIPOLYGON(((295.199999999999989 -295.199999999999989,295.199999999999989 295.199999999999989,-295.199999999999989 295.199999999999989,-295.199999999999989 -295.199999999999989,295.199999999999989 -295.199999999999989),(-179.997681584096682 -159.068022953381728,-179.997941555125578 -158.950077048237887,-180 -158.832148820362704,-180 -14.5241121286848625,-179.998787156680379 -14.4546283814396119,-179.999195199119924 -14.3851352478099308,-179.995132365653177 -14.2452455437218966,-179.992689924229154 -14.1053181682548523,-179.989052871933012 -14.0359190762519734,-179.987035390416196 -13.9664540354863789,-179.978092951651348 -13.8267913392705939,-179.970768603117904 -13.6870344437553584,-179.96471177303323 -13.6178045590987882,-179.960271225550514 -13.5484522436487858,-179.899302518930739 -12.8010583222347041,-179.862001646775752 -12.4192286301278614,-179.812514069087342 -12.0387864551623842,-179.756717919995367 -11.6575893141408002,-179.69354553378804 -11.2702990769800557,-179.617743949785876 -10.8852814175903685,-179.449050736785438 -10.0966985448598905,-179.370713374286112 -9.75497969251792263,-179.282427103876586 -9.41569507620525137,-179.18426728065765 -9.07913428598321026,-178.935424198586816 -8.27008653807789607,-178.812412221610401 -7.89249729706248626,-178.67697357776251 -7.51918515533267851,-178.52206744438692 -7.11329246102736157,-178.372718942212316 -6.74078412878685995,-178.210997411857903 -6.37347828985937515,-177.83796586524096 -5.56325522558086671,-177.683643331306087 -5.24079944287654875,-177.519784336166936 -4.92308352260890025,-177.346534296916872 -4.61038942260158002,-176.888642811189385 -3.8122348268109878,-176.682925174829364 -3.46716074752500347,-176.4657719066781 -3.12916618508967126,-176.203261097574938 -2.73523344331954199,-175.97364845346371 -2.40292746742542196,-175.732982121071331 -2.078537355980135,-175.143129804051284 -1.310831533374758,-174.920901565835123 -1.03035395323309942,-174.690409107515819 -0.756627283942919782,-174.451857396213086 -0.489894938965182547,-173.780108917183412 0.239037812837519681,-173.533488205359163 0.498765442860035435,-173.279226152669509 0.751017352386726955,-173.017549264610096 0.995568826627545533,-172.318055071282714 1.62999550300293983,-172.067822666567167 1.85068903895734671,-171.811551390238662 2.06434045156852441,-171.549439357600335 2.27078457361611186,-171.281689199261223 2.46986180961050472,-171.031950157793972 2.65021087534550936,-170.768065071279523 2.835433324854856,-170.499299340935181 3.01349982052276921,-170.225846968387685 3.18428182963124229,-169.947905338195852 3.34765607758145833,-169.680262243237394 3.50017672557883053,-169.398031980415396 3.65602528487362832,-169.111716805616084 3.80423566024079918,-168.821523388081118 3.94470086983453072,-168.527661196461878 4.07731952245764262,-168.245202320314831 4.20025185193289374,-167.947883471472693 4.32492822022349799,-167.64732257736847 4.44157230975317141,-167.343736590149689 4.55009992398729324,-167.037344645549581 4.6504327250600852,-166.743329216418374 4.74235824805159822,-166.434352475578294 4.83442381331231275,-166.123013965408205 4.91815568760162414,-165.809538417600521 4.99349343122062095,-165.494152106413281 5.06038266355761301,-165.191972700917376 5.12024067253396975,-164.874903279844006 5.17863311131773951,-164.556379617287462 5.2284866074688674,-164.23663163135754 5.26976517552396029,-163.915890123910316 5.3024390196108131,-163.609033520920434 5.32953882673178825,-163.287530010959983 5.35358436207679489,-162.965496567522251 5.36898423202898734,-162.643165642162018 5.375727320588501,-162.320769901163743 5.37380876043037148,-162.012776848660081 5.36783766723553946,-161.690549005093743 5.35725884322304502,-161.368721650836875 5.33802739140809557,-161.047527088684063 5.31015719351933235,-160.727197164665256 5.27366836694196373,-160.421621523858249 5.23469527250653854,-160.102387459886472 5.18961415576110952,-159.784479686856571 5.13597328949175136,-159.468127678314318 5.07381139297753059,-159.153559784816935 5.00317333618393612,-158.853927522957889 4.93164794210813451,-158.541370807245158 4.85258471329888774,-158.231050880339666 4.7651533822312695,-157.923191738695721 4.66941705896188797,-157.618015602513111 4.56544484829227759,-157.327784107566345 4.46219246969767802,-157.025511260386196 4.35005942129268952,-156.726359890085519 4.22984647550804382,-156.430545931383392 4.1016404049654156,-156.138282909974265 3.96553375191790813,-155.860801096610373 3.83174579432549756,-155.572299975036202 3.68783680385857515,-155.287769000407764 3.53622935297387997,-155.007413554087719 3.37703287560877818,-154.731436003441075 3.21036228364411969,-154.46990567067823 3.04758249561918326,-154.198505222998278 2.87355809593285194,-153.931877781715087 2.69230550356012799,-153.670215804986441 2.50395555102093859,-153.413708166777326 2.30864419388203324,-153.171147053156858 2.11875085407343189,-152.87864945418977 1.88227751953715927,-152.593706706865078 1.63675335759936136,-152.316598756515816 1.38241958634308926,-152.047597851087176 1.11952607895504297,-151.758492641328019 0.827987423984159143,-151.497863063903992 0.556792463263721338,-151.245860873059797 0.277562487870442354,-151.002733651293767 -0.00942816944216584574,-150.768720261787024 -0.303897551347931838,-150.580866676220865 -0.54804178071738896,-150.387712159765329 -0.806177457173726353,-150.201562005690334 -1.06940894241931783,-150.022550581694702 -1.33754622957983571,-149.850807102568638 -1.61039577065407258,-149.690225111389282 -1.87328129413026989,-149.633025334764199 -1.97070420726289952,-149.574266655031067 -2.06719491150401513,-149.526997232690093 -2.15129174077182661,-149.478152856477351 -2.23448369888000897,-149.424387935046411 -2.33384350802175922)))", diff --git a/test/algorithms/overlay/overlay.cpp b/test/algorithms/overlay/overlay.cpp index dd7b760648..ee6135ccc1 100644 --- a/test/algorithms/overlay/overlay.cpp +++ b/test/algorithms/overlay/overlay.cpp @@ -55,6 +55,35 @@ struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_v return; } + auto label_component = [](auto const& turn) + { + std::ostringstream out; + auto const& c0 = turn.operations[0].enriched.component_id; + auto const& c1 = turn.operations[1].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(); + }; + + for (auto const& enumerated : boost::geometry::util::enumerate(turns)) { auto index = enumerated.index; @@ -66,8 +95,7 @@ struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_v out //<< " l:" << op.count_left << " r:" << op.count_right //<< " rank:" << op.rank // << " z:" << op.zone - << " region:" << op.region_id - << (op.isolated ? " ISOLATED" : ""); + << " region:" << op.region_id; return out.str(); }; auto label_operation_ids = [&turn](int op_index) @@ -107,6 +135,8 @@ struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_v m_writer.add_property("operation_1", label_operation_ids(1)); m_writer.add_property("enriched_0", label_enriched(0)); m_writer.add_property("enriched_1", label_enriched(1)); + + m_writer.add_property("component", label_component(turn)); } } @@ -131,6 +161,15 @@ void test_overlay(std::string const& caseid, bg::correct(g1); bg::correct(g2); + if (! bg::is_valid(g1)) + { + std::cerr << "WARNING: Invalid input 1: " << caseid << std::endl; + } + if (! bg::is_valid(g2)) + { + std::cerr << "WARNING: Invalid input 2: " << caseid << std::endl; + } + #if defined(TEST_WITH_GEOJSON) std::ostringstream filename; // For QGis, it is usually convenient to always write to the same geojson file. diff --git a/test/algorithms/set_operations/intersection/intersection_multi.cpp b/test/algorithms/set_operations/intersection/intersection_multi.cpp index 7a2298f37b..2008d79e1e 100644 --- a/test/algorithms/set_operations/intersection/intersection_multi.cpp +++ b/test/algorithms/set_operations/intersection/intersection_multi.cpp @@ -166,7 +166,7 @@ void test_areal() case_recursive_boxes_3[0], case_recursive_boxes_3[1], 19, 84, 12.5); // Area from SQL Server - TEST_INTERSECTION_IGNORE(case_recursive_boxes_4, 13, 158, 67.0); + TEST_INTERSECTION(case_recursive_boxes_4, 13, 158, 67.0); // Fixed by replacing handle_tangencies in less_by_segment_ratio sort order // Should contain 6 output polygons diff --git a/test/algorithms/set_operations/set_ops_areal_areal.cpp b/test/algorithms/set_operations/set_ops_areal_areal.cpp index f4157119b5..17649d24bb 100644 --- a/test/algorithms/set_operations/set_ops_areal_areal.cpp +++ b/test/algorithms/set_operations/set_ops_areal_areal.cpp @@ -241,15 +241,15 @@ void test_all(std::string const& name, std::string const& wkt1, std::string cons int test_main(int, char* []) { - TEST_CASE_WITH(case_141_multi, 0, 1, ut_settings().ignore_reverse()); + TEST_CASE(case_141_multi); TEST_CASE(case_142_multi); TEST_CASE(case_143_multi); TEST_CASE(case_144_multi); TEST_CASE(case_145_multi); - TEST_CASE_WITH(case_146_multi, 0, 1, ut_settings().ignore_validity_intersection()); + TEST_CASE(case_146_multi); TEST_CASE(case_147_multi); - TEST_CASE_WITH(issue_1221, 0, 1, ut_settings().ignore_validity_diff()); + TEST_CASE(issue_1221); TEST_CASE(issue_1222); TEST_CASE_WITH(issue_1226, 0, 1, ut_settings().ignore_validity_diff()); @@ -271,10 +271,16 @@ int test_main(int, char* []) TEST_CASE_WITH(issue_1345_a, 1, 0, ut_settings()); TEST_CASE_WITH(issue_1345_b, 1, 0, ut_settings()); - TEST_CASE_WITH(issue_1349, 0, 1, ut_settings().ignore_diff()); + TEST_CASE(issue_1349); TEST_CASE(issue_1349_inverse); - TEST_CASE(issue_1350_comment); + TEST_CASE(case_bitset_1); + TEST_CASE(case_bitset_2); + + // TEST_CASE(case_recursive_boxes_89); + // TEST_CASE(case_recursive_boxes_90); + TEST_CASE_WITH(case_recursive_boxes_89, 0, 1, ut_settings().ignore_diff()); + TEST_CASE_WITH(case_recursive_boxes_90, 0, 1, ut_settings().ignore_diff()); #if defined(BOOST_GEOMETRY_TEST_FAILURES) // Fails in union or intersection, and in difference. Also the union is invalid.