C++: Get list of simple polygons from polygon with holes - c++

I'm struggling with Boost::Polygon - apparently it can do everything except the thing I want. I have a few boundaries describing set of polygons and their holes (in 2d space). In general we can even have hole in a hole (smaller polygon in hole of a bigger polygon), or many holes in one polygon. If it's necessary I can check which boundary describes a hole and which describes a polygon. Sometimes boundaries are separate (and not containing each other), which means we have many polygons. What I want is a method which gives me a set of simple, not containing any holes polygons, which together form input 'holey' polygon.

This is possible with Boost Polygon. You need polygon_set_data::get(), which does the hole fracturing for you in case you convert from a polygon concept supporting holes to one that does not. See: http://www.boost.org/doc/libs/1_65_0/libs/polygon/doc/gtl_polygon_set_concept.htm for more details.
The following is an example, where we represent a polygon with a hole first, then convert it to a simple polygon with only one ring:
#include <boost/polygon/polygon.hpp>
namespace bp = boost::polygon;
int main(void)
{
using SimplePolygon = bp::polygon_data<int>;
using ComplexPolygon = bp::polygon_with_holes_data<int>;
using Point = bp::point_data<int>;
using PolygonSet = bp::polygon_set_data<int>;
using SimplePolygons = std::vector<bp::polygon_data<int>>;
using namespace boost::polygon::operators;
std::vector<Point> points{{5, 0}, {10, 5}, {5, 10}, {0, 5}};
ComplexPolygon p;
bp::set_points(p, points.begin(), points.end());
{
std::vector<Point> innerPoints{{4, 4}, {6, 4}, {6, 6}, {4, 6}};
std::vector<SimplePolygon> inner(1, SimplePolygon{});
bp::set_points(inner.front(), innerPoints.begin(), innerPoints.end());
bp::set_holes(p, inner.begin(), inner.end());
}
PolygonSet complexPolygons;
complexPolygons += p;
SimplePolygons simplePolygons;
complexPolygons.get<SimplePolygons>(simplePolygons);
std::cout << "Fractured:\n";
for (const auto& polygon : simplePolygons)
{
for (const Point& p : polygon)
{
std::cout << '\t' << std::to_string(p.x()) << ", " << std::to_string(p.y())
<< '\n';
}
}
return 0;
}

Related

Spheroid line_interpolate - but in the other direction

