Commit cf2d2aca authored by DonLakeFlyer's avatar DonLakeFlyer

Batch terrain requests

parent dc02d007
...@@ -57,7 +57,6 @@ MissionSettingsItem::MissionSettingsItem(Vehicle* vehicle, QObject* parent) ...@@ -57,7 +57,6 @@ MissionSettingsItem::MissionSettingsItem(Vehicle* vehicle, QObject* parent)
connect(this, &MissionSettingsItem::terrainAltitudeChanged, this, &MissionSettingsItem::_setHomeAltFromTerrain); connect(this, &MissionSettingsItem::terrainAltitudeChanged, this, &MissionSettingsItem::_setHomeAltFromTerrain);
connect(&_plannedHomePositionAltitudeFact, &Fact::valueChanged, this, &MissionSettingsItem::_setDirty);
connect(&_plannedHomePositionAltitudeFact, &Fact::valueChanged, this, &MissionSettingsItem::_updateAltitudeInCoordinate); connect(&_plannedHomePositionAltitudeFact, &Fact::valueChanged, this, &MissionSettingsItem::_updateAltitudeInCoordinate);
connect(&_cameraSection, &CameraSection::dirtyChanged, this, &MissionSettingsItem::_sectionDirtyChanged); connect(&_cameraSection, &CameraSection::dirtyChanged, this, &MissionSettingsItem::_sectionDirtyChanged);
...@@ -297,11 +296,6 @@ void MissionSettingsItem::setMissionEndRTL(bool missionEndRTL) ...@@ -297,11 +296,6 @@ void MissionSettingsItem::setMissionEndRTL(bool missionEndRTL)
void MissionSettingsItem::_setHomeAltFromTerrain(double terrainAltitude) void MissionSettingsItem::_setHomeAltFromTerrain(double terrainAltitude)
{ {
if (!_plannedHomePositionFromVehicle) { if (!_plannedHomePositionFromVehicle) {
// We need to stop this from signalling, Otherwise the dirty but get set on a delay
// which then marks the Plan view as incorrectly dirty
_plannedHomePositionAltitudeFact.setSendValueChangedSignals(false);
_plannedHomePositionAltitudeFact.setRawValue(terrainAltitude); _plannedHomePositionAltitudeFact.setRawValue(terrainAltitude);
_plannedHomePositionAltitudeFact.clearDeferredValueChangeSignal();
_plannedHomePositionAltitudeFact.setSendValueChangedSignals(true);
} }
} }
...@@ -7,9 +7,7 @@ ...@@ -7,9 +7,7 @@
* *
****************************************************************************/ ****************************************************************************/
#pragma once
#ifndef VisualMissionItem_H
#define VisualMissionItem_H
#include <QObject> #include <QObject>
#include <QString> #include <QString>
...@@ -215,5 +213,3 @@ private: ...@@ -215,5 +213,3 @@ private:
double _lastLatTerrainQuery; double _lastLatTerrainQuery;
double _lastLonTerrainQuery; double _lastLonTerrainQuery;
}; };
#endif
...@@ -17,27 +17,63 @@ ...@@ -17,27 +17,63 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QTimer>
ElevationProvider::ElevationProvider(QObject* parent) QGC_LOGGING_CATEGORY(ElevationProviderLog, "ElevationProviderLog")
: QObject(parent)
Q_GLOBAL_STATIC(TerrainBatchManager, _terrainBatchManager)
TerrainBatchManager::TerrainBatchManager(void)
{ {
_batchTimer.setSingleShot(true);
_batchTimer.setInterval(_batchTimeout);
connect(&_batchTimer, &QTimer::timeout, this, &TerrainBatchManager::_sendNextBatch);
}
void TerrainBatchManager::addQuery(ElevationProvider* elevationProvider, const QList<QGeoCoordinate>& coordinates)
{
if (coordinates.length() > 0) {
QueuedRequestInfo_t queuedRequestInfo = { elevationProvider, coordinates };
_requestQueue.append(queuedRequestInfo);
if (!_batchTimer.isActive()) {
_batchTimer.start();
}
}
} }
bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates) void TerrainBatchManager::_sendNextBatch(void)
{ {
if (_state != State::Idle || coordinates.length() == 0) { qCDebug(ElevationProviderLog) << "_sendNextBatch _state:_requestQueue.count" << (int)_state << _requestQueue.count();
return false;
if (_state != State::Idle) {
// Waiting for last download the complete, wait some more
_batchTimer.start();
return;
} }
QUrlQuery query; if (_requestQueue.count() == 0) {
QString points = ""; return;
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 ','
_sentRequests.clear();
// Convert coordinates to point strings for json query
QString points;
foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
SentRequestInfo_t sentRequestInfo = { requestInfo.elevationProvider, requestInfo.coordinates.count() };
qCDebug(ElevationProviderLog) << "Building request: coordinate count" << requestInfo.coordinates.count();
_sentRequests.append(sentRequestInfo);
foreach (const QGeoCoordinate& coord, requestInfo.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
_requestQueue.clear();
QUrlQuery query;
query.addQueryItem(QStringLiteral("points"), points); query.addQueryItem(QStringLiteral("points"), points);
QUrl url(QStringLiteral("https://api.airmap.com/elevation/stage/srtm1/ele")); QUrl url(QStringLiteral("https://api.airmap.com/elevation/stage/srtm1/ele"));
url.setQuery(query); url.setQuery(query);
...@@ -50,28 +86,36 @@ bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinate ...@@ -50,28 +86,36 @@ bool ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinate
QNetworkReply* networkReply = _networkManager.get(request); QNetworkReply* networkReply = _networkManager.get(request);
if (!networkReply) { if (!networkReply) {
return false; _batchFailed();
return;
} }
connect(networkReply, &QNetworkReply::finished, this, &ElevationProvider::_requestFinished); connect(networkReply, &QNetworkReply::finished, this, &TerrainBatchManager::_requestFinished);
_state = State::Downloading; _state = State::Downloading;
return true;
} }
void ElevationProvider::_requestFinished() void TerrainBatchManager::_batchFailed(void)
{
QList<float> noAltitudes;
foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
sentRequestInfo.elevationProvider->_signalTerrainData(false, noAltitudes);
}
_sentRequests.clear();
}
void TerrainBatchManager::_requestFinished()
{ {
qCDebug(ElevationProviderLog) << "_requestFinished";
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender()); QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
QList<float> altitudes;
_state = State::Idle; _state = State::Idle;
// When an error occurs we still end up here // When an error occurs we still end up here
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
QByteArray responseBytes = reply->readAll(); _batchFailed();
reply->deleteLater();
QJsonParseError parseError;
QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
emit terrainData(false, altitudes);
return; return;
} }
...@@ -80,18 +124,52 @@ void ElevationProvider::_requestFinished() ...@@ -80,18 +124,52 @@ void ElevationProvider::_requestFinished()
QJsonParseError parseError; QJsonParseError parseError;
QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError); QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
if (parseError.error != QJsonParseError::NoError) { if (parseError.error != QJsonParseError::NoError) {
emit terrainData(false, altitudes); _batchFailed();
reply->deleteLater();
return; return;
} }
QJsonObject rootObject = responseJson.object(); QJsonObject rootObject = responseJson.object();
QString status = rootObject["status"].toString(); QString status = rootObject["status"].toString();
if (status == "success") { if (status != "success") {
const QJsonArray& dataArray = rootObject["data"].toArray(); _batchFailed();
for (int i = 0; i < dataArray.count(); i++) { reply->deleteLater();
altitudes.push_back(dataArray[i].toDouble()); return;
} }
emit terrainData(true, altitudes); QList<float> altitudes;
const QJsonArray& dataArray = rootObject["data"].toArray();
for (int i = 0; i < dataArray.count(); i++) {
altitudes.push_back(dataArray[i].toDouble());
}
int currentIndex = 0;
foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
QList<float> requestAltitudes = altitudes.mid(currentIndex, sentRequestInfo.cCoord);
sentRequestInfo.elevationProvider->_signalTerrainData(true, requestAltitudes);
currentIndex += sentRequestInfo.cCoord;
} }
emit terrainData(false, altitudes); _sentRequests.clear();
reply->deleteLater();
}
ElevationProvider::ElevationProvider(QObject* parent)
: QObject(parent)
{
}
void ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates)
{
qCDebug(ElevationProviderLog) << "queryTerrainData: coordinate count" << coordinates.count();
if (coordinates.length() == 0) {
return;
}
_terrainBatchManager->addQuery(this, coordinates);
}
void ElevationProvider::_signalTerrainData(bool success, QList<float>& altitudes)
{
emit terrainData(success, altitudes);
} }
...@@ -9,47 +9,72 @@ ...@@ -9,47 +9,72 @@
#pragma once #pragma once
#include "QGCLoggingCategory.h"
#include <QObject> #include <QObject>
#include <QGeoCoordinate> #include <QGeoCoordinate>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QTimer>
/* usage example: Q_DECLARE_LOGGING_CATEGORY(ElevationProviderLog)
ElevationProvider *p = new ElevationProvider();
QList<QGeoCoordinate> coordinates;
QGeoCoordinate c(47.379243, 8.548265);
coordinates.push_back(c);
c.setLatitude(c.latitude()+0.01);
coordinates.push_back(c);
p->queryTerrainData(coordinates);
*/
class ElevationProvider;
class ElevationProvider : public QObject /// Used internally by ElevationProvider to batch requests together
{ class TerrainBatchManager : public QObject {
Q_OBJECT Q_OBJECT
public:
ElevationProvider(QObject* parent = NULL);
/** public:
* Async elevation query for a list of lon,lat coordinates. When the query is done, the terrainData() signal TerrainBatchManager(void);
* is emitted.
* @param coordinates
* @return true on success
*/
bool queryTerrainData(const QList<QGeoCoordinate>& coordinates);
signals: void addQuery(ElevationProvider* elevationProvider, const QList<QGeoCoordinate>& coordinates);
void terrainData(bool success, QList<float> altitudes);
private slots: private slots:
void _requestFinished(); void _sendNextBatch (void);
void _requestFinished (void);
private: private:
typedef struct {
ElevationProvider* elevationProvider;
QList<QGeoCoordinate> coordinates;
} QueuedRequestInfo_t;
typedef struct {
ElevationProvider* elevationProvider;
int cCoord;
} SentRequestInfo_t;
enum class State { enum class State {
Idle, Idle,
Downloading, Downloading,
}; };
State _state = State::Idle; void _batchFailed(void);
QNetworkAccessManager _networkManager;
QList<QueuedRequestInfo_t> _requestQueue;
QList<SentRequestInfo_t> _sentRequests;
State _state = State::Idle;
QNetworkAccessManager _networkManager;
const int _batchTimeout = 500;
QTimer _batchTimer;
};
/// NOTE: ElevationProvider is not thread safe. All instances/calls to ElevationProvider must be on main thread.
class ElevationProvider : public QObject
{
Q_OBJECT
public:
ElevationProvider(QObject* parent = NULL);
/// Async elevation query for a list of lon,lat coordinates. When the query is done, the terrainData() signal
/// is emitted.
/// @param coordinates to query
void queryTerrainData(const QList<QGeoCoordinate>& coordinates);
// Internal method
void _signalTerrainData(bool success, QList<float>& altitudes);
signals:
void terrainData(bool success, QList<float> altitudes);
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment