From 7bd58d04a673bb79df24e5eef8be3be0866b4b6c Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Wed, 21 Apr 2010 14:42:35 +0000 Subject: [PATCH] Moved "dissolve multi_linestring" to separate algorithm "connect" (in Extension) because it is actually not dissolve, and will need separate strategies [SVN r61463] --- .../extensions/algorithms/connect.hpp | 282 ++++++++++++++++++ .../geometry/multi/algorithms/dissolve.hpp | 194 +----------- test/boost.vsprops | 1 + test/extensions/algorithms/connect.cpp | 181 +++++++++++ test/extensions/algorithms/connect.vcproj | 182 +++++++++++ .../algorithms/extension_algorithms.sln | 6 + test/multi/algorithms/multi_dissolve.cpp | 26 -- 7 files changed, 655 insertions(+), 217 deletions(-) create mode 100644 include/boost/geometry/extensions/algorithms/connect.hpp create mode 100644 test/extensions/algorithms/connect.cpp create mode 100644 test/extensions/algorithms/connect.vcproj diff --git a/include/boost/geometry/extensions/algorithms/connect.hpp b/include/boost/geometry/extensions/algorithms/connect.hpp new file mode 100644 index 000000000..76e45b49e --- /dev/null +++ b/include/boost/geometry/extensions/algorithms/connect.hpp @@ -0,0 +1,282 @@ +// Boost.Geometry (aka GGL, Generic Geometry Library) +// +// Copyright Barend Gehrels 2010, Geodan, Amsterdam, the Netherlands. +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_CONNECT_HPP +#define BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_CONNECT_HPP + +#include + + +#include +#include + + +#include +#include +#include +#include +#include + +#include + + + +namespace boost { namespace geometry +{ + + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace connect +{ + +// Dissolve on multi_linestring tries to create larger linestrings from input, +// or closed rings. + +template +struct connect_multi_linestring +{ + typedef typename point_type::type point_type; + typedef typename boost::range_iterator::type iterator_type; + typedef typename boost::range_value::type linestring_type; + typedef typename distance_result::type distance_result_type; + + struct mapped + { + int index; + bool is_from; + mapped(int i, bool f) : index(i), is_from(f) + {} + }; + + // Have a map > such that we can find + // the corresponding point on each end. Note that it uses the + // default "equals" for the point-type + typedef std::multimap + < + point_type, + mapped, + boost::geometry::less + > map_type; + + typedef typename map_type::const_iterator map_iterator_type; + + static inline void copy(linestring_type const& ls, + GeometryOut& target, + bool copy_forward) + { + if (copy_forward) + { + std::copy(boost::begin(ls), boost::end(ls), + std::back_inserter(target)); + } + else + { + std::reverse_copy(boost::begin(ls), boost::end(ls), + std::back_inserter(target)); + } + } + + static inline map_iterator_type find_start(map_type const& map, + std::map& included, int expected_count = 1) + { + for (map_iterator_type it = map.begin(); + it != map.end(); + ++it) + { + typename map_type::size_type count = map.count(it->first); + if (count == expected_count && ! included[it->second.index]) + { + included[it->second.index] = true; + return it; + } + } + + // Not found with one point, try one with two points + // to find rings + if (expected_count == 1) + { + return find_start(map, included, 2); + } + + return map.end(); + } + + template + static inline OutputIterator apply(Multi const& multi, OutputIterator out) + { + if (boost::size(multi) <= 0) + { + return out; + } + + map_type map; + + // 1: fill the map. + int index = 0; + for (iterator_type it = boost::begin(multi); + it != boost::end(multi); + ++it, ++index) + { + linestring_type const& ls = *it; + if (boost::size(ls) > 0) + { + map.insert(std::make_pair(*boost::begin(ls), mapped(index, true))); + map.insert(std::make_pair(*(boost::end(ls) - 1), mapped(index, false))); + } + } + + std::map included; + + // 2: connect the lines + + // 2a: start with one having a unique starting point + map_iterator_type first = find_start(map, included); + bool found = first != map.end(); + if (! found) + { + return out; + } + + int current_index = first->second.index; + GeometryOut current; + copy(multi[current_index], current, first->second.is_from); + + while(found) + { + // 2b: get all candidates, by asking multi-map for range + point_type const& p = *(boost::end(current) - 1); + std::pair range + = map.equal_range(p); + + // Alternatively, we might look for the closest points + if (range.first == map.end()) + { + std::cout << "nothing found" << std::endl; + } + + // 2c: for all candidates get closest one + found = false; + int closest_index = -1; + bool from_is_closest = false; + // TODO: make utility to initalize distance result with large value + distance_result_type min_dist + = make_distance_result(100); + for (map_iterator_type it = range.first; + ! found && it != range.second; + ++it) + { + if (it->second.index != current_index + && ! included[it->second.index]) + { + linestring_type const& ls = multi[it->second.index]; + point_type const& p = it->second.is_from + ? *boost::begin(ls) + : *(boost::end(ls) - 1); + + distance_result_type d = geometry::distance(it->first, p); + if (! found || d < min_dist) + { + closest_index = it->second.index; + from_is_closest = it->second.is_from; + min_dist = d; + + //std::cout << "TO " << geometry::wkt(p) << std::endl; + } + found = true; + } + } + // 2d: if there is a closest one add it + if (found && closest_index >= 0) + { + current_index = closest_index; + included[current_index] = true; + copy(multi[current_index], current, from_is_closest); + } + + if (! found && (included.size() != boost::size(multi))) + { + // Get one which is NOT found and go again + map_iterator_type next = find_start(map, included); + found = next != map.end(); + + if (found) + { + current_index = next->second.index; + + *out++ = current; + geometry::clear(current); + + copy(multi[current_index], current, next->second.is_from); + } + } + } + if (boost::size(current) > 0) + { + *out++ = current; + } + + return out; + } +}; + +}} // namespace detail::connect +#endif + + + +#ifndef DOXYGEN_NO_DISPATCH +namespace dispatch +{ + +template +< + typename GeometryTag, + typename GeometryOutTag, + typename Geometry, + typename GeometryOut +> +struct connect +{}; + + +template +struct connect + : detail::connect::connect_multi_linestring +{}; + + +} // namespace dispatch +#endif // DOXYGEN_NO_DISPATCH + + +template +< + typename Geometry, + typename Collection +> +inline void connect(Geometry const& geometry, Collection& output_collection) +{ + concept::check(); + + typedef typename boost::range_value::type geometry_out; + + concept::check(); + + dispatch::connect + < + typename tag::type, + typename tag::type, + Geometry, + geometry_out + >::apply(geometry, std::back_inserter(output_collection)); +} + + +}} // namespace boost::geometry + + +#endif // BOOST_GEOMETRY_EXTENSIONS_ALGORITHMS_CONNECT_HPP diff --git a/include/boost/geometry/multi/algorithms/dissolve.hpp b/include/boost/geometry/multi/algorithms/dissolve.hpp index a6be54819..e40b828cf 100644 --- a/include/boost/geometry/multi/algorithms/dissolve.hpp +++ b/include/boost/geometry/multi/algorithms/dissolve.hpp @@ -63,191 +63,9 @@ struct dissolve_multi } }; - -// Dissolve on multi_linestring tries to create larger linestrings from input, -// or closed rings. - -template -struct dissolve_multi_linestring -{ - typedef typename point_type::type point_type; - typedef typename boost::range_iterator::type iterator_type; - typedef typename boost::range_value::type linestring_type; - typedef typename distance_result::type distance_result_type; - - struct mapped - { - int index; - bool is_from; - mapped(int i, bool f) : index(i), is_from(f) - {} - }; - - // Have a map > such that we can find - // the corresponding point on each end. Note that it uses the - // default "equals" for the point-type - typedef std::multimap - < - point_type, - mapped, - boost::geometry::less - > map_type; - - typedef typename map_type::const_iterator map_iterator_type; - - static inline void copy(linestring_type const& ls, - GeometryOut& target, - bool copy_forward) - { - if (copy_forward) - { - std::copy(boost::begin(ls), boost::end(ls), - std::back_inserter(target)); - } - else - { - std::reverse_copy(boost::begin(ls), boost::end(ls), - std::back_inserter(target)); - } - } - - static inline map_iterator_type find_start(map_type const& map, - std::map& included, int expected_count = 1) - { - for (map_iterator_type it = map.begin(); - it != map.end(); - ++it) - { - int count = map.count(it->first); - if (count == expected_count && ! included[it->second.index]) - { - included[it->second.index] = true; - return it; - } - } - - // Not found with one point, try one with two points - // to find rings - if (expected_count == 1) - { - return find_start(map, included, 2); - } - - return map.end(); - } - - template - static inline OutputIterator apply(Multi const& multi, OutputIterator out) - { - if (boost::size(multi) <= 0) - { - return out; - } - - map_type map; - - // 1: fill the map. - int index = 0; - for (iterator_type it = boost::begin(multi); - it != boost::end(multi); - ++it, ++index) - { - linestring_type const& ls = *it; - if (boost::size(ls) > 0) - { - map.insert(std::make_pair(*boost::begin(ls), mapped(index, true))); - map.insert(std::make_pair(*(boost::end(ls) - 1), mapped(index, false))); - } - } - - std::map included; - - // 2: merge (dissolve) the lines - - // 2a: start with one having a unique starting point - map_iterator_type first = find_start(map, included); - bool found = first != map.end(); - if (! found) - { - return out; - } - - int current_index = first->second.index; - GeometryOut current; - copy(multi[current_index], current, first->second.is_from); - - while(found) - { - // 2b: get all candidates, ask for range - point_type const& p = *(boost::end(current) - 1); - std::pair range - = map.equal_range(p); - - // 2c: for all candidates get closest one - found = false; - int closest_index = -1; - bool from_is_closest = false; - // TODO: make utility to initalize distance result with large value - distance_result_type min_dist - = make_distance_result(100); - for (map_iterator_type it = range.first; - ! found && it != range.second; - ++it) - { - if (it->second.index != current_index - && ! included[it->second.index]) - { - linestring_type const& ls = multi[it->second.index]; - point_type const& p = it->second.is_from - ? *boost::begin(ls) - : *(boost::end(ls) - 1); - - distance_result_type d = geometry::distance(it->first, p); - if (! found || d < min_dist) - { - closest_index = it->second.index; - from_is_closest = it->second.is_from; - min_dist = d; - - //std::cout << "TO " << geometry::wkt(p) << std::endl; - } - found = true; - } - } - // 2d: if there is a closest one add it - if (found && closest_index >= 0) - { - current_index = closest_index; - included[current_index] = true; - copy(multi[current_index], current, from_is_closest); - } - - if (! found && (included.size() != boost::size(multi))) - { - // Get one which is NOT found and go again - map_iterator_type next = find_start(map, included); - found = next != map.end(); - - if (found) - { - current_index = next->second.index; - - *out++ = current; - geometry::clear(current); - - copy(multi[current_index], current, next->second.is_from); - } - } - } - if (boost::size(current) > 0) - { - *out++ = current; - } - - return out; - } -}; - +// Dissolving multi-linestring is currently moved to extensions/algorithms/connect, +// because it is actually different from dissolving of polygons. +// To be decided what the final behaviour/name is. }} // namespace detail::dissolve #endif @@ -265,12 +83,6 @@ struct dissolve {}; -template -struct dissolve - : detail::dissolve::dissolve_multi_linestring -{}; - - } // namespace dispatch #endif // DOXYGEN_NO_DISPATCH diff --git a/test/boost.vsprops b/test/boost.vsprops index 9bfb5cfa8..1fe123fc6 100644 --- a/test/boost.vsprops +++ b/test/boost.vsprops @@ -7,6 +7,7 @@ +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if defined(TEST_WITH_SVG) +# include +# include +# include +#endif + + +template +void test_connect(std::string const& caseid, Geometry const& geometry, + std::size_t expected_point_count, + double expected_length, double percentage) +{ + namespace bg = boost::geometry; + typedef typename bg::coordinate_type::type coordinate_type; + + std::vector connected_vector; + bg::connect(geometry, connected_vector); + + typename bg::length_result::type length = 0; + std::size_t count = 0; + + BOOST_FOREACH(GeometryOut& dissolved, connected_vector) + { + bg::unique(dissolved); + length += bg::length(dissolved); + count += bg::num_points(dissolved); + } + + BOOST_CHECK_MESSAGE(count == expected_point_count, + "connect: " << caseid + << " #points expected: " << expected_point_count + << " detected: " << count + << " type: " << string_from_type::name() + ); + + + //BOOST_CHECK_EQUAL(holes, expected_hole_count); + BOOST_CHECK_CLOSE(length, expected_length, percentage); + + +#if defined(TEST_WITH_SVG) + { + std::ostringstream filename; + filename << "connect_" + << caseid << "_" + << string_from_type::name() + << ".svg"; + + std::ofstream svg(filename.str().c_str()); + + bg::svg_mapper + < + typename bg::point_type::type + > mapper(svg, 500, 500); + mapper.add(geometry); + + mapper.map(geometry, "opacity:0.6;fill:rgb(0,0,255);stroke:rgb(0,0,0);stroke-width:1"); + BOOST_FOREACH(GeometryOut& dissolved, connected_vector) + { + mapper.map(dissolved, "opacity:0.6;fill:none;stroke:rgb(255,0,0);stroke-width:5"); + } + } +#endif +} + + +template +void test_one(std::string const& caseid, std::string const& wkt, + std::size_t expected_point_count, + double expected_length, double percentage = 0.001) +{ + Geometry geometry; + boost::geometry::read_wkt(wkt, geometry); + + test_connect(caseid, geometry, + expected_point_count, + expected_length, percentage); + +#ifdef BOOST_GEOMETRY_TEST_MULTI_PERMUTATIONS + // Test different combinations of a multi-polygon + + int n = geometry.size(); + + // test them in all orders + std::vector indices; + for (int i = 0; i < n; i++) + { + indices.push_back(i); + } + int permutation = 0; + do + { + std::ostringstream out; + out << caseid; + Geometry geometry2; + for (int i = 0; i < n; i++) + { + int index = indices[i]; + out << "_" << index; + geometry2.push_back(geometry[index]); + } + test_connect(out.str(), geometry2, + expected_point_count, expected_length, percentage); + } while (std::next_permutation(indices.begin(), indices.end())); +#endif + +} + + + + +template +void test_all() +{ + namespace bg = boost::geometry; + + + typedef bg::linestring

