/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 | | } |