TerrainQuery.cc 30.5 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9 10
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#include "TerrainQuery.h"
11 12
#include "QGCMapEngine.h"
#include "QGeoMapReplyQGC.h"
13
#include "QGCApplication.h"
14 15 16 17 18 19

#include <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkReply>
20
#include <QSslConfiguration>
21 22 23 24
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QTimer>
25
#include <QtLocation/private/qgeotilespec_p.h>
26

Andreas Bircher's avatar
Andreas Bircher committed
27
#include <cmath>
28 29

QGC_LOGGING_CATEGORY(TerrainQueryLog, "TerrainQueryLog")
DonLakeFlyer's avatar
DonLakeFlyer committed
30
QGC_LOGGING_CATEGORY(TerrainQueryVerboseLog, "TerrainQueryVerboseLog")
31 32

Q_GLOBAL_STATIC(TerrainAtCoordinateBatchManager, _TerrainAtCoordinateBatchManager)
33
Q_GLOBAL_STATIC(TerrainTileManager, _terrainTileManager)
34

35 36
TerrainAirMapQuery::TerrainAirMapQuery(QObject* parent)
    : TerrainQueryInterface(parent)
37
{
38
    qCDebug(TerrainQueryVerboseLog) << "supportsSsl" << QSslSocket::supportsSsl() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
39 40
}

41 42
void TerrainAirMapQuery::requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates)
{
43
    if (qgcApp()->runningUnitTests()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
44
        emit coordinateHeightsReceived(false, QList<double>());
45 46 47
        return;
    }

48
    QString points;
49
    for (const QGeoCoordinate& coord: coordinates) {
50 51 52 53 54 55 56 57 58 59 60 61 62 63
            points += QString::number(coord.latitude(), 'f', 10) + ","
                    + QString::number(coord.longitude(), 'f', 10) + ",";
    }
    points = points.mid(0, points.length() - 1); // remove the last ',' from string

    QUrlQuery query;
    query.addQueryItem(QStringLiteral("points"), points);

    _queryMode = QueryModeCoordinates;
    _sendQuery(QString() /* path */, query);
}

void TerrainAirMapQuery::requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
{
64
    if (qgcApp()->runningUnitTests()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
65
        emit pathHeightsReceived(false, qQNaN(), qQNaN(), QList<double>());
66 67 68
        return;
    }

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    QString points;
    points += QString::number(fromCoord.latitude(), 'f', 10) + ","
            + QString::number(fromCoord.longitude(), 'f', 10) + ",";
    points += QString::number(toCoord.latitude(), 'f', 10) + ","
            + QString::number(toCoord.longitude(), 'f', 10);

    QUrlQuery query;
    query.addQueryItem(QStringLiteral("points"), points);

    _queryMode = QueryModePath;
    _sendQuery(QStringLiteral("/path"), query);
}

void TerrainAirMapQuery::requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly)
{
84
    if (qgcApp()->runningUnitTests()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
85
        emit carpetHeightsReceived(false, qQNaN(), qQNaN(), QList<QList<double>>());
86 87 88
        return;
    }

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
    QString points;
    points += QString::number(swCoord.latitude(), 'f', 10) + ","
            + QString::number(swCoord.longitude(), 'f', 10) + ",";
    points += QString::number(neCoord.latitude(), 'f', 10) + ","
            + QString::number(neCoord.longitude(), 'f', 10);

    QUrlQuery query;
    query.addQueryItem(QStringLiteral("points"), points);

    _queryMode = QueryModeCarpet;
    _carpetStatsOnly = statsOnly;

    _sendQuery(QStringLiteral("/carpet"), query);
}

