2

So far, when I want to count how many elements in my R-tree satisfy a specific spatial query, it boils down to running the query, collecting the matches and then counting them, roughly as follow:

std::vector<my_type> results;
rtree_ptr->query(bgi::intersects(query_box), std::back_inserter(results));
int nbElements = results.size();

Is there a better way, i.e. a way to directly count without retrieving the actual elements? I haven't found anything to do that but who knows. (I'm building my tree with the packing algorithm, in case it has any relevance.)

My motivation is that I noticed that the speed of my queries depend on the number of matches. If there are 0 matches, the query is more or less instantaneous ; if there are 10 000 matches, it takes several seconds. Since it's possible to determine very fast whether there are any matches, it seems that traversing the tree is extremely fast (at least in the index I made) ; it is collecting all the results that makes the queries slower in case of many matches. Since I'm not interested in collecting but simply counting (at least for some queries), it would be awesome if I could just skip the collecting.

Paul R
  • 208,748
  • 37
  • 389
  • 560
jerorx
  • 568
  • 6
  • 19

2 Answers2

3

You can use a function output iterator:

size_t cardinality = 0; // number of matches in set
auto count_only = boost::make_function_output_iterator([&cardinality] (Tree::value_type const&) { ++cardinality; });

Use it like this:

C++11 using a lambda

Live On Coliru

#include <boost/function_output_iterator.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/index/rtree.hpp>

namespace bgi = boost::geometry::index;
using point = boost::geometry::model::d2::point_xy<int, boost::geometry::cs::cartesian>;
using box = boost::geometry::model::box<point>;

int main()
{
    using Tree = bgi::rtree<box, bgi::rstar<32> >;
    Tree tree;

    size_t cardinality = 0; // number of matches in set
    auto count_only = boost::make_function_output_iterator([&cardinality] (Tree::value_type const&) { ++cardinality; });

    box query_box;
    tree.query(bgi::intersects(query_box), count_only);

    int nbElements = cardinality;

    return nbElements;
}

C++03 using a function object

For C++ you can replace the lambda with a (polymorphic!) function object:

struct count_only_f {
    count_only_f(size_t& card) : _cardinality(&card) { }

    template <typename X>
    void operator()(X) const {
        ++(*_cardinality);
    }

  private:
    size_t *_cardinality;
};

// .... later:
boost::function_output_iterator<count_only_f> count_only(cardinality);

C++03 using Boost Phoenix

I would consider this a good place to use Boost Phoenix:

#include <boost/phoenix.hpp>
// ...

size_t cardinality = 0; // number of matches in set
tree.query(bgi::intersects(query_box), boost::make_function_output_iterator(++boost::phoenix::ref(cardinality)));

Or, more typically with namespace aliases:

#include <boost/phoenix.hpp>
// ...

size_t cardinality = 0; // number of matches in set
tree.query(bgi::intersects(query_box), make_function_output_iterator(++phx::ref(cardinality)));
sehe
  • 374,641
  • 47
  • 450
  • 633
  • As a result of a late brainwave I added another, probably more convenient answer (postponed my bath for that) :) – sehe Mar 25 '15 at 19:58
3

I had a late brainwave. Even better than using function_output_iterator could be using the boost::geometry::index query_iterators.

In principle, it will lead to exactly the same behaviour with slightly simpler code:

box query_box;
auto r = boost::make_iterator_range(bgi::qbegin(tree, bgi::intersects(query_box)), {});
// in c++03, spell out the end iterator: bgi::qend(tree)

size_t nbElements = boost::distance(r);

NOTE: size() is not available because the query_const_iterators are not of the random-access category.

But it may be slightly more comfortable to combine. Say, if you wanted an additional check per item, you'd use standard library algorithms like:

size_t matching = std::count_if(r.begin(), r.end(), some_predicate);

I think the range-based solution is somewhat more flexible (the same code can be used to achieve other algorithms like partial_sort_copy or std::transform which would be hard to fit into the output-iterator idiom from my earlier answer).

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633