TerrainQuery.cc 28.5 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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
    QString points;
    foreach (const QGeoCoordinate& coord, coordinates) {
            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 239 240 241 242
}

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;
    foreach (const QJsonValue& profileValue, profileArray) {
        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)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
345
            QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModeCoordinates, 0, 0, coordinates };
346 347 348 349
            _requestQueue.append(queuedRequestInfo);
            return;
        }

350 351 352 353 354 355 356 357
        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);
        }
358 359 360
    }
}

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

380
    bool error;
381
    QList<double> altitudes;
382
    if (!_getAltitudesForCoordinates(coordinates, altitudes, error)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
383
        QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModePath, latStep, lonStep, coordinates };
384 385 386 387
        _requestQueue.append(queuedRequestInfo);
        return;
    }

388 389 390 391 392 393 394 395
    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);
    }
396 397
}

398
bool TerrainTileManager::_getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error)
399
{
400 401
    error = false;

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
    foreach (const QGeoCoordinate& coordinate, coordinates) {
        QString tileHash = _getTileHash(coordinate);
        _tilesMutex.lock();
        if (!_tiles.contains(tileHash)) {
            qCDebug(TerrainQueryLog) << "Need to download tile " << tileHash;

            // Schedule the fetch task
            if (_state != State::Downloading) {
                QNetworkRequest request = getQGCMapEngine()->urlFactory()->getTileURL(UrlFactory::AirmapElevation, QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1), QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1), 1, &_networkManager);
                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);
417
                connect(reply, &QGeoTiledMapReplyQGC::terrainDone, this, &TerrainTileManager::_terrainDone);
418 419 420 421 422 423 424
                _state = State::Downloading;
            }
            _tilesMutex.unlock();

            return false;
        } else {
            if (_tiles[tileHash].isIn(coordinate)) {
425 426 427 428 429
                double elevation = _tiles[tileHash].elevation(coordinate);
                if (elevation < 0.0) {
                    error = true;
                }
                altitudes.push_back(elevation);
430
            } else {
431
                qCWarning(TerrainQueryLog) << "Error: coordinate not in tile region";
432
                altitudes.push_back(-1.0);
433
                error = true;
434 435 436 437
            }
        }
        _tilesMutex.unlock();
    }
438

439 440 441 442 443
    return true;
}

void TerrainTileManager::_tileFailed(void)
{
Andreas Bircher's avatar
Andreas Bircher committed
444
    QList<double>    noAltitudes;
445 446 447 448

    foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
        if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
            requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
DonLakeFlyer's avatar
DonLakeFlyer committed
449 450
        } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
            requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes);
451 452 453 454 455
        }
    }
    _requestQueue.clear();
}

456
void TerrainTileManager::_terrainDone(QByteArray responseBytes, QNetworkReply::NetworkError error)
457 458 459 460 461
{
    QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
    _state = State::Idle;

    if (!reply) {
462
        qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type.";
463 464 465 466 467 468 469 470
        return;
    }

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

    // handle potential errors
471
    if (error != QNetworkReply::NoError) {
472
        qCWarning(TerrainQueryLog) << "Elevation tile fetching returned error (" << error << ")";
473 474 475 476
        _tileFailed();
        reply->deleteLater();
        return;
    }
477
    if (responseBytes.isEmpty()) {
478
        qCWarning(TerrainQueryLog) << "Error in fetching elevation tile. Empty response.";
479 480 481 482 483
        _tileFailed();
        reply->deleteLater();
        return;
    }

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

486
    TerrainTile* terrainTile = new TerrainTile(responseBytes);
487 488 489 490 491 492 493 494 495
    if (terrainTile->isValid()) {
        _tilesMutex.lock();
        if (!_tiles.contains(hash)) {
            _tiles.insert(hash, *terrainTile);
        } else {
            delete terrainTile;
        }
        _tilesMutex.unlock();
    } else {
496
        qCWarning(TerrainQueryLog) << "Received invalid tile";
497 498 499 500 501
    }
    reply->deleteLater();

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

506
        if (_getAltitudesForCoordinates(requestInfo.coordinates, altitudes, error)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
507
            if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
508 509 510 511 512 513 514 515
                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
516
            } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
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->_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);
                }
525 526 527 528 529 530 531 532 533
            }
            _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
534
    qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret;
535 536 537 538

    return ret;
}

539 540 541 542 543
TerrainAtCoordinateBatchManager::TerrainAtCoordinateBatchManager(void)
{
    _batchTimer.setSingleShot(true);
    _batchTimer.setInterval(_batchTimeout);
    connect(&_batchTimer, &QTimer::timeout, this, &TerrainAtCoordinateBatchManager::_sendNextBatch);
DonLakeFlyer's avatar
DonLakeFlyer committed
544
    connect(&_terrainQuery, &TerrainQueryInterface::coordinateHeightsReceived, this, &TerrainAtCoordinateBatchManager::_coordinateHeights);
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
}

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
561
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
562 563 564

    if (_state != State::Idle) {
        // Waiting for last download the complete, wait some more
DonLakeFlyer's avatar
DonLakeFlyer committed
565
        qCDebug(TerrainQueryLog) << "_sendNextBatch restarting timer";
566 567 568 569 570 571 572 573 574 575 576
        _batchTimer.start();
        return;
    }

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

    _sentRequests.clear();

    // Convert coordinates to point strings for json query
577
    QList<QGeoCoordinate> coords;
578
    int requestQueueAdded = 0;
579 580 581
    foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
        SentRequestInfo_t sentRequestInfo = { requestInfo.terrainAtCoordinateQuery, false, requestInfo.coordinates.count() };
        _sentRequests.append(sentRequestInfo);