Using boost::geometry::line_interpolate with boost::geometry::srs::spheroid, I'm calculating great circle navigation points along the shortest distance between 2 geographic points. The code below calculates the navigation points for the shortest distance around the great circle. In some rare cases, I need to generate the longer distance that wraps around the globe in the wrong direction. For example, when interpolating between a lon/lat of (20, 20) to (30, 20), there only 10 degrees of difference in the shorter direction and 350 degrees in the other. In some cases I would like the ability to want to interpolate in the longer direction (e.g. 350 deg).
This 2d map shows shows the 10 degree longitude difference in red, and 350 degrees green. I drew the green line by hand to the line is only an approximation. How can I get the points for this green line?
This code is based on the example from boost.org, line_interpolate_4_with_strategy
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
int main()
{
typedef boost::geometry::model::d2::point_xy<double, boost::geometry::cs::geographic<boost::geometry::degree> > Point_Type;
using Segment_Type = boost::geometry::model::segment<Point_Type>;
using Multipoint_Type = boost::geometry::model::multi_point<Point_Type>;
boost::geometry::srs::spheroid<double> spheroid(6378137.0, 6356752.3142451793);
boost::geometry::strategy::line_interpolate::geographic<boost::geometry::strategy::vincenty> str(spheroid);
Segment_Type const start_end_points { {20, 20}, {30, 20} }; // lon/lat, interpolate between these two points
double distance { 50000 }; // plot a point ever 50km
Multipoint_Type mp;
boost::geometry::line_interpolate(start_end_points, distance, mp, str);
std::cout << "on segment : " << wkt(mp) << "\n";
return 0;
}
Note that line_interpolate interpolates points on a linestring where a segment between two points follows a geodesic.
Therefore, one workaround could be to create an antipodal point to the centroid of the original segment and create a linestring that follows the requested path. Then call line_interpolate with this linestring. The following code could do the trick.
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
int main()
{
namespace bg = boost::geometry;
using Point_Type = bg::model::d2::point_xy<double, bg::cs::geographic<bg::degree>>;
using Segment_Type = boost::geometry::model::segment<Point_Type>;
using Linstring_Type = bg::model::linestring<Point_Type>;
using Multipoint_Type = bg::model::multi_point<Point_Type>;
bg::srs::spheroid<double> spheroid(6378137.0, 6356752.3142451793);
bg::strategy::line_interpolate::geographic<bg::strategy::vincenty> str(spheroid);
Segment_Type const start_end_points { {20, 20}, {30, 20} };
Point_Type centroid;
bg::centroid(start_end_points, centroid);
Point_Type antipodal_centroid;
bg::set<0>(antipodal_centroid, bg::get<0>(centroid) + 180);
bg::set<1>(antipodal_centroid, bg::get<1>(centroid) * -1);
Linstring_Type line;
line.push_back(start_end_points.first);
line.push_back(antipodal_centroid);
line.push_back(start_end_points.second);
double distance { 50000 }; // plot a point ever 50km
Multipoint_Type mp;
bg::line_interpolate(line, distance, mp, str);
std::cout << "on segment : " << wkt(mp) << "\n";
return 0;
}
The result looks like this:
Note that since the spheroid you are constructing is non-spherical then there is no great circle (apart from equator and meridians) and the geodesic segment is not closed but looks like this. Therefore you will notice that the last interpolated point will be different from the segment's endpoint.

Copying from one dimensional vector vector<int> starts to first element of two dimensional vector pair vector<pair<int,int>>matrix

