boost geometry polygon distance for inside point - c++

I am using boost::geometry to handle some geometrical tasks. I have two requirements that I need to cover:
Handle point -> polygon intersection (inside or not). This works great with boost::geometry::within so thats good
Get the distance of an arbitrary point to the closest edge of the polygon. While points outside of the polygon are handled correctly by boost::geometry::distance, however it seems that it considers polygons solid. So every point inside the polygon obviously has a distance of 0 to the polygon.
I tried experimenting with inner/outer stuff and was wondering if there is a possbility to get the distance for both inside and outside points of a polygon.

In case where point is inside polygon you may speed your code up using comparable_distance instead of distance algorithm. You don't need to calculate the exact distance for every segment-point pair. Find the nearest segment of polygon to the given point using comparable_distance and then calculate the real distance using distance algorithm.
auto distance = std::numeric_limits<float>::max();
if(boost::geometry::within(pt, mPolygon))
{
Segment nearestSegment;
boost::geometry::for_each_segment(mPolygon,
[&distance, &pt, &nearestSegment](const auto& segment)
{
double cmpDst = boost::geometry::comparable_distance(segment,pt);
if (cmpDst < distance)
{
distance = cmpDst;
nearestSegment = segment; // UPDATE NEAREST SEGMENT
}
});
// CALCULATE EXACT DST
distance = boost::geometry::distance(nearestSegment,pt);
} else {
distance = boost::geometry::distance(pt, mPolygon);
}

I have decided to use the following approach which seems to provide the right results so far:
const TPolygonPoint pt{ x, y };
auto distance = std::numeric_limits<float>::max();
if(boost::geometry::within(pt, mPolygon)) {
boost::geometry::for_each_segment(mPolygon, [&distance, &pt](const auto& segment) {
distance = std::min<float>(distance, boost::geometry::distance(segment, pt));
});
} else {
distance = boost::geometry::distance(pt, mPolygon);
}
return distance;
If anyone knows a faster or nicer way, please leave a comment :)

For best performances you should use an RTree with boost::geometry::index. Creating the RTree has a cost, but then computing the ditance of a point to any of the (multi)polygon ring will be much fast. Example code:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <iostream>
#include <vector>
int main()
{
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<double, 2, bg::cs::cartesian> point;
typedef bg::model::polygon<point> polygon;
point p{ 0, 0 };
// create some polygon and fill it with data
polygon poly;
double a = 0;
double as = bg::math::two_pi<double>() / 100;
for (int i = 0; i < 100; ++i, a += as)
{
double c = cos(a);
double s = sin(a);
poly.outer().push_back(point{10 * c, 10 * s});
poly.inners().resize(1);
poly.inners()[0].push_back(point{5 * c, 5 * s});
}
// make sure it is valid
bg::correct(poly);
// create rtree containing objects of type bg::model::pointing_segment
typedef bg::segment_iterator<polygon const> segment_iterator;
typedef std::iterator_traits<segment_iterator>::value_type segment_type;
bgi::rtree<segment_type, bgi::rstar<4> > rtree(bg::segments_begin(poly),
bg::segments_end(poly));
// get 1 nearest segment
std::vector<segment_type> result;
rtree.query(bgi::nearest(p, 1), std::back_inserter(result));
BOOST_ASSERT(!result.empty());
std::cout << bg::wkt(result[0]) << ", " << bg::distance(p, result[0]) << std::endl;
return 0;
}