582
        coords += requestInfo.coordinates;
583 584 585 586
        requestQueueAdded++;
        if (coords.count() > 50) {
            break;
        }
587
    }
588
    _requestQueue = _requestQueue.mid(requestQueueAdded);
DonLakeFlyer's avatar
DonLakeFlyer committed
589
    qCDebug(TerrainQueryLog) << "_sendNextBatch - batch count:request queue count" << coords.count() << _requestQueue.count();
590 591

    _state = State::Downloading;
592
    _terrainQuery.requestCoordinateHeights(coords);
593 594 595 596
}

void TerrainAtCoordinateBatchManager::_batchFailed(void)
{
597
    QList<double> noHeights;
598 599 600 601

    foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
        if (!sentRequestInfo.queryObjectDestroyed) {
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
602
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights);
603 604 605 606 607 608 609 610 611 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
        }
    }
    _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");
}

646
void TerrainAtCoordinateBatchManager::_coordinateHeights(bool success, QList<double> heights)
647 648 649
{
    _state = State::Idle;

DonLakeFlyer's avatar
DonLakeFlyer committed
650
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights success:count" << success << heights.count();
DonLakeFlyer's avatar
DonLakeFlyer committed
651

DonLakeFlyer's avatar
DonLakeFlyer committed
652 653 654 655
    if (!success) {
        _batchFailed();
        return;
    }
656 657 658 659

    int currentIndex = 0;
    foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
        if (!sentRequestInfo.queryObjectDestroyed) {
DonLakeFlyer's avatar
DonLakeFlyer committed
660
            qCDebug(TerrainQueryVerboseLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights returned TerrainCoordinateQuery:count" <<  sentRequestInfo.terrainAtCoordinateQuery << sentRequestInfo.cCoord;
661
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
662
            QList<double> requestAltitudes = heights.mid(currentIndex, sentRequestInfo.cCoord);
663 664 665 666 667
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(true, requestAltitudes);
            currentIndex += sentRequestInfo.cCoord;
        }
    }
    _sentRequests.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
668 669 670 671

    if (_requestQueue.count()) {
        _batchTimer.start();
    }
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
}

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

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

    _TerrainAtCoordinateBatchManager->addQuery(this, coordinates);
}

688
void TerrainAtCoordinateQuery::_signalTerrainData(bool success, QList<double>& heights)
689
{
DonLakeFlyer's avatar
DonLakeFlyer committed
690
    emit terrainDataReceived(success, heights);
691 692 693
}

TerrainPathQuery::TerrainPathQuery(QObject* parent)
694
    : QObject(parent)
695
{
696
    qRegisterMetaType<PathHeightInfo_t>();
DonLakeFlyer's avatar
DonLakeFlyer committed
697
    connect(&_terrainQuery, &TerrainQueryInterface::pathHeightsReceived, this, &TerrainPathQuery::_pathHeights);
698 699 700 701
}

void TerrainPathQuery::requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
{
702
    _terrainQuery.requestPathHeights(fromCoord, toCoord);
703 704
}

705
void TerrainPathQuery::_pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights)
706
{
707
    PathHeightInfo_t pathHeightInfo;
708 709 710
    pathHeightInfo.latStep = latStep;
    pathHeightInfo.lonStep = lonStep;
    pathHeightInfo.heights = heights;
DonLakeFlyer's avatar
DonLakeFlyer committed
711
    emit terrainDataReceived(success, pathHeightInfo);
712 713 714 715 716 717
}

TerrainPolyPathQuery::TerrainPolyPathQuery(QObject* parent)
    : QObject   (parent)
    , _curIndex (0)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
718
    connect(&_pathQuery, &TerrainPathQuery::terrainDataReceived, this, &TerrainPolyPathQuery::_terrainDataReceived);
719 720 721 722 723 724 725 726
}

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

    foreach (const QVariant& geoVar, polyPath) {
        path.append(geoVar.value<QGeoCoordinate>());
727 728
    }

729
    requestData(path);
730 731
}

732 733
void TerrainPolyPathQuery::requestData(const QList<QGeoCoordinate>& polyPath)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
734 735
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::requestData count" << polyPath.count();

736 737 738 739 740 741 742 743
    // 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
744 745
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived success:_curIndex" << success << _curIndex;

746 747
    if (!success) {
        _rgPathHeightInfo.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
748
        emit terrainDataReceived(false /* success */, _rgPathHeightInfo);
749 750 751 752 753 754 755
        return;
    }

    _rgPathHeightInfo.append(pathHeightInfo);

    if (++_curIndex >= _rgCoords.count() - 1) {
        // We've finished all requests
DonLakeFlyer's avatar
DonLakeFlyer committed
756
        qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived complete";
DonLakeFlyer's avatar
DonLakeFlyer committed
757
        emit terrainDataReceived(true /* success */, _rgPathHeightInfo);
758 759 760 761
    } else {
        _pathQuery.requestData(_rgCoords[_curIndex], _rgCoords[_curIndex+1]);
    }
}
DonLakeFlyer's avatar
DonLakeFlyer committed
762 763

TerrainCarpetQuery::TerrainCarpetQuery(QObject* parent)
764
    : QObject(parent)
DonLakeFlyer's avatar
DonLakeFlyer committed
765
{
DonLakeFlyer's avatar
DonLakeFlyer committed
766
    connect(&_terrainQuery, &TerrainQueryInterface::carpetHeightsReceived, this, &TerrainCarpetQuery::terrainDataReceived);
DonLakeFlyer's avatar
DonLakeFlyer committed
767 768 769 770
}

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