/src/proj/src/quadtree.hpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ |
3 | | * Purpose: Implementation of quadtree building and searching functions. |
4 | | * Derived from shapelib, mapserver and GDAL implementations |
5 | | * Author: Even Rouault, <even.rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 1999-2008, Frank Warmerdam |
9 | | * Copyright (c) 2008-2020, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * Permission is hereby granted, free of charge, to any person obtaining a |
12 | | * copy of this software and associated documentation files (the "Software"), |
13 | | * to deal in the Software without restriction, including without limitation |
14 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
15 | | * and/or sell copies of the Software, and to permit persons to whom the |
16 | | * Software is furnished to do so, subject to the following conditions: |
17 | | * |
18 | | * The above copyright notice and this permission notice shall be included |
19 | | * in all copies or substantial portions of the Software. |
20 | | * |
21 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
22 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
24 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
26 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
27 | | * DEALINGS IN THE SOFTWARE. |
28 | | *****************************************************************************/ |
29 | | |
30 | | #ifndef QUADTREE_HPP |
31 | | #define QUADTREE_HPP |
32 | | |
33 | | #include "proj/util.hpp" |
34 | | |
35 | | #include <functional> |
36 | | #include <vector> |
37 | | |
38 | | //! @cond Doxygen_Suppress |
39 | | |
40 | | NS_PROJ_START |
41 | | |
42 | | namespace QuadTree { |
43 | | |
44 | | /* -------------------------------------------------------------------- */ |
45 | | /* If the following is 0.5, psNodes will be split in half. If it */ |
46 | | /* is 0.6 then each apSubNode will contain 60% of the parent */ |
47 | | /* psNode, with 20% representing overlap. This can be help to */ |
48 | | /* prevent small objects on a boundary from shifting too high */ |
49 | | /* up the hQuadTree. */ |
50 | | /* -------------------------------------------------------------------- */ |
51 | | constexpr double DEFAULT_SPLIT_RATIO = 0.55; |
52 | | |
53 | | /** Describe a rectangle */ |
54 | | struct RectObj { |
55 | | double minx = 0; /**< Minimum x */ |
56 | | double miny = 0; /**< Minimum y */ |
57 | | double maxx = 0; /**< Maximum x */ |
58 | | double maxy = 0; /**< Maximum y */ |
59 | | |
60 | | /* Returns whether this rectangle is contained by other */ |
61 | 0 | inline bool isContainedBy(const RectObj &other) const { |
62 | 0 | return minx >= other.minx && maxx <= other.maxx && miny >= other.miny && |
63 | 0 | maxy <= other.maxy; |
64 | 0 | } |
65 | | |
66 | | /* Returns whether this rectangles overlaps other */ |
67 | 0 | inline bool overlaps(const RectObj &other) const { |
68 | 0 | return minx <= other.maxx && maxx >= other.minx && miny <= other.maxy && |
69 | 0 | maxy >= other.miny; |
70 | 0 | } |
71 | | |
72 | | /* Returns whether this rectangles contains the specified point */ |
73 | 0 | inline bool contains(double x, double y) const { |
74 | 0 | return minx <= x && maxx >= x && miny <= y && maxy >= y; |
75 | 0 | } |
76 | | |
77 | | /* Return whether this rectangles is different from other */ |
78 | 0 | inline bool operator!=(const RectObj &other) const { |
79 | 0 | return minx != other.minx || miny != other.miny || maxx != other.maxx || |
80 | 0 | maxy != other.maxy; |
81 | 0 | } |
82 | | }; |
83 | | |
84 | | /** Quadtree */ |
85 | | template <class Feature> class QuadTree { |
86 | | |
87 | | struct Node { |
88 | | RectObj rect{}; /* area covered by this psNode */ |
89 | | |
90 | | /* list of shapes stored at this node. */ |
91 | | std::vector<std::pair<Feature, RectObj>> features{}; |
92 | | |
93 | | std::vector<Node> subnodes{}; |
94 | | |
95 | 0 | explicit Node(const RectObj &rectIn) : rect(rectIn) {} |
96 | | }; |
97 | | |
98 | | Node root{}; |
99 | | unsigned nBucketCapacity = 8; |
100 | | double dfSplitRatio = DEFAULT_SPLIT_RATIO; |
101 | | |
102 | | public: |
103 | | /** Construct a new quadtree with the global bounds of all objects to be |
104 | | * inserted */ |
105 | 0 | explicit QuadTree(const RectObj &globalBounds) : root(globalBounds) {} |
106 | | |
107 | | /** Add a new feature, with its bounds specified in featureBounds */ |
108 | 0 | void insert(const Feature &feature, const RectObj &featureBounds) { |
109 | 0 | insert(root, feature, featureBounds); |
110 | 0 | } |
111 | | |
112 | | #ifdef UNUSED |
113 | | /** Retrieve all features whose bounds intersects aoiRect */ |
114 | | void |
115 | | search(const RectObj &aoiRect, |
116 | | std::vector<std::reference_wrapper<const Feature>> &features) const { |
117 | | search(root, aoiRect, features); |
118 | | } |
119 | | #endif |
120 | | |
121 | | /** Retrieve all features whose bounds contains (x,y) */ |
122 | 0 | void search(double x, double y, std::vector<Feature> &features) const { |
123 | 0 | search(root, x, y, features); |
124 | 0 | } |
125 | | |
126 | | private: |
127 | 0 | void splitBounds(const RectObj &in, RectObj &out1, RectObj &out2) { |
128 | | // The output bounds will be very similar to the input bounds, |
129 | | // so just copy over to start. |
130 | 0 | out1 = in; |
131 | 0 | out2 = in; |
132 | | |
133 | | // Split in X direction. |
134 | 0 | if ((in.maxx - in.minx) > (in.maxy - in.miny)) { |
135 | 0 | const double range = in.maxx - in.minx; |
136 | |
|
137 | 0 | out1.maxx = in.minx + range * dfSplitRatio; |
138 | 0 | out2.minx = in.maxx - range * dfSplitRatio; |
139 | 0 | } |
140 | | |
141 | | // Otherwise split in Y direction. |
142 | 0 | else { |
143 | 0 | const double range = in.maxy - in.miny; |
144 | |
|
145 | 0 | out1.maxy = in.miny + range * dfSplitRatio; |
146 | 0 | out2.miny = in.maxy - range * dfSplitRatio; |
147 | 0 | } |
148 | 0 | } |
149 | | |
150 | | void insert(Node &node, const Feature &feature, |
151 | 0 | const RectObj &featureBounds) { |
152 | 0 | if (node.subnodes.empty()) { |
153 | | // If we have reached the max bucket capacity, try to insert |
154 | | // in a subnode if possible. |
155 | 0 | if (node.features.size() >= nBucketCapacity) { |
156 | 0 | RectObj half1; |
157 | 0 | RectObj half2; |
158 | 0 | RectObj quad1; |
159 | 0 | RectObj quad2; |
160 | 0 | RectObj quad3; |
161 | 0 | RectObj quad4; |
162 | |
|
163 | 0 | splitBounds(node.rect, half1, half2); |
164 | 0 | splitBounds(half1, quad1, quad2); |
165 | 0 | splitBounds(half2, quad3, quad4); |
166 | |
|
167 | 0 | if (node.rect != quad1 && node.rect != quad2 && |
168 | 0 | node.rect != quad3 && node.rect != quad4 && |
169 | 0 | (featureBounds.isContainedBy(quad1) || |
170 | 0 | featureBounds.isContainedBy(quad2) || |
171 | 0 | featureBounds.isContainedBy(quad3) || |
172 | 0 | featureBounds.isContainedBy(quad4))) { |
173 | 0 | node.subnodes.reserve(4); |
174 | 0 | node.subnodes.emplace_back(Node(quad1)); |
175 | 0 | node.subnodes.emplace_back(Node(quad2)); |
176 | 0 | node.subnodes.emplace_back(Node(quad3)); |
177 | 0 | node.subnodes.emplace_back(Node(quad4)); |
178 | |
|
179 | 0 | auto features = std::move(node.features); |
180 | 0 | node.features.clear(); |
181 | 0 | for (auto &pair : features) { |
182 | 0 | insert(node, pair.first, pair.second); |
183 | 0 | } |
184 | | |
185 | | /* recurse back on this psNode now that it has apSubNodes */ |
186 | 0 | insert(node, feature, featureBounds); |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | } |
190 | 0 | } else { |
191 | | // If we have sub nodes, then consider whether this object will |
192 | | // fit in them. |
193 | 0 | for (auto &subnode : node.subnodes) { |
194 | 0 | if (featureBounds.isContainedBy(subnode.rect)) { |
195 | 0 | insert(subnode, feature, featureBounds); |
196 | 0 | return; |
197 | 0 | } |
198 | 0 | } |
199 | 0 | } |
200 | | |
201 | | // If none of that worked, just add it to this nodes list. |
202 | 0 | node.features.push_back( |
203 | 0 | std::pair<Feature, RectObj>(feature, featureBounds)); |
204 | 0 | } |
205 | | |
206 | | #ifdef UNUSED |
207 | | void |
208 | | search(const Node &node, const RectObj &aoiRect, |
209 | | std::vector<std::reference_wrapper<const Feature>> &features) const { |
210 | | // Does this node overlap the area of interest at all? If not, |
211 | | // return without adding to the list at all. |
212 | | if (!node.rect.overlaps(aoiRect)) |
213 | | return; |
214 | | |
215 | | // Add the local features to the list. |
216 | | for (const auto &pair : node.features) { |
217 | | if (pair.second.overlaps(aoiRect)) { |
218 | | features.push_back( |
219 | | std::reference_wrapper<const Feature>(pair.first)); |
220 | | } |
221 | | } |
222 | | |
223 | | // Recurse to subnodes if they exist. |
224 | | for (const auto &subnode : node.subnodes) { |
225 | | search(subnode, aoiRect, features); |
226 | | } |
227 | | } |
228 | | #endif |
229 | | |
230 | | static void search(const Node &node, double x, double y, |
231 | 0 | std::vector<Feature> &features) { |
232 | | // Does this node overlap the area of interest at all? If not, |
233 | | // return without adding to the list at all. |
234 | 0 | if (!node.rect.contains(x, y)) |
235 | 0 | return; |
236 | | |
237 | | // Add the local features to the list. |
238 | 0 | for (const auto &pair : node.features) { |
239 | 0 | if (pair.second.contains(x, y)) { |
240 | 0 | features.push_back(pair.first); |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | | // Recurse to subnodes if they exist. |
245 | 0 | for (const auto &subnode : node.subnodes) { |
246 | 0 | search(subnode, x, y, features); |
247 | 0 | } |
248 | 0 | } |
249 | | }; |
250 | | |
251 | | } // namespace QuadTree |
252 | | |
253 | | NS_PROJ_END |
254 | | |
255 | | //! @endcond |
256 | | |
257 | | #endif // QUADTREE_HPP |