void TerrainAirMapQuery::_sendQuery(const QString& path, const QUrlQuery& urlQuery)
105 106
{
    QUrl url(QStringLiteral("https://api.airmap.com/elevation/v1/ele") + path);
107
    qCDebug(TerrainQueryLog) << "_sendQuery" << url;
108
    url.setQuery(urlQuery);
109 110 111

    QNetworkRequest request(url);

112 113 114 115
    QSslConfiguration sslConf = request.sslConfiguration();
    sslConf.setPeerVerifyMode(QSslSocket::VerifyNone);
    request.setSslConfiguration(sslConf);

116 117 118 119 120 121
    QNetworkProxy tProxy;
    tProxy.setType(QNetworkProxy::DefaultProxy);
    _networkManager.setProxy(tProxy);

    QNetworkReply* networkReply = _networkManager.get(request);
    if (!networkReply) {
122
        qCWarning(TerrainQueryLog) << "QNetworkManager::Get did not return QNetworkReply";
123
        _requestFailed();
124 125
        return;
    }
126
    networkReply->ignoreSslErrors();
127

128
    connect(networkReply, &QNetworkReply::finished, this, &TerrainAirMapQuery::_requestFinished);
129
    connect(networkReply, &QNetworkReply::sslErrors, this, &TerrainAirMapQuery::_sslErrors);
DonLakeFlyer's avatar
DonLakeFlyer committed
130 131 132 133 134 135 136 137
    connect(networkReply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &TerrainAirMapQuery::_requestError);
}

void TerrainAirMapQuery::_requestError(QNetworkReply::NetworkError code)
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());

    if (code != QNetworkReply::NoError) {
138
        qCWarning(TerrainQueryLog) << "_requestError error:url:data" << reply->error() << reply->url() << reply->readAll();
DonLakeFlyer's avatar
DonLakeFlyer committed
139 140
        return;
    }
141 142
}

143 144 145 146 147 148 149 150 151 152 153 154
void TerrainAirMapQuery::_sslErrors(const QList<QSslError> &errors)
{
    for (const auto &error : errors) {
        qCWarning(TerrainQueryLog) << "SSL error: " << error.errorString();

        const auto &certificate = error.certificate();
        if (!certificate.isNull()) {
            qCWarning(TerrainQueryLog) << "SSL Certificate problem: " << certificate.toText();
        }
    }
}

155
void TerrainAirMapQuery::_requestFinished(void)
156 157 158 159
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());

    if (reply->error() != QNetworkReply::NoError) {
160
        qCWarning(TerrainQueryLog) << "_requestFinished error:url:data" << reply->error() << reply->url() << reply->readAll();
161
        reply->deleteLater();
162
        _requestFailed();
163 164 165 166 167 168 169 170 171 172
        return;
    }

    QByteArray responseBytes = reply->readAll();
    reply->deleteLater();

    // Convert the response to Json
    QJsonParseError parseError;
    QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
173
        qCWarning(TerrainQueryLog) << "_requestFinished unable to parse json:" << parseError.errorString();
174
        _requestFailed();
175 176 177 178 179 180 181
        return;
    }

    // Check airmap reponse status
    QJsonObject rootObject = responseJson.object();
    QString status = rootObject["status"].toString();
    if (status != "success") {
182
        qCWarning(TerrainQueryLog) << "_requestFinished status != success:" << status;
183
        _requestFailed();
184 185 186 187
        return;
    }

    // Send back data
188
    const QJsonValue& jsonData = rootObject["data"];
DonLakeFlyer's avatar
DonLakeFlyer committed
189
    qCDebug(TerrainQueryLog) << "_requestFinished success";
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    switch (_queryMode) {
    case QueryModeCoordinates:
        emit _parseCoordinateData(jsonData);
        break;
    case QueryModePath:
        emit _parsePathData(jsonData);
        break;
    case QueryModeCarpet:
        emit _parseCarpetData(jsonData);
        break;
    }
}

void TerrainAirMapQuery::_requestFailed(void)
{
    switch (_queryMode) {
    case QueryModeCoordinates:
DonLakeFlyer's avatar
DonLakeFlyer committed
207
        emit coordinateHeightsReceived(false /* success */, QList<double>() /* heights */);
208 209
        break;
    case QueryModePath:
DonLakeFlyer's avatar
DonLakeFlyer committed
210
        emit pathHeightsReceived(false /* success */, qQNaN() /* latStep */, qQNaN() /* lonStep */, QList<double>() /* heights */);
211 212
        break;
    case QueryModeCarpet:
DonLakeFlyer's avatar
DonLakeFlyer committed
213
        emit carpetHeightsReceived(false /* success */, qQNaN() /* minHeight */, qQNaN() /* maxHeight */, QList<QList<double>>() /* carpet */);
214 215 216 217 218 219 220 221 222 223 224 225
        break;
    }
}

