QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
HeightField.cc
Go to the documentation of this file.
1/****************************************************************************
2 *
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 *
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
7 *
8 ****************************************************************************/
9
10#include "HeightField.h"
11
12#include <utility>
13
14#include "PatchSampler.h"
15#include "QGCLoggingCategory.h"
16
17QGC_LOGGING_CATEGORY(GeoMapHeightFieldLog, "GeoMap.HeightField")
18QGC_LOGGING_CATEGORY(GeoMapHeightFieldVerboseLog, "GeoMap.HeightField.Verbose")
19
20HeightField::HeightField(QObject* parent) : QObject(parent) {}
21
23{
24 // Capture before the move: on rejection the grid has been consumed
25 const int gridWidth = grid.width;
26 const int gridHeight = grid.height;
27 TileMath::TileKey evicted{0, 0, -1};
28 if (!_pyramid.insertTile(key, std::move(grid), &evicted)) {
29 qCWarning(GeoMapHeightFieldLog) << "insertTile rejected: key" << key << "grid" << gridWidth << "x"
30 << gridHeight;
31 return false;
32 }
33 _memoView = ElevationTilePyramid::View{}; // grid pointers die on insert
34 qCDebug(GeoMapHeightFieldVerboseLog) << "inserted tile" << key;
35
36 const auto tileExtent = [](const TileMath::TileKey& k) {
37 const QPointF corner = TileMath::tileMinCorner(k);
38 const double span = TileMath::tileSpanAtZoom(k.zoom);
39 return QRectF(corner.x(), corner.y(), span, span);
40 };
41 if (TileMath::isValidKey(evicted)) {
42 qCDebug(GeoMapHeightFieldVerboseLog) << "evicted tile" << evicted;
43 emit regionChanged(tileExtent(evicted));
44 }
45 emit regionChanged(tileExtent(key));
46 return true;
47}
48
49double HeightField::heightAt(const QPointF& world) const
50{
52
53 // Memoized fast path: the last resolved tile answers when the position
54 // resolves to it (the memo is only populated when no stored descendant
55 // could override it, and every insert invalidates it). The hit test uses
56 // the same key arithmetic as the full lookup — a world-coordinate bounds
57 // check can disagree with tileForWorld by an ulp exactly on a tile
58 // boundary, silently answering with the wrong neighbor's data there.
59 if (_memoView.isValid()) {
60 const int shift = TileMath::kMaxZoom - _memoView.key.zoom;
61 if (((query.x >> shift) == _memoView.key.x) && ((query.y >> shift) == _memoView.key.y)) {
62 const double u = (world.x() - _memoMinX) / _memoSpan;
63 const double v = (_memoMaxY - world.y()) / _memoSpan; // grid origin is the NW corner
64 return PatchSampler::heightAtUV(*_memoView.grid, u, v);
65 }
66 }
67
68 // The pyramid resolves the finest stored cover of the deepest-zoom query
69 const ElevationTilePyramid::View view = _pyramid.bestTileFor(query);
70 if (!view.isValid()) {
71 return 0.0;
72 }
73
74 const QPointF corner = TileMath::tileMinCorner(view.key);
75 const double span = TileMath::tileSpanAtZoom(view.key.zoom);
76 if (!_pyramid.hasDescendant(view.key)) {
77 // Nothing finer exists anywhere inside this tile, so it answers for
78 // every position within its bounds until the next insert
79 _memoView = view;
80 _memoMinX = corner.x();
81 _memoMaxY = corner.y() + span;
82 _memoSpan = span;
83 }
84
85 const double u = (world.x() - corner.x()) / span;
86 const double v = ((corner.y() + span) - world.y()) / span; // grid origin is the NW corner
87 return PatchSampler::heightAtUV(*view.grid, u, v);
88}
89
90QList<float> HeightField::samplePatch(const TileMath::TileKey& key, int gridSize,
91 PatchSampler::EdgeStep* edgeStep) const
92{
93 if ((gridSize < 1) || (gridSize > kMaxGridSize) || !TileMath::isValidKey(key)) {
94 qCWarning(GeoMapHeightFieldLog) << "samplePatch rejected: key" << key << "gridSize" << gridSize;
95 return QList<float>();
96 }
97
98 // Interior vertices resolve the patch's backing view once, by the
99 // patch's own key, and interpolate within that one grid. Boundary
100 // vertices are shared with neighbor patches whose backing views can
101 // differ (adjacent exact tiles, fine tile next to an ancestor-backed
102 // neighbor), so they resolve canonically by position instead: the
103 // deepest-zoom cells touching the vertex, tried east/south first, pick
104 // the same stored tile no matter which patch asks. Positions are exact
105 // dyadic values, so coincident vertices compute bit-identical UVs and
106 // sample bit-identical heights: meshes never crack where data exists.
107 // Interior vertices absorb any mismatch between the two so it ramps in
108 // across the patch instead of forming a cliff one cell inside the edge.
109 return PatchSampler(_pyramid, key, gridSize, edgeStep).sample();
110}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
View bestTileFor(const TileMath::TileKey &key) const
bool insertTile(const TileMath::TileKey &key, Grid grid, TileMath::TileKey *evictedKey=nullptr)
bool hasDescendant(const TileMath::TileKey &key) const
bool insertTile(const TileMath::TileKey &key, ElevationTilePyramid::Grid grid)
QList< float > samplePatch(const TileMath::TileKey &key, int gridSize, PatchSampler::EdgeStep *edgeStep=nullptr) const
double heightAt(const QPointF &world) const
static constexpr int kMaxGridSize
Sanity cap on patch density: rejects absurd sizes before allocation.
Definition HeightField.h:45
void regionChanged(const QRectF &worldRect)
QList< float > sample()
The (gridSize+1)^2 vertex heights, row-major from the NW corner.
static double heightAtUV(const ElevationTilePyramid::Grid &grid, double u, double v)
QPointF tileMinCorner(const TileKey &key)
South-west (minimum x/y) corner of a tile in world meters.
Definition TileMath.cc:58
constexpr int kMaxZoom
Definition TileMath.h:31
bool isValidKey(const TileKey &key)
True if zoom is within [kMinZoom, kMaxZoom] and x/y address a tile at that zoom.
Definition TileMath.cc:44
double tileSpanAtZoom(int zoom)
Edge length of one tile at zoom, in world meters.
Definition TileMath.cc:53
TileKey tileForWorld(const QPointF &world, int zoom)
Tile containing a world point. World coordinates are clamped to the world extent.
Definition TileMath.cc:67
Decoded elevation samples for one tile, row-major from the NW corner.
const Grid * grid
null when nothing stored covers the query
TileMath::TileKey key
stored tile the view samples