TerrainQuery.h 13.7 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9 10 11
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#pragma once

12 13
#include "TerrainTile.h"
#include "QGCMapEngineData.h"
14 15 16 17
#include "QGCLoggingCategory.h"

#include <QObject>
#include <QGeoCoordinate>
Remek Zajac's avatar
Remek Zajac committed
18
#include <QGeoRectangle>
19 20 21
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
22
#include <QtLocation/private/qgeotiledmapreply_p.h>
23 24

Q_DECLARE_LOGGING_CATEGORY(TerrainQueryLog)
DonLakeFlyer's avatar
DonLakeFlyer committed
25
Q_DECLARE_LOGGING_CATEGORY(TerrainQueryVerboseLog)
26 27 28

class TerrainAtCoordinateQuery;

29 30 31
/// Base class for offline/online terrain queries
class TerrainQueryInterface : public QObject
{
32 33 34
    Q_OBJECT

public:
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    TerrainQueryInterface(QObject* parent) : QObject(parent) { }

    /// Request terrain heights for specified coodinates.
    /// Signals: coordinateHeights when data is available
    virtual void requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) = 0;

    /// Requests terrain heights along the path specified by the two coordinates.
    /// Signals: pathHeights
    ///     @param coordinates to query
    virtual void requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) = 0;

    /// Request terrain heights for the rectangular area specified.
    /// Signals: carpetHeights when data is available
    ///     @param swCoord South-West bound of rectangular area to query
    ///     @param neCoord North-East bound of rectangular area to query
    ///     @param statsOnly true: Return only stats, no carpet data
    virtual void requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) = 0;

signals:
DonLakeFlyer's avatar
DonLakeFlyer committed
54
    void coordinateHeightsReceived(bool success, QList<double> heights);
55
    void pathHeightsReceived(bool success, double distanceBetween, double finalDistanceBetween, const QList<double>& heights);
DonLakeFlyer's avatar
DonLakeFlyer committed
56
    void carpetHeightsReceived(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet);
57
};
58

59 60 61 62 63
/// AirMap online implementation of terrain queries
class TerrainAirMapQuery : public TerrainQueryInterface {
    Q_OBJECT

public:
64
    TerrainAirMapQuery(QObject* parent = nullptr);
65

66
    // Overrides from TerrainQueryInterface
67 68 69
    void requestCoordinateHeights   (const QList<QGeoCoordinate>& coordinates) final;
    void requestPathHeights         (const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) final;
    void requestCarpetHeights       (const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) final;
70 71

private slots:
72
    void _requestError              (QNetworkReply::NetworkError code);
73 74
    void _requestFinished           (void);
    void _sslErrors                 (const QList<QSslError> &errors);
75 76

private:
77 78 79 80 81
    void _sendQuery                 (const QString& path, const QUrlQuery& urlQuery);
    void _requestFailed             (void);
    void _parseCoordinateData       (const QJsonValue& coordinateJson);
    void _parsePathData             (const QJsonValue& pathJson);
    void _parseCarpetData           (const QJsonValue& carpetJson);
82 83 84 85 86 87 88 89 90 91

    enum QueryMode {
        QueryModeCoordinates,
        QueryModePath,
        QueryModeCarpet
    };

    QNetworkAccessManager   _networkManager;
    QueryMode               _queryMode;
    bool                    _carpetStatsOnly;
92 93
};

94
/// AirMap offline cachable implementation of terrain queries
95 96 97 98
class TerrainOfflineAirMapQuery : public TerrainQueryInterface {
    Q_OBJECT

public:
99
    TerrainOfflineAirMapQuery(QObject* parent = nullptr);
100 101 102 103 104 105

    // Overrides from TerrainQueryInterface
    void requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) final;
    void requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) final;
    void requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) final;

106
    // Internal methods
107
    void _signalCoordinateHeights(bool success, QList<double> heights);
108
    void _signalPathHeights(bool success, double distanceBetween, double finalDistanceBetween, const QList<double>& heights);
109 110 111 112 113 114 115 116 117 118
    void _signalCarpetHeights(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet);
};

/// Used internally by TerrainOfflineAirMapQuery to manage terrain tiles
class TerrainTileManager : public QObject {
    Q_OBJECT

public:
    TerrainTileManager(void);

119 120 121
    void addCoordinateQuery         (TerrainOfflineAirMapQuery* terrainQueryInterface, const QList<QGeoCoordinate>& coordinates);
    void addPathQuery               (TerrainOfflineAirMapQuery* terrainQueryInterface, const QGeoCoordinate& startPoint, const QGeoCoordinate& endPoint);
    bool getAltitudesForCoordinates (const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error);
122 123

private slots:
124
    void _terrainDone       (QByteArray responseBytes, QNetworkReply::NetworkError error);
125 126 127 128 129 130 131 132 133 134 135 136 137

private:
    enum class State {
        Idle,
        Downloading,
    };

