Skip to content
Terrain.cc 7.2 KiB
Newer Older
/****************************************************************************
 *
 *   (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 "Terrain.h"
#include "QGCMapEngine.h"
#include "QGeoMapReplyQGC.h"

#include <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
DonLakeFlyer's avatar
DonLakeFlyer committed
#include <QTimer>
Andreas Bircher's avatar
Andreas Bircher committed
#include <QtLocation/private/qgeotilespec_p.h>
DonLakeFlyer's avatar
DonLakeFlyer committed
QGC_LOGGING_CATEGORY(ElevationProviderLog, "ElevationProviderLog")
Andreas Bircher's avatar
Andreas Bircher committed

DonLakeFlyer's avatar
DonLakeFlyer committed
Q_GLOBAL_STATIC(TerrainBatchManager, _terrainBatchManager)
Andreas Bircher's avatar
Andreas Bircher committed

DonLakeFlyer's avatar
DonLakeFlyer committed
TerrainBatchManager::TerrainBatchManager(void)
DonLakeFlyer's avatar
DonLakeFlyer committed
void TerrainBatchManager::addQuery(ElevationProvider* elevationProvider, const QList<QGeoCoordinate>& coordinates)
DonLakeFlyer's avatar
DonLakeFlyer committed
    if (coordinates.length() > 0) {
        if (!_getAltitudesForCoordinates(coordinates, altitudes)) {
            QueuedRequestInfo_t queuedRequestInfo = { elevationProvider, coordinates };
            _requestQueue.append(queuedRequestInfo);
DonLakeFlyer's avatar
DonLakeFlyer committed
        }
        qCDebug(ElevationProviderLog) << "All altitudes taken from cached data";
Andreas Bircher's avatar
Andreas Bircher committed
        elevationProvider->_signalTerrainData(coordinates.count() == altitudes.count(), altitudes);
bool TerrainBatchManager::_getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<float>& altitudes)
Andreas Bircher's avatar
Andreas Bircher committed
{
    foreach (const QGeoCoordinate& coordinate, coordinates) {
        QString tileHash = _getTileHash(coordinate);
Andreas Bircher's avatar
Andreas Bircher committed
        _tilesMutex.lock();
        if (!_tiles.contains(tileHash)) {
            qCDebug(ElevationProviderLog) << "Need to download tile " << tileHash;
            if (!_tileDownloadQueue.contains(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);
                    connect(reply, &QGeoTiledMapReplyQGC::finished, this, &TerrainBatchManager::_fetchedTile);
                    connect(reply, &QGeoTiledMapReplyQGC::aborted, this, &TerrainBatchManager::_fetchedTile);
                    _state = State::Downloading;
                }

                _tileDownloadQueue.append(tileHash);
Andreas Bircher's avatar
Andreas Bircher committed
            _tilesMutex.unlock();
Andreas Bircher's avatar
Andreas Bircher committed
        } else {
            if (_tiles[tileHash].isIn(coordinate)) {
                altitudes.push_back(_tiles[tileHash].elevation(coordinate));
            } else {
                qCDebug(ElevationProviderLog) << "Error: coordinate not in tile region";
                altitudes.push_back(-1.0);
Andreas Bircher's avatar
Andreas Bircher committed
            }
        }
        _tilesMutex.unlock();
    }
void TerrainBatchManager::_tileFailed(void)
DonLakeFlyer's avatar
DonLakeFlyer committed
    foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
        requestInfo.elevationProvider->_signalTerrainData(false, noAltitudes);
DonLakeFlyer's avatar
DonLakeFlyer committed
    _requestQueue.clear();
Andreas Bircher's avatar
Andreas Bircher committed

void TerrainBatchManager::_fetchedTile()
Andreas Bircher's avatar
Andreas Bircher committed
{
    QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
Andreas Bircher's avatar
Andreas Bircher committed

Andreas Bircher's avatar
Andreas Bircher committed
    if (!reply) {
        qCDebug(ElevationProviderLog) << "Elevation tile fetched but invalid reply data type.";
Andreas Bircher's avatar
Andreas Bircher committed
        return;
    }

    // remove from download queue
    QGeoTileSpec spec = reply->tileSpec();
    QString hash = QGCMapEngine::getTileHash(UrlFactory::AirmapElevation, spec.x(), spec.y(), spec.zoom());
    _tilesMutex.lock();
    if (_tileDownloadQueue.contains(hash)) {
        _tileDownloadQueue.removeOne(hash);
    } else {
        qCDebug(ElevationProviderLog) << "Loaded elevation tile, but not found in download queue.";
Andreas Bircher's avatar
Andreas Bircher committed
    }
    _tilesMutex.unlock();

    // handle potential errors
    if (reply->error() != QGeoTiledMapReply::NoError) {
        if (reply->error() == QGeoTiledMapReply::CommunicationError) {
            qCDebug(ElevationProviderLog) << "Elevation tile fetching returned communication error. " << reply->errorString();
            qCDebug(ElevationProviderLog) << "Elevation tile fetching returned error. " << reply->errorString();
Andreas Bircher's avatar
Andreas Bircher committed
        _tileFailed();
Andreas Bircher's avatar
Andreas Bircher committed
        reply->deleteLater();
        return;
    }
    if (!reply->isFinished()) {
        qCDebug(ElevationProviderLog) << "Error in fetching elevation tile. Not finished. " << reply->errorString();
Andreas Bircher's avatar
Andreas Bircher committed
        _tileFailed();
Andreas Bircher's avatar
Andreas Bircher committed
        reply->deleteLater();
Andreas Bircher's avatar
Andreas Bircher committed
        return;
    }

Andreas Bircher's avatar
Andreas Bircher committed
    // parse received data and insert into hash table
    QByteArray responseBytes = reply->mapImageData();
Andreas Bircher's avatar
Andreas Bircher committed

    QJsonParseError parseError;
    QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        qCDebug(ElevationProviderLog) << "Could not parse terrain tile " << parseError.errorString();
        qCDebug(ElevationProviderLog) << responseBytes;
Andreas Bircher's avatar
Andreas Bircher committed
        _tileFailed();
        reply->deleteLater();
Andreas Bircher's avatar
Andreas Bircher committed
        return;
    }

    TerrainTile* terrainTile = new TerrainTile(responseJson);
    if (terrainTile->isValid()) {
Andreas Bircher's avatar
Andreas Bircher committed
        _tilesMutex.lock();
Andreas Bircher's avatar
Andreas Bircher committed
        if (!_tiles.contains(hash)) {
Andreas Bircher's avatar
Andreas Bircher committed
            _tiles.insert(hash, *terrainTile);
Andreas Bircher's avatar
Andreas Bircher committed
        } else {
            delete terrainTile;
Andreas Bircher's avatar
Andreas Bircher committed
        }
        _tilesMutex.unlock();
Andreas Bircher's avatar
Andreas Bircher committed
    } else {
        qCDebug(ElevationProviderLog) << "Received invalid tile";
Andreas Bircher's avatar
Andreas Bircher committed
    }
    reply->deleteLater();
Andreas Bircher's avatar
Andreas Bircher committed

    // now try to query the data again
    for (int i = _requestQueue.count() - 1; i >= 0; i--) {
        QList<float> altitudes;
        if (_getAltitudesForCoordinates(_requestQueue[i].coordinates, altitudes)) {
Andreas Bircher's avatar
Andreas Bircher committed
            _requestQueue[i].elevationProvider->_signalTerrainData(_requestQueue[i].coordinates.count() == altitudes.count(), altitudes);
Andreas Bircher's avatar
Andreas Bircher committed
}

QString TerrainBatchManager::_getTileHash(const QGeoCoordinate& coordinate)
Andreas Bircher's avatar
Andreas Bircher committed
{
    QString ret = QGCMapEngine::getTileHash(UrlFactory::AirmapElevation, QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1), QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1), 1);
    qCDebug(ElevationProviderLog) << "Computing unique tile hash for " << coordinate << ret;
Andreas Bircher's avatar
Andreas Bircher committed

    return ret;
}
DonLakeFlyer's avatar
DonLakeFlyer committed
ElevationProvider::ElevationProvider(QObject* parent)
    : QObject(parent)
{

}

bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates)
DonLakeFlyer's avatar
DonLakeFlyer committed
{
    if (coordinates.length() == 0) {
DonLakeFlyer's avatar
DonLakeFlyer committed
    }

    _terrainBatchManager->addQuery(this, coordinates);

Andreas Bircher's avatar
Andreas Bircher committed

void ElevationProvider::_signalTerrainData(bool success, QList<float>& altitudes)
{
    emit terrainData(success, altitudes);
}