You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
790 lines
30 KiB
790 lines
30 KiB
/**************************************************************************** |
|
* |
|
* (c) 2009-2020 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 "TerrainQuery.h" |
|
#include "QGCMapEngine.h" |
|
#include "QGeoMapReplyQGC.h" |
|
#include "QGCApplication.h" |
|
|
|
#include <QUrl> |
|
#include <QUrlQuery> |
|
#include <QNetworkRequest> |
|
#include <QNetworkProxy> |
|
#include <QNetworkReply> |
|
#include <QSslConfiguration> |
|
#include <QJsonDocument> |
|
#include <QJsonObject> |
|
#include <QJsonArray> |
|
#include <QTimer> |
|
#include <QtLocation/private/qgeotilespec_p.h> |
|
|
|
#include <cmath> |
|
|
|
QGC_LOGGING_CATEGORY(TerrainQueryLog, "TerrainQueryLog") |
|
QGC_LOGGING_CATEGORY(TerrainQueryVerboseLog, "TerrainQueryVerboseLog") |
|
|
|
Q_GLOBAL_STATIC(TerrainAtCoordinateBatchManager, _TerrainAtCoordinateBatchManager) |
|
Q_GLOBAL_STATIC(TerrainTileManager, _terrainTileManager) |
|
|
|
TerrainAirMapQuery::TerrainAirMapQuery(QObject* parent) |
|
: TerrainQueryInterface(parent) |
|
{ |
|
qCDebug(TerrainQueryVerboseLog) << "supportsSsl" << QSslSocket::supportsSsl() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString(); |
|
} |
|
|
|
void TerrainAirMapQuery::requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) |
|
{ |
|
if (qgcApp()->runningUnitTests()) { |
|
emit coordinateHeightsReceived(false, QList<double>()); |
|
return; |
|
} |
|
|
|
QString points; |
|
for (const QGeoCoordinate& coord: coordinates) { |
|
points += QString::number(coord.latitude(), 'f', 10) + "," |
|
+ QString::number(coord.longitude(), 'f', 10) + ","; |
|
} |
|
points = points.mid(0, points.length() - 1); // remove the last ',' from string |
|
|
|
QUrlQuery query; |
|
query.addQueryItem(QStringLiteral("points"), points); |
|
|
|
_queryMode = QueryModeCoordinates; |
|
_sendQuery(QString() /* path */, query); |
|
} |
|
|
|
void TerrainAirMapQuery::requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) |
|
{ |
|
if (qgcApp()->runningUnitTests()) { |
|
emit pathHeightsReceived(false, qQNaN(), qQNaN(), QList<double>()); |
|
return; |
|
} |
|
|
|
QString points; |
|
points += QString::number(fromCoord.latitude(), 'f', 10) + "," |
|
+ QString::number(fromCoord.longitude(), 'f', 10) + ","; |
|
points += QString::number(toCoord.latitude(), 'f', 10) + "," |
|
+ QString::number(toCoord.longitude(), 'f', 10); |
|
|
|
QUrlQuery query; |
|
query.addQueryItem(QStringLiteral("points"), points); |
|
|
|
_queryMode = QueryModePath; |
|
_sendQuery(QStringLiteral("/path"), query); |
|
} |
|
|
|
void TerrainAirMapQuery::requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) |
|
{ |
|
if (qgcApp()->runningUnitTests()) { |
|
emit carpetHeightsReceived(false, qQNaN(), qQNaN(), QList<QList<double>>()); |
|
return; |
|
} |
|
|
|
QString points; |
|
points += QString::number(swCoord.latitude(), 'f', 10) + "," |
|
+ QString::number(swCoord.longitude(), 'f', 10) + ","; |
|
points += QString::number(neCoord.latitude(), 'f', 10) + "," |
|
+ QString::number(neCoord.longitude(), 'f', 10); |
|
|
|
QUrlQuery query; |
|
query.addQueryItem(QStringLiteral("points"), points); |
|
|
|
_queryMode = QueryModeCarpet; |
|
_carpetStatsOnly = statsOnly; |
|
|
|
_sendQuery(QStringLiteral("/carpet"), query); |
|
} |
|
|
|
void TerrainAirMapQuery::_sendQuery(const QString& path, const QUrlQuery& urlQuery) |
|
{ |
|
QUrl url(QStringLiteral("https://api.airmap.com/elevation/v1/ele") + path); |
|
qCDebug(TerrainQueryLog) << "_sendQuery" << url; |
|
url.setQuery(urlQuery); |
|
|
|
QNetworkRequest request(url); |
|
|
|
QSslConfiguration sslConf = request.sslConfiguration(); |
|
sslConf.setPeerVerifyMode(QSslSocket::VerifyNone); |
|
request.setSslConfiguration(sslConf); |
|
|
|
QNetworkProxy tProxy; |
|
tProxy.setType(QNetworkProxy::DefaultProxy); |
|
_networkManager.setProxy(tProxy); |
|
|
|
QNetworkReply* networkReply = _networkManager.get(request); |
|
if (!networkReply) { |
|
qCWarning(TerrainQueryLog) << "QNetworkManager::Get did not return QNetworkReply"; |
|
_requestFailed(); |
|
return; |
|
} |
|
networkReply->ignoreSslErrors(); |
|
|
|
connect(networkReply, &QNetworkReply::finished, this, &TerrainAirMapQuery::_requestFinished); |
|
connect(networkReply, &QNetworkReply::sslErrors, this, &TerrainAirMapQuery::_sslErrors); |
|
connect(networkReply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &TerrainAirMapQuery::_requestError); |
|
} |
|
|
|
void TerrainAirMapQuery::_requestError(QNetworkReply::NetworkError code) |
|
{ |
|
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender()); |
|
|
|
if (code != QNetworkReply::NoError) { |
|
qCWarning(TerrainQueryLog) << "_requestError error:url:data" << reply->error() << reply->url() << reply->readAll(); |
|
return; |
|
} |
|
} |
|
|
|
void TerrainAirMapQuery::_sslErrors(const QList<QSslError> &errors) |
|
{ |
|
for (const auto &error : errors) { |
|
qCWarning(TerrainQueryLog) << "SSL error: " << error.errorString(); |
|
|
|
const auto &certificate = error.certificate(); |
|
if (!certificate.isNull()) { |
|
qCWarning(TerrainQueryLog) << "SSL Certificate problem: " << certificate.toText(); |
|
} |
|
} |
|
} |
|
|
|
void TerrainAirMapQuery::_requestFinished(void) |
|
{ |
|
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender()); |
|
|
|
if (reply->error() != QNetworkReply::NoError) { |
|
qCWarning(TerrainQueryLog) << "_requestFinished error:url:data" << reply->error() << reply->url() << reply->readAll(); |
|
reply->deleteLater(); |
|
_requestFailed(); |
|
return; |
|
} |
|
|
|
QByteArray responseBytes = reply->readAll(); |
|
reply->deleteLater(); |
|
|
|
// Convert the response to Json |
|
QJsonParseError parseError; |
|
QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError); |
|
if (parseError.error != QJsonParseError::NoError) { |
|
qCWarning(TerrainQueryLog) << "_requestFinished unable to parse json:" << parseError.errorString(); |
|
_requestFailed(); |
|
return; |
|
} |
|
|
|
// Check airmap reponse status |
|
QJsonObject rootObject = responseJson.object(); |
|
QString status = rootObject["status"].toString(); |
|
if (status != "success") { |
|
qCWarning(TerrainQueryLog) << "_requestFinished status != success:" << status; |
|
_requestFailed(); |
|
return; |
|
} |
|
|
|
// Send back data |
|
const QJsonValue& jsonData = rootObject["data"]; |
|
qCDebug(TerrainQueryLog) << "_requestFinished success"; |
|
switch (_queryMode) { |
|
case QueryModeCoordinates: |
|
emit _parseCoordinateData(jsonData); |
|
break; |
|
case QueryModePath: |
|
emit _parsePathData(jsonData); |
|
break; |
|
case QueryModeCarpet: |
|
emit _parseCarpetData(jsonData); |
|
break; |
|
} |
|
} |
|
|
|
void TerrainAirMapQuery::_requestFailed(void) |
|
{ |
|
switch (_queryMode) { |
|
case QueryModeCoordinates: |
|
emit coordinateHeightsReceived(false /* success */, QList<double>() /* heights */); |
|
break; |
|
case QueryModePath: |
|
emit pathHeightsReceived(false /* success */, qQNaN() /* latStep */, qQNaN() /* lonStep */, QList<double>() /* heights */); |
|
break; |
|
case QueryModeCarpet: |
|
emit carpetHeightsReceived(false /* success */, qQNaN() /* minHeight */, qQNaN() /* maxHeight */, QList<QList<double>>() /* carpet */); |
|
break; |
|
} |
|
} |
|
|
|
void TerrainAirMapQuery::_parseCoordinateData(const QJsonValue& coordinateJson) |
|
{ |
|
QList<double> heights; |
|
const QJsonArray& dataArray = coordinateJson.toArray(); |
|
for (int i = 0; i < dataArray.count(); i++) { |
|
heights.append(dataArray[i].toDouble()); |
|
} |
|
|
|
emit coordinateHeightsReceived(true /* success */, heights); |
|
} |
|
|
|
void TerrainAirMapQuery::_parsePathData(const QJsonValue& pathJson) |
|
{ |
|
QJsonObject jsonObject = pathJson.toArray()[0].toObject(); |
|
QJsonArray stepArray = jsonObject["step"].toArray(); |
|
QJsonArray profileArray = jsonObject["profile"].toArray(); |
|
|
|
double latStep = stepArray[0].toDouble(); |
|
double lonStep = stepArray[1].toDouble(); |
|
|
|
QList<double> heights; |
|
for (const QJsonValue& profileValue: profileArray) { |
|
heights.append(profileValue.toDouble()); |
|
} |
|
|
|
emit pathHeightsReceived(true /* success */, latStep, lonStep, heights); |
|
} |
|
|
|
void TerrainAirMapQuery::_parseCarpetData(const QJsonValue& carpetJson) |
|
{ |
|
QJsonObject jsonObject = carpetJson.toArray()[0].toObject(); |
|
|
|
QJsonObject statsObject = jsonObject["stats"].toObject(); |
|
double minHeight = statsObject["min"].toDouble(); |
|
double maxHeight = statsObject["max"].toDouble(); |
|
|
|
QList<QList<double>> carpet; |
|
if (!_carpetStatsOnly) { |
|
QJsonArray carpetArray = jsonObject["carpet"].toArray(); |
|
|
|
for (int i=0; i<carpetArray.count(); i++) { |
|
QJsonArray rowArray = carpetArray[i].toArray(); |
|
carpet.append(QList<double>()); |
|
|
|
for (int j=0; j<rowArray.count(); j++) { |
|
double height = rowArray[j].toDouble(); |
|
carpet.last().append(height); |
|
} |
|
} |
|
} |
|
|
|
emit carpetHeightsReceived(true /*success*/, minHeight, maxHeight, carpet); |
|
} |
|
|
|
TerrainOfflineAirMapQuery::TerrainOfflineAirMapQuery(QObject* parent) |
|
: TerrainQueryInterface(parent) |
|
{ |
|
qCDebug(TerrainQueryVerboseLog) << "supportsSsl" << QSslSocket::supportsSsl() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString(); |
|
} |
|
|
|
void TerrainOfflineAirMapQuery::requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) |
|
{ |
|
if (qgcApp()->runningUnitTests()) { |
|
emit coordinateHeightsReceived(false, QList<double>()); |
|
return; |
|
} |
|
|
|
if (coordinates.length() == 0) { |
|
return; |
|
} |
|
|
|
_terrainTileManager->addCoordinateQuery(this, coordinates); |
|
} |
|
|
|
void TerrainOfflineAirMapQuery::requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) |
|
{ |
|
if (qgcApp()->runningUnitTests()) { |
|
emit pathHeightsReceived(false, qQNaN(), qQNaN(), QList<double>()); |
|
return; |
|
} |
|
|
|
_terrainTileManager->addPathQuery(this, fromCoord, toCoord); |
|
} |
|
|
|
void TerrainOfflineAirMapQuery::requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) |
|
{ |
|
if (qgcApp()->runningUnitTests()) { |
|
emit carpetHeightsReceived(false, qQNaN(), qQNaN(), QList<QList<double>>()); |
|
return; |
|
} |
|
|
|
// TODO |
|
Q_UNUSED(swCoord); |
|
Q_UNUSED(neCoord); |
|
Q_UNUSED(statsOnly); |
|
qWarning() << "Carpet queries are currently not supported from offline air map data"; |
|
} |
|
|
|
void TerrainOfflineAirMapQuery::_signalCoordinateHeights(bool success, QList<double> heights) |
|
{ |
|
emit coordinateHeightsReceived(success, heights); |
|
} |
|
|
|
void TerrainOfflineAirMapQuery::_signalPathHeights(bool success, double latStep, double lonStep, const QList<double>& heights) |
|
{ |
|
emit pathHeightsReceived(success, latStep, lonStep, heights); |
|
} |
|
|
|
void TerrainOfflineAirMapQuery::_signalCarpetHeights(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet) |
|
{ |
|
emit carpetHeightsReceived(success, minHeight, maxHeight, carpet); |
|
} |
|
|
|
TerrainTileManager::TerrainTileManager(void) |
|
{ |
|
|
|
} |
|
|
|
void TerrainTileManager::addCoordinateQuery(TerrainOfflineAirMapQuery* terrainQueryInterface, const QList<QGeoCoordinate>& coordinates) |
|
{ |
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::addCoordinateQuery count" << coordinates.count(); |
|
|
|
if (coordinates.length() > 0) { |
|
bool error; |
|
QList<double> altitudes; |
|
|
|
if (!getAltitudesForCoordinates(coordinates, altitudes, error)) { |
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count(); |
|
QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModeCoordinates, 0, 0, coordinates }; |
|
_requestQueue.append(queuedRequestInfo); |
|
return; |
|
} |
|
|
|
if (error) { |
|
QList<double> noAltitudes; |
|
qCWarning(TerrainQueryLog) << "addCoordinateQuery: signalling failure due to internal error"; |
|
terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes); |
|
} else { |
|
qCDebug(TerrainQueryLog) << "addCoordinateQuery: All altitudes taken from cached data"; |
|
terrainQueryInterface->_signalCoordinateHeights(coordinates.count() == altitudes.count(), altitudes); |
|
} |
|
} |
|
} |
|
|
|
void TerrainTileManager::addPathQuery(TerrainOfflineAirMapQuery* terrainQueryInterface, const QGeoCoordinate &startPoint, const QGeoCoordinate &endPoint) |
|
{ |
|
// Convert to individual coordinate queries |
|
QList<QGeoCoordinate> coordinates; |
|
double lat = startPoint.latitude(); |
|
double lon = startPoint.longitude(); |
|
double steps = ceil(endPoint.distanceTo(startPoint) / TerrainTile::terrainAltitudeSpacing); |
|
double latDiff = endPoint.latitude() - lat; |
|
double lonDiff = endPoint.longitude() - lon; |
|
for (double i = 0.0; i <= steps; i = i + 1) { |
|
coordinates.append(QGeoCoordinate(lat + latDiff * i / steps, lon + lonDiff * i / steps)); |
|
} |
|
// We always have one too many and we always want the last one to be the endpoint |
|
coordinates.last() = endPoint; |
|
double latStep = coordinates[1].latitude() - coordinates[0].latitude(); |
|
double lonStep = coordinates[1].longitude() - coordinates[0].longitude(); |
|
|
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery start:end:coordCount" << startPoint << endPoint << coordinates.count(); |
|
|
|
bool error; |
|
QList<double> altitudes; |
|
if (!getAltitudesForCoordinates(coordinates, altitudes, error)) { |
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::addPathQuery queue count" << _requestQueue.count(); |
|
QueuedRequestInfo_t queuedRequestInfo = { terrainQueryInterface, QueryMode::QueryModePath, latStep, lonStep, coordinates }; |
|
_requestQueue.append(queuedRequestInfo); |
|
return; |
|
} |
|
|
|
if (error) { |
|
QList<double> noAltitudes; |
|
qCWarning(TerrainQueryLog) << "addPathQuery: signalling failure due to internal error"; |
|
terrainQueryInterface->_signalPathHeights(false, latStep, lonStep, noAltitudes); |
|
} else { |
|
qCDebug(TerrainQueryLog) << "addPathQuery: All altitudes taken from cached data"; |
|
terrainQueryInterface->_signalPathHeights(coordinates.count() == altitudes.count(), latStep, lonStep, altitudes); |
|
} |
|
} |
|
|
|
/// Either returns altitudes from cache or queues database request |
|
/// @param[out] error true: altitude not returned due to error, false: altitudes returned |
|
/// @return true: altitude returned (check error as well), false: database query queued (altitudes not returned) |
|
bool TerrainTileManager::getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error) |
|
{ |
|
error = false; |
|
|
|
for (const QGeoCoordinate& coordinate: coordinates) { |
|
QString tileHash = _getTileHash(coordinate); |
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates hash:coordinate" << tileHash << coordinate; |
|
|
|
_tilesMutex.lock(); |
|
if (_tiles.contains(tileHash)) { |
|
if (_tiles[tileHash].isIn(coordinate)) { |
|
double elevation = _tiles[tileHash].elevation(coordinate); |
|
if (qIsNaN(elevation)) { |
|
error = true; |
|
qCWarning(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates Internal Error: missing elevation in tile cache"; |
|
} else { |
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates returning elevation from tile cache" << elevation; |
|
} |
|
altitudes.push_back(elevation); |
|
} else { |
|
qCWarning(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates Internal Error: coordinate not in tile region"; |
|
altitudes.push_back(qQNaN()); |
|
error = true; |
|
} |
|
} else { |
|
if (_state != State::Downloading) { |
|
QNetworkRequest request = getQGCMapEngine()->urlFactory()->getTileURL("Airmap Elevation", getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation",coordinate.longitude(), 1), getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1), 1, &_networkManager); |
|
qCDebug(TerrainQueryLog) << "TerrainTileManager::getAltitudesForCoordinates query from database" << request.url(); |
|
QGeoTileSpec spec; |
|
spec.setX(getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation", coordinate.longitude(), 1)); |
|
spec.setY(getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1)); |
|
spec.setZoom(1); |
|
spec.setMapId(getQGCMapEngine()->urlFactory()->getIdFromType("Airmap Elevation")); |
|
QGeoTiledMapReplyQGC* reply = new QGeoTiledMapReplyQGC(&_networkManager, request, spec); |
|
connect(reply, &QGeoTiledMapReplyQGC::terrainDone, this, &TerrainTileManager::_terrainDone); |
|
_state = State::Downloading; |
|
} |
|
_tilesMutex.unlock(); |
|
|
|
return false; |
|
} |
|
_tilesMutex.unlock(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void TerrainTileManager::_tileFailed(void) |
|
{ |
|
QList<double> noAltitudes; |
|
|
|
for (const QueuedRequestInfo_t& requestInfo: _requestQueue) { |
|
if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) { |
|
requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes); |
|
} else if (requestInfo.queryMode == QueryMode::QueryModePath) { |
|
requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes); |
|
} |
|
} |
|
_requestQueue.clear(); |
|
} |
|
|
|
void TerrainTileManager::_terrainDone(QByteArray responseBytes, QNetworkReply::NetworkError error) |
|
{ |
|
QGeoTiledMapReplyQGC* reply = qobject_cast<QGeoTiledMapReplyQGC*>(QObject::sender()); |
|
_state = State::Idle; |
|
|
|
if (!reply) { |
|
qCWarning(TerrainQueryLog) << "Elevation tile fetched but invalid reply data type."; |
|
return; |
|
} |
|
|
|
// remove from download queue |
|
QGeoTileSpec spec = reply->tileSpec(); |
|
QString hash = QGCMapEngine::getTileHash("Airmap Elevation", spec.x(), spec.y(), spec.zoom()); |
|
|
|
// handle potential errors |
|
if (error != QNetworkReply::NoError) { |
|
qCWarning(TerrainQueryLog) << "Elevation tile fetching returned error (" << error << ")"; |
|
_tileFailed(); |
|
reply->deleteLater(); |
|
return; |
|
} |
|
if (responseBytes.isEmpty()) { |
|
qCWarning(TerrainQueryLog) << "Error in fetching elevation tile. Empty response."; |
|
_tileFailed(); |
|
reply->deleteLater(); |
|
return; |
|
} |
|
|
|
qCDebug(TerrainQueryLog) << "Received some bytes of terrain data: " << responseBytes.size(); |
|
|
|
TerrainTile* terrainTile = new TerrainTile(responseBytes); |
|
if (terrainTile->isValid()) { |
|
_tilesMutex.lock(); |
|
if (!_tiles.contains(hash)) { |
|
_tiles.insert(hash, *terrainTile); |
|
} else { |
|
delete terrainTile; |
|
} |
|
_tilesMutex.unlock(); |
|
} else { |
|
delete terrainTile; |
|
qCWarning(TerrainQueryLog) << "Received invalid tile"; |
|
} |
|
reply->deleteLater(); |
|
|
|
// now try to query the data again |
|
for (int i = _requestQueue.count() - 1; i >= 0; i--) { |
|
bool error; |
|
QList<double> altitudes; |
|
QueuedRequestInfo_t& requestInfo = _requestQueue[i]; |
|
|
|
if (getAltitudesForCoordinates(requestInfo.coordinates, altitudes, error)) { |
|
if (requestInfo.queryMode == QueryMode::QueryModeCoordinates) { |
|
if (error) { |
|
QList<double> noAltitudes; |
|
qCWarning(TerrainQueryLog) << "_terrainDone(coordinateQuery): signalling failure due to internal error"; |
|
requestInfo.terrainQueryInterface->_signalCoordinateHeights(false, noAltitudes); |
|
} else { |
|
qCDebug(TerrainQueryLog) << "_terrainDone(coordinateQuery): All altitudes taken from cached data"; |
|
requestInfo.terrainQueryInterface->_signalCoordinateHeights(requestInfo.coordinates.count() == altitudes.count(), altitudes); |
|
} |
|
} else if (requestInfo.queryMode == QueryMode::QueryModePath) { |
|
if (error) { |
|
QList<double> noAltitudes; |
|
qCWarning(TerrainQueryLog) << "_terrainDone(coordinateQuery): signalling failure due to internal error"; |
|
requestInfo.terrainQueryInterface->_signalPathHeights(false, requestInfo.latStep, requestInfo.lonStep, noAltitudes); |
|
} else { |
|
qCDebug(TerrainQueryLog) << "_terrainDone(coordinateQuery): All altitudes taken from cached data"; |
|
requestInfo.terrainQueryInterface->_signalPathHeights(requestInfo.coordinates.count() == altitudes.count(), requestInfo.latStep, requestInfo.lonStep, altitudes); |
|
} |
|
} |
|
_requestQueue.removeAt(i); |
|
} |
|
} |
|
} |
|
|
|
QString TerrainTileManager::_getTileHash(const QGeoCoordinate& coordinate) |
|
{ |
|
QString ret = QGCMapEngine::getTileHash( |
|
"Airmap Elevation", |
|
getQGCMapEngine()->urlFactory()->long2tileX("Airmap Elevation", coordinate.longitude(), 1), |
|
getQGCMapEngine()->urlFactory()->lat2tileY("Airmap Elevation", coordinate.latitude(), 1), |
|
1); |
|
qCDebug(TerrainQueryVerboseLog) << "Computing unique tile hash for " << coordinate << ret; |
|
|
|
return ret; |
|
} |
|
|
|
TerrainAtCoordinateBatchManager::TerrainAtCoordinateBatchManager(void) |
|
{ |
|
_batchTimer.setSingleShot(true); |
|
_batchTimer.setInterval(_batchTimeout); |
|
connect(&_batchTimer, &QTimer::timeout, this, &TerrainAtCoordinateBatchManager::_sendNextBatch); |
|
connect(&_terrainQuery, &TerrainQueryInterface::coordinateHeightsReceived, this, &TerrainAtCoordinateBatchManager::_coordinateHeights); |
|
} |
|
|
|
void TerrainAtCoordinateBatchManager::addQuery(TerrainAtCoordinateQuery* terrainAtCoordinateQuery, const QList<QGeoCoordinate>& coordinates) |
|
{ |
|
if (coordinates.length() > 0) { |
|
connect(terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed); |
|
QueuedRequestInfo_t queuedRequestInfo = { terrainAtCoordinateQuery, coordinates }; |
|
_requestQueue.append(queuedRequestInfo); |
|
if (!_batchTimer.isActive()) { |
|
_batchTimer.start(); |
|
} |
|
} |
|
} |
|
|
|
void TerrainAtCoordinateBatchManager::_sendNextBatch(void) |
|
{ |
|
qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count(); |
|
|
|
if (_state != State::Idle) { |
|
// Waiting for last download the complete, wait some more |
|
qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch waiting for current batch, restarting timer"; |
|
_batchTimer.start(); |
|
return; |
|
} |
|
|
|
if (_requestQueue.count() == 0) { |
|
return; |
|
} |
|
|
|
_sentRequests.clear(); |
|
|
|
// Convert coordinates to point strings for json query |
|
QList<QGeoCoordinate> coords; |
|
int requestQueueAdded = 0; |
|
for (const QueuedRequestInfo_t& requestInfo: _requestQueue) { |
|
SentRequestInfo_t sentRequestInfo = { requestInfo.terrainAtCoordinateQuery, false, requestInfo.coordinates.count() }; |
|
_sentRequests.append(sentRequestInfo); |
|
coords += requestInfo.coordinates; |
|
requestQueueAdded++; |
|
if (coords.count() > 50) { |
|
break; |
|
} |
|
} |
|
_requestQueue = _requestQueue.mid(requestQueueAdded); |
|
qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_sendNextBatch requesting next batch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count(); |
|
|
|
_state = State::Downloading; |
|
_terrainQuery.requestCoordinateHeights(coords); |
|
} |
|
|
|
void TerrainAtCoordinateBatchManager::_batchFailed(void) |
|
{ |
|
QList<double> noHeights; |
|
|
|
for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) { |
|
if (!sentRequestInfo.queryObjectDestroyed) { |
|
disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed); |
|
sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(false, noHeights); |
|
} |
|
} |
|
_sentRequests.clear(); |
|
} |
|
|
|
void TerrainAtCoordinateBatchManager::_queryObjectDestroyed(QObject* terrainAtCoordinateQuery) |
|
{ |
|
// Remove/Mark deleted objects queries from queues |
|
|
|
qCDebug(TerrainQueryLog) << "_TerrainAtCoordinateQueryDestroyed TerrainAtCoordinateQuery" << terrainAtCoordinateQuery; |
|
|
|
int i = 0; |
|
while (i < _requestQueue.count()) { |
|
const QueuedRequestInfo_t& requestInfo = _requestQueue[i]; |
|
if (requestInfo.terrainAtCoordinateQuery == terrainAtCoordinateQuery) { |
|
qCDebug(TerrainQueryLog) << "Removing deleted provider from _requestQueue index:terrainAtCoordinateQuery" << i << requestInfo.terrainAtCoordinateQuery; |
|
_requestQueue.removeAt(i); |
|
} else { |
|
i++; |
|
} |
|
} |
|
|
|
for (int i=0; i<_sentRequests.count(); i++) { |
|
SentRequestInfo_t& sentRequestInfo = _sentRequests[i]; |
|
if (sentRequestInfo.terrainAtCoordinateQuery == terrainAtCoordinateQuery) { |
|
qCDebug(TerrainQueryLog) << "Zombieing deleted provider from _sentRequests index:terrainAtCoordinateQuery" << sentRequestInfo.terrainAtCoordinateQuery; |
|
sentRequestInfo.queryObjectDestroyed = true; |
|
} |
|
} |
|
} |
|
|
|
QString TerrainAtCoordinateBatchManager::_stateToString(State state) |
|
{ |
|
switch (state) { |
|
case State::Idle: |
|
return QStringLiteral("Idle"); |
|
case State::Downloading: |
|
return QStringLiteral("Downloading"); |
|
} |
|
|
|
return QStringLiteral("State unknown"); |
|
} |
|
|
|
void TerrainAtCoordinateBatchManager::_coordinateHeights(bool success, QList<double> heights) |
|
{ |
|
_state = State::Idle; |
|
|
|
qCDebug(TerrainQueryLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights signalled success:count" << success << heights.count(); |
|
|
|
if (!success) { |
|
_batchFailed(); |
|
return; |
|
} |
|
|
|
int currentIndex = 0; |
|
for (const SentRequestInfo_t& sentRequestInfo: _sentRequests) { |
|
if (!sentRequestInfo.queryObjectDestroyed) { |
|
qCDebug(TerrainQueryVerboseLog) << "TerrainAtCoordinateBatchManager::_coordinateHeights returned TerrainCoordinateQuery:count" << sentRequestInfo.terrainAtCoordinateQuery << sentRequestInfo.cCoord; |
|
disconnect(sentRequestInfo.terrainAtCoordinateQuery, &TerrainAtCoordinateQuery::destroyed, this, &TerrainAtCoordinateBatchManager::_queryObjectDestroyed); |
|
QList<double> requestAltitudes = heights.mid(currentIndex, sentRequestInfo.cCoord); |
|
sentRequestInfo.terrainAtCoordinateQuery->_signalTerrainData(true, requestAltitudes); |
|
currentIndex += sentRequestInfo.cCoord; |
|
} |
|
} |
|
_sentRequests.clear(); |
|
|
|
if (_requestQueue.count()) { |
|
_batchTimer.start(); |
|
} |
|
} |
|
|
|
TerrainAtCoordinateQuery::TerrainAtCoordinateQuery(QObject* parent) |
|
: QObject(parent) |
|
{ |
|
|
|
} |
|
void TerrainAtCoordinateQuery::requestData(const QList<QGeoCoordinate>& coordinates) |
|
{ |
|
if (coordinates.length() == 0) { |
|
return; |
|
} |
|
|
|
_TerrainAtCoordinateBatchManager->addQuery(this, coordinates); |
|
} |
|
|
|
bool TerrainAtCoordinateQuery::getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error) |
|
{ |
|
return _terrainTileManager->getAltitudesForCoordinates(coordinates, altitudes, error); |
|
} |
|
|
|
void TerrainAtCoordinateQuery::_signalTerrainData(bool success, QList<double>& heights) |
|
{ |
|
emit terrainDataReceived(success, heights); |
|
} |
|
|
|
TerrainPathQuery::TerrainPathQuery(QObject* parent) |
|
: QObject(parent) |
|
{ |
|
qRegisterMetaType<PathHeightInfo_t>(); |
|
connect(&_terrainQuery, &TerrainQueryInterface::pathHeightsReceived, this, &TerrainPathQuery::_pathHeights); |
|
} |
|
|
|
void TerrainPathQuery::requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) |
|
{ |
|
_terrainQuery.requestPathHeights(fromCoord, toCoord); |
|
} |
|
|
|
void TerrainPathQuery::_pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights) |
|
{ |
|
PathHeightInfo_t pathHeightInfo; |
|
pathHeightInfo.latStep = latStep; |
|
pathHeightInfo.lonStep = lonStep; |
|
pathHeightInfo.heights = heights; |
|
emit terrainDataReceived(success, pathHeightInfo); |
|
} |
|
|
|
TerrainPolyPathQuery::TerrainPolyPathQuery(QObject* parent) |
|
: QObject (parent) |
|
, _curIndex (0) |
|
{ |
|
connect(&_pathQuery, &TerrainPathQuery::terrainDataReceived, this, &TerrainPolyPathQuery::_terrainDataReceived); |
|
} |
|
|
|
void TerrainPolyPathQuery::requestData(const QVariantList& polyPath) |
|
{ |
|
QList<QGeoCoordinate> path; |
|
|
|
for (const QVariant& geoVar: polyPath) { |
|
path.append(geoVar.value<QGeoCoordinate>()); |
|
} |
|
|
|
requestData(path); |
|
} |
|
|
|
void TerrainPolyPathQuery::requestData(const QList<QGeoCoordinate>& polyPath) |
|
{ |
|
qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::requestData count" << polyPath.count(); |
|
|
|
// Kick off first request |
|
_rgCoords = polyPath; |
|
_curIndex = 0; |
|
_pathQuery.requestData(_rgCoords[0], _rgCoords[1]); |
|
} |
|
|
|
void TerrainPolyPathQuery::_terrainDataReceived(bool success, const TerrainPathQuery::PathHeightInfo_t& pathHeightInfo) |
|
{ |
|
qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived success:_curIndex" << success << _curIndex; |
|
|
|
if (!success) { |
|
_rgPathHeightInfo.clear(); |
|
emit terrainDataReceived(false /* success */, _rgPathHeightInfo); |
|
return; |
|
} |
|
|
|
_rgPathHeightInfo.append(pathHeightInfo); |
|
|
|
if (++_curIndex >= _rgCoords.count() - 1) { |
|
// We've finished all requests |
|
qCDebug(TerrainQueryLog) << "TerrainPolyPathQuery::_terrainDataReceived complete"; |
|
emit terrainDataReceived(true /* success */, _rgPathHeightInfo); |
|
} else { |
|
_pathQuery.requestData(_rgCoords[_curIndex], _rgCoords[_curIndex+1]); |
|
} |
|
} |
|
|
|
TerrainCarpetQuery::TerrainCarpetQuery(QObject* parent) |
|
: QObject(parent) |
|
{ |
|
connect(&_terrainQuery, &TerrainQueryInterface::carpetHeightsReceived, this, &TerrainCarpetQuery::terrainDataReceived); |
|
} |
|
|
|
void TerrainCarpetQuery::requestData(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) |
|
{ |
|
_terrainQuery.requestCarpetHeights(swCoord, neCoord, statsOnly); |
|
}
|
|
|