TerrainQuery.cc 26.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 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) {
Andreas Bircher's avatar
Andreas Bircher committed
341
        QList<double> altitudes;
342 343

        if (!_getAltitudesForCoordinates(coordinates, altitudes)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
344
            QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModeCoordinates, 0, 0, coordinates };
345 346 347 348 349
            _requestQueue.append(queuedRequestInfo);
            return;
        }

        qCDebug(TerrainQueryLog) << "All altitudes taken from cached data";
Andreas Bircher's avatar
Andreas Bircher committed
350
        terrainQueryInterface->_signalCoordinateHeights(coordinates.count() == altitudes.count(), altitudes);
351 352 353
    }
}

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

    QList<double> altitudes;
    if (!_getAltitudesForCoordinates(coordinates, altitudes)) {
DonLakeFlyer's avatar
DonLakeFlyer committed
375
        QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModePath, latStep, lonStep, coordinates };
376 377 378 379 380
        _requestQueue.append(queuedRequestInfo);
        return;
    }

    qCDebug(TerrainQueryLog) << "All altitudes taken from cached data";
DonLakeFlyer's avatar
DonLakeFlyer committed
381
    terrainQueryInterface->_signalPathHeights(coordinates.count() == altitudes.count(), latStep, lonStep, altitudes);
382 383
}

Andreas Bircher's avatar
Andreas Bircher committed
384
bool TerrainTileManager::_getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes)
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
{
    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);
401
                connect(reply, &QGeoTiledMapReplyQGC::terrainDone, this, &TerrainTileManager::_terrainDone);
402 403 404 405 406 407 408 409 410
                _state = State::Downloading;
            }
            _tilesMutex.unlock();

            return false;
        } else {
            if (_tiles[tileHash].isIn(coordinate)) {
                altitudes.push_back(_tiles[tileHash].elevation(coordinate));
            } else {
411
                qCWarning(TerrainQueryLog) << "Error: coordinate not in tile region";
412 413 414 415 416 417 418 419 420 421
                altitudes.push_back(-1.0);
            }
        }
        _tilesMutex.unlock();
    }
    return true;
}

void TerrainTileManager::_tileFailed(void)
{
Andreas Bircher's avatar
Andreas Bircher committed
422
    QList<double>    noAltitudes;
423 424 425 426

    foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
        if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
            requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
DonLakeFlyer's avatar
DonLakeFlyer committed
427 428
        } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
            requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes);
429 430 431 432 433
        }
    }
    _requestQueue.clear();
}

434
void TerrainTileManager::_terrainDone(QByteArray responseBytes, QNetworkReply::NetworkError error)
435 436 437 438 439
{
    QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
    _state = State::Idle;

    if (!reply) {
440
        qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type.";
441 442 443 444 445 446 447 448
        return;
    }

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

    // handle potential errors
449
    if (error != QNetworkReply::NoError) {
450
        qCWarning(TerrainQueryLog) << "Elevation tile fetching returned error (" << error << ")";
451 452 453 454
        _tileFailed();
        reply->deleteLater();
        return;
    }
455
    if (responseBytes.isEmpty()) {
456
        qCWarning(TerrainQueryLog) << "Error in fetching elevation tile. Empty response.";
457 458 459 460 461
        _tileFailed();
        reply->deleteLater();
        return;
    }

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

464
    TerrainTile* terrainTile = new TerrainTile(responseBytes);
465 466 467 468 469 470 471 472 473
    if (terrainTile->isValid()) {
        _tilesMutex.lock();
        if (!_tiles.contains(hash)) {
            _tiles.insert(hash, *terrainTile);
        } else {
            delete terrainTile;
        }
        _tilesMutex.unlock();
    } else {
474
        qCWarning(TerrainQueryLog) << "Received invalid tile";
475 476 477 478 479
    }
    reply->deleteLater();

    // now try to query the data again
    for (int i = _requestQueue.count() - 1; i >= 0; i--) {
Andreas Bircher's avatar
Andreas Bircher committed
480
        QList<double> altitudes;
DonLakeFlyer's avatar
DonLakeFlyer committed
481 482 483 484 485 486 487
        QueuedRequestInfo_t& requestInfo = _requestQueue[i];

        if (_getAltitudesForCoordinates(requestInfo.coordinates, altitudes)) {
            if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
                requestInfo.terrainQueryInterface->_signalCoordinateHeights(requestInfo.coordinates.count() == altitudes.count(), altitudes);
            } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
                requestInfo.terrainQueryInterface->_signalPathHeights(requestInfo.coordinates.count() == altitudes.count(), requestInfo.latStep, requestInfo.lonStep, altitudes);
488 489 490 491 492 493 494 495 496
            }
            _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
497
    qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret;
498 499 500 501

    return ret;
}

502 503 504 505 506
TerrainAtCoordinateBatchManager::TerrainAtCoordinateBatchManager(void)
{
    _batchTimer.setSingleShot(true);
    _batchTimer.setInterval(_batchTimeout);
    connect(&_batchTimer, &QTimer::timeout, this, &TerrainAtCoordinateBatchManager::_sendNextBatch);
DonLakeFlyer's avatar
DonLakeFlyer committed
507
    connect(&_terrainQuery, &TerrainQueryInterface::coordinateHeightsReceived, this, &TerrainAtCoordinateBatchManager::_coordinateHeights);
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
}

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
524
    qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
525 526 527

    if (_state != State::Idle) {
        // Waiting for last download the complete, wait some more
DonLakeFlyer's avatar
DonLakeFlyer committed
528
        qCDebug(TerrainQueryLog) << "_sendNextBatch restarting timer";
529 530 531 532 533 534 535 536 537 538 539
        _batchTimer.start();
        return;
    }

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

    _sentRequests.clear();

    // Convert coordinates to point strings for json query
