Нужно ли мне проходить иерархию rtree boost для достижения максимальной эффективности?

После некоторого чтения я узнал, что обход иерархии, хотя и возможный, официально не поддерживается в boost rtree. У меня есть несколько разных вариантов использования, в которых я могу обойтись без обхода иерархии, но я не уверен в эффективности. Поэтому я ищу совета о том, как работает boost rtree внутри и каковы лучшие практики для этих случаев использования.

случай 1 - у меня есть 2 дерева, и я хочу отфильтровать эти два дерева для элементов, которые пересекаются хотя бы с одним элементом из другого дерева. У меня есть код ниже, который делает это и дает мне результаты, которые я хочу:

    std::set<int> results2;
    std::vector<rtree3d_item> tempResults, results1;
    std::vector<rtree3d_item>::iterator it;
    tree1->query(bgi::satisfies(
        [&](const rtree3d_item& item) {
            tempResults.clear();
            tree2->query(bgi::intersects(item.first), std::back_inserter(tempResults));
            for (it = tempResults.begin(); it < tempResults.end(); it++)
            {
                results2.insert(it->second);
            }
            return tempResults.size() > 0;
        }
    ), std::back_inserter(results1));

Хотя я получаю желаемые результаты, подход кажется неоптимальным. Лямбда-выражение, которое я передаю в satisfies(), запускается один раз для каждого конечного узла в дереве1. Если бы я мог пройти по иерархии дерева, я мог бы протестировать большие блоки родительских узлов и исключить большие куски дерева1 и сделать процесс намного более эффективным. Почти как дерево1, являющееся древовидной структурой, бесполезно.

случай 2 - я просто хочу запросить rtree для всех элементов, которые пересекаются со сферой. Я делаю это с предикативным лямбда-выражением:

bgi::satisfies(
        [=](const rtree3d_item &item) {
            return bg::distance(item.first, cpt) < radius;
        })

Это выражение запускается один раз для каждого конечного узла rtree внутри, что снова кажется расточительным. Если я проведу этот тест для родительских узлов, я смогу исключить несколько листовых узлов за один раз.

Похоже, я сталкивался с той же ситуацией всякий раз, когда проверял rtree на соответствие условию - (если ограничивающая рамка не удовлетворяет условию, любая ограничивающая рамка, содержащаяся в ней, также не удовлетворяет условию). Я имею в виду, какой смысл иметь «древовидную» структуру, если я собираюсь постоянно тестировать все конечные узлы? Почему boost официально не поддерживает способы обхода иерархии дерева?

Я нашел источник, в котором говорится о неподдерживаемых неофициальных способах (ссылка) сделать это, но я хочу попробовать официальные, поддерживаемые способы оптимизации моего кода.

К вашему сведению, мой опыт работы с C++ ограничен, поэтому приветствуются любые советы, и спасибо!


person Ranjeeth Mahankali    schedule 22.08.2019    source источник
comment
Вы можете связать эти внешние источники. Они окажутся очень полезными для тех, кто отвечает, а также для людей, столкнувшихся с похожими проблемами, как у вас.   -  person sehe    schedule 25.08.2019
comment
Я добавил ссылку.   -  person Ranjeeth Mahankali    schedule 25.08.2019


Ответы (1)


Почему boost официально не поддерживает способы обхода иерархии дерева?

Угадывание:

  • они реализовывали примитивы высокого уровня с дружественным API. Исключение нижних уровней в задокументированном интерфейсе дает гораздо больше гибкости для повторения дизайна этих интерфейсов, не создавая проблем для пользователей библиотеки. Таким образом, конечный результат будет строго лучше, больше низкоуровневых интерфейсов, которые можно будет задокументировать после стабилизации.

  • Семантика обхода будет тесно связана со стратегией балансировки/структурирования. Это означает, что было бы сложно понять/задокументировать значение порядка обхода во всех случаях, что, вероятно, было бы источником ошибки. Отсутствие документирования сигнализирует об этом пользователю (он может использовать его, но на свой страх и риск)


Случай 1