I have multiple 3 one dimensional vectors (vector<int> starts, vector<int> ends, vector<int> points). Each having specific number of elements.
I want to create a two dimensional vector vector<pair<int,int>>matrix in such a sequence :
from beginning of matrix to size of start first element of matrix is elements of vector<int> starts and second element is "-1"
Append now the elements of vector<int> ends to matrix such that first element of matrix is elements of vector<int> ends and second element is "-2"
Append now the elements of vector<int> points to matrix such that first element of matrix is elements of vector<int> points and second element is Index of points.
Visual Representation :-
Input:
starts: {1, 2, 3}
ends: {4, 5, 6}
points: (7, 8, 9}
Output:
matrix: { {1, -1}, {2, -1}, {3, -1}, {4, -2}, {5, -2}, {6, -2}, {7, 0}, {8, 1}, {9, 2} }
Currently I am using a push_back with for-loop function which works perfectly fine but when the input size is big code is very slow.
Code I am using is as follows:
vector<pair<int,int>> fast_count_segments(
vector<int> starts,
vector<int> ends,
vector<int> points)
{
int i = 0;
vector<pair<int,int>>matrix;
for(i; i<starts.size(); i++) {
matrix.push_back(make_pair(starts[i],-1));
}
for(i; i<starts.size()+ends.size(); i++) {
matrix.push_back(make_pair(ends[i-starts.size()],-2));
}
for(i; i<starts.size()+ends.size()+points.size(); i++) {
matrix.push_back(make_pair(
points[i-starts.size()-ends.size()],
i-(starts.size()+ends.size())
));
}
return matrix;
}
Can you please help on how to fill the 2D vector quickly with these requirements without iterating through each element. I am using C++11. Thanks in Advance !!
Preliminary concern: As #datenwolf and others note - Your resulting data structure is not a 2D matrix (unless you mean a boolean matrix in sparse representation). Are you sure that's what you want to be populating?
Regardless, here are a few ideas to possibly improve speed:
Don't take the input vectors by value! That's useless copying... take their .data(), or their .cbegin() iterator, or take a span<int> parameter.
Use the reserve() method on the target vector to avoid multiple re-allocations.
Use .emplace_back() instead of .push_back() to construct the points in place, rather than constructing-then-moving every point. Although, to be honest, the compiler will probably optimize those constructions away, anyway.
Put the .size() values of the input vectors in local variables. This will only help if, for some reason, the compiler suspects that size will not be constant throughout the execution of the function.
Make sure you're passing optimization switches to the compiler (e.g. -O2 or -O3 to GCC and clang). This might seem obvious to you but sometimes it's so obvious you forget to check it's actually been done.
Some aesthetic comments:
No need to use the same counter for all vectors. for(int i = 0; i < whatever; i++) can be used multiple times.
No need for raw for loops, you can use for(const auto& my_element : my_vector) for the first two loops. The third loop is trickier, since you want the index. You can use std::difference() working with iterators, or go with Python-style enumeration described here.
You might consider using std::transform() with a back_emplacer output iterators instead of all three loops. No-loop code! That would mean using std::difference() in the transformer lambda instead of the third loop.
This incorporates the suggestions from #einpoklum's answer, but also cleans up the code.
std::vector<std::pair<int,int>> fast_count_segments(
std::vector<int> const & starts,
std::vector<int> const & ends,
std::vector<int> const & points)
{
std::vector<std::pair<int,int>> matrix(starts.size() + ends.size() + points.size());
auto out = std::transform(starts.cbegin(), starts.cend(),
matrix.begin(),
[](int i) { return std::pair<int,int>{i, -1}; });
out = std::transform(ends.cbegin(), ends.cend(),
out,
[](int i) { return std::pair<int,int>{i, -2}; });
int c = 0;
std::transform(points.cbegin(), points.cend(),
out,
[&c](int i) { return std::pair<int,int>{i, c++}; });
return matrix;
}
You could even write all the transforms as a single expression. Whether this is easier to read is highly subjective, so I'm not recommending it per se. (Try reading it like you would nested function calls.)
std::vector<std::pair<int,int>> fast_count_segments(
std::vector<int> const & starts,
std::vector<int> const & ends,
std::vector<int> const & points)
{
std::vector<std::pair<int,int>> matrix(starts.size() + ends.size() + points.size());
int c = 0;
std::transform(points.cbegin(), points.cend(),
std::transform(ends.cbegin(), ends.cend(),
std::transform(starts.cbegin(), starts.cend(),
matrix.begin(),
[](int i) { return std::pair<int,int>{i, -1}; }),
[](int i) { return std::pair<int,int>{i, -2}; }),
[&c](int i) { return std::pair<int,int>{i, c++}; });
return matrix;
}

Adjust a detected 2D map to a reference 2D map