void TerrainAirMapQuery::_parseCoordinateData(const QJsonValue& coordinateJson)
{
    QList<double> heights;
    const QJsonArray& dataArray = coordinateJson.toArray();
    for (int i = 0; i < dataArray.count(); i++) {
        heights.append(dataArray[i].toDouble());
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
226
    emit coordinateHeightsReceived(true /* success */, heights);
227 228 229 230 231 232 233 234 235 236 237 238
}

void TerrainAirMapQuery::_parsePathData(const QJsonValue& pathJson)
{
    QJsonObject jsonObject =    pathJson.toArray()[0].toObject();
    QJsonArray stepArray =      jsonObject["step"].toArray();
    QJsonArray profileArray =   jsonObject["profile"].toArray();

    double latStep = stepArray[0].toDouble();
    double lonStep = stepArray[1].toDouble();

    QList<double> heights;
239
    for (const QJsonValue& profileValue: profileArray) {
240 241 242
        heights.append(profileValue.toDouble());
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
243
    emit pathHeightsReceived(true /* success */, latStep, lonStep, heights);
244 245 246 247 248 249 250 251
}

void TerrainAirMapQuery::_parseCarpetData(const QJsonValue& carpetJson)
{
    QJsonObject jsonObject =    carpetJson.toArray()[0].toObject();

    QJsonObject statsObject =   jsonObject["stats"].toObject();
    double      minHeight =     statsObject["min"].toDouble();
252
    double      maxHeight =     statsObject["max"].toDouble();
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268

    QList<QList<double>> carpet;
    if (!_carpetStatsOnly) {
        QJsonArray carpetArray =   jsonObject["carpet"].toArray();

        for (int i=0; i<carpetArray.count(); i++) {
            QJsonArray rowArray = carpetArray[i].toArray();
            carpet.append(QList<double>());

            for (int j=0; j<rowArray.count(); j++) {
                double height = rowArray[j].toDouble();
                carpet.last().append(height);
            }
        }
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
269
    emit carpetHeightsReceived(true /*success*/, minHeight, maxHeight, carpet);
270 271
}

272 273 274
TerrainOfflineAirMapQuery::TerrainOfflineAirMapQuery(QObject* parent)
    : TerrainQueryInterface(parent)
{
275
    qCDebug(TerrainQueryVerboseLog) << "supportsSsl" << QSslSocket::supportsSsl() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
276 277 278 279
}

void TerrainOfflineAirMapQuery::requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates)
{
280
    if (qgcApp()->runningUnitTests()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
281
        emit coordinateHeightsReceived(false, QList<double>());
282 283 284
        return;
    }

285
    if (coordinates.length() == 0) {
Andreas Bircher's avatar
Andreas Bircher committed
286
        return;
287 288
    }

Andreas Bircher's avatar
Andreas Bircher committed
289
    _terrainTileManager->addCoordinateQuery(this, coordinates);
290 291 292 293
}

void TerrainOfflineAirMapQuery::requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
{
294
    if (qgcApp()->runningUnitTests()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
295
        emit pathHeightsReceived(false, qQNaN(), qQNaN(), QList<double>());
296 297 298
        return;
    }

299
    _terrainTileManager->addPathQuery(this, fromCoord, toCoord);
300 301 302 303
}

void TerrainOfflineAirMapQuery::requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly)
{
304
    if (qgcApp()->runningUnitTests()) {
DonLakeFlyer's avatar
DonLakeFlyer committed
305
        emit carpetHeightsReceived(false, qQNaN(), qQNaN(), QList<QList<double>>());
306 307 308
        return;
    }

309
    // TODO
310 311 312 313
    Q_UNUSED(swCoord);
    Q_UNUSED(neCoord);
    Q_UNUSED(statsOnly);
    qWarning() << "Carpet queries are currently not supported from offline air map data";
314 315 316 317
}

void TerrainOfflineAirMapQuery::_signalCoordinateHeights(bool success, QList<double> heights)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
318
    emit coordinateHeightsReceived(success, heights);
319 320
}

321
void TerrainOfflineAirMapQuery::_signalPathHeights(bool success, double distanceBetween, double finalDistanceBetween, const QList<double>& heights)
322
{
323
    emit pathHeightsReceived(success, distanceBetween, finalDistanceBetween, heights);
324 325 326 327
}

