Newer
Older
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
/**
* @file
* @brief Map Tile Cache
*
* @author Gus Grubba <mavlink@grubba.com>
*
*/
#include "QGCApplication.h"
#include "AppSettings.h"
#include "SettingsManager.h"
#include <math.h>
#include <QSettings>
#include <QStandardPaths>
#include <QDir>
#include "QGCMapEngine.h"
#include "QGCMapTileSet.h"
Q_DECLARE_METATYPE(QGCMapTask::TaskType)
Q_DECLARE_METATYPE(QGCTile)
Q_DECLARE_METATYPE(QList<QGCTile*>)
static const char* kDbFileName = "qgcMapCache.db";
static QLocale kLocale;
struct stQGeoTileCacheQGCMapTypes {
const char* name;
UrlFactory::MapType type;
};
//-- IMPORTANT
// Changes here must reflect those in QGeoTiledMappingManagerEngineQGC.cpp
static stQGeoTileCacheQGCMapTypes kMapTypes[] = {
{"Google Street Map", UrlFactory::GoogleMap},
{"Google Satellite Map", UrlFactory::GoogleSatellite},
{"Google Terrain Map", UrlFactory::GoogleTerrain},
{"Bing Street Map", UrlFactory::BingMap},
{"Bing Satellite Map", UrlFactory::BingSatellite},
{"Bing Hybrid Map", UrlFactory::BingHybrid},
{"Statkart Terrain Map", UrlFactory::StatkartTopo},
{"ENIRO Terrain Map", UrlFactory::EniroTopo},
{"VWorld Satellite Map", UrlFactory::VWorldSatellite},
{"VWorld Street Map", UrlFactory::VWorldStreet}
{"MapQuest Street Map", UrlFactory::MapQuestMap},
{"MapQuest Satellite Map", UrlFactory::MapQuestSat}
{"Open Street Map", UrlFactory::OpenStreetMap}
*/
};
#define NUM_MAPS (sizeof(kMapTypes) / sizeof(stQGeoTileCacheQGCMapTypes))
static stQGeoTileCacheQGCMapTypes kMapboxTypes[] = {
{"Mapbox Street Map", UrlFactory::MapboxStreets},
{"Mapbox Satellite Map", UrlFactory::MapboxSatellite},
{"Mapbox High Contrast Map",UrlFactory::MapboxHighContrast},
{"Mapbox Light Map", UrlFactory::MapboxLight},
{"Mapbox Dark Map", UrlFactory::MapboxDark},
{"Mapbox Hybrid Map", UrlFactory::MapboxHybrid},
{"Mapbox Wheat Paste Map", UrlFactory::MapboxWheatPaste},
{"Mapbox Streets Basic Map",UrlFactory::MapboxStreetsBasic},
{"Mapbox Comic Map", UrlFactory::MapboxComic},
{"Mapbox Outdoors Map", UrlFactory::MapboxOutdoors},
{"Mapbox Run, Byke and Hike Map", UrlFactory::MapboxRunBikeHike},
{"Mapbox Pencil Map", UrlFactory::MapboxPencil},
{"Mapbox Pirates Map", UrlFactory::MapboxPirates},
{"Mapbox Emerald Map", UrlFactory::MapboxEmerald}
#define NUM_MAPBOXMAPS (sizeof(kMapboxTypes) / sizeof(stQGeoTileCacheQGCMapTypes))
static stQGeoTileCacheQGCMapTypes kEsriTypes[] = {
{"Esri Street Map", UrlFactory::EsriWorldStreet},
{"Esri Satellite Map", UrlFactory::EsriWorldSatellite},
{"Esri Terrain Map", UrlFactory::EsriTerrain}
};
#define NUM_ESRIMAPS (sizeof(kEsriTypes) / sizeof(stQGeoTileCacheQGCMapTypes))
static stQGeoTileCacheQGCMapTypes kElevationTypes[] = {
{"Airmap Elevation Data", UrlFactory::AirmapElevation}
};
#define NUM_ELEVMAPS (sizeof(kElevationTypes) / sizeof(stQGeoTileCacheQGCMapTypes))
static const char* kMaxDiskCacheKey = "MaxDiskCache";
static const char* kMaxMemCacheKey = "MaxMemoryCache";
//-----------------------------------------------------------------------------
// Singleton
static QGCMapEngine* kMapEngine = nullptr;
QGCMapEngine*
getQGCMapEngine()
{
if(!kMapEngine)
kMapEngine = new QGCMapEngine();
return kMapEngine;
}
//-----------------------------------------------------------------------------
Andreas Bircher
committed
const double QGCMapEngine::srtm1TileSize = 0.01;
//-----------------------------------------------------------------------------
void
destroyMapEngine()
{
if(kMapEngine) {
delete kMapEngine;
kMapEngine = nullptr;
}
}
//-----------------------------------------------------------------------------
QGCMapEngine::QGCMapEngine()
#ifdef WE_ARE_KOSHER
//-- TODO: Get proper version
#if defined Q_OS_MAC
, _userAgent("QGroundControl (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/2.9.0")
#elif defined Q_OS_WIN32
, _userAgent("QGroundControl (Windows; Windows NT 6.0) (KHTML, like Gecko) Version/2.9.0")
#else
, _userAgent("QGroundControl (X11; Ubuntu; Linux x86_64) (KHTML, like Gecko) Version/2.9.0")
#endif
#else
#if defined Q_OS_MAC
, _userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0")
#elif defined Q_OS_WIN32
, _userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0")
#else
, _userAgent("Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0")
#endif
#endif
, _maxDiskCache(0)
, _maxMemCache(0)
, _prunning(false)
{
qRegisterMetaType<QGCMapTask::TaskType>();
qRegisterMetaType<QGCTile>();
qRegisterMetaType<QList<QGCTile*>>();
connect(&_worker, &QGCCacheWorker::updateTotals, this, &QGCMapEngine::_updateTotals);
connect(&_worker, &QGCCacheWorker::internetStatus, this, &QGCMapEngine::_internetStatus);
}
//-----------------------------------------------------------------------------
QGCMapEngine::~QGCMapEngine()
{
_worker.quit();
_worker.wait();
if(_urlFactory)
delete _urlFactory;
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::_checkWipeDirectory(const QString& dirPath)
{
QDir dir(dirPath);
if (dir.exists(dirPath)) {
_cacheWasReset = true;
_wipeDirectory(dirPath);
}
}
//-----------------------------------------------------------------------------
void
QString oldCacheDir;
#ifdef __mobile__
oldCacheDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/QGCMapCache55");
#else
oldCacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QGCMapCache55");
oldCacheDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/QGCMapCache100");
oldCacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QGCMapCache100");
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::init()
{
//-- Delete old style caches (if present)
_wipeOldCaches();
//-- Figure out cache path
#ifdef __mobile__
QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/QGCMapCache" CACHE_PATH_VERSION);
#else
QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QGCMapCache" CACHE_PATH_VERSION);
#endif
if(!QDir::root().mkpath(cacheDir)) {
qWarning() << "Could not create mapping disk cache directory: " << cacheDir;
cacheDir = QDir::homePath() + QStringLiteral("/.qgcmapscache/");
if(!QDir::root().mkpath(cacheDir)) {
qWarning() << "Could not create mapping disk cache directory: " << cacheDir;
cacheDir.clear();
}
}
_cachePath = cacheDir;
if(!_cachePath.isEmpty()) {
_cacheFile = kDbFileName;
_worker.setDatabaseFile(_cachePath + "/" + _cacheFile);
qDebug() << "Map Cache in:" << _cachePath << "/" << _cacheFile;
} else {
qCritical() << "Could not find suitable map cache directory.";
}
QGCMapTask* task = new QGCMapTask(QGCMapTask::taskInit);
_worker.enqueueTask(task);
}
//-----------------------------------------------------------------------------
bool
QGCMapEngine::_wipeDirectory(const QString& dirPath)
{
bool result = true;
QDir dir(dirPath);
if (dir.exists(dirPath)) {
Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
if (info.isDir()) {
result = _wipeDirectory(info.absoluteFilePath());
} else {
result = QFile::remove(info.absoluteFilePath());
}
if (!result) {
return result;
}
}
result = dir.rmdir(dirPath);
}
return result;
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::addTask(QGCMapTask* task)
{
_worker.enqueueTask(task);
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::cacheTile(UrlFactory::MapType type, int x, int y, int z, const QByteArray& image, const QString &format, qulonglong set)
{
QString hash = getTileHash(type, x, y, z);
cacheTile(type, hash, image, format, set);
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::cacheTile(UrlFactory::MapType type, const QString& hash, const QByteArray& image, const QString& format, qulonglong set)
{
AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings();
//-- If we are allowed to persist data, save tile to cache
if(!appSettings->disableAllPersistence()->rawValue().toBool()) {
QGCSaveTileTask* task = new QGCSaveTileTask(new QGCCacheTile(hash, image, format, type, set));
_worker.enqueueTask(task);
}
}
//-----------------------------------------------------------------------------
QString
QGCMapEngine::getTileHash(UrlFactory::MapType type, int x, int y, int z)
{
return QString().sprintf("%04d%08d%08d%03d", static_cast<int>(type), x, y, z);
}
//-----------------------------------------------------------------------------
UrlFactory::MapType
QGCMapEngine::hashToType(const QString& hash)
{
QString type = hash.mid(0,4);
return static_cast<UrlFactory::MapType>(type.toInt());
}
//-----------------------------------------------------------------------------
QGCFetchTileTask*
QGCMapEngine::createFetchTileTask(UrlFactory::MapType type, int x, int y, int z)
{
QString hash = getTileHash(type, x, y, z);
QGCFetchTileTask* task = new QGCFetchTileTask(hash);
return task;
}
//-----------------------------------------------------------------------------
QGCTileSet
QGCMapEngine::getTileCount(int zoom, double topleftLon, double topleftLat, double bottomRightLon, double bottomRightLat, UrlFactory::MapType mapType)
{
if(zoom < 1) zoom = 1;
if(zoom > MAX_MAP_ZOOM) zoom = MAX_MAP_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 = (static_cast<quint64>(set.tileX1) - static_cast<quint64>(set.tileX0) + 1) * (static_cast<quint64>(set.tileY1) - static_cast<quint64>(set.tileY0) + 1);
set.tileSize = UrlFactory::averageSizeForType(mapType) * set.tileCount;
return set;
}
//-----------------------------------------------------------------------------
int
QGCMapEngine::long2tileX(double lon, int z)
{
return static_cast<int>(floor((lon + 180.0) / 360.0 * pow(2.0, z)));
}
//-----------------------------------------------------------------------------
int
QGCMapEngine::lat2tileY(double lat, int z)
{
return static_cast<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 static_cast<int>(floor((lon + 180.0) / srtm1TileSize));
}
//-----------------------------------------------------------------------------
int
QGCMapEngine::lat2elevationTileY(double lat, int z)
{
Q_UNUSED(z);
return static_cast<int>(floor((lat + 90.0) / srtm1TileSize));
//-----------------------------------------------------------------------------
UrlFactory::MapType
QGCMapEngine::getTypeFromName(const QString& name)
{
size_t i;
for(i = 0; i < NUM_MAPS; i++) {
if(name.compare(kMapTypes[i].name, Qt::CaseInsensitive) == 0)
return kMapTypes[i].type;
}
for(i = 0; i < NUM_MAPBOXMAPS; i++) {
if(name.compare(kMapboxTypes[i].name, Qt::CaseInsensitive) == 0)
return kMapboxTypes[i].type;
for(i = 0; i < NUM_ESRIMAPS; i++) {
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;
}
//-----------------------------------------------------------------------------
QStringList
QGCMapEngine::getMapNameList()
{
QStringList mapList;
for(size_t i = 0; i < NUM_MAPS; i++) {
mapList << kMapTypes[i].name;
}
if(!qgcApp()->toolbox()->settingsManager()->appSettings()->mapboxToken()->rawValue().toString().isEmpty()) {
if(!qgcApp()->toolbox()->settingsManager()->appSettings()->esriToken()->rawValue().toString().isEmpty()) {
for(size_t i = 0; i < NUM_ESRIMAPS; i++) {
mapList << kEsriTypes[i].name;
}
}
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
return mapList;
}
//-----------------------------------------------------------------------------
quint32
QGCMapEngine::getMaxDiskCache()
{
if(!_maxDiskCache) {
QSettings settings;
_maxDiskCache = settings.value(kMaxDiskCacheKey, 1024).toUInt();
}
return _maxDiskCache;
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::setMaxDiskCache(quint32 size)
{
QSettings settings;
settings.setValue(kMaxDiskCacheKey, size);
_maxDiskCache = size;
}
//-----------------------------------------------------------------------------
quint32
QGCMapEngine::getMaxMemCache()
{
if(!_maxMemCache) {
QSettings settings;
#ifdef __mobile__
_maxMemCache = settings.value(kMaxMemCacheKey, 16).toUInt();
#else
_maxMemCache = settings.value(kMaxMemCacheKey, 128).toUInt();
#endif
}
//-- Size in MB
if(_maxMemCache > 1024)
_maxMemCache = 1024;
return _maxMemCache;
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::setMaxMemCache(quint32 size)
{
//-- Size in MB
if(size > 1024)
size = 1024;
QSettings settings;
settings.setValue(kMaxMemCacheKey, size);
_maxMemCache = size;
}
//-----------------------------------------------------------------------------
QString
QGCMapEngine::bigSizeToString(quint64 size)
{
if(size < 1024)
return kLocale.toString(size);
else if(size < 1024 * 1024)
return kLocale.toString(static_cast<double>(size) / 1024.0, 'f', 1) + "kB";
return kLocale.toString(static_cast<double>(size) / (1024.0 * 1024.0), 'f', 1) + "MB";
return kLocale.toString(static_cast<double>(size) / (1024.0 * 1024.0 * 1024.0), 'f', 1) + "GB";
return kLocale.toString(static_cast<double>(size) / (1024.0 * 1024.0 * 1024.0 * 1024), 'f', 1) + "TB";
}
//-----------------------------------------------------------------------------
QString
{
return kLocale.toString(number);
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::_updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize)
{
emit updateTotals(totaltiles, totalsize, defaulttiles, defaultsize);
quint64 maxSize = static_cast<quint64>(getMaxDiskCache()) * 1024L * 1024L;
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
if(!_prunning && defaultsize > maxSize) {
//-- Prune Disk Cache
_prunning = true;
QGCPruneCacheTask* task = new QGCPruneCacheTask(defaultsize - maxSize);
connect(task, &QGCPruneCacheTask::pruned, this, &QGCMapEngine::_pruned);
getQGCMapEngine()->addTask(task);
}
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::_pruned()
{
_prunning = false;
}
//-----------------------------------------------------------------------------
int
QGCMapEngine::concurrentDownloads(UrlFactory::MapType type)
{
switch(type) {
case UrlFactory::GoogleMap:
case UrlFactory::GoogleSatellite:
case UrlFactory::GoogleTerrain:
case UrlFactory::BingMap:
case UrlFactory::BingSatellite:
case UrlFactory::BingHybrid:
case UrlFactory::StatkartTopo:
case UrlFactory::EsriWorldStreet:
case UrlFactory::EsriWorldSatellite:
case UrlFactory::EsriTerrain:
case UrlFactory::VWorldMap:
case UrlFactory::VWorldSatellite:
case UrlFactory::VWorldStreet:
case UrlFactory::MapQuestMap:
case UrlFactory::MapQuestSat:
return 8;
default:
break;
}
return 6;
}
//-----------------------------------------------------------------------------
QGCCreateTileSetTask::~QGCCreateTileSetTask()
{
//-- If not sent out, delete it
if(!_saved && _tileSet)
delete _tileSet;
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::testInternet()
{
getQGCMapEngine()->addTask(new QGCTestInternetTask());
}
//-----------------------------------------------------------------------------
void
QGCMapEngine::_internetStatus(bool active)
{
if(_isInternetActive != active) {
_isInternetActive = active;
emit internetUpdated();
}
// Resolution math: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale