boost rtree of box gives wrong intersection with segment - c++

Boost rtree gives wrong intersection result for some intersection with segment queries.
In this case the bounding box is a y-planar 10x10 square at y=0. I'm querying with a z-aligned line from (2, 1, 0) to (2, 1, 10). What's interesting is that if I use a box for query instead of a segment then it works as expected. This behavior is also present when the box is not planar, just move the min corner to (0, -5, 0) and it still happens.
Am I using this wrong or is it a bug in boost?
Edit: have tried this on Boost 1.56 and 1.59.
#include <vector>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/segment.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <vector>
#include <iterator>
#include <memory>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<double, 3, bg::cs::cartesian> point_def;
typedef bg::model::box<point_def> box;
typedef bg::model::segment<point_def> segment;
typedef std::pair<box, size_t> tri_box;
typedef bgi::rtree< tri_box, bgi::linear<8>> tree_type;
using namespace std;
TEST(boost_rtree, cant_intersect_box_with_segment) {
vector<tri_box> buff(1);
buff[0].first = box{point_def{0, 0, 0}, point_def{10, 0, 10}};
buff[0].second = 1;
tree_type tree(buff);
segment query{point_def{2, 1, 0}, point_def{2, 1, 10}};
// box query{point_def{2, 1, 0}, point_def{2, 1, 10}};
vector<tri_box> out;
size_t count = tree.query(bgi::intersects(query), back_inserter(out));
ASSERT_EQ(0, count); // fails here
ASSERT_EQ(0, out.size());
}
Edit: issue is being moved to boost mailing list: lists.boost.org/geometry/2015/09/3472.php

As unlikely as it seems, this appears to me to be a bug.
The first version that even compiles this is Boost 1.56. All previous versions fail with
BOOST_MPL_ASSERT_MSG
(
false, NOT_OR_NOT_YET_IMPLEMENTED_FOR_THIS_GEOMETRY_TYPE
, (types<Geometry>)
);
But, even though the code is compiled, it does not seem to be correct...: the intersects call that underlies the query predicate itself returns "false positive" it seems.
Much simplified: Live On Coliru
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/segment.hpp>
namespace bg = boost::geometry;
typedef bg::model::point<int, 3, bg::cs::cartesian> point;
typedef bg::model::box<point> box;
typedef bg::model::segment<point> segment;
int main() {
box y0rect = box{point{0, 0, 0}, point{10, 0, 10}};
segment seg{point{2, 1, 0}, point{2, 1, 10}};
bg::correct(y0rect);
bg::correct(seg);
assert(!bg::intersects(seg, y0rect));
}
UPDATE
Interestingly, it seems to work correctly sometimes for 2d. I'm not sure the outcome isn't simply undefined...
Live On Coliru
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/segment.hpp>
namespace bg = boost::geometry;
typedef bg::model::point<int, 4, bg::cs::cartesian> point;
typedef bg::model::box<point> box;
typedef bg::model::segment<point> segment;
int main() {
box y0rect = box{point{0, 0}, point{10, 10}};
bg::correct(y0rect);
{
segment seg{point{12, 0}, point{20, 10}};
bg::correct(seg);
assert(!bg::intersects(seg, y0rect));
}
{
segment seg{point{2, 0}, point{8, 6}};
bg::correct(seg);
assert(bg::intersects(seg, y0rect));
}
{
segment seg{point{2, 0}, point{18, 6}};
bg::correct(seg);
assert(bg::intersects(seg, y0rect)); // OOPS BREAKS?
}
}

Related

what is boost::geometry::correct doing in this case?