void TerrainOfflineAirMapQuery::_signalCarpetHeights(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
328
    emit carpetHeightsReceived(success, minHeight, maxHeight, carpet);
329 330 331 332 333 334 335
}

TerrainTileManager::TerrainTileManager(void)
{

}

Andreas Bircher's avatar
Andreas Bircher committed
336
void TerrainTileManager::addCoordinateQuery(TerrainOfflineAirMapQuery* terrainQueryInterface, const QList<QGeoCoordinate>& coordinates)
337
{
DonLakeFlyer's avatar
DonLakeFlyer committed
338 339
    qCDebug(TerrainQueryLog) << "TerrainTileManager::addCoordinateQuery count" << coordinates.count();

340
    if (coordinates.length() > 0) {
341
        bool error;
Andreas Bircher's avatar
Andreas Bircher committed
342
        QList<double> altitudes;
343

344
        if (!getAltitudesForCoordinates(coordinates, altitudes, error)) {
Don Gagne's avatar
Don Gagne committed
345
            qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count();
DonLakeFlyer's avatar
DonLakeFlyer committed
346
            QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModeCoordinates, 0, 0, coordinates };
347 348 349 350
            _requestQueue.append(queuedRequestInfo);
            return;
        }

351 352 353 354 355 356 357 358
        if (error) {
            QList<double> noAltitudes;
            qCWarning(TerrainQueryLog) << "addCoordinateQuery: signalling failure due to internal error";
            terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
        } else {
            qCDebug(TerrainQueryLog) << "addCoordinateQuery: All altitudes taken from cached data";
            terrainQueryInterface->_signalCoordinateHeights(coordinates.count() == altitudes.count(), altitudes);
        }
359 360 361
    }
}

362 363
void TerrainTileManager::addPathQuery(TerrainOfflineAirMapQuery* terrainQueryInterface, const QGeoCoordinate &startPoint, const QGeoCoordinate &endPoint)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
364
    // Convert to individual coordinate queries
365 366 367
    QList<QGeoCoordinate> coordinates;
    double lat = startPoint.latitude();
    double lon = startPoint.longitude();
DonLakeFlyer's avatar
DonLakeFlyer committed
368
    double steps = ceil(endPoint.distanceTo(startPoint) / TerrainTile::terrainAltitudeSpacing);
369 370
    double latDiff = endPoint.latitude() - lat;
    double lonDiff = endPoint.longitude() - lon;
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

    double distanceBetween;
    double finalDistanceBetween;
    if (steps == 0) {
        coordinates.append(startPoint);
        coordinates.append(endPoint);
        distanceBetween = finalDistanceBetween = coordinates[0].distanceTo(coordinates[1]);
    } else {
        for (double i = 0.0; i <= steps; i = i + 1) {
            coordinates.append(QGeoCoordinate(lat + latDiff * i / steps, lon + lonDiff * i / steps));
        }
        // We always have one too many and we always want the last one to be the endpoint
        coordinates.last() = endPoint;
        distanceBetween = coordinates[0].distanceTo(coordinates[1]);
        finalDistanceBetween = coordinates[coordinates.count() - 2].distanceTo(coordinates.last());
386
    }
387 388

    //qDebug() << "terrain" << startPoint.distanceTo(endPoint) << coordinates.count() << distanceBetween;
DonLakeFlyer's avatar
DonLakeFlyer committed
389 390

    qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery start:end:coordCount" << startPoint << endPoint << coordinates.count();
391

392
    bool error;
393
    QList<double> altitudes;
394
    if (!getAltitudesForCoordinates(coordinates, altitudes, error)) {
Don Gagne's avatar
Don Gagne committed
395
        qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count();
396
        QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModePath, distanceBetween, finalDistanceBetween, coordinates };
397 398 399 400
        _requestQueue.append(queuedRequestInfo);
        return;
    }

401 402 403
    if (error) {
        QList<double> noAltitudes;
        qCWarning(TerrainQueryLog) << "addPathQuery: signalling failure due to internal error";
404
        terrainQueryInterface->_signalPathHeights(false, distanceBetween, finalDistanceBetween, noAltitudes);
405 406
    } else {
        qCDebug(TerrainQueryLog) << "addPathQuery: All altitudes taken from cached data";
407
        terrainQueryInterface->_signalPathHeights(coordinates.count() == altitudes.count(), distanceBetween, finalDistanceBetween, altitudes);
408
    }