I have a map with some reference positions that correspond to the center (small cross) of some objects like this:
I take pictures to find my objects but in the pictures I have some noise so I can't always find all of the objects, it can be something like this:
From the few found positions I need to know where in the picture the other not found objects should be. I've being reading about this for the last couple of days and experimenting but I can't find a proper way of doing this. In some examples they start by calculating the center of masses and translating them together, then rotating, some other examples use least squares minimization and start by a rotation. I can't use OpenCV or any other APIs, just plain C++. I can use Eigen library if that helps. Can anyone give me some pointers on this?
EDIT:
I've solved the correspondence between points, the picture is never very different from the reference so for each found position I can search for its corresponding reference. In brief, I have one 2D matrix with reference points and another 2D matrix with found points. In the found matrix of points, the not found points are saved as NaN just to keep the same matrix size, the NaN points are not used in the calculations.
Since you have already matched the points to one another, finding the transform is straight forward:
Eigen::Affine2d findAffine(Eigen::Matrix2Xd const& refCloud, Eigen::Matrix2Xd const& targetCloud)
{
// get translation
auto refCom = centerOfMass(refCloud);
auto refAtOrigin = refCloud.colwise() - refCom;
auto targetCom = centerOfMass(targetCloud);
auto targetAtOrigin = targetCloud.colwise() - targetCom;
// get scale
auto scale = targetAtOrigin.rowwise().norm().sum() / refAtOrigin.rowwise().norm().sum();
// get rotation
auto covMat = refAtOrigin * targetAtOrigin.transpose();
auto svd = covMat.jacobiSvd(Eigen::ComputeFullU | Eigen::ComputeFullV);
auto rot = svd.matrixV() * svd.matrixU().transpose();
// combine the transformations
Eigen::Affine2d trans = Eigen::Affine2d::Identity();
trans.translate(targetCom).scale(scale).rotate(rot).translate(-refCom);
return trans;
}
refCloud is your reference point set and targetCloud is the set of points you have found in your image. It is important that the clouds match index wise, so refCloud[n] must be the corresponding point to targetCloud[n]. This means that you have to remove all NaNs from your matrix and cherry pick the correspondances in your reference point set.
Here is a full example. I'm using OpenCV to draw the stuff:
#include <Eigen/Dense>
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
using Point = Eigen::Vector2d;
template <typename TMatrix>
Point centerOfMass(TMatrix const& points)
{
return points.rowwise().sum() / points.cols();
}
Eigen::Affine2d findAffine(Eigen::Matrix2Xd const& refCloud, Eigen::Matrix2Xd const& targetCloud)
{
// get translation
auto refCom = centerOfMass(refCloud);
auto refAtOrigin = refCloud.colwise() - refCom;
auto targetCom = centerOfMass(targetCloud);
auto targetAtOrigin = targetCloud.colwise() - targetCom;
// get scale
auto scale = targetAtOrigin.rowwise().norm().sum() / refAtOrigin.rowwise().norm().sum();
// get rotation
auto covMat = refAtOrigin * targetAtOrigin.transpose();
auto svd = covMat.jacobiSvd(Eigen::ComputeFullU | Eigen::ComputeFullV);
auto rot = svd.matrixV() * svd.matrixU().transpose();
// combine the transformations
Eigen::Affine2d trans = Eigen::Affine2d::Identity();
trans.translate(targetCom).scale(scale).rotate(rot).translate(-refCom);
return trans;
}
void drawCloud(cv::Mat& img, Eigen::Matrix2Xd const& cloud, Point const& origin, Point const& scale, cv::Scalar const& color, int thickness = cv::FILLED)
{
for (int c = 0; c < cloud.cols(); c++)
{
auto p = origin + cloud.col(c).cwiseProduct(scale);
cv::circle(img, {int(p.x()), int(p.y())}, 5, color, thickness, cv::LINE_AA);
}
}
int main()
{
// generate sample reference
std::vector<Point> points = {{4, 9}, {4, 4}, {6, 9}, {6, 4}, {8, 9}, {8, 4}, {10, 9}, {10, 4}, {12, 9}, {12, 4}};
Eigen::Matrix2Xd fullRefCloud(2, points.size());
for (int i = 0; i < points.size(); i++)
fullRefCloud.col(i) = points[i];
// generate sample target
Eigen::Matrix2Xd refCloud = fullRefCloud.leftCols(fullRefCloud.cols() * 0.6);
Eigen::Affine2d refTransformation = Eigen::Affine2d::Identity();
refTransformation.translate(Point(8, -4)).rotate(4.3).translate(-centerOfMass(refCloud)).scale(1.5);
Eigen::Matrix2Xd targetCloud = refTransformation * refCloud;
// find the transformation
auto transform = findAffine(refCloud, targetCloud);
std::cout << "Original: \n" << refTransformation.matrix() << "\n\nComputed: \n" << transform.matrix() << "\n";
// apply the computed transformation
Eigen::Matrix2Xd queryCloud = fullRefCloud.rightCols(fullRefCloud.cols() - refCloud.cols());
queryCloud = transform * queryCloud;
// draw it
Point scale = {15, 15}, origin = {100, 300};
cv::Mat img(600, 600, CV_8UC3);
cv::line(img, {0, int(origin.y())}, {800, int(origin.y())}, {});
cv::line(img, {int(origin.x()), 0}, {int(origin.x()), 800}, {});
drawCloud(img, refCloud, origin, scale, {0, 255, 0});
drawCloud(img, fullRefCloud, origin, scale, {255, 0, 0}, 1);
drawCloud(img, targetCloud, origin, scale, {0, 0, 255});
drawCloud(img, queryCloud, origin, scale, {255, 0, 255}, 1);
cv::flip(img, img, 0);
cv::imshow("img", img);
cv::waitKey();
return 0;
}
I managed to make it work with the code from here:
https://github.com/oleg-alexandrov/projects/blob/master/eigen/Kabsch.cpp
I'm calling the Find3DAffineTransform function and passing it my 2D maps, as this function expects 3D maps I've made all z coordinates = 0 and it works. If I have some time I'll try to adapt it to 2D.
Meanwhile a fellow programmer (Regis :-) found also this, that should work:
https://eigen.tuxfamily.org/dox/group__Geometry__Module.html#gab3f5a82a24490b936f8694cf8fef8e60
Its the function umeyama() that returns the transformation between two point sets. Its part of Eigen library. Didn't have the time to test this.

