TerrainQuery.cc 29.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * 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 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
}

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

    QJsonObject statsObject =   jsonObject["stats"].toObject();
    double      minHeight =     statsObject["min"].toDouble();
    double      maxHeight =     statsObject["min"].toDouble();

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

void TerrainOfflineAirMapQuery::_signalPathHeights(bool success, double latStep, double lonStep, const QList<double>& heights)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
323
    emit pathHeightsReceived(success, latStep, lonStep, 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 371 372 373
    double latDiff = endPoint.latitude() - lat;
    double lonDiff = endPoint.longitude() - lon;
    for (double i = 0.0; i <= steps; i = i + 1) {
        coordinates.append(QGeoCoordinate(lat + latDiff * i / steps, lon + lonDiff * i / steps));
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
374 375 376 377 378 379
    // We always have one too many and we always want the last one to be the endpoint
    coordinates.last() = endPoint;
    double latStep = coordinates[1].latitude() - coordinates[0].latitude();
    double lonStep = coordinates[1].longitude() - coordinates[0].longitude();

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

381
    bool error;
382
    QList<double> altitudes;
383
    if (!_getAltitudesForCoordinates(coordinates, altitudes, error)) {
Don Gagne's avatar
Don Gagne committed
384
        qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count();
DonLakeFlyer's avatar
DonLakeFlyer committed
385
        QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModePath, latStep, lonStep, coordinates };
386 387 388 389
        _requestQueue.append(queuedRequestInfo);
        return;
    }

390 391 392 393 394 395 396 397
    if (error) {
        QList<double> noAltitudes;
        qCWarning(TerrainQueryLog) << "addPathQuery: signalling failure due to internal error";
        terrainQueryInterface->_signalPathHeights(false, latStep, lonStep, noAltitudes);
    } else {
        qCDebug(TerrainQueryLog) << "addPathQuery: All altitudes taken from cached data";
        terrainQueryInterface->_signalPathHeights(coordinates.count() == altitudes.count(), latStep, lonStep, altitudes);
    }
398 399
}

Don Gagne's avatar
Don Gagne committed
400 401 402
/// 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)
403
bool TerrainTileManager::_getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error)
404
{
405 406
    error = false;

407
    for (const QGeoCoordinate& coordinate: coordinates) {
408
        QString tileHash = _getTileHash(coordinate);
Don Gagne's avatar
Don Gagne committed
409
        qCDebug(TerrainQueryLog) << "TerrainTileManager::_getAltitudesForCoordinates hash:coordinate" << tileHash << coordinate;
410

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

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

447 448 449 450 451
    return true;
}

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

454
    for (const QueuedRequestInfo_t& requestInfo: _requestQueue) {
455 456
        if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
            requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
DonLakeFlyer's avatar
DonLakeFlyer committed
457 458
        } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
            requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes);
459 460 461 462 463
        }
    }
    _requestQueue.clear();
}

464
void TerrainTileManager::_terrainDone(QByteArray responseBytes, QNetworkReply::NetworkError error)
465 466 467 468 469
{
    QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
    _state = State::Idle;

    if (!reply) {
470
        qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type.";
471 472 473 474 475
        return;
    }

    // remove from download queue
    QGeoTileSpec spec = reply->tileSpec();
476
    QString hash = QGCMapEngine::getTileHash("AirmapElevation", spec.x(), spec.y(), spec.zoom());
477 478

    // handle potential errors
479
    if (error != QNetworkReply::NoError) {
480
        qCWarning(TerrainQueryLog) << "Elevation tile fetching returned error (" << error << ")";
481 482 483 484
        _tileFailed();
        reply->deleteLater();
        return;
    }
485
    if (responseBytes.isEmpty()) {
486
        qCWarning(TerrainQueryLog) << "Error in fetching elevation tile. Empty response.";
487 488 489 490 491
        _tileFailed();
        reply->deleteLater();
        return;
    }

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

494
    TerrainTile* terrainTile = new TerrainTile(responseBytes);
495 496 497 498 499 500 501 502 503
    if (terrainTile->isValid()) {
        _tilesMutex.lock();
        if (!_tiles.contains(hash)) {
            _tiles.insert(hash, *terrainTile);
        } else {
            delete terrainTile;
        }
        _tilesMutex.unlock();
    } else {
504
        delete terrainTile;
505
        qCWarning(TerrainQueryLog) << "Received invalid tile";
506 507 508 509 510
    }
    reply->deleteLater();

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

515
        if (_getAltitudesForCoordinates(requestInfo.coordinates, altitudes, error)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
516
            if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
517 518 519 520 521 522 523 524
                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
525
            } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
526 527 528 529 530 531 532 533
                if (error) {
                    QList<double> noAltitudes;
                    qCWarning(TerrainQueryLog) << "_terrainDone(coordinateQuery): signalling failure due to internal error";
                    requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes);
                } else {
                    qCDebug(TerrainQueryLog) << "_terrainDone(coordinateQuery): All altitudes taken from cached data";
                    requestInfo.terrainQueryInterface->_signalPathHeights(requestInfo.coordinates.count() == altitudes.count(), requestInfo.latStep, requestInfo.lonStep, altitudes);
                }