You can directly use boost::geometry::distance if you add an inner boundary to the polygon coinciding with the outer boundary [Polygon Concept].
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
namespace bg = boost::geometry;
int main() {
typedef bg::model::point<int, 2, bg::cs::cartesian> point_t;
typedef bg::model::polygon<point_t> polygon_t;
polygon_t poly1;
bg::append (poly1.outer(), point_t (1, -1));
bg::append (poly1.outer(), point_t (1, 1));
bg::append (poly1.outer(), point_t (-1, 1));
bg::append (poly1.outer(), point_t (-1, -1));
bg::append (poly1.outer(), point_t (1, -1));
poly1.inners().resize (1);
bg::append (poly1.inners()[0], point_t (1, -1));
bg::append (poly1.inners()[0], point_t (1, 1));
bg::append (poly1.inners()[0], point_t (-1, 1));
bg::append (poly1.inners()[0], point_t (-1, -1));
bg::append (poly1.inners()[0], point_t (1, -1));
point_t myPoint (0, 0);
std::cout << "Minimal distance: " << bg::distance (poly1, myPoint) << std::endl;
std::cout << "Is within: " << bg::within (myPoint, poly1) << std::endl;
return 0;
}
-> Will return:
Minimal distance: 1
Is within: 0
However, if you do that, points strictly inside the polygon will be considered to lie 'outside' the polygon by boost::geometry::within. If you want both functionalities, you can maintain two separate polygons- one with an inner boundary and one without.

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.

How can we get all the points stored in boost polygon

I am trying to iterate over all the points in the boost polygon.
Is there an API to handle this.?
Here is a simple example of setting and retrieving the BOOST polygon vertex coordinates:
#include <boost/geometry.hpp>
namespace bg = boost::geometry;
typedef bg::model::d2::point_xy<double> boost_point;
typedef bg::model::polygon<boost_point> boost_polygon;
[...]
//setting vertices
boost_polygon poly;
bg::append(poly.outer(), boost_point(-1, -1));
bg::append(poly.outer(), boost_point(-1, 1));
bg::append(poly.outer(), boost_point( 1, 1));
bg::append(poly.outer(), boost_point( 1, -1));
bg::append(poly.outer(), boost_point(-1, -1));
//getting the vertices back
for(auto it = boost::begin(boost::geometry::exterior_ring(poly)); it != boost::end(boost::geometry::exterior_ring(poly)); ++it)
{
double x = bg::get<0>(*it);
double y = bg::get<1>(*it);
//use the coordinates...
}
http://www.boost.org/doc/libs/1_62_0/libs/polygon/doc/gtl_polygon_concept.htm
template <typename T> point_iterator_type begin_points(const T& polygon)
Expects a model of polygon. Returns the begin iterator over the range of points that correspond to vertices of the polygon.
template <typename T> point_iterator_type end_points(const T& polygon)
Expects a model of polygon. Returns the end iterator over the range of points that correspond to vertices of the polygon.
You can also shorten the last loop from Olek's answer with
for( const auto& point : poly.outer() )
{
double x = point.x();
double y = point.y();
//use the coordinates...
}

boost::geometry Most efficient way of measuring max/min distance of a point to a polygon ring

