Skip to content
Snippets Groups Projects
TerrainQuery.cc 29.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /****************************************************************************
     *
     *   (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"
    
    #include "QGCMapEngine.h"
    #include "QGeoMapReplyQGC.h"
    
    #include "QGCApplication.h"
    
    
    #include <QUrl>
    #include <QUrlQuery>
    #include <QNetworkRequest>
    #include <QNetworkProxy>
    #include <QNetworkReply>
    
    #include <QSslConfiguration>
    
    #include <QJsonDocument>
    #include <QJsonObject>
    #include <QJsonArray>
    #include <QTimer>
    
    #include <QtLocation/private/qgeotilespec_p.h>
    
    Andreas Bircher's avatar
    Andreas Bircher committed
    #include <cmath>
    
    
    QGC_LOGGING_CATEGORY(TerrainQueryLog, "TerrainQueryLog")
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    QGC_LOGGING_CATEGORY(TerrainQueryVerboseLog, "TerrainQueryVerboseLog")
    
    
    Q_GLOBAL_STATIC(TerrainAtCoordinateBatchManager, _TerrainAtCoordinateBatchManager)
    
    Q_GLOBAL_STATIC(TerrainTileManager, _terrainTileManager)
    
    TerrainAirMapQuery::TerrainAirMapQuery(QObject* parent)
        : TerrainQueryInterface(parent)
    
        qCDebug(TerrainQueryVerboseLog) << "supportsSsl" << QSslSocket::supportsSsl() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
    
    void TerrainAirMapQuery::requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates)
    {
    
        if (qgcApp()->runningUnitTests()) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit coordinateHeightsReceived(false, QList<double>());
    
        for (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)
    {
    
        if (qgcApp()->runningUnitTests()) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit pathHeightsReceived(false, qQNaN(), qQNaN(), QList<double>());
    
        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)
    {
    
        if (qgcApp()->runningUnitTests()) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit carpetHeightsReceived(false, qQNaN(), qQNaN(), QList<QList<double>>());
    
        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)
    
    {
        QUrl url(QStringLiteral("https://api.airmap.com/elevation/v1/ele") + path);
    
        qCDebug(TerrainQueryLog) << "_sendQuery" << url;
    
        url.setQuery(urlQuery);
    
        QSslConfiguration sslConf = request.sslConfiguration();
        sslConf.setPeerVerifyMode(QSslSocket::VerifyNone);
        request.setSslConfiguration(sslConf);
    
    
        QNetworkProxy tProxy;
        tProxy.setType(QNetworkProxy::DefaultProxy);
        _networkManager.setProxy(tProxy);
    
        QNetworkReply* networkReply = _networkManager.get(request);
        if (!networkReply) {
    
            qCWarning(TerrainQueryLog) << "QNetworkManager::Get did not return QNetworkReply";
    
            _requestFailed();
    
        networkReply->ignoreSslErrors();
    
        connect(networkReply, &QNetworkReply::finished, this, &TerrainAirMapQuery::_requestFinished);
    
        connect(networkReply, &QNetworkReply::sslErrors, this, &TerrainAirMapQuery::_sslErrors);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        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) {
    
            qCWarning(TerrainQueryLog) << "_requestError error:url:data" << reply->error() << reply->url() << reply->readAll();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
    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();
            }
        }
    }
    
    
    void TerrainAirMapQuery::_requestFinished(void)
    
    {
        QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
    
        if (reply->error() != QNetworkReply::NoError) {
    
            qCWarning(TerrainQueryLog) << "_requestFinished error:url:data" << reply->error() << reply->url() << reply->readAll();
    
            _requestFailed();
    
            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) {
    
            qCWarning(TerrainQueryLog) << "_requestFinished unable to parse json:" << parseError.errorString();
    
            _requestFailed();
    
            return;
        }
    
        // Check airmap reponse status
        QJsonObject rootObject = responseJson.object();
        QString status = rootObject["status"].toString();
        if (status != "success") {
    
            qCWarning(TerrainQueryLog) << "_requestFinished status != success:" << status;
    
            _requestFailed();
    
        const QJsonValue& jsonData = rootObject["data"];
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        qCDebug(TerrainQueryLog) << "_requestFinished success";
    
        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
            emit coordinateHeightsReceived(false /* success */, QList<double>() /* heights */);
    
            break;
        case QueryModePath:
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit pathHeightsReceived(false /* success */, qQNaN() /* latStep */, qQNaN() /* lonStep */, QList<double>() /* heights */);
    
            break;
        case QueryModeCarpet:
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit carpetHeightsReceived(false /* success */, qQNaN() /* minHeight */, qQNaN() /* maxHeight */, QList<QList<double>>() /* carpet */);
    
            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
        emit coordinateHeightsReceived(true /* success */, heights);
    
    }
    
    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;
    
        for (const QJsonValue& profileValue: profileArray) {
    
            heights.append(profileValue.toDouble());
        }
    
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        emit pathHeightsReceived(true /* success */, latStep, lonStep, heights);
    
    }
    
    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
        emit carpetHeightsReceived(true /*success*/, minHeight, maxHeight, carpet);
    
    TerrainOfflineAirMapQuery::TerrainOfflineAirMapQuery(QObject* parent)
        : TerrainQueryInterface(parent)
    {
    
        qCDebug(TerrainQueryVerboseLog) << "supportsSsl" << QSslSocket::supportsSsl() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
    
    }
    
    void TerrainOfflineAirMapQuery::requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates)
    {
    
        if (qgcApp()->runningUnitTests()) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit coordinateHeightsReceived(false, QList<double>());
    
        if (coordinates.length() == 0) {
    
    Andreas Bircher's avatar
    Andreas Bircher committed
            return;
    
    Andreas Bircher's avatar
    Andreas Bircher committed
        _terrainTileManager->addCoordinateQuery(this, coordinates);
    
    }
    
    void TerrainOfflineAirMapQuery::requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
    {
    
        if (qgcApp()->runningUnitTests()) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit pathHeightsReceived(false, qQNaN(), qQNaN(), QList<double>());
    
        _terrainTileManager->addPathQuery(this, fromCoord, toCoord);
    
    }
    
    void TerrainOfflineAirMapQuery::requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly)
    {
    
        if (qgcApp()->runningUnitTests()) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit carpetHeightsReceived(false, qQNaN(), qQNaN(), QList<QList<double>>());
    
        Q_UNUSED(swCoord);
        Q_UNUSED(neCoord);
        Q_UNUSED(statsOnly);
        qWarning() << "Carpet queries are currently not supported from offline air map data";
    
    }
    
    void TerrainOfflineAirMapQuery::_signalCoordinateHeights(bool success, QList<double> heights)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        emit coordinateHeightsReceived(success, heights);
    
    }
    
    void TerrainOfflineAirMapQuery::_signalPathHeights(bool success, double latStep, double lonStep, const QList<double>& heights)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        emit pathHeightsReceived(success, latStep, lonStep, heights);
    
    }
    
    void TerrainOfflineAirMapQuery::_signalCarpetHeights(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        emit carpetHeightsReceived(success, minHeight, maxHeight, carpet);
    
    Andreas Bircher's avatar
    Andreas Bircher committed
    void TerrainTileManager::addCoordinateQuery(TerrainOfflineAirMapQuery* terrainQueryInterface, const QList<QGeoCoordinate>& coordinates)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        qCDebug(TerrainQueryLog) << "TerrainTileManager::addCoordinateQuery count" << coordinates.count();
    
    
        if (coordinates.length() > 0) {
    
    Don Gagne's avatar
     
    Don Gagne committed
            bool error;
    
    Andreas Bircher's avatar
    Andreas Bircher committed
            QList<double> altitudes;
    
    Don Gagne's avatar
     
    Don Gagne committed
            if (!_getAltitudesForCoordinates(coordinates, altitudes, error)) {
    
    Don Gagne's avatar
     
    Don Gagne committed
                qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModeCoordinates, 0, 0, coordinates };
    
                _requestQueue.append(queuedRequestInfo);
                return;
            }
    
    
    Don Gagne's avatar
     
    Don Gagne committed
            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);
            }
    
    void TerrainTileManager::addPathQuery(TerrainOfflineAirMapQuery* terrainQueryInterface, const QGeoCoordinate &startPoint, const QGeoCoordinate &endPoint)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        // Convert to individual coordinate queries
    
        QList<QGeoCoordinate> coordinates;
        double lat = startPoint.latitude();
        double lon = startPoint.longitude();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        double steps = ceil(endPoint.distanceTo(startPoint) / TerrainTile::terrainAltitudeSpacing);
    
        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
        // 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();
    
    Don Gagne's avatar
     
    Don Gagne committed
        bool error;
    
        QList<double> altitudes;
    
    Don Gagne's avatar
     
    Don Gagne committed
        if (!_getAltitudesForCoordinates(coordinates, altitudes, error)) {
    
    Don Gagne's avatar
     
    Don Gagne committed
            qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModePath, latStep, lonStep, coordinates };
    
            _requestQueue.append(queuedRequestInfo);
            return;
        }
    
    
    Don Gagne's avatar
     
    Don Gagne committed
        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);
        }
    
    Don Gagne's avatar
     
    Don Gagne committed
    /// 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)
    
    Don Gagne's avatar
     
    Don Gagne committed
    bool TerrainTileManager::_getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error)
    
    Don Gagne's avatar
     
    Don Gagne committed
        error = false;
    
    
        for (const QGeoCoordinate& coordinate: coordinates) {
    
            QString tileHash = _getTileHash(coordinate);
    
    Don Gagne's avatar
     
    Don Gagne committed
            qCDebug(TerrainQueryLog) << "TerrainTileManager::_getAltitudesForCoordinates hash:coordinate" << tileHash << coordinate;
    
    Don Gagne's avatar
     
    Don Gagne committed
            _tilesMutex.lock();
            if (_tiles.contains(tileHash)) {
                if (_tiles[tileHash].isIn(coordinate)) {
                    double elevation = _tiles[tileHash].elevation(coordinate);
    
    Don Gagne's avatar
     
    Don Gagne committed
                    if (qIsNaN(elevation)) {
    
    Don Gagne's avatar
     
    Don Gagne committed
                        error = true;
                        qCWarning(TerrainQueryLog) << "TerrainTileManager::_getAltitudesForCoordinates Internal Error: negative elevation in tile cache";
                    } else {
    
    Don Gagne's avatar
     
    Don Gagne committed
                        qCDebug(TerrainQueryLog) << "TerrainTileManager::_getAltitudesForCoordinates returning elevation from tile cache" << elevation;
    
    Don Gagne's avatar
     
    Don Gagne committed
                    }
                    altitudes.push_back(elevation);
                } else {
                    qCWarning(TerrainQueryLog) << "TerrainTileManager::_getAltitudesForCoordinates Internal Error: coordinate not in tile region";
    
    Don Gagne's avatar
     
    Don Gagne committed
                    altitudes.push_back(qQNaN());
    
    Don Gagne's avatar
     
    Don Gagne committed
                    error = true;
                }
            } else {
    
                if (_state != State::Downloading) {
    
                    QNetworkRequest request = getQGCMapEngine()->urlFactory()->getTileURL("AirmapElevation", QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1), QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1), 1, &_networkManager);
    
    Don Gagne's avatar
     
    Don Gagne committed
                    qCDebug(TerrainQueryLog) << "TerrainTileManager::_getAltitudesForCoordinates query from database" << request.url();
    
                    QGeoTileSpec spec;
                    spec.setX(QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1));
                    spec.setY(QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1));
                    spec.setZoom(1);
    
                    spec.setMapId(getQGCMapEngine()->urlFactory()->getIdFromType("AirmapElevation"));
    
                    QGeoTiledMapReplyQGC* reply = new QGeoTiledMapReplyQGC(&_networkManager, request, spec);
    
                    connect(reply, &QGeoTiledMapReplyQGC::terrainDone, this, &TerrainTileManager::_terrainDone);
    
                    _state = State::Downloading;
                }
                _tilesMutex.unlock();
    
                return false;
            }
            _tilesMutex.unlock();
        }
    
    Don Gagne's avatar
     
    Don Gagne committed
    
    
        return true;
    }
    
    void TerrainTileManager::_tileFailed(void)
    {
    
    Andreas Bircher's avatar
    Andreas Bircher committed
        QList<double>    noAltitudes;
    
        for (const QueuedRequestInfo_t& requestInfo: _requestQueue) {
    
            if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
                requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
                requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes);
    
    void TerrainTileManager::_terrainDone(QByteArray responseBytes, QNetworkReply::NetworkError error)
    
    {
        QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
        _state = State::Idle;
    
        if (!reply) {
    
            qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type.";
    
            return;
        }
    
        // remove from download queue
        QGeoTileSpec spec = reply->tileSpec();
    
        QString hash = QGCMapEngine::getTileHash("AirmapElevation", spec.x(), spec.y(), spec.zoom());
    
        if (error != QNetworkReply::NoError) {
    
            qCWarning(TerrainQueryLog) << "Elevation tile fetching returned error (" << error << ")";
    
            _tileFailed();
            reply->deleteLater();
            return;
        }
    
        if (responseBytes.isEmpty()) {
    
            qCWarning(TerrainQueryLog) << "Error in fetching elevation tile. Empty response.";
    
    Don Gagne's avatar
     
    Don Gagne committed
        qCDebug(TerrainQueryLog) << "Received some bytes of terrain data: " << responseBytes.size();
    
        TerrainTile* terrainTile = new TerrainTile(responseBytes);
    
        if (terrainTile->isValid()) {
            _tilesMutex.lock();
            if (!_tiles.contains(hash)) {
                _tiles.insert(hash, *terrainTile);
            } else {
                delete terrainTile;
            }
            _tilesMutex.unlock();
        } else {
    
            qCWarning(TerrainQueryLog) << "Received invalid tile";
    
        }
        reply->deleteLater();
    
        // now try to query the data again
        for (int i = _requestQueue.count() - 1; i >= 0; i--) {
    
    Don Gagne's avatar
     
    Don Gagne committed
            bool error;
    
    Andreas Bircher's avatar
    Andreas Bircher committed
            QList<double> altitudes;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            QueuedRequestInfo_t& requestInfo = _requestQueue[i];
    
    
    Don Gagne's avatar
     
    Don Gagne committed
            if (_getAltitudesForCoordinates(requestInfo.coordinates, altitudes, error)) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) {
    
    Don Gagne's avatar
     
    Don Gagne committed
                    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
                } else if (requestInfo.queryMode == QueryMode::QueryModePath) {
    
    Don Gagne's avatar
     
    Don Gagne committed
                    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);
                    }
    
                }
                _requestQueue.removeAt(i);
            }
        }
    }
    
    QString TerrainTileManager::_getTileHash(const QGeoCoordinate& coordinate)
    {
    
        QString ret = QGCMapEngine::getTileHash("AirmapElevation", QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1), QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1), 1);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret;
    
    TerrainAtCoordinateBatchManager::TerrainAtCoordinateBatchManager(void)
    {
        _batchTimer.setSingleShot(true);
        _batchTimer.setInterval(_batchTimeout);
        connect(&_batchTimer, &QTimer::timeout, this, &TerrainAtCoordinateBatchManager::_sendNextBatch);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(&_terrainQuery, &TerrainQueryInterface::coordinateHeightsReceived, this, &TerrainAtCoordinateBatchManager::_coordinateHeights);
    
    }
    
    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
        qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
    
    
        if (_state != State::Idle) {
            // Waiting for last download the complete, wait some more
    
    Don Gagne's avatar
     
    Don Gagne committed
            qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch waiting for current batch, restarting timer";
    
            _batchTimer.start();
            return;
        }
    
        if (_requestQueue.count() == 0) {
            return;
        }
    
        _sentRequests.clear();
    
        // Convert coordinates to point strings for json query
    
        QList<QGeoCoordinate> coords;
    
        int requestQueueAdded = 0;
    
        for (const QueuedRequestInfo_t& requestInfo: _requestQueue) {
    
            SentRequestInfo_t sentRequestInfo = { requestInfo.terrainAtCoordinateQuery, false, requestInfo.coordinates.count() };
            _sentRequests.append(sentRequestInfo);
    
            coords += requestInfo.coordinates;
    
            requestQueueAdded++;
            if (coords.count() > 50) {
                break;
            }
    
        _requestQueue = _requestQueue.mid(requestQueueAdded);
    
    Don Gagne's avatar
     
    Don Gagne committed
        qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch requesting next batch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
    
    
        _state = State::Downloading;
    
        _terrainQuery.requestCoordinateHeights(coords);
    
    }
    
    void TerrainAtCoordinateBatchManager::_batchFailed(void)
    {
    
        QList<double> noHeights;
    
        for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
    
            if (!sentRequestInfo.queryObjectDestroyed) {
                disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
    
                sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights);
    
            }
        }
        _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");
    }
    
    
    void TerrainAtCoordinateBatchManager::_coordinateHeights(bool success, QList<double> heights)
    
    Don Gagne's avatar
     
    Don Gagne committed
        qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights signalled success:count" << success << heights.count();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (!success) {
            _batchFailed();
            return;
        }
    
        for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) {
    
            if (!sentRequestInfo.queryObjectDestroyed) {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                qCDebug(TerrainQueryVerboseLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights returned TerrainCoordinateQuery:count" <<  sentRequestInfo.terrainAtCoordinateQuery << sentRequestInfo.cCoord;
    
                disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed);
    
                QList<double> requestAltitudes = heights.mid(currentIndex, sentRequestInfo.cCoord);
    
                sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(true, requestAltitudes);
                currentIndex += sentRequestInfo.cCoord;
            }
        }
        _sentRequests.clear();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
        if (_requestQueue.count()) {
            _batchTimer.start();
        }
    
    }
    
    TerrainAtCoordinateQuery::TerrainAtCoordinateQuery(QObject* parent)
        : QObject(parent)
    {
    
    }
    void TerrainAtCoordinateQuery::requestData(const QList<QGeoCoordinate>& coordinates)
    {
        if (coordinates.length() == 0) {
            return;
        }
    
        _TerrainAtCoordinateBatchManager->addQuery(this, coordinates);
    }
    
    
    void TerrainAtCoordinateQuery::_signalTerrainData(bool success, QList<double>& heights)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        emit terrainDataReceived(success, heights);
    
    }
    
    TerrainPathQuery::TerrainPathQuery(QObject* parent)
    
        : QObject(parent)
    
        qRegisterMetaType<PathHeightInfo_t>();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(&_terrainQuery, &TerrainQueryInterface::pathHeightsReceived, this, &TerrainPathQuery::_pathHeights);
    
    }
    
    void TerrainPathQuery::requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord)
    {
    
        _terrainQuery.requestPathHeights(fromCoord, toCoord);
    
    void TerrainPathQuery::_pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights)
    
        PathHeightInfo_t pathHeightInfo;
    
        pathHeightInfo.latStep = latStep;
        pathHeightInfo.lonStep = lonStep;
        pathHeightInfo.heights = heights;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        emit terrainDataReceived(success, pathHeightInfo);
    
    }
    
    TerrainPolyPathQuery::TerrainPolyPathQuery(QObject* parent)
        : QObject   (parent)
        , _curIndex (0)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(&_pathQuery, &TerrainPathQuery::terrainDataReceived, this, &TerrainPolyPathQuery::_terrainDataReceived);
    
    }
    
    void TerrainPolyPathQuery::requestData(const QVariantList& polyPath)
    {
        QList<QGeoCoordinate> path;
    
    
        for (const QVariant& geoVar: polyPath) {
    
            path.append(geoVar.value<QGeoCoordinate>());
    
        requestData(path);
    
    void TerrainPolyPathQuery::requestData(const QList<QGeoCoordinate>& polyPath)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::requestData count" << polyPath.count();
    
    
        // 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
        qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived success:_curIndex" << success << _curIndex;
    
    
        if (!success) {
            _rgPathHeightInfo.clear();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit terrainDataReceived(false /* success */, _rgPathHeightInfo);
    
            return;
        }
    
        _rgPathHeightInfo.append(pathHeightInfo);
    
        if (++_curIndex >= _rgCoords.count() - 1) {
            // We've finished all requests
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived complete";
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            emit terrainDataReceived(true /* success */, _rgPathHeightInfo);
    
        } else {
            _pathQuery.requestData(_rgCoords[_curIndex], _rgCoords[_curIndex+1]);
        }
    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    TerrainCarpetQuery::TerrainCarpetQuery(QObject* parent)
    
        : QObject(parent)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        connect(&_terrainQuery, &TerrainQueryInterface::carpetHeightsReceived, this, &TerrainCarpetQuery::terrainDataReceived);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    void TerrainCarpetQuery::requestData(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly)
    {
    
        _terrainQuery.requestCarpetHeights(swCoord, neCoord, statsOnly);