TerrainQuery.cc 29.7 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
    //qDebug() << "TerrainTileManager::addPathQuery()" << startPoint;
366 367 368
    QList<QGeoCoordinate> coordinates;
    double lat = startPoint.latitude();
    double lon = startPoint.longitude();
DonLakeFlyer's avatar
DonLakeFlyer committed
369
    double steps = ceil(endPoint.distanceTo(startPoint) / TerrainTile::terrainAltitudeSpacing);
370 371
//    if (qFuzzyIsNull(steps))
//        return;
372 373 374 375 376
    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
377 378 379 380 381 382
    // 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();
383

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

393 394 395 396 397 398 399 400
    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);
    }
401 402
}

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

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

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

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

450 451 452 453 454
    return true;
}

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

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

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

    if (!reply) {
473
        qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type.";
474 475 476 477 478 479 480 481
        return;
    }

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

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

495 496
    qWarning() << "Received some bytes of terrain data: " << responseBytes.size();

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

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

517
        if (_getAltitudesForCoordinates(requestInfo.coordinates, altitudes, error)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
518
            if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
519 520 521 522 523 524 525 526
                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
527
            } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
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->_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);
                }
536 537 538 539 540 541 542 543 544
            }
            _requestQueue.removeAt(i);
        }
    }
}

QString TerrainTileManager::_getTileHash(const QGeoCoordinate& coordinate)
{
    QString ret = QGCMapEngine::getTileHash(UrlFactory::AirmapElevation, QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1), QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1), 1);
DonLakeFlyer's avatar
DonLakeFlyer committed
545
    qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret;
546 547 548 549

    return ret;
}

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

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

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

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

    _sentRequests.clear();

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

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

void TerrainAtCoordinateBatchManager::_batchFailed(void)
{
608
    QList<double> noHeights;
609

610
    for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
611 612
        if (!sentRequestInfo.queryObjectDestroyed) {
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
613
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights);
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 655 656
        }
    }
    _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");
}

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

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

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

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

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

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

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

    _TerrainAtCoordinateBatchManager->addQuery(this, coordinates);
}

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

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

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

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

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

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

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

740
    requestData(path);
741 742
}

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

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

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

    _rgPathHeightInfo.append(pathHeightInfo);

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

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

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