534 535 536 537 538 539 540 541
            }
            _requestQueue.removeAt(i);
        }
    }
}

QString TerrainTileManager::_getTileHash(const QGeoCoordinate& coordinate)
{
542
    QString ret = QGCMapEngine::getTileHash("AirmapElevation", getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation", coordinate.longitude(), 1), getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1), 1);
DonLakeFlyer's avatar
DonLakeFlyer committed
543
    qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret;
544 545 546 547

    return ret;
}

548 549 550 551 552
TerrainAtCoordinateBatchManager::TerrainAtCoordinateBatchManager(void)
{
    _batchTimer.setSingleShot(true);
    _batchTimer.setInterval(_batchTimeout);
    connect(&_batchTimer, &QTimer::timeout, this, &TerrainAtCoordinateBatchManager::_sendNextBatch);
DonLakeFlyer's avatar
DonLakeFlyer committed
553
    connect(&_terrainQuery, &TerrainQueryInterface::coordinateHeightsReceived, this, &TerrainAtCoordinateBatchManager::_coordinateHeights);
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
}

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
570
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
571 572 573

    if (_state != State::Idle) {
        // Waiting for last download the complete, wait some more
Don Gagne's avatar
Don Gagne committed
574
        qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch waiting for current batch, restarting timer";
575 576 577 578 579 580 581 582 583 584 585
        _batchTimer.start();
        return;
    }

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

    _sentRequests.clear();

    // Convert coordinates to point strings for json query
586
    QList<QGeoCoordinate> coords;
587
    int requestQueueAdded = 0;
588
    for (const QueuedRequestInfo_t& requestInfo: _requestQueue) {
589 590
        SentRequestInfo_t sentRequestInfo = { requestInfo.terrainAtCoordinateQuery, false, requestInfo.coordinates.count() };
        _sentRequests.append(sentRequestInfo);
591
        coords += requestInfo.coordinates;
592 593 594 595
        requestQueueAdded++;
        if (coords.count() > 50) {
            break;
        }
596
    }
597
    _requestQueue = _requestQueue.mid(requestQueueAdded);
Don Gagne's avatar
Don Gagne committed
598
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch requesting next batch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
599 600

    _state = State::Downloading;
601
    _terrainQuery.requestCoordinateHeights(coords);
602 603 604 605
}

void TerrainAtCoordinateBatchManager::_batchFailed(void)
{
606
    QList<double> noHeights;
607

608
    for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
609 610
        if (!sentRequestInfo.queryObjectDestroyed) {
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
611
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights);
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 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
        }
    }
    _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");
}

655
void TerrainAtCoordinateBatchManager::_coordinateHeights(bool success, QList<double> heights)
656 657 658
{
    _state = State::Idle;

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

DonLakeFlyer's avatar
DonLakeFlyer committed
661 662 663 664
    if (!success) {
        _batchFailed();
        return;
    }
665 666

    int currentIndex = 0;
667
    for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
668
        if (!sentRequestInfo.queryObjectDestroyed) {
DonLakeFlyer's avatar
DonLakeFlyer committed
669
            qCDebug(TerrainQueryVerboseLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights returned TerrainCoordinateQuery:count" <<  sentRequestInfo.terrainAtCoordinateQuery << sentRequestInfo.cCoord;
670
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
671
            QList<double> requestAltitudes = heights.mid(currentIndex, sentRequestInfo.cCoord);
672 673 674 675 676
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(true, requestAltitudes);
            currentIndex += sentRequestInfo.cCoord;
        }
    }
    _sentRequests.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
677 678 679 680

    if (_requestQueue.count()) {
        _batchTimer.start();
    }
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
}