I have been using boost::geometry library in a program, mostly for handling polygon objects.
I am now trying to optimize my code to scale better with larger polygons. One my functions checks for a given polygon and a given point (usually inside the polygon) the minimum and maximum distance between the point and polygon outer ring.
I do it by looping on the polygon edges:
polygon pol;
point myPoint;
double min = 9999999, max = 0;
for(auto it1 = boost::begin(bg::exterior_ring(pol)); it1 != boost::end(bg::exterior_ring(pol)); ++it1){
double distance = bg::distance(*it1, myPoint);
if(max < distance)
max = distance;
if(min > distance)
min = distance;
}
I am hoping that there are algorithms faster than this one, linear in the polygon number of edges. Is there such a thing already inside the boost::geometry library?
I'd suggest you can use the builtin strategies for finding the minimum distance between the polygon and the point:
Live On Coliru
#include <boost/geometry.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/io/io.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/algorithms/distance.hpp>
namespace bg = boost::geometry;
using point = bg::model::d2::point_xy<double>;
using polygon = bg::model::polygon<point>;
int main() {
polygon pol;
boost::geometry::read_wkt(
"POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3)"
"(4.0 2.0, 4.2 1.4, 4.8 1.9, 4.4 2.2, 4.0 2.0))", pol);
point myPoint(7, 7);
double min = 9999999, max = 0;
std::cout << "Minimal distance: " << bg::distance(pol, myPoint);
}
Prints
Minimal distance: 4.71699
Further hints:
You should consider ranking the distances first using comparable_distance. As you can see the sample there suggests looping over all the sampled distances... so I don't think the library has a better offering at this time.
More sophisticated algorithms are planned, of which a number may be related to this problem:
http://boost-geometry.203548.n3.nabble.com/distance-between-geometries-td4025549.html
mailing list thread http://lists.boost.org/geometry/2013/08/2446.php
another here http://lists.boost.org/geometry/2013/09/2494.php
Note also that Boost Geometry Index has a related predicate comparable_distance_far but it's not exposed as of yet.
Summary
You can improve at least a bit by using comparable_distance here for now.
Features have been planned and it looks like there is a good chance that requesting them on the mailing list/Boost Trac will help getting them there.
For best performances you should use an RTree with boost::geometry::index. Creating the RTree has a cost, but then computing the ditance of a point to any of the (multi)polygon ring will be much faster. Example code:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <iostream>
#include <vector>
int main()
{
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<double, 2, bg::cs::cartesian> point;
typedef bg::model::polygon<point> polygon;
point p{ 0, 0 };
// create some polygon and fill it with data
polygon poly;
double a = 0;
double as = bg::math::two_pi<double>() / 100;
for (int i = 0; i < 100; ++i, a += as)
{
double c = cos(a);
double s = sin(a);
poly.outer().push_back(point{10 * c, 10 * s});
poly.inners().resize(1);
poly.inners()[0].push_back(point{5 * c, 5 * s});
}
// make sure it is valid
bg::correct(poly);
// create rtree containing objects of type bg::model::pointing_segment
typedef bg::segment_iterator<polygon const> segment_iterator;
typedef std::iterator_traits<segment_iterator>::value_type segment_type;
bgi::rtree<segment_type, bgi::rstar<4> > rtree(bg::segments_begin(poly),
bg::segments_end(poly));
// get 1 nearest segment
std::vector<segment_type> result;
rtree.query(bgi::nearest(p, 1), std::back_inserter(result));
BOOST_ASSERT(!result.empty());
std::cout << bg::wkt(result[0]) << ", " << bg::distance(p, result[0]) << std::endl;
return 0;
}

How to calculate distance from point o filled/unfilled rectangle

