QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PatchSampler.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 "PatchSampler.h"
11
12#include <algorithm>
13#include <cmath>
14
15#include "BilinearUV.h"
16
17double PatchSampler::heightAtUV(const ElevationTilePyramid::Grid& grid, double u, double v)
18{
19 const auto at = [&grid](int x, int y) { return double(grid.heights[(qsizetype(y) * grid.width) + x]); };
20 return bilinearAtUV(grid.width, grid.height, u, v, at);
21}
22
23PatchSampler::PatchSampler(const ElevationTilePyramid& pyramid, const TileMath::TileKey& key, int gridSize,
24 EdgeStep* edgeStep)
25 : _pyramid(pyramid)
26 , _key(key)
27 , _gridSize(gridSize)
28 , _shiftToMax(TileMath::kMaxZoom - key.zoom)
29 , _patchView(pyramid.bestTileFor(key))
30 , _edgeStep(edgeStep)
31{}
32
34{
35 const int verticesPerEdge = _gridSize + 1;
36 _north.resize(verticesPerEdge);
37 _south.resize(verticesPerEdge);
38 _west.resize(verticesPerEdge);
39 _east.resize(verticesPerEdge);
40
41 QList<float> heights;
42 heights.reserve(qsizetype(verticesPerEdge) * verticesPerEdge);
43 bool mismatched = false;
44 for (int row = 0; row <= _gridSize; row++) {
45 const qint64 m = (qint64(_key.y) * _gridSize) + row;
46 for (int col = 0; col <= _gridSize; col++) {
47 const qint64 n = (qint64(_key.x) * _gridSize) + col;
48 const float own = _patchView.isValid() ? _viewHeight(_patchView, n, m) : 0.0f;
49 if ((row > 0) && (row < _gridSize) && (col > 0) && (col < _gridSize)) {
50 heights.append(own);
51 continue;
52 }
53 const BoundaryVertex vertex = _boundaryVertex(n, m, own);
54 heights.append(vertex.height);
55 mismatched = mismatched || (vertex.mismatch != 0.0f);
56 if (row == 0) {
57 _north[col] = vertex;
58 }
59 if (row == _gridSize) {
60 _south[col] = vertex;
61 }
62 if (col == 0) {
63 _west[row] = vertex;
64 }
65 if (col == _gridSize) {
66 _east[row] = vertex;
67 }
68 }
69 }
70 if (!mismatched) {
71 return heights;
72 }
73
74 QList<float> corrections(heights.size(), 0.0f);
75 for (int row = 1; row < _gridSize; row++) {
76 for (int col = 1; col < _gridSize; col++) {
77 const qsizetype index = (qsizetype(row) * verticesPerEdge) + col;
78 corrections[index] = _interiorCorrection(row, col);
79 heights[index] += corrections[index];
80 }
81 }
82 if (_edgeStep) {
83 _trackEdgeStep(corrections);
84 }
85 return heights;
86}
87
91float PatchSampler::_viewHeight(const ElevationTilePyramid::View& view, qint64 n, qint64 m) const
92{
93 const double u = (std::ldexp(double(n), view.key.zoom - _key.zoom) / _gridSize) - view.key.x;
94 const double v = (std::ldexp(double(m), view.key.zoom - _key.zoom) / _gridSize) - view.key.y;
95 return static_cast<float>(heightAtUV(*view.grid, u, v));
96}
97
100int PatchSampler::_touchingCells(qint64 s, qint64 (&cells)[2]) const
101{
102 const qint64 cellCount = qint64(1) << TileMath::kMaxZoom;
103 int count = 0;
104 const qint64 cell = s / _gridSize;
105 if (cell < cellCount) {
106 cells[count++] = cell;
107 }
108 if (((s % _gridSize) == 0) && (cell > 0)) {
109 cells[count++] = cell - 1;
110 }
111 return count;
112}
113
114ElevationTilePyramid::View PatchSampler::_resolveCell(qint64 cx, qint64 cy)
115{
116 const TileMath::TileKey tile{int(cx >> _shiftToMax), int(cy >> _shiftToMax), _key.zoom};
117 for (const TileMemo& memo : _memos) {
118 if (memo.tile == tile) {
119 return memo.view;
120 }
121 }
122 const ElevationTilePyramid::View view =
123 _pyramid.bestTileFor(TileMath::TileKey{int(cx), int(cy), TileMath::kMaxZoom});
124 if (!_pyramid.hasDescendant(tile) && (_memos.size() < _memos.capacity())) {
125 _memos.append({tile, view});
126 }
127 return view;
128}
129
130PatchSampler::BoundaryVertex PatchSampler::_boundaryVertex(qint64 n, qint64 m, float own)
131{
132 qint64 xCells[2];
133 qint64 yCells[2];
134 const int xCount = _touchingCells(n << _shiftToMax, xCells);
135 const int yCount = _touchingCells(m << _shiftToMax, yCells);
136
137 // Fixed candidate order derived purely from the position: every patch
138 // sharing this vertex walks the same cells and returns the first
139 // resolvable view, so the height is canonical. No stored data touching
140 // the vertex leaves it at zero, which every sharer agrees on.
141 BoundaryVertex vertex;
142 for (int yi = 0; (yi < yCount) && (vertex.zoom < 0); yi++) {
143 for (int xi = 0; xi < xCount; xi++) {
144 const ElevationTilePyramid::View view = _resolveCell(xCells[xi], yCells[yi]);
145 if (view.isValid()) {
146 vertex.height = _viewHeight(view, n, m);
147 vertex.zoom = view.key.zoom;
148 break;
149 }
150 }
151 }
152 const float mismatch = vertex.height - own;
153 vertex.mismatch = std::isfinite(mismatch) ? mismatch : 0.0f; // one bad sample must not smear the interior
154 return vertex;
155}
156
161float PatchSampler::_interiorCorrection(int row, int col) const
162{
163 const auto weight = [](int distance) { return 1.0 / (double(distance) * distance); };
164 const double north = weight(row);
165 const double south = weight(_gridSize - row);
166 const double west = weight(col);
167 const double east = weight(_gridSize - col);
168 const double sum = (north * _north[col].mismatch) + (south * _south[col].mismatch) + (west * _west[row].mismatch) +
169 (east * _east[row].mismatch);
170 return static_cast<float>(sum / (north + south + west + east));
171}
172
173void PatchSampler::_trackEdgeStep(const QList<float>& corrections)
174{
175 const int ownZoom = _patchView.isValid() ? _patchView.key.zoom : -1;
176 const int cornerSpan = _gridSize / 4;
177 for (int row = 0; row <= _gridSize; row++) {
178 const bool fullRow = (row == 0) || (row == _gridSize);
179 for (int col = 0; col <= _gridSize; col += (fullRow ? 1 : _gridSize)) {
180 const int along = fullRow ? col : row;
181 if (std::min(along, _gridSize - along) < cornerSpan) {
182 continue;
183 }
184 const BoundaryVertex& vertex = (row == 0) ? _north[col]
185 : (row == _gridSize) ? _south[col]
186 : (col == 0) ? _west[row]
187 : _east[row];
188 // Same-zoom neighbors differ only by edge clamping of real data: not a cliff
189 if (vertex.zoom == ownZoom) {
190 continue;
191 }
192 float inward = 0.0f;
193 if (_gridSize > 1) {
194 const int inwardRow = std::clamp(row, 1, _gridSize - 1);
195 const int inwardCol = std::clamp(col, 1, _gridSize - 1);
196 inward = corrections[(qsizetype(inwardRow) * (_gridSize + 1)) + inwardCol];
197 }
198 const float step = std::abs(vertex.mismatch - inward);
199 if (!(step > _edgeStep->step)) {
200 continue;
201 }
202 _edgeStep->step = step;
203 _edgeStep->row = row;
204 _edgeStep->col = col;
205 _edgeStep->ownZoom = ownZoom;
206 _edgeStep->boundaryZoom = vertex.zoom;
207 }
208 }
209}
double bilinearAtUV(int w, int h, double u, double v, SampleAt at)
Definition BilinearUV.h:24
View bestTileFor(const TileMath::TileKey &key) const
bool hasDescendant(const TileMath::TileKey &key) const
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)
PatchSampler(const ElevationTilePyramid &pyramid, const TileMath::TileKey &key, int gridSize, EdgeStep *edgeStep=nullptr)
edgeStep, when set, receives the patch's largest EdgeStep
double distance(const Ecef &left, const Ecef &right)
Straight-line distance between left and right, in metres.
constexpr int kMaxZoom
Definition TileMath.h:31
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
int row
boundary vertex of the largest step
int boundaryZoom
zoom of the tile the boundary vertex resolved to
int ownZoom
zoom of the patch's backing tile; -1 = no data