linestring; + typedef bg::multi_linestring multi_linestring; + + test_one("ls_simplex", + "MULTILINESTRING((0 0,1 1),(1 1,2 2))", + 3, 2 * std::sqrt(2.0)); + + // Opposites, forming one line + test_one("ls_simplex_opposite_to", + "MULTILINESTRING((0 0,1 1),(2 2,1 1))", + 3, 2 * std::sqrt(2.0)); + test_one("ls_simplex_opposite_from", + "MULTILINESTRING((1 1,0 0),(1 1,2 2))", + 3, 2 * std::sqrt(2.0)); + + // Two output linestrings + test_one("ls_simplex_two", + "MULTILINESTRING((0 0,1 1),(1 1,2 2),(3 3,4 4),(4 4,5 5))", + 6, 4 * std::sqrt(2.0)); + + // Linestrings forming a ring + test_one("ls_simplex_ring", + "MULTILINESTRING((0 0,0 1),(1 1,1 0),(0 1,1 1),(1 0,0 0))", + 5, 4.0); + + // disconnected ring + test_one("ls_disconnected_ring", + "MULTILINESTRING((0 0,0 1.01),(1.02 1.03,0.99 0),(0 0.98,1.001 1),(1.01 0,0 0))", + 5, 4.0); +} + + +int test_main(int, char* []) +{ + test_all >(); + + return 0; +} diff --git a/test/extensions/algorithms/connect.vcproj b/test/extensions/algorithms/connect.vcproj new file mode 100644 index 000000000..529019abf --- /dev/null +++ b/test/extensions/algorithms/connect.vcproj @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/extensions/algorithms/extension_algorithms.sln b/test/extensions/algorithms/extension_algorithms.sln index b8d185c92..d165d9ca4 100644 --- a/test/extensions/algorithms/extension_algorithms.sln +++ b/test/extensions/algorithms/extension_algorithms.sln @@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "selected", "selected.vcproj EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "remove_spikes", "remove_spikes.vcproj", "{3EA21C81-DE4A-410D-AABE-CDC5091DAB6E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "connect", "connect.vcproj", "{D502500F-AF66-4F24-8735-09DF8A7DF6AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -31,6 +33,10 @@ Global {3EA21C81-DE4A-410D-AABE-CDC5091DAB6E}.Debug|Win32.Build.0 = Debug|Win32 {3EA21C81-DE4A-410D-AABE-CDC5091DAB6E}.Release|Win32.ActiveCfg = Release|Win32 {3EA21C81-DE4A-410D-AABE-CDC5091DAB6E}.Release|Win32.Build.0 = Release|Win32 + {D502500F-AF66-4F24-8735-09DF8A7DF6AA}.Debug|Win32.ActiveCfg = Debug|Win32 + {D502500F-AF66-4F24-8735-09DF8A7DF6AA}.Debug|Win32.Build.0 = Debug|Win32 + {D502500F-AF66-4F24-8735-09DF8A7DF6AA}.Release|Win32.ActiveCfg = Release|Win32 + {D502500F-AF66-4F24-8735-09DF8A7DF6AA}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/multi/algorithms/multi_dissolve.cpp b/test/multi/algorithms/multi_dissolve.cpp index 4dc427fe9..5558037bd 100644 --- a/test/multi/algorithms/multi_dissolve.cpp +++ b/test/multi/algorithms/multi_dissolve.cpp @@ -58,32 +58,6 @@ void test_all() "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((3 1,5 4,8 4,3 1)))", 1, 18, 19.5206); - // Linestrings - typedef bg::linestring

linestring; - typedef bg::multi_linestring multi_linestring; - - test_one("ls_simplex", - "MULTILINESTRING((0 0,1 1),(1 1,2 2))", - 0, 3, 2 * std::sqrt(2.0)); - - // Opposites, forming one line - test_one("ls_simplex_opposite_to", - "MULTILINESTRING((0 0,1 1),(2 2,1 1))", - 0, 3, 2 * std::sqrt(2.0)); - test_one("ls_simplex_opposite_from", - "MULTILINESTRING((1 1,0 0),(1 1,2 2))", - 0, 3, 2 * std::sqrt(2.0)); - - // Two output linestrings - test_one("ls_simplex_two", - "MULTILINESTRING((0 0,1 1),(1 1,2 2),(3 3,4 4),(4 4,5 5))", - 0, 6, 4 * std::sqrt(2.0)); - - // Linestrings forming a ring - test_one("ls_simplex_ring", - "MULTILINESTRING((0 0,0 1),(1 1,1 0),(0 1,1 1),(1 0,0 0))", - 0, 5, 4.0); - }