I am visualising different sorts of geometries using Qt 5.
There I have a QRect that is either visualised as filled or not.
Now I want to calculate the distance of a QPoint to that rectangle using boost::geometry.
A point within the rectangle should have a distance of 0 when filled, and the distance to the next line when not filled.
Since the documentation of Box does not mention that it is a shape I thought I could use it for this case and adapted the Box concept to QRect.
The following example does not work though, since a Box is treated as shape and therefore always "filled".
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <QtCore/QPoint>
#include <QtCore/QRect>
BOOST_GEOMETRY_REGISTER_POINT_2D_GET_SET(QPoint, int, boost::geometry::cs::cartesian, x, y, setX, setY);
namespace boost { namespace geometry {
namespace traits
{
template <> struct tag<QRect> { typedef box_tag type; };
template <> struct point_type<QRect> { typedef QPoint type; };
template <std::size_t Index, std::size_t Dimension>
struct indexed_access<QRect, Index, Dimension>
{
typedef typename geometry::coordinate_type<QRect>::type coordinate_type;
static inline coordinate_type get(const QRect &r)
{
if (Index == boost::geometry::min_corner)
return geometry::get<Dimension>(r.topLeft());
else
return geometry::get<Dimension>(r.bottomRight());
}
};
}
}}
double distance(const QPoint &p, const QRect &r, const bool filled)
{
if (filled && r.contains(p))
return 0.0;
else
return boost::geometry::distance(p, r);
}
int main()
{
QRect r(QPoint(0, 0), QPoint(20, 10));
QPoint p(5, 5); // whithin rect
// 0, instead of 5
std::cout << "not filled: " << distance(p, r, false) << '\n';
// 0, as expected
std::cout << "filled: " << distance(p, r, true) << '\n';
}
Run g++ -Wall -O2 -fPIC main.cpp -I/usr/include/qt -lQtCore to build this on Linux.
I could of course use the LineString for the not filled case, though then there would be dynamic allocations.
Unless I create a manualy adaption which uses an underlying QRect, which would be quite some work.
How do I best tackle this issue?
Indeed you're right line-string is required because Box implies a filled shape. Same thing for polygons, actually, in my quick test.
You could of course create a fake "holey" polygon that has an edge of some small width. But that's cheating and certainly less efficient
Indeed, you can use linestring here:
Live On Coliru
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
using namespace boost::geometry;
int main()
{
using Point = model::d2::point_xy<double>;
using Rect = model::linestring<Point>;
Rect rect;
rect.insert(rect.end(), {
Point { 0, 0 },
Point { 10, 0 },
Point { 10, 20 },
Point { 0, 20 },
Point { 0, 0 },
});
std::cout << "distance point within: " << distance(rect, Point(5, 5)) << '\n'; // 0
std::cout << "distance point not within: " << distance(rect, Point(15, 5)) << '\n'; // 5
}
Which prints
distance point within: 5
distance point not within: 5
I don't see any reason to believe that the linestring is less efficient than the polygon (it's basically the same as just the outer ring of a polygon).
However, indeed box testing might be faster. I suggest you profile it. If it's faster, just use the box in case the shape is known to be "filled" and a linestring otherwise.
A relative easy way to support non-filled QRect is to use the LineString concept.
To avoid overhead of a allocations std::array could be used.
Based on the intial code, the following parts need to be added:
#include <array>
using RectLineString = std::array<QPoint, 5>;
BOOST_GEOMETRY_REGISTER_LINESTRING(RectLineString)
double distance(const QPoint &p, const QRect &r, const bool filled)
{
if (filled && r.contains(p))
return 0.0;
else
{
RectLineString rls;
fillRectLineString(rls, rect);
return boost::geometry::distance(p, rls);
}
}
What fillrectLineString should look like depends on how you want to handle the issue that QRect::bottomRight() returns QPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1).
So I provide two versions here:
// bottomRight() is QPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1)
void fillRectLineString1(RectLineString &rls, const QRect &rect)
{
rls[0] = rect.topLeft();
rls[1] = rect.topRight();
rls[2] = rect.bottomRight();
rls[3] = rect.bottomLeft();
rls[4] = rect.topLeft();
}
// bottomRight() is QPoint(rect.x() + rect.width(), rect.y() + rect.height())
void fillRectLineString2(RectLineString &rls, const QRect &rect)
{
rls[0] = QPoint(rect.x(), rect.y());
rls[1] = QPoint(rect.x() + rect.width(), rect.y());
rls[2] = QPoint(rect.x() + rect.width(), rect.y() + rect.height());
rls[3] = QPoint(rect.x(), rect.y() + rect.height());
rls[4] = QPoint(rect.x(), rect.y());
}

Boost::Geometry - Find area of 2d polygon in 3d space?

