Having a matrix MxN of integers how to group them into polygons with boost geometry?

无人久伴 提交于 2019-12-01 10:44:05

In short, if I got the question right, I'd simply let Boost Geometry do most of the work.

For a sample matrix of NxM, create NxM 'flyweight' rectangle polygons to correspond to each matrix cell visually.

Now, using an iterative deepening algorithm, find all groups:

* for each _unvisited_ cell in matrix
  * start a new group
  * [visit:] 
     - mark _visited_
     - for each neighbour with equal value: 
          - add to curent group and
          - recurse [visit:]

Note that the result of this algorithm could be distinct groups with the same values (representing disjunct polygons). E.g. the value 2 from the sample in the OP would result in two groups.

Now for each group, simply call Boost Geometry's Union_ algorithm to find the consolidated polygon to represent that group.

Sample implementation

Update Here is a non-optimized implementation in C++11:

Edit See here for C++03 version (using Boost)

The sample data used in the test corresponds to the matrix from the question.

#include <iostream>
#include <array>
#include <vector>
#include <set>

namespace mxdetail
{
    typedef size_t cell_id; // row * COLS + col

    template <typename T> struct area
    {
        T value;
        std::vector<cell_id> cells;
    };

    template <typename T, size_t Rows, size_t Cols>
        std::vector<area<T> > getareas(const std::array<std::array<T, Cols>, Rows>& matrix)
    {
        typedef std::array<std::array<T, Cols>, Rows> mtx;
        std::vector<area<T> > areas;

        struct visitor_t
        {
            const mtx& matrix;
            std::set<cell_id> visited;

            visitor_t(const mtx& mtx) : matrix(mtx) { }

            area<T> start(const int row, const int col)
            {
                area<T> result;
                visit(row, col, result);
                return result;
            }

            void visit(const int row, const int col, area<T>& current)
            {
                const cell_id id = row*Cols+col;
                if (visited.end() != visited.find(id))
                    return;

                bool matches = current.cells.empty() || (matrix[row][col] == current.value);

                if (matches)
                {
                    visited.insert(id);
                    current.value = matrix[row][col];
                    current.cells.push_back(id);

                    // process neighbours
                    for (int nrow=std::max(0, row-1); nrow < std::min((int) Rows, row+2); nrow++)
                    for (int ncol=std::max(0, col-1); ncol < std::min((int) Cols, col+2); ncol++)
                        /* if (ncol!=col || nrow!=row) */
                            visit(nrow, ncol, current);
                }
            }
        } visitor(matrix);

        for (int r=0; r < Rows; r++)
            for (int c=0; c < Cols; c++)
            {
                auto area = visitor.start(r,c);
                if (!area.cells.empty()) // happens when startpoint already visited
                    areas.push_back(area);
            }

        return areas;
    }
}

int main()
{
    typedef std::array<int, 3> row;
    std::array<row, 4> matrix = { 
        row { 1  , 2, 3, },
        row { 1  , 3, 3, },
        row { 1  , 3, 3, },
        row { 100, 2, 1, },
    };

    auto areas = mxdetail::getareas(matrix);

    std::cout << "areas detected: " << areas.size() << std::endl;
    for (const auto& area : areas)
    {
        std::cout << "area of " << area.value << ": ";
        for (auto pt : area.cells)
        {
            int row = pt / 3, col = pt % 3;
            std::cout << "(" << row << "," << col << "), ";
        }
        std::cout << std::endl;
    }
}

Compiled with gcc-4.6 -std=c++0x the output is:

areas detected: 6
area of 1: (0,0), (1,0), (2,0), 
area of 2: (0,1), 
area of 3: (0,2), (1,1), (1,2), (2,1), (2,2), 
area of 100: (3,0), 
area of 2: (3,1), 
area of 1: (3,2), 

When number of points is big (say, more than 1000x1000), the solution above would gobble a lot of memory. And this is exactly what happened to the topic-starter.

Below I show more scalable approach.

I would separate two problems here: one is to find the areas, another is to convert them into polygons.

The first problem is actually equivalent to finding the connected components of the grid graph where neighbors has edges if and only if they have equal "colors" attached to it. One can use a grid graph from .

#include <boost/graph/grid_graph.hpp>
// Define dimension lengths, a MxN in this case
boost::array<int, 2> lengths = { { M, N } };

// Create a MxN two-dimensional, unwrapped grid graph 
grid_graph<2> graph(lengths);

Next, we should convert a given matrix M into an edge filter: grid edges are present iff the "color" of the neighbors are the same.

template <class Matrix>
struct GridEdgeFilter
{  
    typedef grid_graph<2> grid;
    GridEdgeFilter(const Matrix & m, const grid&g):_m(&m),_g(&g){}

    /// \return true iff edge is present in the graph
    bool operator()(grid::edge_descriptor e) const
    {
        grid::vertex_descriptor src = source(e,*_g), tgt = target(e,*_g);
        //src[0] is x-coord of src, etc. The value (*m)[x,y] is the color of the point (x,y).
        //Edge is preserved iff matrix values are equal
        return (*_m)[src[0],src[1]] == (*_m)[tgt[0],tgt[1]];
    }

    const Matrix * _m;
    const grid* _g;
};

Finally, we define a boost::filtered_graph of grid and EdgeFilter and call Boost.Graph algorithm for connected components.

Each connected component represents a set of points of a single color i.e. exactly the area we want to transform into a polygon.

Here we have another issue. Boost.Geometry only allows to merge polygons one by one. Hence it becomes very slow when number of polygons is big.

The better way is to use Boost.Polygon, namely its Property Merge functionality. One starts with empty property_merge object, and goes on by inserting rectangles of given color (you can set color as a property). Then one calls the method merge and gets a polygon_set as the output.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!