Terrain.cc 10.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/****************************************************************************
 *
 *   (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 <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

Andreas Bircher's avatar
Andreas Bircher committed
21 22
QGC_LOGGING_CATEGORY(TerrainLog, "TerrainLog")

Andreas Bircher's avatar
Andreas Bircher committed
23 24 25 26
QMutex                          ElevationProvider::_tilesMutex;
QHash<QString, TerrainTile>     ElevationProvider::_tiles;
QStringList                     ElevationProvider::_downloadQueue;

27 28 29 30 31 32
ElevationProvider::ElevationProvider(QObject* parent)
    : QObject(parent)
{

}

Andreas Bircher's avatar
Andreas Bircher committed
33
bool ElevationProvider::queryTerrainDataPoints(const QList<QGeoCoordinate>& coordinates)
34
{
Andreas Bircher's avatar
Andreas Bircher committed
35
    if (_state != State::Idle || coordinates.length() == 0) {
36 37 38 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
        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
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
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) {
        QString uniqueTileId = _uniqueTileId(coordinate);
        _tilesMutex.lock();
        if (!_tiles.contains(uniqueTileId)) {
            qCDebug(TerrainLog) << "Need to download tile " << uniqueTileId;
            _downloadQueue.append(uniqueTileId);
            needToDownload = true;
        } else {
            if (!needToDownload) {
                if (_tiles[uniqueTileId].isIn(coordinate)) {
                    altitudes.push_back(_tiles[uniqueTileId].elevation(coordinate));
                } 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;

    _downloadTiles();
    return true;
}

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
bool ElevationProvider::cacheTerrainTiles(const QGeoCoordinate& southWest, const QGeoCoordinate& northEast)
{
    qCDebug(TerrainLog) << "Cache terrain tiles southWest:northEast" << southWest << northEast;

    // Correct corners of the tile to fixed grid
    QGeoCoordinate southWestCorrected;
    southWestCorrected.setLatitude(floor(southWest.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
    southWestCorrected.setLongitude(floor(southWest.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
    QGeoCoordinate northEastCorrected;
    northEastCorrected.setLatitude(ceil(northEast.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
    northEastCorrected.setLongitude(ceil(northEast.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);

    // Add all tiles to download queue
    for (double lat = southWestCorrected.latitude() + TerrainTile::srtm1TileSize / 2.0; lat < northEastCorrected.latitude(); lat += TerrainTile::srtm1TileSize) {
        for (double lon = southWestCorrected.longitude() + TerrainTile::srtm1TileSize / 2.0; lon < northEastCorrected.longitude(); lon += TerrainTile::srtm1TileSize) {
            QString uniqueTileId = _uniqueTileId(QGeoCoordinate(lat, lon));
            _tilesMutex.lock();
            if (_downloadQueue.contains(uniqueTileId) || _tiles.contains(uniqueTileId)) {
                _tilesMutex.unlock();
                continue;
            }
Andreas Bircher's avatar
Andreas Bircher committed
130
            _downloadQueue.append(uniqueTileId);
131 132 133 134 135 136 137 138 139
            _tilesMutex.unlock();
            qCDebug(TerrainLog) << "Adding tile to download queue: " << uniqueTileId;
        }
    }

    _downloadTiles();
    return true;
}

Andreas Bircher's avatar
Andreas Bircher committed
140 141 142 143 144 145 146 147 148 149
bool ElevationProvider::cacheTerrainTiles(const QList<QGeoCoordinate>& coordinates)
{
    if (coordinates.length() == 0) {
        return false;
    }

    for (const auto& coordinate : coordinates) {
        QString uniqueTileId = _uniqueTileId(coordinate);
        _tilesMutex.lock();
        if (_downloadQueue.contains(uniqueTileId) || _tiles.contains(uniqueTileId)) {
150
            _tilesMutex.unlock();
Andreas Bircher's avatar
Andreas Bircher committed
151
            continue;
Andreas Bircher's avatar
Andreas Bircher committed
152
        }
Andreas Bircher's avatar
Andreas Bircher committed
153
        _downloadQueue.append(uniqueTileId);
Andreas Bircher's avatar
Andreas Bircher committed
154 155 156 157 158 159 160 161
        _tilesMutex.unlock();
        qCDebug(TerrainLog) << "Adding tile to download queue: " << uniqueTileId;
    }

    _downloadTiles();
    return true;
}

162 163 164 165 166 167 168 169 170 171 172 173
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
174
        qCDebug(TerrainLog) << "Received " <<  responseJson;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
        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
199 200 201 202 203 204 205 206 207 208 209 210 211

void ElevationProvider::_requestFinishedTile()
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
    _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);
        qCDebug(TerrainLog) << "ERROR: Received " << responseJson;
212 213
        // TODO (birchera): Handle error in downloading data
        _downloadTiles();
Andreas Bircher's avatar
Andreas Bircher committed
214 215 216 217 218 219 220 221
        return;
    }

    QByteArray responseBytes = reply->readAll();

    QJsonParseError parseError;
    QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
222 223
        // TODO (birchera): Handle error in downloading data
        _downloadTiles();
Andreas Bircher's avatar
Andreas Bircher committed
224 225 226 227 228 229 230 231 232 233 234
        return;
    }

    TerrainTile* tile = new TerrainTile(responseJson);
    if (tile->isValid()) {
        _tilesMutex.lock();
        if (!_tiles.contains(_uniqueTileId(tile->centerCoordinate()))) {
            _tiles.insert(_uniqueTileId(tile->centerCoordinate()), *tile);
        } else {
            delete tile;
        }
235
        _tilesMutex.unlock();
Andreas Bircher's avatar
Andreas Bircher committed
236 237 238 239 240 241 242 243 244 245 246
    }

    _downloadTiles();
}

void ElevationProvider::_downloadTiles(void)
{
    if (_state == State::Idle && _downloadQueue.count() > 0) {
        QUrlQuery query;
        _tilesMutex.lock();
        qCDebug(TerrainLog) << "Starting download for " << _downloadQueue.first();
Andreas Bircher's avatar
Andreas Bircher committed
247
        query.addQueryItem(QStringLiteral("points"), _downloadQueue.first().replace("_", ","));
Andreas Bircher's avatar
Andreas Bircher committed
248 249
        _downloadQueue.pop_front();
        _tilesMutex.unlock();
250
        QUrl url(QStringLiteral("https://api.airmap.com/elevation/stage/srtm1/ele/carpet"));
Andreas Bircher's avatar
Andreas Bircher committed
251 252
        url.setQuery(query);

Andreas Bircher's avatar
Andreas Bircher committed
253
        qWarning() << "url" << url;
Andreas Bircher's avatar
Andreas Bircher committed
254 255 256 257 258 259 260 261
        QNetworkRequest request(url);

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

        QNetworkReply* networkReply = _networkManager.get(request);
        if (!networkReply) {
Andreas Bircher's avatar
Andreas Bircher committed
262
            return;
Andreas Bircher's avatar
Andreas Bircher committed
263 264 265 266 267
        }

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

        _state = State::Downloading;
Andreas Bircher's avatar
Andreas Bircher committed
268 269
    } else if (_state == State::Idle) {
        queryTerrainData(_coordinates);
Andreas Bircher's avatar
Andreas Bircher committed
270 271 272 273 274
    }
}

QString ElevationProvider::_uniqueTileId(const QGeoCoordinate& coordinate)
{
275 276 277 278
    // Compute corners of the tile
    QGeoCoordinate southWest;
    southWest.setLatitude(floor(coordinate.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
    southWest.setLongitude(floor(coordinate.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
Andreas Bircher's avatar
Andreas Bircher committed
279
    QGeoCoordinate northEast;
280 281
    northEast.setLatitude(ceil(coordinate.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
    northEast.setLongitude(ceil(coordinate.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize);
Andreas Bircher's avatar
Andreas Bircher committed
282

283 284 285
    // Generate uniquely identifying string
    QString ret = QString::number(southWest.latitude(), 'f', 6) + "_" + QString::number(southWest.longitude(), 'f', 6) + "_" +
                  QString::number(northEast.latitude(), 'f', 6) + "_" + QString::number(northEast.longitude(), 'f', 6);
Andreas Bircher's avatar
Andreas Bircher committed
286
    qCDebug(TerrainLog) << "Computing unique tile id for " << coordinate << ret;
Andreas Bircher's avatar
Andreas Bircher committed
287 288 289

    return ret;
}