The following code generates the output I expect:
MULTILINESTRING((5 5,4 4),(2 2,1 1))
However, if I remove the call to boost::geometry::correct() it returns the incorrect result:
MULTILINESTRING((5 5,1 1))
Code below:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/multi/geometries/multi_linestring.hpp>
#include <iostream>
namespace bg = boost::geometry;
namespace bgm = boost::geometry::model;
using point = bgm::point<double, 2, bg::cs::cartesian>;
using polygon = bgm::polygon<point>;
using polyline = bgm::linestring<point>;
using polylines = bgm::multi_linestring<polyline>;
int main()
{
polygon poly = {
{ {1,5}, {5,5}, {5,1}, {1,1} },
{ {2,4}, {2,2}, {4,2}, {4,4} }
};
polyline line = { {6,6},{0,0} };
bg::correct(poly);
polylines result;
bg::intersection(poly, line, result);
std::cout << bg::wkt(result) << "\n";
return 0;
}
The geometry defined in the above looks like the following. The red line segment and blue polygon with a hole should intersect to the green line segments.
I defined the vertices of the hole in counter-clockwise order as it is typical in computational geometry for holes to have reverse orientation of non-holes (It's also typical for non-holes to have counter-clockwise orientation but boost::geometry seems to default to clockwise). If I flip the orientation of the hole it does not fix the problem (although I do get a different wrong result). I am not sure what else correct could be doing.
boost::correct() is closing both the inner and outer polygons.
That is, the following returns the expected output:
namespace bg = boost::geometry;
namespace bgm = boost::geometry::model;
using point = bgm::point<double, 2, bg::cs::cartesian>;
using polygon = bgm::polygon<point>;
using polyline = bgm::linestring<point>;
using polylines = bgm::multi_linestring<polyline>;
int main()
{
polygon poly = {
{ {1,5}, {5,5}, {5,1}, {1, 1}, {1,5}},
{ {2,4}, {2,2}, {4,2}, {4,4}, {2,4}}
};
polyline line = { {6,6},{0,0} };
polylines result;
bg::intersection(poly, line, result);
std::cout << bg::wkt(result) << "\n";
return 0;
}

What define's Boost's svg_mapper scaling and translation?

This code:
#include <fstream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
namespace bg = boost::geometry;
int main()
{
std::ofstream svg ( "test.svg" );
boost::geometry::svg_mapper<bg::model::d2::point_xy<double>, true, double> mapper ( svg, 6000, 3000 );
bg::model::polygon<bg::model::d2::point_xy<double>> square{
{{0, 0}, {0, 1000}, {1000, 1000}, {1000, 0}, {0, 0}}};
const std::string style{"fill-opacity:1.0;fill:rgb(128,128,128);stroke:rgb(0,0,0);stroke-width:5"};
mapper.add ( square );
mapper.map ( square, style, 1.0 );
}
Produces this svg:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill-rule="evenodd"><path d="M 1500,3000 L 1500,0 L 4500,0 L 4500,3000 L 1500,3000 z " style="fill-opacity:1.0;fill:rgb(128,128,128);stroke:rgb(0,0,0);stroke-width:5"/></g>
</svg>
The following conversions happen from the input polygon to the mapped svg geometries:
(0, 0) -> (1500,3000)
(0, 1000) -> (1500,0)
(1000, 1000) -> (4500,0)
(1000, 0) -> (4500,3000)
(0, 0) -> (1500,3000)
Staring at it a bit you see there is some transformation applied, something like this:
+1500 in x
+3000 in y
3x scale in x
-3x scale in y
My question is - What drives that transformation and can I prevent it? And if I can't prevent it, can I retrieve it or calculate it myself?
Reason being is I'm producing many complex SVG's and would like them to all be in the same frame. So if there is a circle at pixels (10,10) in one, I would like all the images to be of the same size with the circle in the exact same location. I tried to accomplish this with viewBox but the scaling and translation was too hard to predict to keep the images consistent.
svg_mapper calculates a bounding box from all add-ed geometries.
Then, a map_transformer is used to scale down to the desired width/height.
Contrary to what you might expect, add doesn't do anything besides expanding the bounding box. Likewise, after the first map call, no other add has any effect on the bounding-box used for the transformations.
In other words, you can use some kind of fixed bounding box, add only that, and then map your geometries into that "canvas":
Demo
#include <fstream>
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
namespace bg = boost::geometry;
using V = /*long*/ double;
using P = bg::model::d2::point_xy<V>;
using B = bg::model::box<P>;
int main()
{
auto verify = [](auto& g) {
if (std::string r; !bg::is_valid(g, r)) {
std::cout << "Correcting " << r << "\n";
bg::correct(g);
}
};
V side = 1000;
bg::model::polygon<P> square{
{{0, 0}, {0, side}, {side, side}, {side, 0}, {0, 0}},
};
verify(square);
std::array steps {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,};
for (unsigned i = 0; i < steps.size(); ++i) {
{
std::ofstream svg("test" + std::to_string(i) + ".svg");
bg::svg_mapper<P, true, V> mapper(svg, 400, 400);
auto clone = square;
V ofs = (steps[i] / 5. - 1.0) * side;
for (auto& p : boost::make_iterator_range(bg::points_begin(clone), bg::points_end(clone)))
bg::add_point(p, P{ofs, ofs});
std::cout << i << ": " << bg::wkt(square) << " " << bg::wkt(clone) << "\n";
mapper.add(B{{-side, -side}, {2 * side, 2 * side}});
//mapper.add(square); // no effect, already within bounding box
//mapper.add(clone); // no effect, already within bounding box
mapper.map(square, "fill-opacity:0.1;fill:rgb(128,0,0)", 1.0);
mapper.map(clone, "fill-opacity:0.1;fill:rgb(0,0,128)", 1.0);
}
}
}
Which creates a series of svgs that I can show as a poor man's animation to show that the positioning of the square is constant:

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.

GEOS OverlayOp intersection operation

I am using GEOS 3.6.2 to compute an intersection between two polygons. I was able to construct my polygons, but when I try to compute the intersection it won't work.
Compiling my program in Debug mode, I get the error message:
The inferior stopped because it received a signal from the operating
system.
Signal name : SIGSEG
Signal meaning : Segmentation fault
Any idea where I'm wrong?
Here is my code:
#include <geos/geom/Polygon.h>
#include <geos/geom/LinearRing.h>
#include <geos/geom/CoordinateSequenceFactory.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/geom/Geometry.h>
#include <geos/operation/overlay/OverlayOp.h>
#include <iostream>
#include <array>
////////////////////////////////////////////////////////////////////////////////
geos::geom::Polygon* MakePoly(std::vector<std::vector<int>> const& polyCoords)
{
geos::geom::GeometryFactory* factory = geos::geom::GeometryFactory::create().get();
geos::geom::CoordinateSequence* temp = factory->getCoordinateSequenceFactory()->create((std::size_t) 0, 0);
std::vector<std::vector<int>>::const_iterator it_x = polyCoords.begin();
int size = it_x->size();
for (int i=0; i<size; i++)
{
temp->add(geos::geom::Coordinate(polyCoords[0][i], polyCoords[1][i]));
}
geos::geom::LinearRing *shell=factory->createLinearRing(temp);
//NULL in this case could instead be a collection of one or more holes
//in the interior of the polygon
return factory->createPolygon(shell,NULL);
}
////////////////////////////////////////////////////////////////////////////////
int main()
{
// Create geometry.
std::vector<std::vector<int>> polyCoords1 = {
{1, 1, 2, 2, 1, 1, 4, 5, 4, 1},
{1, 2, 2, 4, 4, 5, 5, 3, 1, 1}
};
geos::geom::Polygon* poly1 = MakePoly(polyCoords1);
std::vector<std::vector<int>> polyCoords2 = {
{4, 4, 6, 6, 4},
{1, 5, 5, 1, 1}
};
geos::geom::Polygon* poly2 = MakePoly(polyCoords2);
// Actually perform the operation.
geos::operation::overlay::OverlayOp intersection(poly1, poly2);
// Extracting the geometry of the intersection (position of the error).
geos::geom::Geometry* intersectionGeo = intersection.getResultGeometry( geos::operation::overlay::OverlayOp::OpCode::opINTERSECTION );
std::cout<<intersectionGeo->getArea()<<std::endl;
}
The problem in your code is getting the GeometryFactory pointer.
geos::geom::GeometryFactory::create() returns a smart pointer (std::unique_ptr) so after this line:
geos::geom::GeometryFactory* factory = geos::geom::GeometryFactory::create().get();
The unique_ptr returned by create is disposed.
Change that line with:
geos::geom::GeometryFactory::Ptr factory = geos::geom::GeometryFactory::create();
And the code works.

Boost::Geometry: how to join intersecting polygons in a multi_polygon?

So I want to join all interrelated poligons in a multi_polygon. How to do such thing?
We have such image (of one green multi_polygon) which we want to optimize (we can see yellow doted lines - result of simplification that apparently was performed on each poligon of multi_polygon not on multi_polygon in general):
And here is compilable code to generate such image:
#include <iostream>
#include <fstream>
#include <boost/assign.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/extensions/io/svg/svg_mapper.hpp>
template <typename Geometry1, typename Geometry2>
void create_svg(std::string const& filename, Geometry1 const& a, Geometry2 const& b)
{
typedef typename boost::geometry::point_type<Geometry1>::type point_type;
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<point_type> mapper(svg, 400, 400);
mapper.add(a);
mapper.add(b);
mapper.map(a, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
mapper.map(b, "opacity:0.8;fill:none;stroke:rgb(255,128,0);stroke-width:4;stroke-dasharray:1,7;stroke-linecap:round");
}
boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > make_point(int x, int y)
{
boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > return_item;
boost::geometry::model::d2::point_xy<double> p1(x, y);
boost::geometry::model::d2::point_xy<double> p2(x-1, y);
boost::geometry::model::d2::point_xy<double> p3(x-1, y-1);
boost::geometry::model::d2::point_xy<double> p4(x, y-1);
boost::geometry::append( return_item, p1);
boost::geometry::append( return_item, p2);
boost::geometry::append( return_item, p3);
boost::geometry::append( return_item, p4);
return return_item;
}
int main()
{
// create a container for joined points structure
boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > output, simpl;
// join points one by one (because one day we would have many=))
output.push_back(make_point(1,1));
boost::geometry::correct(output);
output.push_back(make_point(2,1));
boost::geometry::correct(output);
output.push_back(make_point(3,1));
boost::geometry::correct(output);
output.push_back(make_point(4,1));
boost::geometry::correct(output);
output.push_back(make_point(5,1));
boost::geometry::correct(output);
output.push_back(make_point(2,2));
boost::geometry::correct(output);
output.push_back(make_point(3,2));
boost::geometry::correct(output);
output.push_back(make_point(5,2));
boost::geometry::correct(output);
output.push_back(make_point(5,5));
boost::geometry::correct(output);
// simplify joined structure
boost::geometry::simplify(output, simpl, 0.5);
// create an svg image
create_svg("make_envelope.svg", output, simpl );
}
requires at least boost 1.47.0 and 3 files from boost/geometry/extensions/io/svg/
what I need is simple: how to group interrelated poligons? In this case we shall get 2 poligons in our multy_poligon like shown here - red and green:
Update:
So I found this info on dissolve and created sample code that uses rings for cels creation:
#include <iostream>
#include <fstream>
#include <boost/assign.hpp>
//Boost
#include <boost/algorithm/string.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
#include <boost/foreach.hpp>
//Boost Geometry extensions (from trunk)
#include <boost/geometry/extensions/io/svg/svg_mapper.hpp>
template <typename Geometry1, typename Geometry2>
void create_svg(std::string const& filename, Geometry1 const& a, Geometry2 const& b)
{
typedef typename boost::geometry::point_type<Geometry1>::type point_type;
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<point_type> mapper(svg, 400, 400);
mapper.add(a);
mapper.add(b);
mapper.map(a, "fill-rule:nonzero;fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2;");
mapper.map(b, "opacity:0.8;fill:none;stroke:rgb(255,128,0);stroke-width:4;stroke-dasharray:1,7;stroke-linecap:round");
}
void make_point(int x, int y, boost::geometry::model::ring<boost::geometry::model::d2::point_xy<double> > & ring)
{
using namespace boost::assign;
ring +=
boost::geometry::model::d2::point_xy<double>(x-1, y-1),
boost::geometry::model::d2::point_xy<double>(x, y-1),
boost::geometry::model::d2::point_xy<double>(x, y),
boost::geometry::model::d2::point_xy<double>(x-1, y),
boost::geometry::model::d2::point_xy<double>(x-1, y-1);
}
int main()
{
using namespace boost::assign;
boost::geometry::model::ring<boost::geometry::model::d2::point_xy<double> > ring0, ring1,ring;
boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > outputw;
make_point(1, 1, ring) ;
make_point(2, 1, ring) ;
make_point(3, 1, ring) ;
make_point(4, 1, ring) ;
make_point(5, 1, ring) ;
make_point(2, 2, ring) ;
make_point(3, 2, ring) ;
make_point(5, 2, ring) ;
boost::geometry::model::ring<boost::geometry::model::d2::point_xy<double> > output;
boost::geometry::simplify(ring, output, 1);
// create an svg image
create_svg("make_envelope.svg", ring, output );
}
It returns such image of ring:
If we could use dissolve to turn it into poligon that would really solve some of my problems. But looks like currently we can not due to this compiler errors problem described here
What about using Qt. If you use a QPolygonF you can call unite which does exactly what you need. After unison you can extract the points and put them back into your boost container.
If qt is not an option take a look at the algorithms proposed here http://www.wykobi.com
You can use this function from boost library:
void union_(Geometry1 const & geometry1, Geometry2 const & geometry2, Collection & output_collection)
It takes two geometries and unites them if they have intersections or puts them into one collection (vector, deque, etc.) if not.