Search code examples
c++databaseboostboost-geometryr-tree

How can I use the Rtree of the Boost Library in C++?


I am a asked to write a function that takes a "LatLon" as input (LatLon is a class with 2 doubles: latitude and longitude) and returns the the ID (int) of the closest intersection to that position. I am given functions that return the location of any intersection, and also that return the distance between two positions. Because of "performance tests", my instructors have suggested me to store the locations of all Intersections in a R-Tree (from the boost library), where it would be faster to find the closest intersection, rather than iterating over all the intersections. However, I am just learning how R-Trees work, and I am having trouble in how to create one.

The problem is that I have seen several examples online where they create R-Trees, but what really confuses me is that they only use two arguments, rather than four, in one of the constructors. For example, they use:

bgi::rtree< value_t, bgi::quadratic<16> > rtree_;

where value_t is a pair of box and unsigned int, and the other argument is the size, however if I try to make:

bgi::rtree< point, bgi::quadratic<16>> intersectionRTree;

where point is a pair of unsigned int and LatLon, the compiler complains that i am not using the proper constructor, and that it should have four arguments instead of two.

I have read online, and find that I should be using this constructor:

rtree(parameters_type const &, indexable_getter const &, value_equal const &, allocator_type const &)

However, I don't understand the description of each argument, so I don't know how to use this constructor. So, can you please help me to understand what to do? And if possible, could you give me short example? Thank you very much.

This is the LatLon class. It is read only, so I cannot modify it:

class LatLon{

public:
LatLon(){}
explicit LatLon(float lat_, float lon_) : m_lat(lat_),m_lon(lon_){}

double lat() const { return m_lat; }
double lon() const { return m_lon; }

private:
float m_lat = std::numeric_limits<float>::quiet_NaN();
float m_lon = std::numeric_limits<float>::quiet_NaN();

friend class boost::serialization::access;
template<class Archive>void serialize(Archive& ar, unsigned int)
    { ar & m_lat & m_lon; }
};

std::ostream& operator<<(std::ostream& os,LatLon);
std::array<LatLon,4> bounds_to_corners(std::pair<LatLon,LatLon> bounds);

This is why I am trying to do:

#include "LatLon.h"
#include <string>
#include <vector>
#include <cmath>
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <algorithm>

namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
using namespace std;
typedef pair<unsigned,LatLon> point;

bgi::rtree<point, bgi::quadratic<16>> intersectionRTree;

for (unsigned intersection = 0; intersection < intersectionNumber; intersection++) 
{point intP = make_pair(intersection,getIntersectionPosition(intersection));
intersectionRTree.insert(intP);}

The function getIntersectionPosition returns a LatLon, intersectionNumber is the maximum number of intersections, and intersection is the index of a intersection.

The compiler gives me along error detail that only sends me to another files, but actually never tolds me where I am wrong.


Solution

  • In order to use a type in Boost.Geometry as one of the supported Geometries you have to adapt this type to one of the Boost.Geometry concepts. This is how you're telling the library how it can use objects of this type, how to access coordinates, which coordinate system and coordinate types are used, etc.

    In the case of Point: http://www.boost.org/doc/libs/1_63_0/libs/geometry/doc/html/geometry/reference/concepts/concept_point.html

    For convenience Boost.Geometry provides macros doing it for you in the most typical cases: http://www.boost.org/doc/libs/1_63_0/libs/geometry/doc/html/geometry/reference/adapted/register.html

    You example is slightly problematic, your Point doesn't define setters, only getters so some of the operations won't work with it, e.g. it cannot be stored in bg::model::box needed by spatial query (e.g. bgi::intersects()) or knn query in non-cartesian coordinate system (bgi::nearest()) because the coordinates may be normalized internally. So in order to use it directly you'd have to do more things than are usually needed.

    So if I were you I'd simply store Point of type bg::model::point in the R-tree and convert LatLon into it before insertion and then back after performing a query.

    But if you really want to use LatLon class with the library you could:

    Btw, if you don't want to implement your own IndexableGetter (3rd template parameter of the rtree) you have to put the Point in the std::pair as the First type. Below I assumed you want to use spherical_equatorial coordinate system. I also assumed the coordinate type is float because this is what you actually store inside LatLon in your example.

    #include <boost/geometry.hpp>
    #include <boost/geometry/geometries/register/point.hpp>
    #include <boost/geometry/index/rtree.hpp>
    #include <iostream>
    #include <limits>
    #include <vector>
    
    namespace bg = boost::geometry;
    namespace bgi = boost::geometry::index;
    
    class LatLon
    {
    public:
        LatLon(){}
        explicit LatLon(float lat_, float lon_) : m_lat(lat_),m_lon(lon_){}
    
        float lat() const { return m_lat; }
        float lon() const { return m_lon; }
    
    private:
        float m_lat = std::numeric_limits<float>::quiet_NaN();
        float m_lon = std::numeric_limits<float>::quiet_NaN();
    };
    
    struct MyLatLon
    {
        MyLatLon() {}
        MyLatLon(float lat_, float lon_) : ll(lat_, lon_){}
    
        float get_lat() const { return ll.lat(); }
        float get_lon() const { return ll.lon(); }
        void set_lat(float v) { ll = LatLon(v, ll.lon()); }
        void set_lon(float v) { ll = LatLon(ll.lat(), v); }
    
        LatLon ll;
    };
    
    BOOST_GEOMETRY_REGISTER_POINT_2D_GET_SET(MyLatLon, float,
                                             bg::cs::spherical_equatorial<bg::degree>,
                                             get_lon, get_lat, set_lon, set_lat)
    
    int main()
    {
        typedef std::pair<MyLatLon, unsigned> point_pair;
    
        bgi::rtree<point_pair, bgi::quadratic<16>> intersectionRTree;
    
        intersectionRTree.insert(std::make_pair(MyLatLon(0, 0), 0));
        intersectionRTree.insert(std::make_pair(MyLatLon(2, 2), 1));
    
        bg::model::box<MyLatLon> b(MyLatLon(1, 1), MyLatLon(3, 3));
        std::vector<point_pair> result1;
        intersectionRTree.query(bgi::intersects(b), std::back_inserter(result1));
        if (! result1.empty())
            std::cout << bg::wkt(result1[0].first) << std::endl;
    
        std::vector<point_pair> result2;
        intersectionRTree.query(bgi::nearest(MyLatLon(0, 1), 1), std::back_inserter(result2));
        if (! result2.empty())
            std::cout << bg::wkt(result2[0].first) << std::endl;
    }