TerrainQuery.h 13.6 KB
Newer Older
1 2
/****************************************************************************
 *
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>
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
};
298

299 300 301 302 303 304 305 306
///
/// @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.
///
class UnitTestTerrainQuery : public TerrainQueryInterface {
public:
DonLakeFlyer's avatar
DonLakeFlyer committed
307

308 309
    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;
DonLakeFlyer's avatar
DonLakeFlyer committed
310

311 312
    /// @brief Point Nemo is a point on Earth furthest from land
    static const QGeoCoordinate pointNemo;
DonLakeFlyer's avatar
DonLakeFlyer committed
313

314 315 316 317 318 319 320
    ///
    /// @brief flat10Region is a region with constant 10m terrain elevation
    ///
    struct Flat10Region : public QGeoRectangle {
        Flat10Region(const QGeoRectangle& region)
        :QGeoRectangle(region)
        {}
DonLakeFlyer's avatar
DonLakeFlyer committed
321

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
        static const double elevationMts;
    };
    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 const double minElevationMts;
        static const double maxElevationMts;
        static const double dElevationMts;
    };
    static const LinearSlopeRegion linearSlopeRegion;
DonLakeFlyer's avatar
DonLakeFlyer committed
339

340 341 342 343 344 345 346
    UnitTestTerrainQuery(TerrainQueryInterface* parent = nullptr);

    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;
    QList<double> requestCoordinateHeightsSync(const QList<QGeoCoordinate>& coordinates);
    QPair<QList<QGeoCoordinate>, QList<double>> requestPathHeightsSync(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord);
DonLakeFlyer's avatar
DonLakeFlyer committed
347 348
};