    enum QueryMode {
        QueryModeCoordinates,
        QueryModePath,
        QueryModeCarpet
    };

Andreas Bircher's avatar
Andreas Bircher committed
138 139 140
    typedef struct {
        TerrainOfflineAirMapQuery*  terrainQueryInterface;
        QueryMode                   queryMode;
141 142
        double                      distanceBetween;        // Distance between each returned height
        double                      finalDistanceBetween;   // Distance between for final height
143
        QList<QGeoCoordinate>       coordinates;
Andreas Bircher's avatar
Andreas Bircher committed
144 145
    } QueuedRequestInfo_t;

146 147
    void    _tileFailed                         (void);
    QString _getTileHash                        (const QGeoCoordinate& coordinate);
148 149 150 151 152 153 154 155 156

    QList<QueuedRequestInfo_t>  _requestQueue;
    State                       _state = State::Idle;
    QNetworkAccessManager       _networkManager;

    QMutex                      _tilesMutex;
    QHash<QString, TerrainTile> _tiles;
};

157
/// Used internally by TerrainAtCoordinateQuery to batch coordinate requests together
158
class TerrainAtCoordinateBatchManager : public QObject {
159 160 161 162 163 164 165 166 167 168
    Q_OBJECT

public:
    TerrainAtCoordinateBatchManager(void);

    void addQuery(TerrainAtCoordinateQuery* terrainAtCoordinateQuery, const QList<QGeoCoordinate>& coordinates);

private slots:
    void _sendNextBatch         (void);
    void _queryObjectDestroyed  (QObject* elevationProvider);
169
    void _coordinateHeights     (bool success, QList<double> heights);
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196

private:
    typedef struct {
        TerrainAtCoordinateQuery*   terrainAtCoordinateQuery;
        QList<QGeoCoordinate>       coordinates;
    } QueuedRequestInfo_t;

    typedef struct {
        TerrainAtCoordinateQuery*   terrainAtCoordinateQuery;
        bool                        queryObjectDestroyed;
        int                         cCoord;
    } SentRequestInfo_t;


    enum class State {
        Idle,
        Downloading,
    };

    void _batchFailed(void);
    QString _stateToString(State state);

    QList<QueuedRequestInfo_t>  _requestQueue;
    QList<SentRequestInfo_t>    _sentRequests;
    State                       _state = State::Idle;
    const int                   _batchTimeout = 500;
    QTimer                      _batchTimer;
197
    TerrainOfflineAirMapQuery   _terrainQuery;
198 199
};

200 201 202 203 204 205 206 207 208
// IMPORTANT NOTE: The terrain query objects below must continue to live until the the terrain system signals data back through them.
// Because of that it makes object lifetime tricky. Normally you would use autoDelete = true such they delete themselves when they
// complete. The case for using autoDelete=false is where the query has not been "newed" as a standalone object.
//
// Another typical use case is to query some terrain data and while you are waiting for it to come back the underlying reason
// for that query changes and you end up needed to query again for a new set of data. In this case you are no longer intersted
// in the results of the previous query. The way to do that is to disconnect the data received signal on the old stale query
// when you create the new query.

209 210 211 212 213
/// NOTE: TerrainAtCoordinateQuery is not thread safe. All instances/calls to ElevationProvider must be on main thread.
class TerrainAtCoordinateQuery : public QObject
{
    Q_OBJECT
public:
214 215
    /// @param autoDelete true: object will delete itself after it signals results
    TerrainAtCoordinateQuery(bool autoDelete);
216

DonLakeFlyer's avatar
DonLakeFlyer committed
217 218 219
    /// Async terrain query for a list of lon,lat coordinates. When the query is done, the terrainData() signal
    /// is emitted.
    ///     @param coordinates to query
220 221
    void requestData(const QList<QGeoCoordinate>& coordinates);

222 223 224 225 226
    /// 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)
    static bool getAltitudesForCoordinates(const QList<QGeoCoordinate>& coordinates, QList<double>& altitudes, bool& error);

227
    // Internal method
228
    void _signalTerrainData(bool success, QList<double>& heights);
229 230

signals:
DonLakeFlyer's avatar
DonLakeFlyer committed
231
    void terrainDataReceived(bool success, QList<double> heights);
232 233 234

private:
    bool _autoDelete;
235 236
};

237
class TerrainPathQuery : public QObject
238 239 240 241
{
    Q_OBJECT

public:
242 243
    /// @param autoDelete true: object will delete itself after it signals results
    TerrainPathQuery(bool autoDelete);
244

DonLakeFlyer's avatar
DonLakeFlyer committed
245 246 247
    /// Async terrain query for terrain heights between two lat/lon coordinates. When the query is done, the terrainData() signal
    /// is emitted.
    ///     @param coordinates to query
248 249
    void requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord);

