Skip to content

Commit 3e65a36

Browse files
filter: Add FilterSphere (#16)
* Add FilterSphere * Use point converter instead of scalar converter * Use std::clamp * Remove unused ConverterNoOpScalar Co-authored-by: Tilmann Zäschke <[email protected]>
1 parent 93ac9c8 commit 3e65a36

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ There are numerous ways to improve performance. The following list gives an over
444444
4) **Use non-box query shapes**. Depending on the use case it may be more suitable to use a custom filter for querier.
445445
For example:
446446

447-
`tree.for_each(callback, MySphereFIlter(center, radius, tree.converter()));`
447+
`tree.for_each(callback, FilterSphere(center, radius, tree.converter()));`
448448

449449
5) **Use a different data converter**. The default converter of the PH-Tree results in a reasonably fast index.
450450
Its biggest advantage is that it provides lossless conversion from floating point coordinates to PH-Tree coordinates

phtree/common/filter.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "flat_array_map.h"
2424
#include "flat_sparse_map.h"
2525
#include "tree_stats.h"
26+
#include <algorithm>
2627
#include <cassert>
2728
#include <cmath>
2829
#include <functional>
@@ -150,6 +151,73 @@ class FilterAABB {
150151
const CONVERTER converter_;
151152
};
152153

154+
155+
/*
156+
* The sphere filter can be used to query a point tree for a sphere.
157+
*/
158+
template <
159+
typename CONVERTER = ConverterIEEE<3>,
160+
typename DISTANCE = DistanceEuclidean<3>>
161+
class FilterSphere {
162+
using KeyExternal = typename CONVERTER::KeyExternal;
163+
using KeyInternal = typename CONVERTER::KeyInternal;
164+
using ScalarInternal = typename CONVERTER::ScalarInternal;
165+
using ScalarExternal = typename CONVERTER::ScalarExternal;
166+
167+
public:
168+
FilterSphere(
169+
const KeyExternal& center,
170+
const ScalarExternal& radius,
171+
CONVERTER converter = CONVERTER(),
172+
DISTANCE distance_function = DISTANCE())
173+
: center_external_{center}
174+
, center_internal_{converter.pre(center)}
175+
, radius_{radius}
176+
, converter_{converter}
177+
, distance_function_{distance_function} {};
178+
179+
template <typename T>
180+
[[nodiscard]] bool IsEntryValid(const KeyInternal& key, const T& value) const {
181+
KeyExternal point = converter_.post(key);
182+
return distance_function_(center_external_, point) <= radius_;
183+
}
184+
185+
/*
186+
* Calculate whether AABB encompassing all possible points in the node intersects with the
187+
* sphere.
188+
*/
189+
[[nodiscard]] bool IsNodeValid(const KeyInternal& prefix, int bits_to_ignore) const {
190+
// we always want to traverse the root node (bits_to_ignore == 64)
191+
192+
if (bits_to_ignore >= (MAX_BIT_WIDTH<ScalarInternal> - 1)) {
193+
return true;
194+
}
195+
196+
ScalarInternal node_min_bits = MAX_MASK<ScalarInternal> << bits_to_ignore;
197+
ScalarInternal node_max_bits = ~node_min_bits;
198+
199+
KeyInternal closest_in_bounds;
200+
for (size_t i = 0; i < prefix.size(); ++i) {
201+
// calculate lower and upper bound for dimension for given node
202+
ScalarInternal lo = prefix[i] & node_min_bits;
203+
ScalarInternal hi = prefix[i] | node_max_bits;
204+
205+
// choose value closest to center for dimension
206+
closest_in_bounds[i] = std::clamp(center_internal_[i], lo, hi);
207+
}
208+
209+
KeyExternal closest_point = converter_.post(closest_in_bounds);
210+
return distance_function_(center_external_, closest_point) <= radius_;
211+
}
212+
213+
private:
214+
const KeyExternal center_external_;
215+
const KeyExternal center_internal_;
216+
const ScalarExternal radius_;
217+
const CONVERTER converter_;
218+
const DISTANCE distance_function_;
219+
};
220+
153221
} // namespace improbable::phtree
154222

155223
#endif // PHTREE_COMMON_FILTERS_H

phtree/common/filter_test.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,30 @@
2020

2121
using namespace improbable::phtree;
2222

23+
TEST(PhTreeFilterTest, FilterSphereTest) {
24+
FilterSphere<ConverterNoOp<2, scalar_64_t>, DistanceEuclidean<2>> filter{{5, 3}, 5};
25+
// root is always valid
26+
ASSERT_TRUE(filter.IsNodeValid({0, 0}, 63));
27+
// valid because node encompasses the circle
28+
ASSERT_TRUE(filter.IsNodeValid({1, 1}, 10));
29+
// valid because circle encompasses the node
30+
ASSERT_TRUE(filter.IsNodeValid({5, 5}, 2));
31+
// valid because circle encompasses the node AABB
32+
ASSERT_TRUE(filter.IsNodeValid({7, 7}, 1));
33+
// valid because circle touches the edge of the node AABB
34+
ASSERT_TRUE(filter.IsNodeValid({5, 9}, 1));
35+
// valid because circle cuts edge of node AABB
36+
ASSERT_TRUE(filter.IsNodeValid({12, 7}, 3));
37+
ASSERT_TRUE(filter.IsNodeValid({10, 7}, 2));
38+
// invalid because node is just outside the circle
39+
ASSERT_FALSE(filter.IsNodeValid({5, 10}, 1));
40+
ASSERT_FALSE(filter.IsNodeValid({12, 12}, 3));
41+
42+
ASSERT_TRUE(filter.IsEntryValid({3, 7}, nullptr));
43+
ASSERT_TRUE(filter.IsEntryValid({5, 8}, nullptr));
44+
ASSERT_FALSE(filter.IsEntryValid({3, 8}, nullptr));
45+
}
46+
2347
TEST(PhTreeFilterTest, BoxFilterTest) {
2448
FilterAABB<ConverterNoOp<2, scalar_64_t>> filter{{3, 3}, {7, 7}};
2549
// root is always valid

0 commit comments

Comments
 (0)