diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index df35d1347b0f77228e422d81b470377cab20f83a..bcc159abbb69fef7cec7883dc2411f63b991a815 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -569,6 +569,8 @@ HEADERS += \ src/Settings/UnitsSettings.h \ src/Settings/VideoSettings.h \ src/Terrain.h \ + src/TerrainCacheTileServer.h \ + src/TerrainTile.h \ src/Vehicle/MAVLinkLogManager.h \ src/VehicleSetup/JoystickConfigController.h \ src/comm/LinkConfiguration.h \ @@ -756,6 +758,8 @@ SOURCES += \ src/Settings/UnitsSettings.cc \ src/Settings/VideoSettings.cc \ src/Terrain.cc \ + src/TerrainCacheTileServer.cc \ + src/TerrainTile.cc\ src/Vehicle/MAVLinkLogManager.cc \ src/VehicleSetup/JoystickConfigController.cc \ src/comm/LinkConfiguration.cc \ diff --git a/src/Terrain.h b/src/Terrain.h index bfc987f74da16a984d9c201126106606f759b92c..9b3217ecce0e768b6e7cb0637b2c84f125c62021 100644 --- a/src/Terrain.h +++ b/src/Terrain.h @@ -13,6 +13,8 @@ #include #include +#include "TerrainCacheTileServer.h" + /* usage example: ElevationProvider *p = new ElevationProvider(); QList coordinates; @@ -38,6 +40,13 @@ public: */ bool queryTerrainData(const QList& coordinates); + /** + * + * + * + */ + bool cacheTerrainData(const QGeoCoordinate& southWest, const QGeoCoordinate& northEast); + signals: void terrainData(bool success, QList altitudes); diff --git a/src/TerrainCacheTileServer.cc b/src/TerrainCacheTileServer.cc new file mode 100644 index 0000000000000000000000000000000000000000..1dde372a71e1d8ae2ef3dde1c8d5db7c5c562944 --- /dev/null +++ b/src/TerrainCacheTileServer.cc @@ -0,0 +1,6 @@ +#include "TerrainCacheTileServer.h" + +TerrainCacheTileServer::TerrainCacheTileServer() +{ + +} diff --git a/src/TerrainCacheTileServer.h b/src/TerrainCacheTileServer.h new file mode 100644 index 0000000000000000000000000000000000000000..6edc14dc5a1039fd8327b377e126753263282944 --- /dev/null +++ b/src/TerrainCacheTileServer.h @@ -0,0 +1,19 @@ +#ifndef TERRAINCACHESERVER_H +#define TERRAINCACHESERVER_H + +#include "TerrainTile.h" + +class TerrainCacheTileServer +{ +public: + TerrainCacheTileServer(); + + bool cacheTerrainData(const QGeoCoordinate& southWest, const QGeoCoordinate& northEast); + + bool cached(const QGeoCoordinate& coord); + +private: + QStringList _downloadQueue; +}; + +#endif // TERRAINCACHESERVER_H diff --git a/src/TerrainTile.cc b/src/TerrainTile.cc new file mode 100644 index 0000000000000000000000000000000000000000..e919137798098ac6d0b030b316cd61de73445e68 --- /dev/null +++ b/src/TerrainTile.cc @@ -0,0 +1,156 @@ +#include "TerrainTile.h" +#include "JsonHelper.h" + +#include +#include +#include + +QGC_LOGGING_CATEGORY(TerrainTileLog, "TerrainTileLog") + +const char* TerrainTile::_jsonStatusKey = "status"; +const char* TerrainTile::_jsonDataKey = "data"; +const char* TerrainTile::_jsonBoundsKey = "bounds"; +const char* TerrainTile::_jsonSouthWestKey = "sw"; +const char* TerrainTile::_jsonNorthEastKey = "ne"; +const char* TerrainTile::_jsonStatsKey = "stats"; +const char* TerrainTile::_jsonMaxElevationKey = "max"; +const char* TerrainTile::_jsonMinElevationKey = "min"; +const char* TerrainTile::_jsonAvgElevationKey = "avg"; +const char* TerrainTile::_jsonCarpetKey = "carpet"; + +TerrainTile::TerrainTile() + : _minElevation(-1.0) + , _maxElevation(-1.0) + , _avgElevation(-1.0) + , _isValid(false) +{ + +} + +TerrainTile::~TerrainTile() +{ +} + +TerrainTile::TerrainTile(QJsonDocument doc) + : _minElevation(-1.0) + , _maxElevation(-1.0) + , _avgElevation(-1.0) + , _isValid(false) +{ + if (!doc.isObject()) { + qCDebug(TerrainTileLog) << "Terrain tile json doc is no object"; + return; + } + QJsonObject rootObject = doc.object(); + + QString errorString; + QList rootVersionKeyInfoList = { + { _jsonStatusKey, QJsonValue::String, true }, + { _jsonDataKey, QJsonValue::String, true }, + }; + if (!JsonHelper::validateKeys(rootObject, rootVersionKeyInfoList, errorString)) { + qCDebug(TerrainTileLog) << "Error in reading json: " << errorString; + return false; + } + + if (rootObject[_jsonStatusKey].toString() != "success") { + qCDebug(TerrainTileLog) << "Invalid terrain tile."; + return; + } + const QJsonObject& dataObject = rootObject[_jsonDataKey].toObject(); + QList dataVersionKeyInfoList = { + { _jsonBoundsKey, QJsonValue::Object, true }, + { _jsonStatsKey, QJsonValue::Object, true }, + { _jsonCarpetKey, QJsonValue::Array, true }, + }; + if (!JsonHelper::validateKeys(dataObject, dataVersionKeyInfoList, errorString)) { + qCDebug(TerrainTileLog) << "Error in reading json: " << errorString; + return false; + } + + // Bounds + const QJsonObject& boundsObject = dataObject[_jsonBoundsKey].toObject(); + QList boundsVersionKeyInfoList = { + { _jsonSouthWestKey, QJsonValue::Array, true }, + { _jsonNorthEastKey, QJsonValue::Array, true }, + }; + if (!JsonHelper::validateKeys(boundsObject, boundsVersionKeyInfoList, errorString)) { + qCDebug(TerrainTileLog) << "Error in reading json: " << errorString; + return false; + } + const QJsonArray& swArray = boundsObject[_jsonSouthWestKey].toArray(); + const QJsonArray& neArray = boundsObject[_jsonNorthEastKey].toArray(); + if (swArray.count() < 2 || neArray.count() < 2 ) { + qCDebug(TerrainTileLog) << "Incomplete bounding location"; + return; + } + _southWest.setLatitude(swArray[0].toDouble()); + _southWest.setLongitude(swArray[1].toDouble()); + _northEast.setLatitude(neArray[0].toDouble()); + _northEast.setLongitude(neArray[1].toDouble()); + + // Stats + const QJsonObject& statsObject = dataObject[_jsonBoundsKey].toObject(); + QList statsVersionKeyInfoList = { + { _jsonMaxElevationKey, QJsonValue::Double, true }, + { _jsonMinElevationKey, QJsonValue::Double, true }, + { _jsonAvgElevationKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(statsObject, statsVersionKeyInfoList, errorString)) { + qCDebug(TerrainTileLog) << "Error in reading json: " << errorString; + return false; + } + _maxElevation = statsObject[_jsonMaxElevationKey].toInt(); + _minElevation = statsObject[_jsonMinElevationKey].toInt(); + _avgElevation = statsObject[_jsonAvgElevationKey].toInt(); + + // Carpet + const QJsonArray& carpetArray = dataObject[_jsonCarpetKey].toArray(); + if (carpetArray.count() != _gridSize) { + qCDebug(TerrainTileLog) << "Expected array of " << _gridSize << ", instead got " << carpetArray.count(); + return; + } + for (int i = 0; i < _gridSize; i++) { + const QJsonArray& row = carpetArray[i].toArray(); + if (row.count() != _gridSize) { + qCDebug(TerrainTileLog) << "Expected row array of " << _gridSize << ", instead got " << row.count(); + return; + } + for (int j = 0; j < _gridSize; j++) { + _data[i][j] = row[j].toDouble(); + } + } + _isValid = true; +} + +bool TerrainTile::isIn(QGeoCoordinate coord) +{ + if (!_isValid) { + qCDebug(TerrainTileLog) << "isIn requested, but tile not valid"; + return false; + } + bool ret = coord.latitude() >= _southWest.longitude() && coord.longitude() >= _southWest.longitude() && + coord.latitude() <= _northEast.longitude() && coord.longitude() <= _northEast.longitude(); + qCDebug(TerrainTileLog) << "Checking isIn: " << coord << " , in sw " << _southWest << " , ne " << _northEast << ": " << ret; + return ret; +} + +float TerrainTile::elevation(const QGeoCoordinate& coord) +{ + if (_isValid) { + qCDebug << "elevation: " << coord << " , in sw " << _southWest << " , ne " << _northEast; + // Get the index at resolution of 1 arc second + int indexLat = std::round((coord.latitude() - _southWest.latitude()) / _srtm1Increment); + int indexLon = std::round((coord.longitude() - _southWest.longitude()) / _srtm1Increment); + qCDebug << "indexLat:indexLon" << indexLat << indexLon; // TODO (birchera): Move this down to the next debug output, once this is all properly working. + Q_ASSERT(indexLat >= 0); + Q_ASSERT(indexLat < _gridSize); + Q_ASSERT(indexLon >= 0); + Q_ASSERT(indexLon < _gridSize); + qCDebug << "elevation" << _data[indexLat][indexLon]; + return _data[indexLat][indexLon]; + } else { + qCDebug(TerrainTileLog) << "Asking for elevation, but no valid data."; + return -1.0; + } +} diff --git a/src/TerrainTile.h b/src/TerrainTile.h new file mode 100644 index 0000000000000000000000000000000000000000..69e9db28cb920ddf154d89f2f223e6c85f7c4f1b --- /dev/null +++ b/src/TerrainTile.h @@ -0,0 +1,95 @@ +#ifndef TERRAINTILE_H +#define TERRAINTILE_H + +#include "QGCLoggingCategory.h" + +#include + +#define TERRAIN_TILE_SIZE 90 + +Q_DECLARE_LOGGING_CATEGORY(TerrainTileLog) + +class TerrainTile +{ +public: + TerrainTile(); + ~TerrainTile(); + + /** + * Constructor from json doc with elevation data (either from file or web) + * + * @param json doc + */ + TerrainTile(QJsonDocument doc); + + /** + * Check for whether a coordinate lies within this tile + * + * @param coordinate + * @return true if within + */ + bool isIn(QGeoCoordinate coord); + + /** + * Check whether valid data is loaded + * + * @return true if data is valid + */ + bool isValid(void) { return _isValid; } + + /** + * Evaluates the elevation at the given coordinate + * + * @param coordinate + * @return elevation + */ + float elevation(const QGeoCoordinate& coord); + + /** + * Accessor for the minimum elevation of the tile + * + * @return minimum elevation + */ + float minElevation(void) { return _minElevation; } + + /** + * Accessor for the maximum elevation of the tile + * + * @return maximum elevation + */ + float maxElevation(void) { return _maxElevation; } + + /** + * Accessor for the average elevation of the tile + * + * @return average elevation + */ + float avgElevation(void) { return _avgElevation; } + +private: + QGeoCoordinate _southWest; /// South west corner of the tile + QGeoCoordinate _northEast; /// North east corner of the tile + + float _minElevation; /// Minimum elevation in tile + float _maxElevation; /// Maximum elevation in tile + float _avgElevation; /// Average elevation of the tile + + float _data[TERRAIN_TILE_SIZE][TERRAIN_TILE_SIZE]; /// elevation data + bool _isValid; /// data loaded is valid + static const int _gridSize = TERRAIN_TILE_SIZE; /// tile grid size in lat and lon + static const float _srtm1Increment = 1.0 / (60.0 * 60.0); /// grid spacing in degree + + // Json keys + static const char* _jsonStatusKey; + static const char* _jsonDataKey; + static const char* _jsonBoundsKey; + static const char* _jsonSouthWestKey; + static const char* _jsonNorthEastKey; + static const char* _jsonStatsKey; + static const char* _jsonMaxElevationKey; + static const char* _jsonMinElevationKey; + static const char* _jsonAvgElevationKey; + static const char* _jsonCarpetKey; +}; + +#endif // TERRAINTILE_H