How to Store some information at internal nodes of r-tree?

I am new to boost and c++. I am trying to code r tree using boost library. In my code, i want to store some information x at each internal node. I have two questions now.
1) How to perform traversing(depth-first) in the r star tree?
2) Suppose I can traverse the nodes of tree. There needs to be some member variables defined for the Box(INTERNAL node) class where I can store x at each node. What would be appropriate and efficient method for it?
From reading your comments I get an impression that you only want to store additional data in points stored in the R-tree and not to store additional data in the internal nodes of the R-tree. So here is an example showing how to store points with additional data and how to perform a query to get some of them. In the example I also show how to achieve the same with std::pair holding a point and some additional data which works by default and you do not have to register your own point type.
Includes:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <vector>
#include <iostream>
Namespaces for convenience:
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
Definition of your own point type with 2-d coordinates and additional data (color):
enum color {red, green, blue};
struct my_point
{
double x, y;
color c;
};
Adaptation of my_point to Boost.Geometry Point concept with a macro so the library knows that this struct is a 2-d Point and how to get the coordinates:
BOOST_GEOMETRY_REGISTER_POINT_2D(my_point, double, bg::cs::cartesian, x, y)
Some Boost.Geometry models that will be used as well:
typedef bg::model::point<double, 2, bg::cs::cartesian> bg_point;
typedef bg::model::box<bg_point> bg_box;
Main:
int main()
{
{
Creation of the R-tree and insertion of several points:
bgi::rtree<my_point, bgi::rstar<4> > rtree;
rtree.insert(my_point{ 0, 0, red });
rtree.insert(my_point{ 1, 1, green });
rtree.insert(my_point{ 2, 5, blue });
rtree.insert(my_point{ 7, 3, red });
rtree.insert(my_point{ 8, 8, green });
rtree.insert(my_point{ 1, 9, blue });
Query for points that intersect the following box and are red:
std::vector<my_point> res;
rtree.query(bgi::intersects(bg_box{ {1, 1}, {8, 8} })
&& bgi::satisfies([](my_point const& p) {
return p.c == red;
}),
std::back_inserter(res));
Print the result:
for (my_point const& p : res)
std::cout << bg::wkt(p) << std::endl;
}
The same but std::pair<bg_point, color> is used instead of my_point so no registration is needed:
{
bgi::rtree<std::pair<bg_point, color>, bgi::rstar<4> > rtree;
rtree.insert(std::pair<bg_point, color>{ {0, 0}, red });
rtree.insert(std::pair<bg_point, color>{ {1, 1}, green });
rtree.insert(std::pair<bg_point, color>{ {2, 5}, blue });
rtree.insert(std::pair<bg_point, color>{ {7, 3}, red });
rtree.insert(std::pair<bg_point, color>{ {8, 8}, green });
rtree.insert(std::pair<bg_point, color>{ {1, 9}, blue });
std::vector<std::pair<bg_point, color> > res;
rtree.query(bgi::intersects(bg_box{ { 1, 1 },{ 8, 8 } })
&& bgi::satisfies([](std::pair<bg_point, color> const& p) {
return p.second == red;
}),
std::back_inserter(res));
for (std::pair<bg_point, color> const& p : res)
std::cout << bg::wkt(p.first) << std::endl;
}
}
The program above prints the following line two times:
POINT(7 3)
This is the only one red point which intersects the box.
Original answer (if you indeed want to modify the internal structure of the R-tree):
What you want to do is not supported from the public R-tree interface. You'd have to play with the internals which might change in the future.
Here is a thread explaining how you can write a visitor to traverse the R-tree nodes.
Using your own node types is harder. You'd have to:
add new node tag like this
specialize internal and leaf nodes (adding members you like in nodes) and all other required classes for this tag like in this file
implement your own R-tree parameters type, e.g. based on bgi::rstar, like this
specialize bgi::detail::rtree::options_type in order to tell the R-tree what nodes should be used for your parameters type like this
See also this node implementation used for R-tree testing. This is a node which can throw an exception on construction. It's used to test exception-safety.