I'm trying to get area of 2d polygon in 3d space. Is there any way to do this by Boost::Geometry? Here is my implementation, but it returns 0 all the time:
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/io/wkt/wkt.hpp>
namespace bg = boost::geometry;
typedef bg::model::point<double, 3, bg::cs::cartesian> point3d;
int main()
{
bg::model::multi_point<point3d> square;
bg::read_wkt("MULTIPOINT((0 0 0), (0 2 0), (0 2 2), (0 0 2), (0 0 0))", square);
double area = bg::area(square);
std::cout << "Area: " << area << std::endl;
return 0;
}
UPD: Actually, I have the same issue with the simple 2d multi point square:
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/io/wkt/wkt.hpp>
namespace bg = boost::geometry;
typedef bg::model::point<double, 2, bg::cs::cartesian> point2d;
int main()
{
bg::model::multi_point<point2d> square;
bg::read_wkt("MULTIPOINT((0 0), (2 0), (2 2), (0 2))", square);
double area = bg::area(square);
std::cout << "Area: " << area << std::endl;
return 0;
}
Here is the result:
$ ./test_area
Area: 0
UPD: Looks like area calculation in the boost::geometry availabe only for the 2 dimensional polygons.
I wouldn't expect a collection of points to have an area. You would need the equivalent of a model::polygon<poind3d> but that does not appear to be supported at the moment.
If the points are guaranteed to be co-planar and the segment do not intersect each other, you could decompose the polygons as a series of triangles and compute the area with a little bit of linear-algebra, based on the following formula for the area of a triangle:
In case of non-convex polygons, the sum of the areas need to be adapted to subtract areas outside the polygon. The easiest way to achieve this is by using signed areas for the triangles, including positive contributions from right-hand triangles, and negative contributions from left-hand triangles:
Note that there seems to be some plans to include a cross_product implementation in Boost, but it doesn't appear to be included as of version 1.56. The following replacement should do the trick for your use-case:
point3d cross_product(const point3d& p1, const point3d& p2)
{
double x = bg::get<0>(p1);
double y = bg::get<1>(p1);
double z = bg::get<2>(p1);
double u = bg::get<0>(p2);
double v = bg::get<1>(p2);
double w = bg::get<2>(p2);
return point3d(y*w-z*v, z*u-x*w, x*v-y*u);
}
point3d cross_product(const bg::model::segment<point3d>& p1
, const bg::model::segment<point3d>& p2)
{
point3d v1(p1.second);
point3d v2(p2.second);
bg::subtract_point(v1, p1.first);
bg::subtract_point(v2, p2.first);
return cross_product(v1, v2);
}
The area can then be computed with something such as:
// compute the are of a collection of 3D points interpreted as a 3D polygon
// Note that there are no checks as to whether or not the points are
// indeed co-planar.
double area(bg::model::multi_point<point3d>& polygon)
{
if (polygon.size()<3) return 0;
bg::model::segment<point3d> v1(polygon[1], polygon[0]);
bg::model::segment<point3d> v2(polygon[2], polygon[0]);
// Compute the cross product for the first pair of points, to handle
// shapes that are not convex.
point3d n1 = cross_product(v1, v2);
double normSquared = bg::dot_product(n1, n1);
if (normSquared > 0)
{
bg::multiply_value(n1, 1.0/sqrt(normSquared));
}
// sum signed areas of triangles
double result = 0.0;
for (size_t i=1; i<polygon.size(); ++i)
{
bg::model::segment<point3d> v1(polygon[0], polygon[i-1]);
bg::model::segment<point3d> v2(polygon[0], polygon[i]);
result += bg::dot_product(cross_product(v1, v2), n1);
}
result *= 0.5;
return abs(result);
}
I am not familiar with the geometry section of boost, but with my knowledge of geometry, I can say that it would not be much different in 3D than 2D. Although there might be something in boost already, You could write your own method that does this fairly easily.
EDIT:
da code monkey pointed out that the shoelace formula would be more efficient in this way, because it is less complicated, and faster.
Original Idea below:
To calculate this, I would first tessellate the polygon into triangles, because any polygon can be split up into a number of triangles. I would take each of these triangles, and calculate the area of each of them. To do this in 3d space, the same concepts apply. To get the base, you take △ABC and arbitrarily assign —AB as base, —BC as height, and —CA as hypotenuse. Just do (—AB*—BC)/2 . Just add up the areas of each triangle.
I do not know if boost has a built in tessellate method, and this would be fairly difficult to implement in c++, but you would probably want to create a triangle fan of some sort. (NOTE: this only applies to convex polygons). If you do have a concave polygon, you should look into this: http://www.cs.unc.edu/~dm/CODE/GEM/chapter.html I will leave putting this into c++ as an exercise, but the process is fairly simple.