13#include <QtCore/QDateTime>
14#include <QtLocation/private/qgeotilespec_p.h>
15#include <QtNetwork/QNetworkAccessManager>
16#include <QtNetwork/QNetworkRequest>
25 constexpr int kMaxCarpetGridSize = 10000;
32 return _terrainTileManager();
37 , _networkManager(new QNetworkAccessManager(this))
39 qCDebug(TerrainTileManagerLog) <<
this;
48 qCDebug(TerrainTileManagerLog) <<
this;
57 for (
const QGeoCoordinate &coordinate: coordinates) {
59 provider->getMapName(),
60 provider->long2tileX(coordinate.longitude(), 1),
61 provider->lat2tileY(coordinate.latitude(), 1),
64 qCDebug(TerrainTileManagerLog) <<
"hash:coordinate" << tileHash << coordinate;
68 const double elevation = tile->
elevation(coordinate);
69 if (qIsNaN(elevation)) {
71 qCWarning(TerrainTileManagerLog) <<
"Internal Error: missing elevation in tile cache";
73 qCDebug(TerrainTileManagerLog) <<
"returning elevation from tile cache" << elevation;
75 altitudes.push_back(elevation);
76 }
else if (_isFailedTile(tileHash)) {
80 altitudes.push_back(qQNaN());
83 spec.setX(provider->long2tileX(coordinate.longitude(), 1));
84 spec.setY(provider->lat2tileY(coordinate.latitude(), 1));
86 spec.setMapId(provider->getMapId());
89 (void) connect(reply, &QGeoTiledMapReplyQGC::finished,
this, &TerrainTileManager::_terrainDone);
106 qCDebug(TerrainTileManagerLog) <<
"count" << coordinates.count();
108 if (coordinates.isEmpty()) {
113 QList<double> altitudes;
115 qCDebug(TerrainTileManagerLog) <<
"queue count" << _requestQueue.count();
116 const QueuedRequestInfo_t queuedRequestInfo = {
117 terrainQueryInterface,
126 _requestQueue.enqueue(queuedRequestInfo);
131 QList<double> noAltitudes;
132 qCWarning(TerrainTileManagerLog) <<
"signalling failure due to internal error";
137 qCDebug(TerrainTileManagerLog) <<
"all altitudes taken from cached data";
143 double distanceBetween;
144 double finalDistanceBetween;
145 const QList<QGeoCoordinate> coordinates = _pathQueryToCoords(startPoint, endPoint, distanceBetween, finalDistanceBetween);
148 QList<double> altitudes;
150 qCDebug(TerrainTileManagerLog) <<
"queue count" << _requestQueue.count();
151 const QueuedRequestInfo_t queuedRequestInfo = {
152 terrainQueryInterface,
155 finalDistanceBetween,
161 _requestQueue.enqueue(queuedRequestInfo);
166 QList<double> noAltitudes;
167 qCWarning(TerrainTileManagerLog) <<
"signalling failure due to internal error";
168 terrainQueryInterface->
signalPathHeights(
false, distanceBetween, finalDistanceBetween, noAltitudes);
172 qCDebug(TerrainTileManagerLog) <<
"all altitudes taken from cached data";
173 terrainQueryInterface->
signalPathHeights((coordinates.count() == altitudes.count()), distanceBetween, finalDistanceBetween, altitudes);
178 if (swCoord.longitude() > neCoord.longitude() || swCoord.latitude() > neCoord.latitude()) {
179 qCWarning(TerrainTileManagerLog) <<
"Invalid carpet bounds: SW must be south-west of NE";
180 terrainQueryInterface->
signalCarpetHeights(
false, qQNaN(), qQNaN(), QList<QList<double>>());
187 if (gridSizeLat <= 0 || gridSizeLon <= 0) {
188 qCWarning(TerrainTileManagerLog) <<
"Carpet area too small";
189 terrainQueryInterface->
signalCarpetHeights(
false, qQNaN(), qQNaN(), QList<QList<double>>());
193 if (gridSizeLat > kMaxCarpetGridSize || gridSizeLon > kMaxCarpetGridSize) {
194 qCWarning(TerrainTileManagerLog) <<
"Carpet area too large"
195 <<
"gridSizeLat:" << gridSizeLat
196 <<
"gridSizeLon:" << gridSizeLon
197 <<
"maxGridSize:" << kMaxCarpetGridSize;
198 terrainQueryInterface->
signalCarpetHeights(
false, qQNaN(), qQNaN(), QList<QList<double>>());
202 QList<QGeoCoordinate> coordinates;
203 for (
int latIdx = 0; latIdx <= gridSizeLat; latIdx++) {
205 for (
int lonIdx = 0; lonIdx <= gridSizeLon; lonIdx++) {
207 (void) coordinates.append(QGeoCoordinate(lat, lon));
212 QList<double> altitudes;
214 qCDebug(TerrainTileManagerLog) <<
"carpet query queued, count" << _requestQueue.count();
215 const QueuedRequestInfo_t queuedRequestInfo = {
216 terrainQueryInterface,
225 _requestQueue.enqueue(queuedRequestInfo);
230 qCWarning(TerrainTileManagerLog) <<
"signalling carpet failure due to internal error";
231 terrainQueryInterface->
signalCarpetHeights(
false, qQNaN(), qQNaN(), QList<QList<double>>());
235 double minHeight, maxHeight;
236 QList<QList<double>> carpet;
237 _processCarpetResults(altitudes, gridSizeLat + 1, gridSizeLon + 1, statsOnly, minHeight, maxHeight, carpet);
239 qCDebug(TerrainTileManagerLog) <<
"carpet altitudes from cached data, min:" << minHeight <<
"max:" << maxHeight;
243QList<QGeoCoordinate> TerrainTileManager::_pathQueryToCoords(
const QGeoCoordinate &fromCoord,
const QGeoCoordinate &toCoord,
double &distanceBetween,
double &finalDistanceBetween)
251 if (coordinates.size() >= 2) {
255 distanceBetween = finalDistanceBetween = totalDistance;
258 qCDebug(TerrainTileManagerLog) <<
"fromCoord:toCoord:distanceBetween:finalDistanceBetween:coordCount"
259 << fromCoord << toCoord << distanceBetween << finalDistanceBetween << coordinates.count();
264void TerrainTileManager::_tileFailed()
266 QList<double> noAltitudes;
268 for (
const QueuedRequestInfo_t &requestInfo: _requestQueue) {
269 if (requestInfo.terrainQueryInterface.isNull()) {
272 switch (requestInfo.queryMode) {
274 requestInfo.terrainQueryInterface->signalCoordinateHeights(
false, noAltitudes);
277 requestInfo.terrainQueryInterface->signalPathHeights(
false, requestInfo.distanceBetween, requestInfo.finalDistanceBetween, noAltitudes);
280 requestInfo.terrainQueryInterface->signalCarpetHeights(
false, qQNaN(), qQNaN(), QList<QList<double>>());
287 _requestQueue.clear();
290void TerrainTileManager::_terrainDone()
296 qCWarning(TerrainTileManagerLog) <<
"Elevation tile fetched but invalid reply data type.";
299 reply->deleteLater();
301 const QByteArray responseBytes = reply->mapImageData();
302 const QGeoTileSpec spec = reply->tileSpec();
306 if (reply->error() != QGeoTiledMapReplyQGC::NoError) {
307 const bool firstFailure = _recordFailedTile(hash);
309 qCWarning(TerrainTileManagerLog) <<
"Elevation tile fetching returned error:" << reply->errorString();
311 qCDebug(TerrainTileManagerLog) <<
"Elevation tile fetching returned error (suppressed):" << reply->errorString();
317 if (responseBytes.isEmpty()) {
318 const bool firstFailure = _recordFailedTile(hash);
320 qCWarning(TerrainTileManagerLog) <<
"Error in fetching elevation tile. Empty response.";
322 qCDebug(TerrainTileManagerLog) <<
"Error in fetching elevation tile. Empty response (suppressed).";
328 _clearFailedTile(hash);
330 qCDebug(TerrainTileManagerLog) <<
"Received some bytes of terrain data:" << responseBytes.size();
332 _cacheTile(responseBytes, hash);
334 for (qsizetype i = _requestQueue.count() - 1; i >= 0; i--) {
336 QList<double> altitudes;
337 QueuedRequestInfo_t &requestInfo = _requestQueue[i];
339 if (requestInfo.terrainQueryInterface.isNull()) {
340 _requestQueue.removeAt(i);
348 switch (requestInfo.queryMode) {
351 qCWarning(TerrainTileManagerLog) <<
"signalling failure due to internal error";
352 QList<double> noAltitudes;
353 requestInfo.terrainQueryInterface->signalCoordinateHeights(
false, noAltitudes);
355 qCDebug(TerrainTileManagerLog) <<
"All altitudes taken from cached data";
356 requestInfo.terrainQueryInterface->signalCoordinateHeights(requestInfo.coordinates.count() == altitudes.count(), altitudes);
361 qCWarning(TerrainTileManagerLog) <<
"signalling failure due to internal error";
362 QList<double> noAltitudes;
363 requestInfo.terrainQueryInterface->signalPathHeights(
false, requestInfo.distanceBetween, requestInfo.finalDistanceBetween, noAltitudes);
365 qCDebug(TerrainTileManagerLog) <<
"All altitudes taken from cached data";
366 requestInfo.terrainQueryInterface->signalPathHeights(requestInfo.coordinates.count() == altitudes.count(), requestInfo.distanceBetween, requestInfo.finalDistanceBetween, altitudes);
371 qCWarning(TerrainTileManagerLog) <<
"signalling carpet failure due to internal error";
372 requestInfo.terrainQueryInterface->signalCarpetHeights(
false, qQNaN(), qQNaN(), QList<QList<double>>());
374 double minHeight, maxHeight;
375 QList<QList<double>> carpet;
376 _processCarpetResults(altitudes, requestInfo.carpetGridSizeLat, requestInfo.carpetGridSizeLon,
377 requestInfo.carpetStatsOnly, minHeight, maxHeight, carpet);
379 qCDebug(TerrainTileManagerLog) <<
"carpet altitudes from cached data, min:" << minHeight <<
"max:" << maxHeight;
380 requestInfo.terrainQueryInterface->signalCarpetHeights(
true, minHeight, maxHeight, carpet);
387 _requestQueue.removeAt(i);
391void TerrainTileManager::_cacheTile(
const QByteArray &data,
const QString &hash)
396 qCWarning(TerrainTileManagerLog) <<
"Received invalid tile";
400 QMutexLocker locker(&_tilesMutex);
401 if (!_tiles.contains(hash)) {
402 (void) _tiles.insert(hash, terrainTile);
408TerrainTile *TerrainTileManager::_getCachedTile(
const QString &hash)
410 QMutexLocker locker(&_tilesMutex);
412 if (!_tiles.contains(hash)) {
424bool TerrainTileManager::_isFailedTile(
const QString &hash)
426 QMutexLocker locker(&_tilesMutex);
428 const auto it = _failedTiles.constFind(hash);
429 if (it == _failedTiles.constEnd()) {
432 if (QDateTime::currentMSecsSinceEpoch() - it.value() < kFailedTileBackoffMs) {
435 _failedTiles.erase(it);
439bool TerrainTileManager::_recordFailedTile(
const QString &hash)
441 QMutexLocker locker(&_tilesMutex);
443 const qint64 now = QDateTime::currentMSecsSinceEpoch();
449 if (now - _lastFailedTileSweepMs >= kFailedTileBackoffMs) {
450 _lastFailedTileSweepMs = now;
451 for (
auto it = _failedTiles.begin(); it != _failedTiles.end();) {
452 if (now - it.value() >= kFailedTileBackoffMs) {
453 it = _failedTiles.erase(it);
460 const bool firstFailure = !_failedTiles.contains(hash);
461 _failedTiles.insert(hash, now);
465void TerrainTileManager::_clearFailedTile(
const QString &hash)
467 QMutexLocker locker(&_tilesMutex);
469 _failedTiles.remove(hash);
472void TerrainTileManager::_processCarpetResults(
const QList<double> &altitudes,
int gridSizeLat,
int gridSizeLon,
473 bool statsOnly,
double &minHeight,
double &maxHeight, QList<QList<double>> &carpet)
475 minHeight = std::numeric_limits<double>::max();
476 maxHeight = std::numeric_limits<double>::lowest();
479 for (
int latIdx = 0; latIdx < gridSizeLat; latIdx++) {
481 for (
int lonIdx = 0; lonIdx < gridSizeLon; lonIdx++) {
482 const double height = altitudes[idx++];
483 minHeight = qMin(minHeight, height);
484 maxHeight = qMax(maxHeight, height);
486 (void) row.append(height);
490 (void) carpet.append(row);
Q_GLOBAL_STATIC(FirmwarePluginFactoryRegister, _firmwarePluginFactoryRegisterInstance)
Geographic coordinate conversion utilities using GeographicLib.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::shared_ptr< const MapProvider > SharedMapProvider
static QNetworkRequest getNetworkRequest(int mapId, int x, int y, int zoom)
FlightMapSettings * flightMapSettings() const
static SettingsManager * instance()
Base class for offline/online terrain queries.
void signalPathHeights(bool success, double distanceBetween, double finalDistanceBetween, const QList< double > &heights)
void signalCoordinateHeights(bool success, const QList< double > &heights)
void signalCarpetHeights(bool success, double minHeight, double maxHeight, const QList< QList< double > > &carpet)
static constexpr double kTileValueSpacingDegrees
1 Arc-Second spacing of elevation values
static constexpr double kTileValueSpacingMeters
bool getAltitudesForCoordinates(const QList< QGeoCoordinate > &coordinates, QList< double > &altitudes, bool &error)
TerrainTileManager(QObject *parent=nullptr)
void addPathQuery(TerrainQueryInterface *terrainQueryInterface, const QGeoCoordinate &startPoint, const QGeoCoordinate &endPoint)
void addCarpetQuery(TerrainQueryInterface *terrainQueryInterface, const QGeoCoordinate &swCoord, const QGeoCoordinate &neCoord, bool statsOnly)
void addCoordinateQuery(TerrainQueryInterface *terrainQueryInterface, const QList< QGeoCoordinate > &coordinates)
double elevation(const QGeoCoordinate &coordinate) const
static QString getTileHash(QStringView type, int x, int y, int z)
static QString getProviderTypeFromQtMapId(int qtMapId)
static std::shared_ptr< const MapProvider > getMapProviderFromProviderType(QStringView type)
QList< QGeoCoordinate > interpolatePath(const QGeoCoordinate &from, const QGeoCoordinate &to, int numPoints)
double geodesicDistance(const QGeoCoordinate &from, const QGeoCoordinate &to)
void configureProxy(QNetworkAccessManager *manager)
Set up default proxy configuration on a network manager.