Using unordered_map to store key-value pairs in STL

I have to store some data in my program as described below.
The data is high dimensional coordinates and the number of points in those coordinates. Following would be a simple example (with coordinate dimension 5):
coordinate # of points
(3, 5, 3, 5, 7) 6
(6, 8, 5, 8, 9) 4
(4, 8, 6, 7, 9) 3
Please note that even if I use 5 dimensions as an example, the actual problem is of 20 dimensions. The coordinates are always integers.
I want to store this information in some kind of data structure. The first thing that comes to my mind is a hash table. I tried unordered_map in STL. But cannot figure out how to use the coordinates as the key in unordered_map. Defining it as:
unordered_map<int[5], int> umap;
or,
unordered_map<int[], int> umap;
gives me a compilation error. What am I doing wrong?
unordered_map needs to know how to hash your coordinates. In addition, it needs a way to compare coordinates for equality.
You can wrap your coordinates in a class or struct and provide a custom operator == to compare coordinate points. Then you need to specialise std::hash to be able to use your Point struct as a key in unordered_map. While comparing coordinates for equality is fairly straightforward, it is up to you to decide how coordinates are hashed. The following is an overview of what you need to implement:
#include <vector>
#include <unordered_map>
#include <cmath>
class Point
{
std::vector<int> coordinates;
public:
inline bool operator == (const std::vector<int>& _other)
{
if (coordinates.size() != _other.size())
{
return false;
}
for (uint c = 0; c < coordinates.size(); ++c)
{
if (coordinates[c] != _other[c])
{
return false;
}
}
return true;
}
};
namespace std
{
template<>
struct hash<Point>
{
std::size_t operator() (const Point& _point) const noexcept
{
std::size_t hash;
// See https://www.boost.org/doc/libs/1_67_0/doc/html/hash/reference.html#boost.hash_combine
// for an example of hash implementation for std::vector.
// Using Boost just for this might be an overkill - you could use just the hash_combine code here.
return hash;
}
};
}
int main()
{
std::unordered_map<Point, int> points;
// Use points...
return 0;
}
In case you know how many coordinates you are going to have and you can name them like this
struct Point
{
int x1;
int x2;
int x3;
// ...
}
you could use a header-only hashing library I wrote exactly for this purpose. Your mileage may vary.
Hacky way
I've seen this being used in programming competitions for ease of use. You can convert the set of points to a string(concatenate each coordinate and separate them with a space or any other special character) and then use unordered_map<string, int>
unordered_map<string, int> map; int p[5] = {3, 5, 3, 5, 7};
string point = to_string(p[0]) + " " + to_string(p[1]) + " " to_string(p[2]) + " " to_string(p[3]) + " " to_string(p[4]);
map[point] = 6;