MauiKit Image Tools

geolocation/kdtree.cpp
1/*
2 * file: KDTree.hpp
3 * author: J. Frederico Carvalho
4 *
5 * This is an adaptation of the KD-tree implementation in rosetta code
6 * https://rosettacode.org/wiki/K-d_tree
7 *
8 * It is a reimplementation of the C code using C++. It also includes a few
9 * more queries than the original, namely finding all points at a distance
10 * smaller than some given distance to a point.
11 *
12 */
13
14#include <algorithm>
15#include <cmath>
16#include <functional>
17#include <iterator>
18#include <limits>
19#include <memory>
20#include <vector>
21
22#include <QDebug>
23
24#include "kdtree.hpp"
25
26KDNode::KDNode() = default;
27
28KDNode::KDNode(const point_t &pt, const size_t &idx_, const KDNodePtr &left_,
29 const KDNodePtr &right_) {
30 x = pt;
31 index = idx_;
32 left = left_;
33 right = right_;
34}
35
36KDNode::KDNode(const pointIndex &pi, const KDNodePtr &left_,
37 const KDNodePtr &right_) {
38 x = pi.first;
39 index = pi.second;
40 left = left_;
41 right = right_;
42}
43
44KDNode::~KDNode() = default;
45
46double KDNode::coord(const size_t &idx) { return x.at(idx); }
47KDNode::operator bool() { return (!x.empty()); }
48KDNode::operator point_t() { return x; }
49KDNode::operator size_t() { return index; }
50KDNode::operator pointIndex() { return pointIndex(x, index); }
51
52KDNodePtr NewKDNodePtr() {
53 KDNodePtr mynode = std::make_shared< KDNode >();
54 return mynode;
55}
56
57inline double dist2(const point_t &a, const point_t &b) {
58 double distc = 0;
59 for (size_t i = 0; i < a.size(); i++) {
60 double di = a.at(i) - b.at(i);
61 distc += di * di;
62 }
63 return distc;
64}
65
66inline double dist2(const KDNodePtr &a, const KDNodePtr &b) {
67 return dist2(a->x, b->x);
68}
69
70inline double dist(const point_t &a, const point_t &b) {
71 return std::sqrt(dist2(a, b));
72}
73
74inline double dist(const KDNodePtr &a, const KDNodePtr &b) {
75 return std::sqrt(dist2(a, b));
76}
77
78comparer::comparer(size_t idx_) : idx{idx_} {};
79
80inline bool comparer::compare_idx(const pointIndex &a, //
81 const pointIndex &b //
82) {
83 return (a.first.at(idx) < b.first.at(idx)); //
84}
85
86inline void sort_on_idx(const pointIndexArr::iterator &begin, //
87 const pointIndexArr::iterator &end, //
88 size_t idx) {
89 comparer comp(idx);
90 comp.idx = idx;
91
92 using std::placeholders::_1;
93 using std::placeholders::_2;
94
95 std::nth_element(begin, begin + std::distance(begin, end) / 2,
96 end, std::bind(&comparer::compare_idx, comp, _1, _2));
97}
98
99using pointVec = std::vector< point_t >;
100
101KDNodePtr KDTree::make_tree(const pointIndexArr::iterator &begin, //
102 const pointIndexArr::iterator &end, //
103 const size_t &length, //
104 const size_t &level //
105) {
106 if (begin == end) {
107 return NewKDNodePtr(); // empty tree
108 }
109
110 size_t dim = begin->first.size();
111
112 if (length > 1) {
113 sort_on_idx(begin, end, level);
114 }
115
116 auto middle = begin + (length / 2);
117
118 auto l_begin = begin;
119 auto l_end = middle;
120 auto r_begin = middle + 1;
121 auto r_end = end;
122
123 size_t l_len = length / 2;
124 size_t r_len = length - l_len - 1;
125
126 KDNodePtr left;
127 if (l_len > 0 && dim > 0) {
128 left = make_tree(l_begin, l_end, l_len, (level + 1) % dim);
129 } else {
130 left = leaf;
131 }
132 KDNodePtr right;
133 if (r_len > 0 && dim > 0) {
134 right = make_tree(r_begin, r_end, r_len, (level + 1) % dim);
135 } else {
136 right = leaf;
137 }
138
139 // KDNode result = KDNode();
140 return std::make_shared< KDNode >(*middle, left, right);
141}
142
143KDTree::KDTree(pointVec point_array) {
144
145 qDebug() << "CREATING KDETREE INSTANCE";
146
147 leaf = std::make_shared< KDNode >();
148 // iterators
149 pointIndexArr arr;
150 for (size_t i = 0; i < point_array.size(); i++) {
151 arr.push_back(pointIndex(point_array.at(i), i));
152 }
153
154 auto begin = arr.begin();
155 auto end = arr.end();
156
157 size_t length = arr.size();
158 size_t level = 0; // starting
159
160 root = KDTree::make_tree(begin, end, length, level);
161
162 m_empty = point_array.size();
163}
164
165KDNodePtr KDTree::nearest_( //
166 const KDNodePtr &branch, //
167 const point_t &pt, //
168 const size_t &level, //
169 const KDNodePtr &best, //
170 const double &best_dist //
171) {
172 double d, dx, dx2;
173
174 if (!bool(*branch)) {
175 return NewKDNodePtr(); // basically, null
176 }
177
178 point_t branch_pt(*branch);
179 size_t dim = branch_pt.size();
180
181 d = dist2(branch_pt, pt);
182 dx = branch_pt.at(level) - pt.at(level);
183 dx2 = dx * dx;
184
185 KDNodePtr best_l = best;
186 double best_dist_l = best_dist;
187
188 if (d < best_dist) {
189 best_dist_l = d;
190 best_l = branch;
191 }
192
193 size_t next_lv = (level + 1) % dim;
194 KDNodePtr section;
195 KDNodePtr other;
196
197 // select which branch makes sense to check
198 if (dx > 0) {
199 section = branch->left;
200 other = branch->right;
201 } else {
202 section = branch->right;
203 other = branch->left;
204 }
205
206 // keep nearest neighbor from further down the tree
207 KDNodePtr further = nearest_(section, pt, next_lv, best_l, best_dist_l);
208 if (!further->x.empty()) {
209 double dl = dist2(further->x, pt);
210 if (dl < best_dist_l) {
211 best_dist_l = dl;
212 best_l = further;
213 }
214 }
215 // only check the other branch if it makes sense to do so
216 if (dx2 < best_dist_l) {
217 further = nearest_(other, pt, next_lv, best_l, best_dist_l);
218 if (!further->x.empty()) {
219 double dl = dist2(further->x, pt);
220 if (dl < best_dist_l) {
221 best_dist_l = dl;
222 best_l = further;
223 }
224 }
225 }
226
227 return best_l;
228};
229
230// default caller
231KDNodePtr KDTree::nearest_(const point_t &pt) {
232 size_t level = 0;
233 // KDNodePtr best = branch;
234 double branch_dist = dist2(point_t(*root), pt);
235 return nearest_(root, // beginning of tree
236 pt, // point we are querying
237 level, // start from level 0
238 root, // best is the root
239 branch_dist); // best_dist = branch_dist
240};
241
242point_t KDTree::nearest_point(const point_t &pt) {
243 return point_t(*nearest_(pt));
244};
245size_t KDTree::nearest_index(const point_t &pt) {
246 return size_t(*nearest_(pt));
247};
248
249pointIndex KDTree::nearest_pointIndex(const point_t &pt) {
250 KDNodePtr Nearest = nearest_(pt);
251 return pointIndex(point_t(*Nearest), size_t(*Nearest));
252}
253
254bool KDTree::empty()
255{
256 return m_empty;
257}
258
259pointIndexArr KDTree::neighborhood_( //
260 const KDNodePtr &branch, //
261 const point_t &pt, //
262 const double &rad, //
263 const size_t &level //
264) {
265 double d, dx, dx2;
266
267 if (!bool(*branch)) {
268 // branch has no point, means it is a leaf,
269 // no points to add
270 return pointIndexArr();
271 }
272
273 size_t dim = pt.size();
274
275 double r2 = rad * rad;
276
277 d = dist2(point_t(*branch), pt);
278 dx = point_t(*branch).at(level) - pt.at(level);
279 dx2 = dx * dx;
280
281 pointIndexArr nbh, nbh_s, nbh_o;
282 if (d <= r2) {
283 nbh.push_back(pointIndex(*branch));
284 }
285
286 //
287 KDNodePtr section;
288 KDNodePtr other;
289 if (dx > 0) {
290 section = branch->left;
291 other = branch->right;
292 } else {
293 section = branch->right;
294 other = branch->left;
295 }
296
297 nbh_s = neighborhood_(section, pt, rad, (level + 1) % dim);
298 nbh.insert(nbh.end(), nbh_s.begin(), nbh_s.end());
299 if (dx2 < r2) {
300 nbh_o = neighborhood_(other, pt, rad, (level + 1) % dim);
301 nbh.insert(nbh.end(), nbh_o.begin(), nbh_o.end());
302 }
303
304 return nbh;
305};
306
307pointIndexArr KDTree::neighborhood( //
308 const point_t &pt, //
309 const double &rad) {
310 size_t level = 0;
311 return neighborhood_(root, pt, rad, level);
312}
313
314pointVec KDTree::neighborhood_points( //
315 const point_t &pt, //
316 const double &rad) {
317 size_t level = 0;
318 pointIndexArr nbh = neighborhood_(root, pt, rad, level);
319 pointVec nbhp;
320 nbhp.resize(nbh.size());
321 std::transform(nbh.begin(), nbh.end(), nbhp.begin(),
322 [](pointIndex x) { return x.first; });
323 return nbhp;
324}
325
326indexArr KDTree::neighborhood_indices( //
327 const point_t &pt, //
328 const double &rad) {
329 size_t level = 0;
330 pointIndexArr nbh = neighborhood_(root, pt, rad, level);
331 indexArr nbhi;
332 nbhi.resize(nbh.size());
333 std::transform(nbh.begin(), nbh.end(), nbhi.begin(),
334 [](pointIndex x) { return x.second; });
335 return nbhi;
336}
QStringView level(QStringView ifopt)
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:59:01 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.