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.
Related
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.
I have a set of 2D points. I need to find a minimum area ellipse enclosing all the points. Could someone give an idea of how the problem has to be tackled. For a circle it was simple. The largest distance between the center and the point. But for an ellipse its quite complicated which I do not know. I have to implement this in c++.
These don't go as far as giving you C++ code, but they include in-depth discussion of effective algorithms for what you need to do.
https://www.cs.cornell.edu/cv/OtherPdf/Ellipse.pdf
http://www.stsci.edu/~RAB/Backup%20Oct%2022%202011/f_3_CalculationForWFIRSTML/Gaertner%20&%20Schoenherr.pdf
The other answers here give approximation schemes or only provide links. We can do better.
Your question is addressed by the paper "Smallest Enclosing Ellipses -- Fast and Exact" by Gärtner and Schönherr (1997). The same authors provide a C++ implementation in their 1998 paper "Smallest Enclosing Ellipses -- An Exact and Generic Implementation in C++". This algorithm is implemented in a more usable form in CGAL here.
However, CGAL only provides the general equation for the ellipse, so we use a few transforms to get a parametric equation suitable for plotting.
All this is included in the implementation below.
Using WebPlotDigitizer to extract your data while choosing arbitrary values for the lengths of the axes, but preserving their aspect ratio, gives:
-1.1314123177813773 4.316368664322679
1.345680085331649 5.1848164974519015
2.2148682495160603 3.9139687117291504
0.9938150357523803 3.2732678860664475
-0.24524315569075128 3.0455750009876343
-1.4493153715482157 2.4049282977126376
0.356472958558844 0.0699802473037554
2.8166270295895384 0.9211630387547896
3.7889384901038987 -0.8484766720657362
1.3457654169794182 -1.6996053411290646
2.9287101489353287 -3.1919219373444463
0.8080480385572635 -3.990389523169913
0.46847074625686425 -4.008682890214516
-1.6521060324734327 -4.8415723146209455
Fitting this using the program below gives:
a = 3.36286
b = 5.51152
cx = 0.474112
cy = -0.239756
theta = -0.0979706
We can then plot this with gnuplot
set parametric
plot "points" pt 7 ps 2, [0:2*pi] a*cos(t)*cos(theta) - b*sin(t)*sin(theta) + cx, a*cos(t)*sin(theta) + b*sin(t)*cos(theta) +
cy lw 2
to get
Implementation
The code below does this:
// Compile with clang++ -DBOOST_ALL_NO_LIB -DCGAL_USE_GMPXX=1 -O2 -g -DNDEBUG -Wall -Wextra -pedantic -march=native -frounding-math main.cpp -lgmpxx -lmpfr -lgmp
#include <CGAL/Cartesian.h>
#include <CGAL/Min_ellipse_2.h>
#include <CGAL/Min_ellipse_2_traits_2.h>
#include <CGAL/Exact_rational.h>
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
typedef CGAL::Exact_rational NT;
typedef CGAL::Cartesian<NT> K;
typedef CGAL::Point_2<K> Point;
typedef CGAL::Min_ellipse_2_traits_2<K> Traits;
typedef CGAL::Min_ellipse_2<Traits> Min_ellipse;
struct EllipseCanonicalEquation {
double semimajor; // Length of semi-major axis
double semiminor; // Length of semi-minor axis
double cx; // x-coordinate of center
double cy; // y-coordinate of center
double theta; // Rotation angle
};
std::vector<Point> read_points_from_file(const std::string &filename){
std::vector<Point> ret;
std::ifstream fin(filename);
float x,y;
while(fin>>x>>y){
std::cout<<x<<" "<<y<<std::endl;
ret.emplace_back(x, y);
}
return ret;
}
// Uses "Smallest Enclosing Ellipses -- An Exact and Generic Implementation in C++"
// under the hood.
EllipseCanonicalEquation get_min_area_ellipse_from_points(const std::vector<Point> &pts){
// Compute minimum ellipse using randomization for speed
Min_ellipse me2(pts.data(), pts.data()+pts.size(), true);
std::cout << "done." << std::endl;
// If it's degenerate, the ellipse is a line or a point
assert(!me2.is_degenerate());
// Get coefficients for the equation
// r*x^2 + s*y^2 + t*x*y + u*x + v*y + w = 0
double r, s, t, u, v, w;
me2.ellipse().double_coefficients(r, s, t, u, v, w);
// Convert from CGAL's coefficients to Wikipedia's coefficients
// A*x^2 + B*x*y + C*y^2 + D*x + E*y + F = 0
const double A = r;
const double B = t;
const double C = s;
const double D = u;
const double E = v;
const double F = w;
// Get the canonical form parameters
// Using equations from https://en.wikipedia.org/wiki/Ellipse#General_ellipse
const auto a = -std::sqrt(2*(A*E*E+C*D*D-B*D*E+(B*B-4*A*C)*F)*((A+C)+std::sqrt((A-C)*(A-C)+B*B)))/(B*B-4*A*C);
const auto b = -std::sqrt(2*(A*E*E+C*D*D-B*D*E+(B*B-4*A*C)*F)*((A+C)-std::sqrt((A-C)*(A-C)+B*B)))/(B*B-4*A*C);
const auto cx = (2*C*D-B*E)/(B*B-4*A*C);
const auto cy = (2*A*E-B*D)/(B*B-4*A*C);
double theta;
if(B!=0){
theta = std::atan(1/B*(C-A-std::sqrt((A-C)*(A-C)+B*B)));
} else if(A<C){
theta = 0;
} else { //A>C
theta = M_PI;
}
return EllipseCanonicalEquation{a, b, cx, cy, theta};
}
int main(int argc, char** argv){
if(argc!=2){
std::cerr<<"Provide name of input containing a list of x,y points"<<std::endl;
std::cerr<<"Syntax: "<<argv[0]<<" <Filename>"<<std::endl;
return -1;
}
const auto pts = read_points_from_file(argv[1]);
const auto eq = get_min_area_ellipse_from_points(pts);
// Convert canonical equation for rotated ellipse to parametric based on:
// https://math.stackexchange.com/a/2647450/14493
std::cout << "Ellipse has the parametric equation " << std::endl;
std::cout << "x(t) = a*cos(t)*cos(theta) - b*sin(t)*sin(theta) + cx"<<std::endl;
std::cout << "y(t) = a*cos(t)*sin(theta) + b*sin(t)*cos(theta) + cy"<<std::endl;
std::cout << "with" << std::endl;
std::cout << "a = " << eq.semimajor << std::endl;
std::cout << "b = " << eq.semiminor << std::endl;
std::cout << "cx = " << eq.cx << std::endl;
std::cout << "cy = " << eq.cy << std::endl;
std::cout << "theta = " << eq.theta << std::endl;
return 0;
}
Not sure if I can prove it, but it seems to me that the optimal solution would be characterized by tangenting (at least) 3 of the points, while all the other points are inside the ellipse (think about it!). So if nothing else, you should be able to brute force it by checking all ~n^3 triplets of points and checking if they define a solution. Should be possible to improve on that by removing all points that would have to be strictly inside any surrounding ellipse, but I'm not sure how that could be done. Maybe by sorting the points by x and y coordinates and then doing something fancy.
Not a complete solution, but it's a start.
EDIT:
Unfortunately 3 points aren't enough to define an ellipse. But perhaps if you restrict it to the ellipse of the smallest area tangenting 3 points?
as Rory Daulton suggest you need to clearly specify the constraints of solution and removal of any will greatly complicates things. For starters assume this for now:
it is 2D problem
ellipse is axis aligned
center is arbitrary instead of (0,0)
I would attack this as standard genere and test problem with approximation search (which is hybrid between binary search and linear search) to speed it up (but you can also try brute force from start so you see if it works).
compute constraints of solution
To limit the search you need to find approximate placement position and size of the ellipse. For that you can use out-scribed circle for your points. It is clear that ellipse area will be less or equal to the circle and placement will be near by. The circle does not have to be necessarily the smallest one possible so we can use for example this:
find bounding box of the points
let the circle be centered to that bounding box and with radius be the max distance from its center to any of the points.
This will be O(n) complexity where n is number of your points.
search "all" the possible ellipses and remember best solution
so we need to find ellipse center (x0,y0) and semi-axises rx,ry while area = M_PI*rx*ry is minimal. With approximation search each variable has factor of O(log(m)) and each iteration need to test validity which is O(n) so final complexity would be O(n.log^4(m)) where m is average number of possible variations of each search parameter (dependent on accuracy and search constraints). With simple brute search it would be O(n.m^4) which is really scary especially for floating point where m can be really big.
To speed this up we know that the area of ellipse will be less then or equal to area of found circle so we can ignore all the bigger ellipses. The constrains to rx,ry can be derived from the aspect ratio of the bounding box +/- some reserve.
Here simple small C++ example using that approx class from link above:
//---------------------------------------------------------------------------
// input points
const int n=15; // number of random points to test
float pnt[n][2];
// debug bounding box
float box_x0,box_y0,box_x1,box_y1;
// debug outscribed circle
float circle_x,circle_y,circle_r;
// solution ellipse
float ellipse_x,ellipse_y,ellipse_rx,ellipse_ry;
//---------------------------------------------------------------------------
void compute(float x0,float y0,float x1,float y1) // cal with bounding box where you want your points will be generated
{
int i;
float x,y;
// generate n random 2D points inside defined area
Randomize();
for (i=0;i<n;i++)
{
pnt[i][0]=x0+(x1-x0)*Random();
pnt[i][1]=y0+(y1-y0)*Random();
}
// compute bounding box
x0=pnt[0][0]; x1=x0;
y0=pnt[0][1]; y1=y0;
for (i=0;i<n;i++)
{
x=pnt[i][0]; if (x0>x) x0=x; if (x1<x) x1=x;
y=pnt[i][1]; if (y0>y) y0=y; if (y1<y) y1=y;
}
box_x0=x0; box_x1=x1;
box_y0=y0; box_y1=y1;
// "outscribed" circle
circle_x=0.5*(x0+x1);
circle_y=0.5*(y0+y1);
circle_r=0.0;
for (i=0;i<n;i++)
{
x=pnt[i][0]-circle_x; x*=x;
y=pnt[i][1]-circle_y; y*=y; x+=y;
if (circle_r<x) circle_r=x;
}
circle_r=sqrt(circle_r);
// smallest area ellipse
int N;
double m,e,step,area;
approx ax,ay,aa,ab;
N=3; // number of recursions each one improves accuracy with factor 10
area=circle_r*circle_r; // solution will not be bigger that this
step=((x1-x0)+(y1-y0))*0.05; // initial position/size step for the search as 1/10 of avg bounding box size
for (ax.init( x0, x1,step,N,&e);!ax.done;ax.step()) // search x0
for (ay.init( y0, y1,step,N,&e);!ay.done;ay.step()) // search y0
for (aa.init(0.5*(x1-x0),2.0*circle_r,step,N,&e);!aa.done;aa.step()) // search rx
for (ab.init(0.5*(y1-y0),2.0*circle_r,step,N,&e);!ab.done;ab.step()) // search ry
{
e=aa.a*ab.a;
// is ellipse outscribed?
if (aa.a>=ab.a)
{
m=aa.a/ab.a; // convert to circle of radius rx
for (i=0;i<n;i++)
{
x=(pnt[i][0]-ax.a); x*=x;
y=(pnt[i][1]-ay.a)*m; y*=y;
// throw away this ellipse if not
if (x+y>aa.a*aa.a) { e=2.0*area; break; }
}
}
else{
m=ab.a/aa.a; // convert to circle of radius ry
for (i=0;i<n;i++)
{
x=(pnt[i][0]-ax.a)*m; x*=x;
y=(pnt[i][1]-ay.a); y*=y;
// throw away this ellipse if not
if (x+y>ab.a*ab.a) { e=2.0*area; break; }
}
}
}
ellipse_x =ax.aa;
ellipse_y =ay.aa;
ellipse_rx=aa.aa;
ellipse_ry=ab.aa;
}
//---------------------------------------------------------------------------
Even this simple example with only 15 points took around 2 seconds to compute. You can improve performance by adding heuristics like test only areas lower then circle_r^2 etc, or better select solution area with some math rule. If you use brute force instead of approximation search that expect the computation time could be even minutes or more hence the O(scary)...
Beware this example will not work for any aspect ratio of the points as I hardcoded the upper bound for rx,ry to 2.0*circle_r which may not be enough. Instead you can compute the upper bound from aspect ratio of the points and or condition that rx*ry<=circle_r^2...
There are also other ("faster") methods for example variation of CCD (cyclic coordinate descend) can be used. But such methods usually can not guarantee that optimal solution will be found or any at all ...
Here overview of the example output:
The dots are individual points from pnt[n], the gray dashed stuff are bounding box and used out-scribed circle. The green ellipse is found solution.
Code for MVEE (minimal volume enclosing ellipse) can be found here, and works even for non-centered and rotated ellipses:
https://github.com/chrislarson1/MVEE
My related code:
bool _mvee(const std::vector<cv::Point> & contour, cv::RotatedRect & ellipse, const float epsilon, const float lmc) {
std::vector<cv::Point> hull;
cv::convexHull(contour, hull);
mvee::Mvee B;
std::vector<std::vector<double>> X;
// speedup: the mve-ellipse on the convex hull should be the same theoretically as the one on the entire contour
for (const auto &points : hull) {
std::vector<double> p = {double(points.x), double(points.y)};
X.push_back(p); // speedup: the mve-ellipse on part of the points (e.g. one every 4) should be similar
}
B.compute(X, epsilon, lmc); // <-- call to the MVEE algorithm
cv::Point2d center(B.centroid()[0], B.centroid()[1]);
cv::Size2d size(B.radii()[0] * 2, B.radii()[1] * 2);
float angle = asin(B.pose()[1][0]) * 180 / CV_PI;
if (B.pose()[0][0] < 0) angle *= -1;
ellipse = cv::RotatedRect(center, size, angle);
if (std::isnan(ellipse.size.height)) {
LOG_ERR("pupil with nan size");
return false;
}
return true;
}
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;
}
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.
I have two transformers, a translation and a rotation as follows:
namespace bg = boost::geometry;
namespace trans = bg::strategy::transform;
trans::translate_transformer<point, point> translate(px, py);
trans::rotate_transformer<point, point, bg::radian> rotate(rz);
How do I combine them into one, so that I don't have to call bg::transform twice each time and use an intermediate variable?
Both translate and rotate are affine transformations, i.e., they can be represented using a matrix. Therefore, all you have to do is to create a new transformer whose matrix is equal to the product of the matrices of the two transforms.
trans::ublas_transformer<point, point, 2, 2> translateRotate(prod(rotate.matrix(), translate.matrix()));
Here is a full working example:
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/strategies/transform/matrix_transformers.hpp>
namespace bg = boost::geometry;
namespace trans = bg::strategy::transform;
typedef bg::model::d2::point_xy<double> point;
int main()
{
trans::translate_transformer<point, point> translate(0, 1);
trans::rotate_transformer<point, point, bg::degree> rotate(90);
trans::ublas_transformer<point, point, 2, 2> translateRotate(prod(rotate.matrix(), translate.matrix()));
point p;
translateRotate.apply(point(0, 0), p);
std::cout << bg::get<0>(p) << " " << bg::get<1>(p) << std::endl;
}
Be very careful regarding the order of the matrices in the multiplication. The example above first translates, then rotates.