250
    typedef struct {
251 252 253
        double          distanceBetween;        ///< Distance between each height value
        double          finalDistanceBetween;   ///< Distance between final two height values
        QList<double>   heights;                ///< Terrain heights along path
254 255 256 257
    } PathHeightInfo_t;

signals:
    /// Signalled when terrain data comes back from server
DonLakeFlyer's avatar
DonLakeFlyer committed
258
    void terrainDataReceived(bool success, const PathHeightInfo_t& pathHeightInfo);
259

260
private slots:
261
    void _pathHeights(bool success, double distanceBetween, double finalDistanceBetween, const QList<double>& heights);
262 263

private:
264 265
    bool                        _autoDelete;
    TerrainOfflineAirMapQuery   _terrainQuery;
266 267 268 269 270 271 272 273 274
};

Q_DECLARE_METATYPE(TerrainPathQuery::PathHeightInfo_t)

class TerrainPolyPathQuery : public QObject
{
    Q_OBJECT

public:
275 276
    /// @param autoDelete true: object will delete itself after it signals results
    TerrainPolyPathQuery(bool autoDelete);
277

DonLakeFlyer's avatar
DonLakeFlyer committed
278 279 280
    /// Async terrain query for terrain heights for the paths between each specified QGeoCoordinate.
    /// When the query is done, the terrainData() signal is emitted.
    ///     @param polyPath List of QGeoCoordinate
281 282
    void requestData(const QVariantList& polyPath);
    void requestData(const QList<QGeoCoordinate>& polyPath);
283 284 285

signals:
    /// Signalled when terrain data comes back from server
DonLakeFlyer's avatar
DonLakeFlyer committed
286
    void terrainDataReceived(bool success, const QList<TerrainPathQuery::PathHeightInfo_t>& rgPathHeightInfo);
287 288 289 290 291

private slots:
    void _terrainDataReceived(bool success, const TerrainPathQuery::PathHeightInfo_t& pathHeightInfo);

private:
292 293
    bool                                        _autoDelete;
    int                                         _curIndex = 0;
294 295 296
    QList<QGeoCoordinate>                       _rgCoords;
    QList<TerrainPathQuery::PathHeightInfo_t>   _rgPathHeightInfo;
    TerrainPathQuery                            _pathQuery;
297
};
Remek Zajac's avatar
Remek Zajac committed
298 299

///
Remek Zajac's avatar
Remek Zajac committed
300 301 302 303
/// @brief The MockTerrainQuery class provides unit test terrain query responses for the disconnected environment.
/// @details It provides preset, emulated, 1 arc-second (SRMT1) resultion regions that are either
/// flat, sloped or rugged in a fashion that aids testing terrain-sensitive functionality. All emulated
/// regions are positioned around Point Nemo - should real terrain became useful and checked in one day.
Remek Zajac's avatar
Remek Zajac committed
304
///
Remek Zajac's avatar
Remek Zajac committed
305
class UnitTestTerrainQuery : public TerrainQueryInterface {
Remek Zajac's avatar
Remek Zajac committed
306
public:
Remek Zajac's avatar
Remek Zajac committed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341

    static constexpr double regionExtentDeg = 0.1; //every region 0.1deg x 0.1deg across (around 11km north to south)
    static constexpr double one_second_deg  = 1.0/3600;

    /// @brief Point Nemo is a point on Earth furthest from land
    static const QGeoCoordinate pointNemo;

    ///
    /// @brief flat10Region is a region with constant 10m terrain elevation
    ///
    struct Flat10Region : public QGeoRectangle {
        Flat10Region(const QGeoRectangle& region)
        :QGeoRectangle(region)
        {}

        static constexpr double elevationMts = 10;
    };
    static const Flat10Region flat10Region;

    ///
    /// @brief linearSlopeRegion is a region with a linear west to east slope raising from -100m to 1000m
    ///
    struct LinearSlopeRegion : public QGeoRectangle {
        LinearSlopeRegion(const QGeoRectangle& region)
        :QGeoRectangle(region)
        {}

        static constexpr double minElevationMts = -100;
        static constexpr double maxElevationMts = 1000;
        static constexpr double dElevationMts = maxElevationMts-minElevationMts;
    };
    static const LinearSlopeRegion linearSlopeRegion;

    UnitTestTerrainQuery(TerrainQueryInterface* parent = nullptr);

Remek Zajac's avatar
Remek Zajac committed
342 343 344
    void requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) Q_DECL_OVERRIDE;
    void requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) Q_DECL_OVERRIDE;
    void requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) Q_DECL_OVERRIDE;
Remek Zajac's avatar
Remek Zajac committed
345 346
    QList<double> requestCoordinateHeightsSync(const QList<QGeoCoordinate>& coordinates);
    QPair<QList<QGeoCoordinate>, QList<double>> requestPathHeightsSync(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord);
Remek Zajac's avatar
Remek Zajac committed
347
};
Remek Zajac's avatar
Remek Zajac committed
348