TerrainAtCoordinateQuery::TerrainAtCoordinateQuery(QObject* parent)
    : QObject(parent)
{

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

    _TerrainAtCoordinateBatchManager->addQuery(this, coordinates);
}

697
void TerrainAtCoordinateQuery::_signalTerrainData(bool success, QList<double>& heights)
698
{
DonLakeFlyer's avatar
DonLakeFlyer committed
699
    emit terrainDataReceived(success, heights);
700 701 702
}

TerrainPathQuery::TerrainPathQuery(QObject* parent)
703
    : QObject(parent)
704
{
705
    qRegisterMetaType<PathHeightInfo_t>();
DonLakeFlyer's avatar
DonLakeFlyer committed
706
    connect(&_terrainQuery, &TerrainQueryInterface::pathHeightsReceived, this, &TerrainPathQuery::_pathHeights);
707 708 709 710
}

void TerrainPathQuery::requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
{
711
    _terrainQuery.requestPathHeights(fromCoord, toCoord);
712 713
}

714
void TerrainPathQuery::_pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights)
715
{
716
    PathHeightInfo_t pathHeightInfo;
717 718 719
    pathHeightInfo.latStep = latStep;
    pathHeightInfo.lonStep = lonStep;
    pathHeightInfo.heights = heights;
DonLakeFlyer's avatar
DonLakeFlyer committed
720
    emit terrainDataReceived(success, pathHeightInfo);
721 722 723 724 725 726
}

TerrainPolyPathQuery::TerrainPolyPathQuery(QObject* parent)
    : QObject   (parent)
    , _curIndex (0)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
727
    connect(&_pathQuery, &TerrainPathQuery::terrainDataReceived, this, &TerrainPolyPathQuery::_terrainDataReceived);
728 729 730 731 732 733
}

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

734
    for (const QVariant& geoVar: polyPath) {
735
        path.append(geoVar.value<QGeoCoordinate>());
736 737
    }

738
    requestData(path);
739 740
}

741 742
void TerrainPolyPathQuery::requestData(const QList<QGeoCoordinate>& polyPath)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
743 744
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::requestData count" << polyPath.count();

745 746 747 748 749 750 751 752
    // 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
753 754
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived success:_curIndex" << success << _curIndex;

755 756
    if (!success) {
        _rgPathHeightInfo.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
757
        emit terrainDataReceived(false /* success */, _rgPathHeightInfo);
758 759 760 761 762 763 764
        return;
    }

    _rgPathHeightInfo.append(pathHeightInfo);

    if (++_curIndex >= _rgCoords.count() - 1) {
        // We've finished all requests
DonLakeFlyer's avatar
DonLakeFlyer committed
765
        qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived complete";
DonLakeFlyer's avatar
DonLakeFlyer committed
766
        emit terrainDataReceived(true /* success */, _rgPathHeightInfo);
767 768 769 770
    } else {
        _pathQuery.requestData(_rgCoords[_curIndex], _rgCoords[_curIndex+1]);
    }
}
DonLakeFlyer's avatar
DonLakeFlyer committed
771 772

TerrainCarpetQuery::TerrainCarpetQuery(QObject* parent)
773
    : QObject(parent)
DonLakeFlyer's avatar
DonLakeFlyer committed
774
{
DonLakeFlyer's avatar
DonLakeFlyer committed
775
    connect(&_terrainQuery, &TerrainQueryInterface::carpetHeightsReceived, this, &TerrainCarpetQuery::terrainDataReceived);
DonLakeFlyer's avatar
DonLakeFlyer committed
776 777 778 779
}

void TerrainCarpetQuery::requestData(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly)
{
780
    _terrainQuery.requestCarpetHeights(swCoord, neCoord, statsOnly);
DonLakeFlyer's avatar
DonLakeFlyer committed
781
}