409 410
}

Don Gagne's avatar
Don Gagne committed
411 412 413
/// Either returns altitudes from cache or queues database request
///     @param[out] error true: altitude not returned due to error, false: altitudes returned
/// @return true: altitude returned (check error as well), false: database query queued (altitudes not returned)
414
bool TerrainTileManager::getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error)
415
{
416 417
    error = false;

418
    for (const QGeoCoordinate& coordinate: coordinates) {
419
        QString tileHash = _getTileHash(coordinate);
420
        qCDebug(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates hash:coordinate" << tileHash << coordinate;
421

Don Gagne's avatar
Don Gagne committed
422 423 424 425
        _tilesMutex.lock();
        if (_tiles.contains(tileHash)) {
            if (_tiles[tileHash].isIn(coordinate)) {
                double elevation = _tiles[tileHash].elevation(coordinate);
426
                if (qIsNaN(elevation)) {
Don Gagne's avatar
Don Gagne committed
427
                    error = true;
428
                    qCWarning(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates Internal Error: missing elevation in tile cache";
Don Gagne's avatar
Don Gagne committed
429
                } else {
430
                    qCDebug(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates returning elevation from tile cache" << elevation;
Don Gagne's avatar
Don Gagne committed
431 432 433
                }
                altitudes.push_back(elevation);
            } else {
434
                qCWarning(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates Internal Error: coordinate not in tile region";
435
                altitudes.push_back(qQNaN());
Don Gagne's avatar
Don Gagne committed
436 437 438
                error = true;
            }
        } else {
439
            if (_state != State::Downloading) {
440
                QNetworkRequest request = getQGCMapEngine()->urlFactory()->getTileURL("Airmap Elevation", getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation",coordinate.longitude(), 1), getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1), 1, &_networkManager);
441
                qCDebug(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates query from database" << request.url();
442
                QGeoTileSpec spec;
443 444
                spec.setX(getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation", coordinate.longitude(), 1));
                spec.setY(getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1));
445
                spec.setZoom(1);
446
                spec.setMapId(getQGCMapEngine()->urlFactory()->getIdFromType("Airmap Elevation"));
447
                QGeoTiledMapReplyQGC* reply = new QGeoTiledMapReplyQGC(&_networkManager, request, spec);
448
                connect(reply, &QGeoTiledMapReplyQGC::terrainDone, this, &TerrainTileManager::_terrainDone);
449 450 451 452 453 454 455 456
                _state = State::Downloading;
            }
            _tilesMutex.unlock();

            return false;
        }
        _tilesMutex.unlock();
    }
457

458 459 460 461 462
    return true;
}

void TerrainTileManager::_tileFailed(void)
{
Andreas Bircher's avatar
Andreas Bircher committed
463
    QList<double>    noAltitudes;
464

465
    for (const QueuedRequestInfo_t& requestInfo: _requestQueue) {
466 467
        if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
            requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
DonLakeFlyer's avatar
DonLakeFlyer committed
468
        } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
469
            requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.distanceBetween, requestInfo.finalDistanceBetween, noAltitudes);
470 471 472 473 474
        }
    }
    _requestQueue.clear();
}

475
void TerrainTileManager::_terrainDone(QByteArray responseBytes, QNetworkReply::NetworkError error)
476 477 478 479 480
{
    QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
    _state = State::Idle;

    if (!reply) {
481
        qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type.";
482 483 484 485 486
        return;
    }

    // remove from download queue
    QGeoTileSpec spec = reply->tileSpec();
487
    QString hash = QGCMapEngine::getTileHash("Airmap Elevation", spec.x(), spec.y(), spec.zoom());
488 489

    // handle potential errors
490
    if (error != QNetworkReply::NoError) {
491
        qCWarning(TerrainQueryLog) << "Elevation tile fetching returned error (" << error << ")";
492 493 494 495
        _tileFailed();
        reply->deleteLater();
        return;
    }
496
    if (responseBytes.isEmpty()) {
497
        qCWarning(TerrainQueryLog) << "Error in fetching elevation tile. Empty response.";
498 499 500 501 502
        _tileFailed();
        reply->deleteLater();
        return;
    }

Don Gagne's avatar
Don Gagne committed
503
    qCDebug(TerrainQueryLog) << "Received some bytes of terrain data: " << responseBytes.size();
504

505
    TerrainTile* terrainTile = new TerrainTile(responseBytes);
506 507 508 509 510 511 512 513 514
    if (terrainTile->isValid()) {
        _tilesMutex.lock();
        if (!_tiles.contains(hash)) {
            _tiles.insert(hash, *terrainTile);
        } else {
            delete terrainTile;
        }
        _tilesMutex.unlock();
    } else {
515
        delete terrainTile;
516
        qCWarning(TerrainQueryLog) << "Received invalid tile";
517 518 519 520 521
    }
    reply->deleteLater();

    // now try to query the data again
    for (int i = _requestQueue.count() - 1; i >= 0; i--) {
522
        bool error;
Andreas Bircher's avatar
Andreas Bircher committed
523
        QList<double> altitudes;
DonLakeFlyer's avatar
DonLakeFlyer committed
524 525
        QueuedRequestInfo_t& requestInfo = _requestQueue[i];

526
        if (getAltitudesForCoordinates(requestInfo.coordinates, altitudes, error)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
527
            if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
528 529 530 531 532 533 534 535
                if (error) {
                    QList<double> noAltitudes;
                    qCWarning(TerrainQueryLog) << "_terrainDone(coordinateQuery): signalling failure due to internal error";
                    requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
                } else {
                    qCDebug(TerrainQueryLog) << "_terrainDone(coordinateQuery): All altitudes taken from cached data";
                    requestInfo.terrainQueryInterface->_signalCoordinateHeights(requestInfo.coordinates.count() == altitudes.count(), altitudes);
                }
DonLakeFlyer's avatar
DonLakeFlyer committed
536
            } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
537 538 539
                if (error) {
                    QList<double> noAltitudes;
                    qCWarning(TerrainQueryLog) << "_terrainDone(coordinateQuery): signalling failure due to internal error";
540
                    requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.distanceBetween, requestInfo.finalDistanceBetween, noAltitudes);
541 542
                } else {
                    qCDebug(TerrainQueryLog) << "_terrainDone(coordinateQuery): All altitudes taken from cached data";
543
                    requestInfo.terrainQueryInterface->_signalPathHeights(requestInfo.coordinates.count() == altitudes.count(), requestInfo.distanceBetween, requestInfo.finalDistanceBetween, altitudes);
544
                }
545 546 547 548 549 550 551 552
            }
            _requestQueue.removeAt(i);
        }
    }
}