540
    QList<QGeoCoordinate> coords;
541
    int requestQueueAdded = 0;
542 543 544
    foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
        SentRequestInfo_t sentRequestInfo = { requestInfo.terrainAtCoordinateQuery, false, requestInfo.coordinates.count() };
        _sentRequests.append(sentRequestInfo);
545
        coords += requestInfo.coordinates;
546 547 548 549
        requestQueueAdded++;
        if (coords.count() > 50) {
            break;
        }
550
    }
551
    _requestQueue = _requestQueue.mid(requestQueueAdded);
DonLakeFlyer's avatar
DonLakeFlyer committed
552
    qCDebug(TerrainQueryLog) << "_sendNextBatch - batch count:request queue count" << coords.count() << _requestQueue.count();
553 554

    _state = State::Downloading;
555
    _terrainQuery.requestCoordinateHeights(coords);
556 557 558 559
}

void TerrainAtCoordinateBatchManager::_batchFailed(void)
{
560
    QList<double> noHeights;
561 562 563 564

    foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
        if (!sentRequestInfo.queryObjectDestroyed) {
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
565
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights);
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
        }
    }
    _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");
}

609
void TerrainAtCoordinateBatchManager::_coordinateHeights(bool success, QList<double> heights)
610 611 612
{
    _state = State::Idle;

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

DonLakeFlyer's avatar
DonLakeFlyer committed
615 616 617 618
    if (!success) {
        _batchFailed();
        return;
    }
619 620 621 622

    int currentIndex = 0;
    foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
        if (!sentRequestInfo.queryObjectDestroyed) {
DonLakeFlyer's avatar
DonLakeFlyer committed
623
            qCDebug(TerrainQueryVerboseLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights returned TerrainCoordinateQuery:count" <<  sentRequestInfo.terrainAtCoordinateQuery << sentRequestInfo.cCoord;
624
            disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
625
            QList<double> requestAltitudes = heights.mid(currentIndex, sentRequestInfo.cCoord);
626 627 628 629 630
            sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(true, requestAltitudes);
            currentIndex += sentRequestInfo.cCoord;
        }
    }
    _sentRequests.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
631 632 633 634

    if (_requestQueue.count()) {
        _batchTimer.start();
    }
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
}

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

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

    _TerrainAtCoordinateBatchManager->addQuery(this, coordinates);
}

651
void TerrainAtCoordinateQuery::_signalTerrainData(bool success, QList<double>& heights)
652
{
DonLakeFlyer's avatar
DonLakeFlyer committed
653
    emit terrainDataReceived(success, heights);
654 655 656
}

TerrainPathQuery::TerrainPathQuery(QObject* parent)
657
    : QObject(parent)
658
{
659
    qRegisterMetaType<PathHeightInfo_t>();
DonLakeFlyer's avatar
DonLakeFlyer committed
660
    connect(&_terrainQuery, &TerrainQueryInterface::pathHeightsReceived, this, &TerrainPathQuery::_pathHeights);
661 662 663 664
}

void TerrainPathQuery::requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
{
665
    _terrainQuery.requestPathHeights(fromCoord, toCoord);
666 667
}

668
void TerrainPathQuery::_pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights)
669
{
670
    PathHeightInfo_t pathHeightInfo;
671 672 673
    pathHeightInfo.latStep = latStep;
    pathHeightInfo.lonStep = lonStep;
    pathHeightInfo.heights = heights;
DonLakeFlyer's avatar
DonLakeFlyer committed
674
    emit terrainDataReceived(success, pathHeightInfo);
675 676 677 678 679 680
}

TerrainPolyPathQuery::TerrainPolyPathQuery(QObject* parent)
    : QObject   (parent)
    , _curIndex (0)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
681
    connect(&_pathQuery, &TerrainPathQuery::terrainDataReceived, this, &TerrainPolyPathQuery::_terrainDataReceived);
682 683 684 685 686 687 688 689
}

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

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

692
    requestData(path);
693 694
}

695 696
void TerrainPolyPathQuery::requestData(const QList<QGeoCoordinate>& polyPath)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
697 698
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::requestData count" << polyPath.count();

699 700 701 702 703 704 705 706
    // 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
707 708
    qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived success:_curIndex" << success << _curIndex;

709 710
    if (!success) {
        _rgPathHeightInfo.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
711
        emit terrainDataReceived(false /* success */, _rgPathHeightInfo);
712 713 714 715 716 717 718
        return;
    }

    _rgPathHeightInfo.append(pathHeightInfo);

    if (++_curIndex >= _rgCoords.count() - 1) {
        // We've finished all requests
DonLakeFlyer's avatar
DonLakeFlyer committed
719
        qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived complete";
DonLakeFlyer's avatar
DonLakeFlyer committed
720
        emit terrainDataReceived(true /* success */, _rgPathHeightInfo);
721 722 723 724
    } else {
        _pathQuery.requestData(_rgCoords[_curIndex], _rgCoords[_curIndex+1]);
    }
}
DonLakeFlyer's avatar
DonLakeFlyer committed
725 726

TerrainCarpetQuery::TerrainCarpetQuery(QObject* parent)
727
    : QObject(parent)
DonLakeFlyer's avatar
DonLakeFlyer committed
728
{
DonLakeFlyer's avatar
DonLakeFlyer committed
729
    connect(&_terrainQuery, &TerrainQueryInterface::carpetHeightsReceived, this, &TerrainCarpetQuery::terrainDataReceived);
DonLakeFlyer's avatar
DonLakeFlyer committed
730 731 732 733
}

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