Согласовано. Я бы сказал, что в идеале вы должны выполнить BFS для первого дерева и запросить пересечения со вторым деревом. Таким образом, вы можете быстро удалить «участки» дерева (поддеревья), которые не представляют интереса.

Основываясь на коде, приведенном здесь разработчиком библиотеки, я придумал грубый, минимальный посетитель:

namespace rtree = bgi::detail::rtree;

template <typename Predicate, typename Value, typename Options, typename Box, typename Allocators>
struct BFSQuery : public rtree::visitor<Value, typename Options::parameters_type, Box, Allocators, typename Options::node_tag, true>::type
{
    typedef typename rtree::internal_node<Value, typename Options::parameters_type, Box, Allocators, typename Options::node_tag>::type internal_node;
    typedef typename rtree::leaf<Value, typename Options::parameters_type, Box, Allocators, typename Options::node_tag>::type leaf;

    inline BFSQuery(Predicate const& p) : pr(p) {}

    inline void operator()(internal_node const& n) {
        for (auto&& [bounds, node] : rtree::elements(n))
            if (pr(bounds))
                rtree::apply_visitor(*this, *node);
    }

    inline void operator()(leaf const& n) {
        for (auto& item : rtree::elements(n))
            if (pr(item)) results.insert(&item);
    }

    Predicate const& pr;

    std::set<Value const*> results;
};

Примечание: один случайный выбор заключался в дедупликации результатов с использованием std::set, что имеет побочный эффект, заключающийся в том, что результаты будут в неопределенном порядке («порядок адресов», если хотите).

Сам алгоритм может быть довольно простым, используя этого посетителя:

template <typename TreeA, typename TreeB, typename F>
void tree_insersects(TreeA const& a, TreeB const& b, F action) {
    using V = rtree::utilities::view<TreeA>;
    V av(a);

    auto const pred = [&b](auto const& bounds) {
        return bgi::qbegin(b, bgi::intersects(bounds)) != bgi::qend(b);
    };

    BFSQuery<
      decltype(pred),
      typename V::value_type,
      typename V::options_type,
      typename V::box_type,
      typename V::allocators_type
    > vis(pred);

    av.apply_visitor(vis);

    auto tr = av.translator();
    for (auto* hit : vis.results)
        action(tr(*hit));
}

Обратите внимание, что он максимально общий.

Используй это:

Жить на Coliru

int main() {
    using Box = bg::model::box<bg::model::d2::point_xy<int> >;

    // generate some boxes with nesting
    bgi::rtree<Box, bgi::rstar<5>> a;
    for (auto [k,l] : { std::pair(0, 1), std::pair(-1, 2) }) {
        std::generate_n(bgi::insert_iterator(a), 10,
            [k,l,i=1]() mutable { Box b{ {i+k,i+k}, {i+l,i+l} }; i+=2; return b; });
    }

    // another simple tree to intersect with
    bgi::rtree<Box, bgi::quadratic<16> > b;
    b.insert({ {9,9}, {12,12} });
    b.insert({ {-9,-9}, {1,2} });

    Demo::tree_insersects(a, b, [](auto& value) {
        std::cout << "intersects: " << bg::dsv(value) << "\n";
    });
}

Принты (порядок может меняться):

intersects: ((1, 1), (2, 2))
intersects: ((0, 0), (3, 3))
intersects: ((11, 11), (12, 12))
intersects: ((10, 10), (13, 13))
intersects: ((12, 12), (15, 15))
intersects: ((6, 6), (9, 9))
intersects: ((8, 8), (11, 11))
intersects: ((9, 9), (10, 10))

Случай 2

Я думаю, вы можете добиться этого с помощью стандартного предиката запроса:

for (auto& value : boost::make_iterator_range(bgi::qbegin(a, bgi::intersects(sphere)), {})) {
    std::cout << "intersects: " << bg::dsv(value) << "\n";
}

Смотрите Прямой эфир на Coliru


ПОЛНЫЙ СПИСОК Дело №1

Алгоритма tree_insersects для потомков

Жить на Coliru