QString TerrainTileManager::_getTileHash(const QGeoCoordinate& coordinate)
{
553 554 555 556 557
    QString ret = QGCMapEngine::getTileHash(
        "Airmap Elevation",
        getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation", coordinate.longitude(), 1),
        getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1),
        1);
DonLakeFlyer's avatar
DonLakeFlyer committed
558
    qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret;
559 560 561 562

    return ret;
}

563 564 565 566 567
TerrainAtCoordinateBatchManager::TerrainAtCoordinateBatchManager(void)
{
    _batchTimer.setSingleShot(true);
    _batchTimer.setInterval(_batchTimeout);
    connect(&_batchTimer, &QTimer::timeout, this, &TerrainAtCoordinateBatchManager::_sendNextBatch);
DonLakeFlyer's avatar
DonLakeFlyer committed
568
    connect(&_terrainQuery, &TerrainQueryInterface::coordinateHeightsReceived, this, &TerrainAtCoordinateBatchManager::_coordinateHeights);
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
}

void TerrainAtCoordinateBatchManager::addQuery(TerrainAtCoordinateQuery* terrainAtCoordinateQuery, const QList<QGeoCoordinate>& coordinates)
{
    if (coordinates.length() > 0) {
        connect(terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
        QueuedRequestInfo_t queuedRequestInfo = { terrainAtCoordinateQuery, coordinates };
        _requestQueue.append(queuedRequestInfo);
        if (!_batchTimer.isActive()) {
            _batchTimer.start();
        }
    }
}

void TerrainAtCoordinateBatchManager::_sendNextBatch(void)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
585
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
586 587 588

    if (_state != State::Idle) {
        // Waiting for last download the complete, wait some more
Don Gagne's avatar
Don Gagne committed
589
        qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch waiting for current batch, restarting timer";
590 591 592 593 594 595 596 597 598 599 600
        _batchTimer.start();
        return;
    }

    if (_requestQueue.count() == 0) {
        return;
    }

    _sentRequests.clear();

    // Convert coordinates to point strings for json query
601
    QList<QGeoCoordinate> coords;
602
    int requestQueueAdded = 0;
603
    for (const QueuedRequestInfo_t& requestInfo: _requestQueue) {
604 605
        SentRequestInfo_t sentRequestInfo = { requestInfo.terrainAtCoordinateQuery, false, requestInfo.coordinates.count() };
        _sentRequests.append(sentRequestInfo);
606
        coords += requestInfo.coordinates;
607 608 609 610
        requestQueueAdded++;
        if (coords.count() > 50) {
            break;
        }
611
    }
612
    _requestQueue = _requestQueue.mid(requestQueueAdded);
Don Gagne's avatar
Don Gagne committed
613
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch requesting next batch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
614 615

    _state = State::Downloading;
616
    _terrainQuery.requestCoordinateHeights(coords);
617 618 619 620
}

