Terrain.cc 6.89 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 44
        qCDebug(ElevationProviderLog) << "All altitudes taken from cached data";
        elevationProvider->_signalTerrainData(true, 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
            }
74 75

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

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

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

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

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

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

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

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

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

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

    // now try to query the data again
162 163 164 165
    for (int i = _requestQueue.count() - 1; i >= 0; i--) {
        QList<float> altitudes;
        if (_getAltitudesForCoordinates(_requestQueue[i].coordinates, altitudes)) {
            _requestQueue[i].elevationProvider->_signalTerrainData(true, altitudes);
166 167 168
            _requestQueue.removeAt(i);
        }
    }
Andreas Bircher's avatar
Andreas Bircher committed
169 170
}

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

    return ret;
}
178

DonLakeFlyer's avatar
DonLakeFlyer committed
179 180 181 182 183
ElevationProvider::ElevationProvider(QObject* parent)
    : QObject(parent)
{

}
184 185

bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates)
DonLakeFlyer's avatar
DonLakeFlyer committed
186 187
{
    if (coordinates.length() == 0) {
188
        return false;
DonLakeFlyer's avatar
DonLakeFlyer committed
189 190 191 192
    }

    _terrainBatchManager->addQuery(this, coordinates);

193
    return false;
194
}