Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/MapServer/src/flatgeobuf/packedrtree.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  FlatGeobuf
4
 * Purpose:  Packed RTree management
5
 * Author:   Björn Harrtell <bjorn at wololo dot org>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018-2020, Björn Harrtell <bjorn at wololo dot org>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
// NOTE: The upstream of this file is in https://github.com/bjornharrtell/flatgeobuf/tree/master/src/cpp
30
31
#ifdef GDAL_COMPILATION
32
#include "cpl_port.h"
33
#else
34
#define CPL_IS_LSB 1
35
#endif
36
37
#include "packedrtree.h"
38
39
#include <map>
40
#include <unordered_map>
41
#include <iostream>
42
43
namespace mapserver
44
{
45
namespace FlatGeobuf
46
{
47
48
const NodeItem &NodeItem::expand(const NodeItem &r)
49
0
{
50
0
    if (r.minX < minX) minX = r.minX;
51
0
    if (r.minY < minY) minY = r.minY;
52
0
    if (r.maxX > maxX) maxX = r.maxX;
53
0
    if (r.maxY > maxY) maxY = r.maxY;
54
0
    return *this;
55
0
}
56
57
NodeItem NodeItem::create(uint64_t offset)
58
0
{
59
0
    return {
60
0
        std::numeric_limits<double>::infinity(),
61
0
        std::numeric_limits<double>::infinity(),
62
0
        -1 * std::numeric_limits<double>::infinity(),
63
0
        -1 * std::numeric_limits<double>::infinity(),
64
0
        offset
65
0
    };
66
0
}
67
68
bool NodeItem::intersects(const NodeItem &r) const
69
0
{
70
0
    if (maxX < r.minX) return false;
71
0
    if (maxY < r.minY) return false;
72
0
    if (minX > r.maxX) return false;
73
0
    if (minY > r.maxY) return false;
74
0
    return true;
75
0
}
76
77
std::vector<double> NodeItem::toVector()
78
0
{
79
0
    return std::vector<double> { minX, minY, maxX, maxY };
80
0
}
81
82
// Based on public domain code at https://github.com/rawrunprotected/hilbert_curves
83
uint32_t hilbert(uint32_t x, uint32_t y)
84
0
{
85
0
    uint32_t a = x ^ y;
86
0
    uint32_t b = 0xFFFF ^ a;
87
0
    uint32_t c = 0xFFFF ^ (x | y);
88
0
    uint32_t d = x & (y ^ 0xFFFF);
89
90
0
    uint32_t A = a | (b >> 1);
91
0
    uint32_t B = (a >> 1) ^ a;
92
0
    uint32_t C = ((c >> 1) ^ (b & (d >> 1))) ^ c;
93
0
    uint32_t D = ((a & (c >> 1)) ^ (d >> 1)) ^ d;
94
95
0
    a = A; b = B; c = C; d = D;
96
0
    A = ((a & (a >> 2)) ^ (b & (b >> 2)));
97
0
    B = ((a & (b >> 2)) ^ (b & ((a ^ b) >> 2)));
98
0
    C ^= ((a & (c >> 2)) ^ (b & (d >> 2)));
99
0
    D ^= ((b & (c >> 2)) ^ ((a ^ b) & (d >> 2)));
100
101
0
    a = A; b = B; c = C; d = D;
102
0
    A = ((a & (a >> 4)) ^ (b & (b >> 4)));
103
0
    B = ((a & (b >> 4)) ^ (b & ((a ^ b) >> 4)));
104
0
    C ^= ((a & (c >> 4)) ^ (b & (d >> 4)));
105
0
    D ^= ((b & (c >> 4)) ^ ((a ^ b) & (d >> 4)));
106
107
0
    a = A; b = B; c = C; d = D;
108
0
    C ^= ((a & (c >> 8)) ^ (b & (d >> 8)));
109
0
    D ^= ((b & (c >> 8)) ^ ((a ^ b) & (d >> 8)));
110
111
0
    a = C ^ (C >> 1);
112
0
    b = D ^ (D >> 1);
113
114
0
    uint32_t i0 = x ^ y;
115
0
    uint32_t i1 = b | (0xFFFF ^ (i0 | a));
116
117
0
    i0 = (i0 | (i0 << 8)) & 0x00FF00FF;
118
0
    i0 = (i0 | (i0 << 4)) & 0x0F0F0F0F;
119
0
    i0 = (i0 | (i0 << 2)) & 0x33333333;
120
0
    i0 = (i0 | (i0 << 1)) & 0x55555555;
121
122
0
    i1 = (i1 | (i1 << 8)) & 0x00FF00FF;
123
0
    i1 = (i1 | (i1 << 4)) & 0x0F0F0F0F;
124
0
    i1 = (i1 | (i1 << 2)) & 0x33333333;
125
0
    i1 = (i1 | (i1 << 1)) & 0x55555555;
126
127
0
    uint32_t value = ((i1 << 1) | i0);
128
129
0
    return value;
130
0
}
131
132
uint32_t hilbert(const NodeItem &r, uint32_t hilbertMax, const double minX, const double minY, const double width, const double height)
133
0
{
134
0
    uint32_t x = 0;
135
0
    uint32_t y = 0;
136
0
    uint32_t v;
137
0
    if (width != 0.0)
138
0
        x = static_cast<uint32_t>(floor(hilbertMax * ((r.minX + r.maxX) / 2 - minX) / width));
139
0
    if (height != 0.0)
140
0
        y = static_cast<uint32_t>(floor(hilbertMax * ((r.minY + r.maxY) / 2 - minY) / height));
141
0
    v = hilbert(x, y);
142
0
    return v;
143
0
}
144
145
const uint32_t hilbertMax = (1 << 16) - 1;
146
147
void hilbertSort(std::vector<std::shared_ptr<Item>> &items)
148
0
{
149
0
    NodeItem extent = calcExtent(items);
150
0
    const double minX = extent.minX;
151
0
    const double minY = extent.minY;
152
0
    const double width = extent.width();
153
0
    const double height = extent.height();
154
0
    std::sort(items.begin(), items.end(), [minX, minY, width, height] (std::shared_ptr<Item> a, std::shared_ptr<Item> b) {
155
0
        uint32_t ha = hilbert(a->nodeItem, hilbertMax, minX, minY, width, height);
156
0
        uint32_t hb = hilbert(b->nodeItem, hilbertMax, minX, minY, width, height);
157
0
        return ha > hb;
158
0
    });
159
0
}
160
161
void hilbertSort(std::vector<NodeItem> &items)
162
0
{
163
0
    NodeItem extent = calcExtent(items);
164
0
    const double minX = extent.minX;
165
0
    const double minY = extent.minY;
166
0
    const double width = extent.width();
167
0
    const double height = extent.height();
168
0
    std::sort(items.begin(), items.end(), [minX, minY, width, height] (const NodeItem &a, const NodeItem &b) {
169
0
        uint32_t ha = hilbert(a, hilbertMax, minX, minY, width, height);
170
0
        uint32_t hb = hilbert(b, hilbertMax, minX, minY, width, height);
171
0
        return ha > hb;
172
0
    });
173
0
}
174
175
NodeItem calcExtent(const std::vector<std::shared_ptr<Item>> &items)
176
0
{
177
0
    return std::accumulate(items.begin(), items.end(), NodeItem::create(0), [] (NodeItem a, const std::shared_ptr<Item>& b) {
178
0
        return a.expand(b->nodeItem);
179
0
    });
180
0
}
181
182
NodeItem calcExtent(const std::vector<NodeItem> &nodes)
183
0
{
184
0
    return std::accumulate(nodes.begin(), nodes.end(), NodeItem::create(0), [] (NodeItem a, const NodeItem &b) {
185
0
        return a.expand(b);
186
0
    });
187
0
}
188
189
void PackedRTree::init(const uint16_t nodeSize)
190
0
{
191
0
    if (nodeSize < 2)
192
0
        throw std::invalid_argument("Node size must be at least 2");
193
0
    if (_numItems == 0)
194
0
        throw std::invalid_argument("Cannot create empty tree");
195
0
    _nodeSize = std::min(std::max(nodeSize, static_cast<uint16_t>(2)), static_cast<uint16_t>(65535));
196
0
    _levelBounds = generateLevelBounds(_numItems, _nodeSize);
197
0
    _numNodes = _levelBounds.front().second;
198
0
    _nodeItems = new NodeItem[static_cast<size_t>(_numNodes)];
199
0
}
200
201
0
std::vector<std::pair<uint64_t, uint64_t>> PackedRTree::generateLevelBounds(const uint64_t numItems, const uint16_t nodeSize) {
202
0
    if (nodeSize < 2)
203
0
        throw std::invalid_argument("Node size must be at least 2");
204
0
    if (numItems == 0)
205
0
        throw std::invalid_argument("Number of items must be greater than 0");
206
0
    if (numItems > std::numeric_limits<uint64_t>::max() - ((numItems / nodeSize) * 2))
207
0
        throw std::overflow_error("Number of items too large");
208
209
    // number of nodes per level in bottom-up order
210
0
    std::vector<uint64_t> levelNumNodes;
211
0
    uint64_t n = numItems;
212
0
    uint64_t numNodes = n;
213
0
    levelNumNodes.push_back(n);
214
0
    do {
215
0
        n = (n + nodeSize - 1) / nodeSize;
216
0
        numNodes += n;
217
0
        levelNumNodes.push_back(n);
218
0
    } while (n != 1);
219
220
    // bounds per level in reversed storage order (top-down)
221
0
    std::vector<uint64_t> levelOffsets;
222
0
    n = numNodes;
223
0
    for (auto size : levelNumNodes)
224
0
        levelOffsets.push_back(n -= size);
225
0
    std::reverse(levelOffsets.begin(), levelOffsets.end());
226
0
    std::reverse(levelNumNodes.begin(), levelNumNodes.end());
227
0
    std::vector<std::pair<uint64_t, uint64_t>> levelBounds;
228
0
    for (size_t i = 0; i < levelNumNodes.size(); i++)
229
0
        levelBounds.push_back(std::pair<uint64_t, uint64_t>(levelOffsets[i], levelOffsets[i] + levelNumNodes[i]));
230
0
    std::reverse(levelBounds.begin(), levelBounds.end());
231
0
    return levelBounds;
232
0
}
233
234
void PackedRTree::generateNodes()
235
0
{
236
0
    for (uint32_t i = 0; i < _levelBounds.size() - 1; i++) {
237
0
        auto pos = _levelBounds[i].first;
238
0
        auto end = _levelBounds[i].second;
239
0
        auto newpos = _levelBounds[i + 1].first;
240
0
        while (pos < end) {
241
0
            NodeItem node = NodeItem::create(pos);
242
0
            for (uint32_t j = 0; j < _nodeSize && pos < end; j++)
243
0
                node.expand(_nodeItems[pos++]);
244
0
            _nodeItems[newpos++] = node;
245
0
        }
246
0
    }
247
0
}
248
249
void PackedRTree::fromData(const void *data)
250
0
{
251
0
    auto buf = reinterpret_cast<const uint8_t *>(data);
252
0
    const NodeItem *pn = reinterpret_cast<const NodeItem *>(buf);
253
0
    for (uint64_t i = 0; i < _numNodes; i++) {
254
0
        NodeItem n = *pn++;
255
0
        _nodeItems[i] = n;
256
0
        _extent.expand(n);
257
0
    }
258
0
}
259
260
PackedRTree::PackedRTree(const std::vector<std::shared_ptr<Item>> &items, const NodeItem &extent, const uint16_t nodeSize) :
261
0
    _extent(extent),
262
0
    _numItems(items.size())
263
0
{
264
0
    init(nodeSize);
265
0
    for (size_t i = 0; i < _numItems; i++)
266
0
        _nodeItems[_numNodes - _numItems + i] = items[i]->nodeItem;
267
0
    generateNodes();
268
0
}
269
270
PackedRTree::PackedRTree(const std::vector<NodeItem> &nodes, const NodeItem &extent, const uint16_t nodeSize) :
271
0
    _extent(extent),
272
0
    _numItems(nodes.size())
273
0
{
274
0
    init(nodeSize);
275
0
    for (size_t i = 0; i < _numItems; i++)
276
0
        _nodeItems[_numNodes - _numItems + i] = nodes[i];
277
0
    generateNodes();
278
0
}
279
280
PackedRTree::PackedRTree(const void *data, const uint64_t numItems, const uint16_t nodeSize) :
281
0
    _extent(NodeItem::create(0)),
282
0
    _numItems(numItems)
283
0
{
284
0
    init(nodeSize);
285
0
    fromData(data);
286
0
}
287
288
std::vector<SearchResultItem> PackedRTree::search(double minX, double minY, double maxX, double maxY) const
289
0
{
290
0
    uint64_t leafNodesOffset = _levelBounds.front().first;
291
0
    NodeItem n { minX, minY, maxX, maxY, 0 };
292
0
    std::vector<SearchResultItem> results;
293
0
    std::unordered_map<uint64_t, uint64_t> queue;
294
0
    queue.insert(std::pair<uint64_t, uint64_t>(0, _levelBounds.size() - 1));
295
0
    while(queue.size() != 0) {
296
0
        auto next = queue.begin();
297
0
        uint64_t nodeIndex = next->first;
298
0
        uint64_t level = next->second;
299
0
        queue.erase(next);
300
0
        bool isLeafNode = nodeIndex >= _numNodes - _numItems;
301
        // find the end index of the node
302
0
        uint64_t end = std::min(static_cast<uint64_t>(nodeIndex + _nodeSize), _levelBounds[static_cast<size_t>(level)].second);
303
        // search through child nodes
304
0
        for (uint64_t pos = nodeIndex; pos < end; pos++) {
305
0
            auto nodeItem = _nodeItems[static_cast<size_t>(pos)];
306
0
            if (!n.intersects(nodeItem))
307
0
                continue;
308
0
            if (isLeafNode)
309
0
                results.push_back({ nodeItem.offset, pos - leafNodesOffset });
310
0
            else
311
0
                queue.insert(std::pair<uint64_t, uint64_t>(nodeItem.offset, level - 1));
312
0
        }
313
0
    }
314
0
    return results;
315
0
}
316
317
std::vector<SearchResultItem> PackedRTree::streamSearch(
318
    const uint64_t numItems, const uint16_t nodeSize, const NodeItem &item,
319
    const std::function<void(uint8_t *, size_t, size_t)> &readNode)
320
0
{
321
0
    auto levelBounds = generateLevelBounds(numItems, nodeSize);
322
0
    uint64_t leafNodesOffset = levelBounds.front().first;
323
0
    uint64_t numNodes = levelBounds.front().second;
324
0
    auto nodeItems = std::vector<NodeItem>(nodeSize);
325
0
    uint8_t *nodesBuf = reinterpret_cast<uint8_t *>(nodeItems.data());
326
    // use ordered search queue to make index traversal in sequential order
327
0
    std::map<uint64_t, uint64_t> queue;
328
0
    std::vector<SearchResultItem> results;
329
0
    queue.insert(std::pair<uint64_t, uint64_t>(0, levelBounds.size() - 1));
330
0
    while(queue.size() != 0) {
331
0
        auto next = queue.begin();
332
0
        uint64_t nodeIndex = next->first;
333
0
        uint64_t level = next->second;
334
0
        queue.erase(next);
335
0
        bool isLeafNode = nodeIndex >= numNodes - numItems;
336
        // find the end index of the node
337
0
        uint64_t end = std::min(static_cast<uint64_t>(nodeIndex + nodeSize), levelBounds[static_cast<size_t>(level)].second);
338
0
        uint64_t length = end - nodeIndex;
339
0
        readNode(nodesBuf, static_cast<size_t>(nodeIndex * sizeof(NodeItem)), static_cast<size_t>(length * sizeof(NodeItem)));
340
#if !CPL_IS_LSB
341
        for( size_t i = 0; i < static_cast<size_t>(length); i++ )
342
        {
343
            CPL_LSBPTR64(&nodeItems[i].minX);
344
            CPL_LSBPTR64(&nodeItems[i].minY);
345
            CPL_LSBPTR64(&nodeItems[i].maxX);
346
            CPL_LSBPTR64(&nodeItems[i].maxY);
347
            CPL_LSBPTR64(&nodeItems[i].offset);
348
        }
349
#endif
350
        // search through child nodes
351
0
        for (uint64_t pos = nodeIndex; pos < end; pos++) {
352
0
            uint64_t nodePos = pos - nodeIndex;
353
0
            auto nodeItem = nodeItems[static_cast<size_t>(nodePos)];
354
0
            if (!item.intersects(nodeItem))
355
0
                continue;
356
0
            if (isLeafNode)
357
0
                results.push_back({ nodeItem.offset, pos - leafNodesOffset });
358
0
            else
359
0
                queue.insert(std::pair<uint64_t, uint64_t>(nodeItem.offset, level - 1));
360
0
        }
361
0
    }
362
0
    return results;
363
0
}
364
365
0
uint64_t PackedRTree::size() const { return _numNodes * sizeof(NodeItem); }
366
367
uint64_t PackedRTree::size(const uint64_t numItems, const uint16_t nodeSize)
368
0
{
369
0
    if (nodeSize < 2)
370
0
        throw std::invalid_argument("Node size must be at least 2");
371
0
    if (numItems == 0)
372
0
        throw std::invalid_argument("Number of items must be greater than 0");
373
0
    const uint16_t nodeSizeMin = std::min(std::max(nodeSize, static_cast<uint16_t>(2)), static_cast<uint16_t>(65535));
374
    // limit so that resulting size in bytes can be represented by uint64_t
375
0
    if (numItems > static_cast<uint64_t>(1) << 56)
376
0
        throw std::overflow_error("Number of items must be less than 2^56");
377
0
    uint64_t n = numItems;
378
0
    uint64_t numNodes = n;
379
0
    do {
380
0
        n = (n + nodeSizeMin - 1) / nodeSizeMin;
381
0
        numNodes += n;
382
0
    } while (n != 1);
383
0
    return numNodes * sizeof(NodeItem);
384
0
}
385
386
0
void PackedRTree::streamWrite(const std::function<void(uint8_t *, size_t)> &writeData) {
387
#if !CPL_IS_LSB
388
    for( size_t i = 0; i < static_cast<size_t>(_numNodes); i++ )
389
    {
390
        CPL_LSBPTR64(&_nodeItems[i].minX);
391
        CPL_LSBPTR64(&_nodeItems[i].minY);
392
        CPL_LSBPTR64(&_nodeItems[i].maxX);
393
        CPL_LSBPTR64(&_nodeItems[i].maxY);
394
        CPL_LSBPTR64(&_nodeItems[i].offset);
395
    }
396
#endif
397
0
    writeData(reinterpret_cast<uint8_t *>(_nodeItems), static_cast<size_t>(_numNodes * sizeof(NodeItem)));
398
#if !CPL_IS_LSB
399
    for( size_t i = 0; i < static_cast<size_t>(_numNodes); i++ )
400
    {
401
        CPL_LSBPTR64(&_nodeItems[i].minX);
402
        CPL_LSBPTR64(&_nodeItems[i].minY);
403
        CPL_LSBPTR64(&_nodeItems[i].maxX);
404
        CPL_LSBPTR64(&_nodeItems[i].maxY);
405
        CPL_LSBPTR64(&_nodeItems[i].offset);
406
    }
407
#endif
408
0
}
409
410
0
NodeItem PackedRTree::getExtent() const { return _extent; }
411
412
}
413
}