Coverage Report

Created: 2025-06-13 06:29

/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