void TerrainAtCoordinateBatchManager::_batchFailed(void)
{
621
    QList<double> noHeights;
622

623
    for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
624 625
        if (!sentRequestInfo.queryObjectDestroyed) {
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
626
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights);
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
        }
    }
    _sentRequests.clear();
}

void TerrainAtCoordinateBatchManager::_queryObjectDestroyed(QObject* terrainAtCoordinateQuery)
{
    // Remove/Mark deleted objects queries from queues

    qCDebug(TerrainQueryLog) << "_TerrainAtCoordinateQueryDestroyed TerrainAtCoordinateQuery" << terrainAtCoordinateQuery;

    int i = 0;
    while (i < _requestQueue.count()) {
        const QueuedRequestInfo_t& requestInfo = _requestQueue[i];
        if (requestInfo.terrainAtCoordinateQuery == terrainAtCoordinateQuery) {
            qCDebug(TerrainQueryLog) << "Removing deleted provider from _requestQueue index:terrainAtCoordinateQuery" << i << requestInfo.terrainAtCoordinateQuery;
            _requestQueue.removeAt(i);
        } else {
            i++;
        }
    }

    for (int i=0; i<_sentRequests.count(); i++) {
        SentRequestInfo_t& sentRequestInfo = _sentRequests[i];
        if (sentRequestInfo.terrainAtCoordinateQuery == terrainAtCoordinateQuery) {
            qCDebug(TerrainQueryLog) << "Zombieing deleted provider from _sentRequests index:terrainAtCoordinateQuery" << sentRequestInfo.terrainAtCoordinateQuery;
            sentRequestInfo.queryObjectDestroyed = true;
        }
    }
}

QString TerrainAtCoordinateBatchManager::_stateToString(State state)
{
    switch (state) {
    case State::Idle:
        return QStringLiteral("Idle");
    case State::Downloading:
        return QStringLiteral("Downloading");
    }

    return QStringLiteral("State unknown");
}

670
void TerrainAtCoordinateBatchManager::_coordinateHeights(bool success, QList<double> heights)
671 672 673
{
    _state = State::Idle;

Don Gagne's avatar
Don Gagne committed
674
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights signalled success:count" << success << heights.count();
DonLakeFlyer's avatar
DonLakeFlyer committed
675

DonLakeFlyer's avatar
DonLakeFlyer committed
676 677 678 679
    if (!success) {
        _batchFailed();
        return;
    }
680 681

    int currentIndex = 0;
682
    for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
683
        if (!sentRequestInfo.queryObjectDestroyed) {
DonLakeFlyer's avatar
DonLakeFlyer committed
684
            qCDebug(TerrainQueryVerboseLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights returned TerrainCoordinateQuery:count" <<  sentRequestInfo.terrainAtCoordinateQuery << sentRequestInfo.cCoord;
685
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
686
            QList<double> requestAltitudes = heights.mid(currentIndex, sentRequestInfo.cCoord);
687 688 689 690 691
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(true, requestAltitudes);
            currentIndex += sentRequestInfo.cCoord;
        }
    }
    _sentRequests.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
692 693 694 695

    if (_requestQueue.count()) {
        _batchTimer.start();
    }
696 697
}

