Terrain.cc 7.3 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 "Terrain.h"
11
#include "QGCMapEngine.h"
12 13
#include "QGeoMapReplyQGC.h"
#include <QtLocation/private/qgeotilespec_p.h>
14 15 16 17 18 19 20 21 22 23

#include <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

Andreas Bircher's avatar
Andreas Bircher committed
24 25
QGC_LOGGING_CATEGORY(TerrainLog, "TerrainLog")

Andreas Bircher's avatar
Andreas Bircher committed
26 27 28 29
QMutex                          ElevationProvider::_tilesMutex;
QHash<QString, TerrainTile>     ElevationProvider::_tiles;
QStringList                     ElevationProvider::_downloadQueue;

30 31 32 33 34 35
ElevationProvider::ElevationProvider(QObject* parent)
    : QObject(parent)
{

}

Andreas Bircher's avatar
Andreas Bircher committed
36
bool ElevationProvider::queryTerrainDataPoints(const QList<QGeoCoordinate>& coordinates)
37
{
Andreas Bircher's avatar
Andreas Bircher committed
38
    if (_state != State::Idle || coordinates.length() == 0) {
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
        return false;
    }

    QUrlQuery query;
    QString points = "";
    for (const auto& coordinate : coordinates) {
        points += QString::number(coordinate.latitude(), 'f', 10) + ","
                + QString::number(coordinate.longitude(), 'f', 10) + ",";
    }
    points = points.mid(0, points.length() - 1); // remove the last ','

    query.addQueryItem(QStringLiteral("points"), points);
    QUrl url(QStringLiteral("https://api.airmap.com/elevation/stage/srtm1/ele"));
    url.setQuery(query);

    QNetworkRequest request(url);

    QNetworkProxy tProxy;
    tProxy.setType(QNetworkProxy::DefaultProxy);
    _networkManager.setProxy(tProxy);

    QNetworkReply* networkReply = _networkManager.get(request);
    if (!networkReply) {
        return false;
    }

    connect(networkReply, &QNetworkReply::finished, this, &ElevationProvider::_requestFinished);

    _state = State::Downloading;
    return true;
}

Andreas Bircher's avatar
Andreas Bircher committed
71 72 73 74 75 76 77 78 79
bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates)
{
    if (_state != State::Idle || coordinates.length() == 0) {
        return false;
    }

    QList<float> altitudes;
    bool needToDownload = false;
    foreach (QGeoCoordinate coordinate, coordinates) {
80
        QString tileHash = _getTileHash(coordinate);
Andreas Bircher's avatar
Andreas Bircher committed
81
        _tilesMutex.lock();
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
        if (!_tiles.contains(tileHash)) {
            qCDebug(TerrainLog) << "Need to download tile " << tileHash;

            if (!_downloadQueue.contains(tileHash)) {
                // Schedule the fetch task

                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, &ElevationProvider::_fetchedTile);
                connect(reply, &QGeoTiledMapReplyQGC::aborted, this, &ElevationProvider::_fetchedTile);

                _downloadQueue.append(tileHash);
            }
Andreas Bircher's avatar
Andreas Bircher committed
100 101 102
            needToDownload = true;
        } else {
            if (!needToDownload) {
103 104
                if (_tiles[tileHash].isIn(coordinate)) {
                    altitudes.push_back(_tiles[tileHash].elevation(coordinate));
Andreas Bircher's avatar
Andreas Bircher committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
                } else {
                    qCDebug(TerrainLog) << "Error: coordinate not in tile region";
                    altitudes.push_back(-1.0);
                }
            }
        }
        _tilesMutex.unlock();
    }

    if (!needToDownload) {
        qCDebug(TerrainLog) << "All altitudes taken from cached data";
        emit terrainData(true, altitudes);
        _coordinates.clear();
        return true;
    }

    _coordinates = coordinates;

123
    return false;
124 125
}

126 127 128 129 130 131 132 133 134 135 136 137
void ElevationProvider::_requestFinished()
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
    QList<float> altitudes;
    _state = State::Idle;

    // When an error occurs we still end up here
    if (reply->error() != QNetworkReply::NoError) {
        QByteArray responseBytes = reply->readAll();

        QJsonParseError parseError;
        QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
Andreas Bircher's avatar
Andreas Bircher committed
138
        qCDebug(TerrainLog) << "Received " <<  responseJson;
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
        emit terrainData(false, altitudes);
        return;
    }

    QByteArray responseBytes = reply->readAll();

    QJsonParseError parseError;
    QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        emit terrainData(false, altitudes);
        return;
    }
    QJsonObject rootObject = responseJson.object();
    QString status = rootObject["status"].toString();
    if (status == "success") {
        const QJsonArray& dataArray = rootObject["data"].toArray();
        for (int i = 0; i < dataArray.count(); i++) {
            altitudes.push_back(dataArray[i].toDouble());
        }

        emit terrainData(true, altitudes);
    }
    emit terrainData(false, altitudes);
}
Andreas Bircher's avatar
Andreas Bircher committed
163

164
void ElevationProvider::_fetchedTile()
Andreas Bircher's avatar
Andreas Bircher committed
165
{
166
    QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender());
Andreas Bircher's avatar
Andreas Bircher committed
167

168 169 170 171 172 173 174
    if (!reply || !reply->isFinished()) {
        if (reply) {
            qCDebug(TerrainLog) << "Error in fetching elevation tile: " << reply->errorString();
            reply->deleteLater();
        } else {
            qCDebug(TerrainLog) << "Elevation tile fetched but invalid reply data type.";
        }
Andreas Bircher's avatar
Andreas Bircher committed
175 176 177
        return;
    }

178
    QByteArray responseBytes = reply->mapImageData();
Andreas Bircher's avatar
Andreas Bircher committed
179 180 181 182

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

189 190
    TerrainTile* terrainTile = new TerrainTile(responseJson);
    if (terrainTile->isValid()) {
Andreas Bircher's avatar
Andreas Bircher committed
191
        _tilesMutex.lock();
192 193
        if (!_tiles.contains(_getTileHash(terrainTile->centerCoordinate()))) {
            _tiles.insert(_getTileHash(terrainTile->centerCoordinate()), *terrainTile);
Andreas Bircher's avatar
Andreas Bircher committed
194
        } else {
195
            delete terrainTile;
Andreas Bircher's avatar
Andreas Bircher committed
196
        }
197 198
        if (_downloadQueue.contains(_getTileHash(terrainTile->centerCoordinate()))) {
            _downloadQueue.removeOne(_getTileHash(terrainTile->centerCoordinate()));
Andreas Bircher's avatar
Andreas Bircher committed
199
        }
200
        _tilesMutex.unlock();
Andreas Bircher's avatar
Andreas Bircher committed
201
    }
202
    reply->deleteLater();
Andreas Bircher's avatar
Andreas Bircher committed
203 204
}

205
QString ElevationProvider::_getTileHash(const QGeoCoordinate& coordinate)
Andreas Bircher's avatar
Andreas Bircher committed
206
{
207 208
    QString ret = QGCMapEngine::getTileHash(UrlFactory::AirmapElevation, QGCMapEngine::long2elevationTileX(coordinate.longitude(), 1), QGCMapEngine::lat2elevationTileY(coordinate.latitude(), 1), 1);
    qCDebug(TerrainLog) << "Computing unique tile hash for " << coordinate << ret;
Andreas Bircher's avatar
Andreas Bircher committed
209 210 211

    return ret;
}