Terrain.cc 7.2 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
#include "QGeoMapReplyQGC.h"
13 14 15 16 17 18 19 20 21

#include <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
DonLakeFlyer's avatar
DonLakeFlyer committed
22
#include <QTimer>
Andreas Bircher's avatar
Andreas Bircher committed
23
#include <QtLocation/private/qgeotilespec_p.h>
24

DonLakeFlyer's avatar
DonLakeFlyer committed
25
QGC_LOGGING_CATEGORY(ElevationProviderLog, "ElevationProviderLog")
Andreas Bircher's avatar
Andreas Bircher committed
26

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

DonLakeFlyer's avatar
DonLakeFlyer committed
29
TerrainBatchManager::TerrainBatchManager(void)
30 31 32
{
}

DonLakeFlyer's avatar
DonLakeFlyer committed
33
void TerrainBatchManager::addQuery(ElevationProvider* elevationProvider, const QList<QGeoCoordinate>& coordinates)
34
{
DonLakeFlyer's avatar
DonLakeFlyer committed
35
    if (coordinates.length() > 0) {
36
        QList<float> altitudes;
37

38 39 40
        if (!_getAltitudesForCoordinates(coordinates, altitudes)) {
            QueuedRequestInfo_t queuedRequestInfo = { elevationProvider, coordinates };
            _requestQueue.append(queuedRequestInfo);
DonLakeFlyer's avatar
DonLakeFlyer committed
41
        }
42

43
        qCDebug(ElevationProviderLog) << "All altitudes taken from cached data";
Andreas Bircher's avatar
Andreas Bircher committed
44
        elevationProvider->_signalTerrainData(coordinates.count() == altitudes.count(), altitudes);
45 46 47
    }
}

48
bool TerrainBatchManager::_getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<float>& altitudes)
Andreas Bircher's avatar
Andreas Bircher committed
49
{
50
    foreach (const QGeoCoordinate& coordinate, coordinates) {
51
        QString tileHash = _getTileHash(coordinate);
Andreas Bircher's avatar
Andreas Bircher committed
52
        _tilesMutex.lock();
53
        if (!_tiles.contains(tileHash)) {
54
            qCDebug(ElevationProviderLog) << "Need to download tile " << tileHash;
55

56
            if (!_tileDownloadQueue.contains(tileHash)) {
57 58
                // Schedule the fetch task

59 60 61 62 63 64 65 66 67 68 69 70 71 72
                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);
73
            }
Andreas Bircher's avatar
Andreas Bircher committed
74
            _tilesMutex.unlock();
75 76

            return false;
Andreas Bircher's avatar
Andreas Bircher committed
77
        } else {
78 79 80 81 82
            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
83 84 85 86
            }
        }
        _tilesMutex.unlock();
    }
87
    return true;
88 89
}

90
void TerrainBatchManager::_tileFailed(void)
91
{
92
    QList<float>    noAltitudes;
93

DonLakeFlyer's avatar
DonLakeFlyer committed
94
    foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
95
        requestInfo.elevationProvider->_signalTerrainData(false, noAltitudes);
96
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
97
    _requestQueue.clear();
98
}
Andreas Bircher's avatar
Andreas Bircher committed
99

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

Andreas Bircher's avatar
Andreas Bircher committed
104
    if (!reply) {
105
        qCDebug(ElevationProviderLog) << "Elevation tile fetched but invalid reply data type.";
Andreas Bircher's avatar
Andreas Bircher committed
106 107 108 109 110 111 112
        return;
    }

    // remove from download queue
    QGeoTileSpec spec = reply->tileSpec();
    QString hash = QGCMapEngine::getTileHash(UrlFactory::AirmapElevation, spec.x(), spec.y(), spec.zoom());
    _tilesMutex.lock();
113 114 115 116
    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
117 118 119 120 121 122
    }
    _tilesMutex.unlock();

    // handle potential errors
    if (reply->error() != QGeoTiledMapReply::NoError) {
        if (reply->error() == QGeoTiledMapReply::CommunicationError) {
123
            qCDebug(ElevationProviderLog) << "Elevation tile fetching returned communication error. " << reply->errorString();
124
        } else {
125
            qCDebug(ElevationProviderLog) << "Elevation tile fetching returned error. " << reply->errorString();
126
        }
Andreas Bircher's avatar
Andreas Bircher committed
127
        _tileFailed();
Andreas Bircher's avatar
Andreas Bircher committed
128 129 130 131
        reply->deleteLater();
        return;
    }
    if (!reply->isFinished()) {
132
        qCDebug(ElevationProviderLog) << "Error in fetching elevation tile. Not finished. " << reply->errorString();
Andreas Bircher's avatar
Andreas Bircher committed
133
        _tileFailed();
Andreas Bircher's avatar
Andreas Bircher committed
134
        reply->deleteLater();
Andreas Bircher's avatar
Andreas Bircher committed
135 136 137
        return;
    }

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

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

151 152
    TerrainTile* terrainTile = new TerrainTile(responseJson);
    if (terrainTile->isValid()) {
Andreas Bircher's avatar
Andreas Bircher committed
153
        _tilesMutex.lock();
Andreas Bircher's avatar
Andreas Bircher committed
154
        if (!_tiles.contains(hash)) {
Andreas Bircher's avatar
Andreas Bircher committed
155
            _tiles.insert(hash, *terrainTile);
Andreas Bircher's avatar
Andreas Bircher committed
156
        } else {
157
            delete terrainTile;
Andreas Bircher's avatar
Andreas Bircher committed
158
        }
159
        _tilesMutex.unlock();
Andreas Bircher's avatar
Andreas Bircher committed
160
    } else {
161
        qCDebug(ElevationProviderLog) << "Received invalid tile";
Andreas Bircher's avatar
Andreas Bircher committed
162
    }
163
    reply->deleteLater();
Andreas Bircher's avatar
Andreas Bircher committed
164 165

    // now try to query the data again
166 167 168
    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
169
            _requestQueue[i].elevationProvider->_signalTerrainData(_requestQueue[i].coordinates.count() == altitudes.count(), altitudes);
170 171 172
            _requestQueue.removeAt(i);
        }
    }
Andreas Bircher's avatar
Andreas Bircher committed
173 174
}

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

    return ret;
}
182

DonLakeFlyer's avatar
DonLakeFlyer committed
183 184 185 186 187
ElevationProvider::ElevationProvider(QObject* parent)
    : QObject(parent)
{

}
188 189

bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates)
DonLakeFlyer's avatar
DonLakeFlyer committed
190 191
{
    if (coordinates.length() == 0) {
192
        return false;
DonLakeFlyer's avatar
DonLakeFlyer committed
193 194 195 196
    }

    _terrainBatchManager->addQuery(this, coordinates);

197
    return false;
198
}
Andreas Bircher's avatar
Andreas Bircher committed
199 200 201 202 203

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