698 699
TerrainAtCoordinateQuery::TerrainAtCoordinateQuery(bool autoDelete)
    : _autoDelete(autoDelete)
700 701 702 703 704 705 706 707 708 709 710 711
{

}
void TerrainAtCoordinateQuery::requestData(const QList<QGeoCoordinate>& coordinates)
{
    if (coordinates.length() == 0) {
        return;
    }

    _TerrainAtCoordinateBatchManager->addQuery(this, coordinates);
}

712 713 714 715 716
bool TerrainAtCoordinateQuery::getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error)
{
    return _terrainTileManager->getAltitudesForCoordinates(coordinates, altitudes, error);
}

717
void TerrainAtCoordinateQuery::_signalTerrainData(bool success, QList<double>& heights)
718
{
DonLakeFlyer's avatar
DonLakeFlyer committed
719
    emit terrainDataReceived(success, heights);
720 721 722
    if (_autoDelete) {
        deleteLater();
    }
723 724
}

725 726
TerrainPathQuery::TerrainPathQuery(bool autoDelete)
   : _autoDelete   (autoDelete)
727
{
728
    qRegisterMetaType<PathHeightInfo_t>();
DonLakeFlyer's avatar
DonLakeFlyer committed
729
    connect(&_terrainQuery, &TerrainQueryInterface::pathHeightsReceived, this, &TerrainPathQuery::_pathHeights);
730 731 732 733
}

void TerrainPathQuery::requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
{
734
    _terrainQuery.requestPathHeights(fromCoord, toCoord);
735 736
}

737
void TerrainPathQuery::_pathHeights(bool success, double distanceBetween, double finalDistanceBetween, const QList<double>& heights)
738
{
739
    PathHeightInfo_t pathHeightInfo;
740 741 742
    pathHeightInfo.distanceBetween =        distanceBetween;
    pathHeightInfo.finalDistanceBetween =   finalDistanceBetween;
    pathHeightInfo.heights =                heights;
DonLakeFlyer's avatar
DonLakeFlyer committed
743
    emit terrainDataReceived(success, pathHeightInfo);
744 745 746
    if (_autoDelete) {
        deleteLater();
    }
747 748
}

749 750 751
TerrainPolyPathQuery::TerrainPolyPathQuery(bool autoDelete)
    : _autoDelete   (autoDelete)
    , _pathQuery    (false /* autoDelete */)
752
{
DonLakeFlyer's avatar
DonLakeFlyer committed
753
    connect(&_pathQuery, &TerrainPathQuery::terrainDataReceived, this, &TerrainPolyPathQuery::_terrainDataReceived);
754 755 756 757 758 759
}

void TerrainPolyPathQuery::requestData(const QVariantList& polyPath)
{
    QList<QGeoCoordinate> path;

760
    for (const QVariant& geoVar: polyPath) {
761
        path.append(geoVar.value<QGeoCoordinate>());
762 763
    }

764
    requestData(path);
765 766
}

767 768
void TerrainPolyPathQuery::requestData(const QList<QGeoCoordinate>& polyPath)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
769 770
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::requestData count" << polyPath.count();

771 772 773 774 775 776 777 778
    // Kick off first request
    _rgCoords = polyPath;
    _curIndex = 0;
    _pathQuery.requestData(_rgCoords[0], _rgCoords[1]);
}

void TerrainPolyPathQuery::_terrainDataReceived(bool success, const TerrainPathQuery::PathHeightInfo_t& pathHeightInfo)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
779 780
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived success:_curIndex" << success << _curIndex;

781 782
    if (!success) {
        _rgPathHeightInfo.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
783
        emit terrainDataReceived(false /* success */, _rgPathHeightInfo);
784 785 786 787 788 789 790
        return;
    }

    _rgPathHeightInfo.append(pathHeightInfo);

    if (++_curIndex >= _rgCoords.count() - 1) {
        // We've finished all requests
DonLakeFlyer's avatar
DonLakeFlyer committed
791
        qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived complete";
DonLakeFlyer's avatar
DonLakeFlyer committed
792
        emit terrainDataReceived(true /* success */, _rgPathHeightInfo);
793 794 795
        if (_autoDelete) {
            deleteLater();
        }
796 797 798 799
    } else {
        _pathQuery.requestData(_rgCoords[_curIndex], _rgCoords[_curIndex+1]);
    }
}