From 83643d6854ab61a8a56ee85ffdff53999f00689d Mon Sep 17 00:00:00 2001 From: Andreas Bircher Date: Thu, 23 Nov 2017 17:05:02 -0500 Subject: [PATCH] Cache offline elevation maps --- src/QtLocationPlugin/QGCMapEngine.cpp | 48 +++++++++++++++++-- src/QtLocationPlugin/QGCMapEngine.h | 5 ++ src/QtLocationPlugin/QGCMapUrlEngine.cpp | 21 ++++++++ src/QtLocationPlugin/QGCMapUrlEngine.h | 4 +- .../QMLControl/OfflineMap.qml | 8 ++++ .../QMLControl/QGCMapEngineManager.cc | 23 ++++++++- .../QMLControl/QGCMapEngineManager.h | 8 ++-- src/Terrain.cc | 21 ++++---- src/TerrainTile.cc | 6 +-- src/TerrainTile.h | 3 -- 10 files changed, 121 insertions(+), 26 deletions(-) diff --git a/src/QtLocationPlugin/QGCMapEngine.cpp b/src/QtLocationPlugin/QGCMapEngine.cpp index 571ce6927..65fdcab0d 100644 --- a/src/QtLocationPlugin/QGCMapEngine.cpp +++ b/src/QtLocationPlugin/QGCMapEngine.cpp @@ -91,6 +91,12 @@ stQGeoTileCacheQGCMapTypes kEsriTypes[] = { #define NUM_ESRIMAPS (sizeof(kEsriTypes) / sizeof(stQGeoTileCacheQGCMapTypes)) +stQGeoTileCacheQGCMapTypes kElevationTypes[] = { + {"Airmap Elevation Data", UrlFactory::AirmapElevation} +}; + +#define NUM_ELEVMAPS (sizeof(kElevationTypes) / sizeof(stQGeoTileCacheQGCMapTypes)) + static const char* kMaxDiskCacheKey = "MaxDiskCache"; static const char* kMaxMemCacheKey = "MaxMemoryCache"; @@ -105,6 +111,9 @@ getQGCMapEngine() return kMapEngine; } +//----------------------------------------------------------------------------- +const double QGCMapEngine::srtm1TileSize = 0.025; + //----------------------------------------------------------------------------- void destroyMapEngine() @@ -296,10 +305,17 @@ QGCMapEngine::getTileCount(int zoom, double topleftLon, double topleftLat, doubl if(zoom < 1) zoom = 1; if(zoom > MAX_MAP_ZOOM) zoom = MAX_MAP_ZOOM; QGCTileSet set; - set.tileX0 = long2tileX(topleftLon, zoom); - set.tileY0 = lat2tileY(topleftLat, zoom); - set.tileX1 = long2tileX(bottomRightLon, zoom); - set.tileY1 = lat2tileY(bottomRightLat, zoom); + if (mapType != UrlFactory::AirmapElevation) { + set.tileX0 = long2tileX(topleftLon, zoom); + set.tileY0 = lat2tileY(topleftLat, zoom); + set.tileX1 = long2tileX(bottomRightLon, zoom); + set.tileY1 = lat2tileY(bottomRightLat, zoom); + } else { + set.tileX0 = long2elevationTileX(topleftLon, zoom); + set.tileY0 = lat2elevationTileY(bottomRightLat, zoom); + set.tileX1 = long2elevationTileX(bottomRightLon, zoom); + set.tileY1 = lat2elevationTileY(topleftLat, zoom); + } set.tileCount = (quint64)((quint64)set.tileX1 - (quint64)set.tileX0 + 1) * (quint64)((quint64)set.tileY1 - (quint64)set.tileY0 + 1); set.tileSize = UrlFactory::averageSizeForType(mapType) * set.tileCount; return set; @@ -319,6 +335,22 @@ QGCMapEngine::lat2tileY(double lat, int z) return (int)(floor((1.0 - log( tan(lat * M_PI/180.0) + 1.0 / cos(lat * M_PI/180.0)) / M_PI) / 2.0 * pow(2.0, z))); } +//----------------------------------------------------------------------------- +int +QGCMapEngine::long2elevationTileX(double lon, int z) +{ + Q_UNUSED(z); + return (int)(floor((lon + 180.0) / srtm1TileSize)); +} + +//----------------------------------------------------------------------------- +int +QGCMapEngine::lat2elevationTileY(double lat, int z) +{ + Q_UNUSED(z); + return (int)(floor((lat + 90.0) / srtm1TileSize)); +} + //----------------------------------------------------------------------------- UrlFactory::MapType QGCMapEngine::getTypeFromName(const QString& name) @@ -336,6 +368,10 @@ QGCMapEngine::getTypeFromName(const QString& name) if(name.compare(kEsriTypes[i].name, Qt::CaseInsensitive) == 0) return kEsriTypes[i].type; } + for(i = 0; i < NUM_ELEVMAPS; i++) { + if(name.compare(kElevationTypes[i].name, Qt::CaseInsensitive) == 0) + return kElevationTypes[i].type; + } return UrlFactory::Invalid; } @@ -357,6 +393,9 @@ QGCMapEngine::getMapNameList() mapList << kEsriTypes[i].name; } } + for(size_t i = 0; i < NUM_ELEVMAPS; i++) { + mapList << kElevationTypes[i].name; + } return mapList; } @@ -469,6 +508,7 @@ QGCMapEngine::concurrentDownloads(UrlFactory::MapType type) case UrlFactory::EsriWorldStreet: case UrlFactory::EsriWorldSatellite: case UrlFactory::EsriTerrain: + case UrlFactory::AirmapElevation: return 12; /* case UrlFactory::MapQuestMap: diff --git a/src/QtLocationPlugin/QGCMapEngine.h b/src/QtLocationPlugin/QGCMapEngine.h index e0fd28036..352fcbd19 100644 --- a/src/QtLocationPlugin/QGCMapEngine.h +++ b/src/QtLocationPlugin/QGCMapEngine.h @@ -94,12 +94,17 @@ public: static QGCTileSet getTileCount (int zoom, double topleftLon, double topleftLat, double bottomRightLon, double bottomRightLat, UrlFactory::MapType mapType); static int long2tileX (double lon, int z); static int lat2tileY (double lat, int z); + static int long2elevationTileX (double lon, int z); + static int lat2elevationTileY (double lat, int z); static QString getTileHash (UrlFactory::MapType type, int x, int y, int z); static UrlFactory::MapType getTypeFromName (const QString &name); static QString bigSizeToString (quint64 size); static QString numberToString (quint64 number); static int concurrentDownloads (UrlFactory::MapType type); + /// size of an elevation tile in degree + static const double srtm1TileSize; + private slots: void _updateTotals (quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize); void _pruned (); diff --git a/src/QtLocationPlugin/QGCMapUrlEngine.cpp b/src/QtLocationPlugin/QGCMapUrlEngine.cpp index b429cd562..b61079153 100644 --- a/src/QtLocationPlugin/QGCMapUrlEngine.cpp +++ b/src/QtLocationPlugin/QGCMapUrlEngine.cpp @@ -36,6 +36,7 @@ static const unsigned char pngSignature[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00}; static const unsigned char jpegSignature[] = {0xFF, 0xD8, 0xFF, 0x00}; static const unsigned char gifSignature[] = {0x47, 0x49, 0x46, 0x38, 0x00}; +static const unsigned char jsonSignature[] = {0x7A, 0x22, 0x00}; // two characters '{"' //----------------------------------------------------------------------------- UrlFactory::UrlFactory() @@ -85,6 +86,8 @@ UrlFactory::getImageFormat(MapType type, const QByteArray& image) format = "jpg"; else if (image.startsWith(reinterpret_cast(gifSignature))) format = "gif"; + else if (image.startsWith(reinterpret_cast(jsonSignature))) + format = "json"; else { switch (type) { case GoogleMap: @@ -119,6 +122,9 @@ UrlFactory::getImageFormat(MapType type, const QByteArray& image) case BingHybrid: format = "jpg"; break; + case AirmapElevation: + format = "json"; + break; default: qWarning("UrlFactory::getImageFormat() Unknown map id %d", type); break; @@ -177,6 +183,10 @@ UrlFactory::getTileURL(MapType type, int x, int y, int zoom, QNetworkAccessManag } return request; + case AirmapElevation: + request.setRawHeader("Referrer", "https://api.airmap.com/"); + break; + default: break; } @@ -392,6 +402,14 @@ UrlFactory::_getURL(MapType type, int x, int y, int zoom, QNetworkAccessManager* } } break; + case AirmapElevation: + { + return QString("https://api.airmap.com/elevation/stage/srtm1/ele/carpet?points=%1,%2,%3,%4").arg(static_cast(x)*QGCMapEngine::srtm1TileSize - 180.0).arg( + static_cast(y)*QGCMapEngine::srtm1TileSize - 90.0).arg( + static_cast(x + 1)*QGCMapEngine::srtm1TileSize - 180.0).arg( + static_cast(y + 1)*QGCMapEngine::srtm1TileSize - 90.0); + } + break; default: qWarning("Unknown map id %d\n", type); @@ -534,6 +552,7 @@ UrlFactory::_tryCorrectGoogleVersions(QNetworkAccessManager* networkManager) #define AVERAGE_MAPBOX_SAT_MAP 15739 #define AVERAGE_MAPBOX_STREET_MAP 5648 #define AVERAGE_TILE_SIZE 13652 +#define AVERAGE_AIRMAP_ELEV_SIZE 34000 //----------------------------------------------------------------------------- quint32 @@ -557,6 +576,8 @@ UrlFactory::averageSizeForType(MapType type) case MapboxStreetsBasic: case MapboxRunBikeHike: return AVERAGE_MAPBOX_STREET_MAP; + case AirmapElevation: + return AVERAGE_AIRMAP_ELEV_SIZE; case GoogleLabels: case MapboxDark: case MapboxLight: diff --git a/src/QtLocationPlugin/QGCMapUrlEngine.h b/src/QtLocationPlugin/QGCMapUrlEngine.h index b9791ff2e..87cf64e7b 100644 --- a/src/QtLocationPlugin/QGCMapUrlEngine.h +++ b/src/QtLocationPlugin/QGCMapUrlEngine.h @@ -72,7 +72,9 @@ public: EsriWorldStreet = 7000, EsriWorldSatellite = 7001, - EsriTerrain = 7002 + EsriTerrain = 7002, + + AirmapElevation = 8000 }; UrlFactory (); diff --git a/src/QtLocationPlugin/QMLControl/OfflineMap.qml b/src/QtLocationPlugin/QMLControl/OfflineMap.qml index e2d336c84..cd132a5af 100644 --- a/src/QtLocationPlugin/QMLControl/OfflineMap.qml +++ b/src/QtLocationPlugin/QMLControl/OfflineMap.qml @@ -732,6 +732,14 @@ QGCView { } } } + QGCCheckBox { + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Fetch elevation data") + checked: QGroundControl.mapEngineManager.fetchElevation + onClicked: QGroundControl.mapEngineManager.fetchElevation = checked + visible: mapType != "Airmap Elevation Data" + } } Rectangle { diff --git a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc index fce1e1228..ea64716f8 100644 --- a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc +++ b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.cc @@ -41,6 +41,7 @@ QGCMapEngineManager::QGCMapEngineManager(QGCApplication* app, QGCToolbox* toolbo , _setID(UINT64_MAX) , _freeDiskSpace(0) , _diskSpace(0) + , _fetchElevation(true) , _actionProgress(0) , _importAction(ActionNone) , _importReplace(false) @@ -157,8 +158,26 @@ QGCMapEngineManager::startDownload(const QString& name, const QString& mapType) } else { qWarning() << "QGCMapEngineManager::startDownload() No Tiles to save"; } - // TODO: this could also get some feedback - _elevationProvider.cacheTerrainTiles(QGeoCoordinate(_bottomRightLat, _topleftLon), QGeoCoordinate(_topleftLat, _bottomRightLon)); + if (mapType != "Airmap Elevation Data" && _fetchElevation) { + QGCCachedTileSet* set = new QGCCachedTileSet(name + " Elevation"); + set->setMapTypeStr("Airmap Elevation Data"); + set->setTopleftLat(_topleftLat); + set->setTopleftLon(_topleftLon); + set->setBottomRightLat(_bottomRightLat); + set->setBottomRightLon(_bottomRightLon); + set->setMinZoom(1); + set->setMaxZoom(1); + set->setTotalTileSize(_totalSet.tileSize); + set->setTotalTileCount(_totalSet.tileCount); + set->setType(QGCMapEngine::getTypeFromName("Airmap Elevation Data")); + QGCCreateTileSetTask* task = new QGCCreateTileSetTask(set); + //-- Create Tile Set (it will also create a list of tiles to download) + connect(task, &QGCCreateTileSetTask::tileSetSaved, this, &QGCMapEngineManager::_tileSetSaved); + connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError); + getQGCMapEngine()->addTask(task); + } else { + qWarning() << "QGCMapEngineManager::startDownload() No Tiles to save"; + } } //----------------------------------------------------------------------------- diff --git a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h index 73c9b733e..3a5d283d9 100644 --- a/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h +++ b/src/QtLocationPlugin/QMLControl/QGCMapEngineManager.h @@ -19,7 +19,6 @@ #include "QGCLoggingCategory.h" #include "QGCMapEngine.h" #include "QGCMapTileSet.h" -#include "Terrain.h" Q_DECLARE_LOGGING_CATEGORY(QGCMapEngineManagerLog) @@ -51,6 +50,7 @@ public: Q_PROPERTY(quint32 maxMemCache READ maxMemCache WRITE setMaxMemCache NOTIFY maxMemCacheChanged) Q_PROPERTY(quint32 maxDiskCache READ maxDiskCache WRITE setMaxDiskCache NOTIFY maxDiskCacheChanged) Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) + Q_PROPERTY(bool fetchElevation READ fetchElevation WRITE setFetchElevation NOTIFY fetchElevationChanged) //-- Disk Space in MB Q_PROPERTY(quint32 freeDiskSpace READ freeDiskSpace NOTIFY freeDiskSpaceChanged) Q_PROPERTY(quint32 diskSpace READ diskSpace CONSTANT) @@ -89,6 +89,7 @@ public: quint32 maxMemCache (); quint32 maxDiskCache (); QString errorMessage () { return _errorMessage; } + bool fetchElevation () { return _fetchElevation; } quint64 freeDiskSpace () { return _freeDiskSpace; } quint64 diskSpace () { return _diskSpace; } int selectedCount (); @@ -101,6 +102,7 @@ public: void setImportReplace (bool replace) { _importReplace = replace; emit importReplaceChanged(); } void setImportAction (ImportAction action) {_importAction = action; emit importActionChanged(); } void setErrorMessage (const QString& error) { _errorMessage = error; emit errorMessageChanged(); } + void setFetchElevation (bool fetchElevation) { _fetchElevation = fetchElevation; emit fetchElevationChanged(); } // Override from QGCTool void setToolbox(QGCToolbox *toolbox); @@ -116,6 +118,7 @@ signals: void maxMemCacheChanged (); void maxDiskCacheChanged (); void errorMessageChanged (); + void fetchElevationChanged (); void freeDiskSpaceChanged (); void selectedCountChanged (); void actionProgressChanged (); @@ -150,11 +153,10 @@ private: quint32 _diskSpace; QmlObjectListModel _tileSets; QString _errorMessage; + bool _fetchElevation; int _actionProgress; ImportAction _importAction; bool _importReplace; - - ElevationProvider _elevationProvider; }; #endif diff --git a/src/Terrain.cc b/src/Terrain.cc index dd3205318..988842f08 100644 --- a/src/Terrain.cc +++ b/src/Terrain.cc @@ -8,6 +8,7 @@ ****************************************************************************/ #include "Terrain.h" +#include "QGCMapEngine.h" #include #include @@ -112,15 +113,15 @@ bool ElevationProvider::cacheTerrainTiles(const QGeoCoordinate& southWest, const // Correct corners of the tile to fixed grid QGeoCoordinate southWestCorrected; - southWestCorrected.setLatitude(floor(southWest.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); - southWestCorrected.setLongitude(floor(southWest.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); + southWestCorrected.setLatitude(floor(southWest.latitude()/QGCMapEngine::srtm1TileSize)*QGCMapEngine::srtm1TileSize); + southWestCorrected.setLongitude(floor(southWest.longitude()/QGCMapEngine::srtm1TileSize)*QGCMapEngine::srtm1TileSize); QGeoCoordinate northEastCorrected; - northEastCorrected.setLatitude(ceil(northEast.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); - northEastCorrected.setLongitude(ceil(northEast.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); + northEastCorrected.setLatitude(ceil(northEast.latitude()/QGCMapEngine::srtm1TileSize)*QGCMapEngine::srtm1TileSize); + northEastCorrected.setLongitude(ceil(northEast.longitude()/QGCMapEngine::srtm1TileSize)*QGCMapEngine::srtm1TileSize); // Add all tiles to download queue - for (double lat = southWestCorrected.latitude() + TerrainTile::srtm1TileSize / 2.0; lat < northEastCorrected.latitude(); lat += TerrainTile::srtm1TileSize) { - for (double lon = southWestCorrected.longitude() + TerrainTile::srtm1TileSize / 2.0; lon < northEastCorrected.longitude(); lon += TerrainTile::srtm1TileSize) { + for (double lat = southWestCorrected.latitude() + QGCMapEngine::srtm1TileSize / 2.0; lat < northEastCorrected.latitude(); lat += QGCMapEngine::srtm1TileSize) { + for (double lon = southWestCorrected.longitude() + QGCMapEngine::srtm1TileSize / 2.0; lon < northEastCorrected.longitude(); lon += QGCMapEngine::srtm1TileSize) { QString uniqueTileId = _uniqueTileId(QGeoCoordinate(lat, lon)); _tilesMutex.lock(); if (_downloadQueue.contains(uniqueTileId) || _tiles.contains(uniqueTileId)) { @@ -252,11 +253,11 @@ QString ElevationProvider::_uniqueTileId(const QGeoCoordinate& coordinate) { // Compute corners of the tile QGeoCoordinate southWest; - southWest.setLatitude(floor(coordinate.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); - southWest.setLongitude(floor(coordinate.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); + southWest.setLatitude(floor(coordinate.latitude()/QGCMapEngine::srtm1TileSize)*QGCMapEngine::srtm1TileSize); + southWest.setLongitude(floor(coordinate.longitude()/QGCMapEngine::srtm1TileSize)*QGCMapEngine::srtm1TileSize); QGeoCoordinate northEast; - northEast.setLatitude(ceil(coordinate.latitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); - northEast.setLongitude(ceil(coordinate.longitude()/TerrainTile::srtm1TileSize)*TerrainTile::srtm1TileSize); + northEast.setLatitude(floor(coordinate.latitude()/QGCMapEngine::srtm1TileSize + 1)*QGCMapEngine::srtm1TileSize); + northEast.setLongitude(floor(coordinate.longitude()/QGCMapEngine::srtm1TileSize + 1)*QGCMapEngine::srtm1TileSize); // Generate uniquely identifying string QString ret = QString::number(southWest.latitude(), 'f', 6) + "_" + QString::number(southWest.longitude(), 'f', 6) + "_" + diff --git a/src/TerrainTile.cc b/src/TerrainTile.cc index fddc0bae3..38a1d277e 100644 --- a/src/TerrainTile.cc +++ b/src/TerrainTile.cc @@ -1,5 +1,6 @@ #include "TerrainTile.h" #include "JsonHelper.h" +#include "QGCMapEngine.h" #include #include @@ -7,7 +8,6 @@ QGC_LOGGING_CATEGORY(TerrainTileLog, "TerrainTileLog") -const double TerrainTile::srtm1TileSize = 0.025; const char* TerrainTile::_jsonStatusKey = "status"; const char* TerrainTile::_jsonDataKey = "data"; const char* TerrainTile::_jsonBoundsKey = "bounds"; @@ -141,8 +141,8 @@ float TerrainTile::elevation(const QGeoCoordinate& coordinate) const if (_isValid) { qCDebug(TerrainTileLog) << "elevation: " << coordinate << " , in sw " << _southWest << " , ne " << _northEast; // Get the index at resolution of 1 arc second - int indexLat = round((coordinate.latitude() - _southWest.latitude()) * (gridSize - 1) / srtm1TileSize); - int indexLon = round((coordinate.longitude() - _southWest.longitude()) * (gridSize - 1) / srtm1TileSize); + int indexLat = round((coordinate.latitude() - _southWest.latitude()) * (gridSize - 1) / QGCMapEngine::srtm1TileSize); + int indexLon = round((coordinate.longitude() - _southWest.longitude()) * (gridSize - 1) / QGCMapEngine::srtm1TileSize); qCDebug(TerrainTileLog) << "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); diff --git a/src/TerrainTile.h b/src/TerrainTile.h index 4ac61de1d..3b757b901 100644 --- a/src/TerrainTile.h +++ b/src/TerrainTile.h @@ -76,9 +76,6 @@ public: /// tile grid size in lat and lon static const int gridSize = TERRAIN_TILE_SIZE; - /// size of a tile in degree - static const double srtm1TileSize; - private: QGeoCoordinate _southWest; /// South west corner of the tile QGeoCoordinate _northEast; /// North east corner of the tile -- 2.22.0