#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/geometry/index/detail/utilities.hpp>
#include <boost/geometry/index/predicates.hpp>
#include <iostream>

namespace bg = boost::geometry;
namespace bgi = bg::index;

namespace Demo {
    namespace rtree = bgi::detail::rtree;

    template <typename Predicate, typename Value, typename Options, typename Box, typename Allocators>
    struct BFSQuery : public rtree::visitor<Value, typename Options::parameters_type, Box, Allocators, typename Options::node_tag, true>::type
    {
        typedef typename rtree::internal_node<Value, typename Options::parameters_type, Box, Allocators, typename Options::node_tag>::type internal_node;
        typedef typename rtree::leaf<Value, typename Options::parameters_type, Box, Allocators, typename Options::node_tag>::type leaf;

        inline BFSQuery(Predicate const& p) : pr(p) {}

        inline void operator()(internal_node const& n) {
            for (auto&& [bounds, node] : rtree::elements(n)) {
                if (pr(bounds))
                    rtree::apply_visitor(*this, *node);
            }
        }

        inline void operator()(leaf const& n) {
            for (auto& item : rtree::elements(n))
                if (pr(item)) results.insert(&item);
        }

        Predicate const& pr;

        std::set<Value const*> results;
    };

    template <typename TreeA, typename TreeB, typename F> void tree_insersects(TreeA const& a, TreeB const& b, F action) {
        using V = rtree::utilities::view<TreeA>;
        V av(a);

        auto const pred = [&b](auto const& bounds) {
            return bgi::qbegin(b, bgi::intersects(bounds)) != bgi::qend(b);
        };

        BFSQuery<
          decltype(pred),
          typename V::value_type,
          typename V::options_type,
          typename V::box_type,
          typename V::allocators_type
        > vis(pred);

        av.apply_visitor(vis);

        auto tr = av.translator();
        for (auto* hit : vis.results)
            action(tr(*hit));
    }
}

int main() {
    using Box = bg::model::box<bg::model::d2::point_xy<int> >;

    // generate some boxes with nesting
    bgi::rtree<Box, bgi::rstar<5>> a;
    for (auto [k,l] : { std::pair(0, 1), std::pair(-1, 2) }) {
        std::generate_n(bgi::insert_iterator(a), 10,
            [k,l,i=1]() mutable { Box b{ {i+k,i+k}, {i+l,i+l} }; i+=2; return b; });
    }

    // another simple tree to intersect with
    bgi::rtree<Box, bgi::quadratic<16> > b;
    b.insert({ {9,9}, {12,12} });
    b.insert({ {-9,-9}, {1,2} });

    Demo::tree_insersects(a, b, [](auto& value) {
        std::cout << "intersects: " << bg::dsv(value) << "\n";
    });
}
person sehe    schedule 25.08.2019
comment
Спасибо за такой подробный ответ! Очень помогает! - person Ranjeeth Mahankali; 25.08.2019
comment
Дополнительный вопрос - для случая 2 созданная вами сфера представляет собой многоугольник с 4 точками (квадрат), верно? но это не дало бы мне тех же результатов, что и проверка реальной сферы (или круга в 2d). Или я что-то упускаю? - person Ranjeeth Mahankali; 26.08.2019
comment
У меня не было настроения делать лучшее приближение сферы (слишком много времени читал документы). Вы можете предотвратить такое несоответствие, предоставив свой код, поэтому я могу просто скопировать/вставить. - person sehe; 26.08.2019
comment
Я не аппроксимировал сферу многоугольником/многогранником. В моем случае мне действительно нужно проверить сферу без каких-либо приближений, поэтому я использовал функцию boost:geometry::distance, чтобы проверить, находится ли поле в пределах радиуса. Поскольку аппроксимация на самом деле не вариант для меня, я просто буду использовать ту же стратегию BFS и для запроса сферы. Спасибо ! - person Ranjeeth Mahankali; 26.08.2019
comment
Вы можете использовать подход ограничивающей рамки и отсеять ложные срабатывания. - person sehe; 26.08.2019