QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCMapEngineManager.cc
Go to the documentation of this file.
2
3#include <QtCore/QApplicationStatic>
4#include <QtCore/QDir>
5#include <QtCore/QDirIterator>
6#include <QtCore/QRegularExpression>
7#include <QtCore/QSettings>
8#include <QtCore/QStorageInfo>
9#include <QtCore/QTemporaryDir>
10#include <QtQml/QQmlEngine>
11
13#include "FlightMapSettings.h"
14#include "QGCCachedTileSet.h"
15#include "QGCFormat.h"
16#include "QGCCompression.h"
17#include "QGCCompressionJob.h"
18#include "QGCLoggingCategory.h"
19#include "QGCMapEngine.h"
20#include "QGCMapTasks.h"
21#include "QGCMapUrlEngine.h"
23#include "QmlObjectListModel.h"
24#include "SettingsManager.h"
25
26using namespace Qt::StringLiterals;
27
28QGC_LOGGING_CATEGORY(QGCMapEngineManagerLog, "QtLocationPlugin.QGCMapEngineManager")
29
31
33{
34 return _mapEngineManager();
35}
36
38 : QObject(parent)
39 , _tileSets(new QmlObjectListModel(this))
40{
41 qCDebug(QGCMapEngineManagerLog) << this;
42
43 (void) qmlRegisterUncreatableType<QGCMapEngineManager>("QGroundControl.QGCMapEngineManager", 1, 0, "QGCMapEngineManager", "Reference only");
44
45 (void) connect(getQGCMapEngine(), &QGCMapEngine::updateTotals, this, &QGCMapEngineManager::_updateTotals, Qt::UniqueConnection);
46}
47
49{
50 _tileSets->clear();
51
52 qCDebug(QGCMapEngineManagerLog) << this;
53}
54
55void QGCMapEngineManager::updateForCurrentView(double lon0, double lat0, double lon1, double lat1, int minZoom, int maxZoom, const QString &mapName)
56{
57 _topleftLat = lat0;
58 _topleftLon = lon0;
59 _bottomRightLat = lat1;
60 _bottomRightLon = lon1;
61 _minZoom = minZoom;
62 _maxZoom = maxZoom;
63
64 _imageSet.clear();
65 _elevationSet.clear();
66
67 for (int z = minZoom; z <= maxZoom; z++) {
68 const QGCTileSet set = UrlFactory::getTileCount(z, lon0, lat0, lon1, lat1, mapName);
69 _imageSet += set;
70 }
71
72 if (_fetchElevation) {
73 const QString elevationProviderName = SettingsManager::instance()->flightMapSettings()->elevationMapProvider()->rawValue().toString();
74 const QGCTileSet set = UrlFactory::getTileCount(1, lon0, lat0, lon1, lat1, elevationProviderName);
75 _elevationSet += set;
76 }
77
78 emit tileCountChanged();
79 emit tileSizeChanged();
80
81 qCDebug(QGCMapEngineManagerLog) << lat0 << lon0 << lat1 << lon1 << minZoom << maxZoom;
82}
83
85{
86 return QGC::numberToString(_imageSet.tileCount + _elevationSet.tileCount);
87}
88
90{
91 return QGC::bigSizeToString(_imageSet.tileSize + _elevationSet.tileSize);
92}
93
95{
96 if (_tileSets->count() > 0) {
97 _tileSets->clear();
98 emit tileSetsChanged();
99 }
100
102 (void) connect(task, &QGCFetchTileSetTask::tileSetFetched, this, &QGCMapEngineManager::_tileSetFetched);
103 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
104 if (!getQGCMapEngine()->addTask(task)) {
105 task->deleteLater();
106 }
107}
108
109void QGCMapEngineManager::_tileSetFetched(QGCCachedTileSet *tileSet)
110{
111 if (tileSet->type() == QStringLiteral("Invalid")) {
112 tileSet->setMapTypeStr(QStringLiteral("Various"));
113 }
114
115 tileSet->setManager(this);
116 _tileSets->append(tileSet);
117 emit tileSetsChanged();
118}
119
120void QGCMapEngineManager::startDownload(const QString &name, const QString &mapType)
121{
122 if (_imageSet.tileSize > 0) {
123 QGCCachedTileSet* const set = new QGCCachedTileSet(name);
124 set->setMapTypeStr(mapType);
125 set->setTopleftLat(_topleftLat);
126 set->setTopleftLon(_topleftLon);
127 set->setBottomRightLat(_bottomRightLat);
128 set->setBottomRightLon(_bottomRightLon);
129 set->setMinZoom(_minZoom);
130 set->setMaxZoom(_maxZoom);
131 set->setTotalTileSize(_imageSet.tileSize);
132 set->setTotalTileCount(static_cast<quint32>(_imageSet.tileCount));
133 set->setType(mapType);
134
136 (void) connect(task, &QGCCreateTileSetTask::tileSetSaved, this, &QGCMapEngineManager::_tileSetSaved);
137 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
138 if (!getQGCMapEngine()->addTask(task)) {
139 task->deleteLater();
140 }
141 } else {
142 qCWarning(QGCMapEngineManagerLog) << "No Tiles to save";
143 }
144
145 const int mapid = UrlFactory::getQtMapIdFromProviderType(mapType);
146 if (_fetchElevation && !UrlFactory::isElevation(mapid)) {
147 QGCCachedTileSet* const set = new QGCCachedTileSet(name + QStringLiteral(" Elevation"));
148 const QString elevationProviderName = SettingsManager::instance()->flightMapSettings()->elevationMapProvider()->rawValue().toString();
149 set->setMapTypeStr(elevationProviderName);
150 set->setTopleftLat(_topleftLat);
151 set->setTopleftLon(_topleftLon);
152 set->setBottomRightLat(_bottomRightLat);
153 set->setBottomRightLon(_bottomRightLon);
154 set->setMinZoom(1);
155 set->setMaxZoom(1);
156 set->setTotalTileSize(_elevationSet.tileSize);
157 set->setTotalTileCount(static_cast<quint32>(_elevationSet.tileCount));
158 set->setType(elevationProviderName);
159
161 (void) connect(task, &QGCCreateTileSetTask::tileSetSaved, this, &QGCMapEngineManager::_tileSetSaved);
162 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
163 if (!getQGCMapEngine()->addTask(task)) {
164 task->deleteLater();
165 }
166 } else {
167 qCWarning(QGCMapEngineManagerLog) << "No Tiles to save";
168 }
169}
170
171void QGCMapEngineManager::_tileSetSaved(QGCCachedTileSet *set)
172{
173 qCDebug(QGCMapEngineManagerLog) << "New tile set saved (" << set->name() << "). Starting download...";
174
175 _tileSets->append(set);
176 emit tileSetsChanged();
177 set->createDownloadTask();
178}
179
180void QGCMapEngineManager::saveSetting(const QString &key, const QString &value)
181{
182 QSettings settings;
183 settings.beginGroup(kQmlOfflineMapKeyName);
184 settings.setValue(key, value);
185}
186
187QString QGCMapEngineManager::loadSetting(const QString &key, const QString &defaultValue)
188{
189 QSettings settings;
190 settings.beginGroup(kQmlOfflineMapKeyName);
191 return settings.value(key, defaultValue).toString();
192}
193
194QStringList QGCMapEngineManager::mapTypeList(const QString &provider)
195{
196 QStringList mapStringList = mapList();
197 mapStringList = mapStringList.filter(QRegularExpression(provider));
198
199 static const QRegularExpression providerType = QRegularExpression(uR"(^([^\ ]*) (.*)$)"_s);
200 (void) mapStringList.replaceInStrings(providerType, "\\2");
201 (void) mapStringList.removeDuplicates();
202
203 return mapStringList;
204}
205
207{
208 qCDebug(QGCMapEngineManagerLog) << "Deleting tile set" << tileSet->name();
209
210 if (tileSet->defaultSet()) {
211 for (qsizetype i = 0; i < _tileSets->count(); i++ ) {
212 QGCCachedTileSet* const set = qobject_cast<QGCCachedTileSet*>(_tileSets->get(i));
213 if (set) {
214 set->setDeleting(true);
215 }
216 }
217
218 QGCResetTask *task = new QGCResetTask();
219 (void) connect(task, &QGCResetTask::resetCompleted, this, &QGCMapEngineManager::_resetCompleted);
220 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
221 if (!getQGCMapEngine()->addTask(task)) {
222 task->deleteLater();
223 }
224 } else {
225 tileSet->setDeleting(true);
226
227 QGCDeleteTileSetTask *task = new QGCDeleteTileSetTask(tileSet->id());
228 (void) connect(task, &QGCDeleteTileSetTask::tileSetDeleted, this, &QGCMapEngineManager::_tileSetDeleted);
229 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
230 if (!getQGCMapEngine()->addTask(task)) {
231 task->deleteLater();
232 }
233 }
234}
235
236void QGCMapEngineManager::renameTileSet(QGCCachedTileSet *tileSet, const QString &newName)
237{
238 int idx = 1;
239 QString name = newName;
240 while (findName(name)) {
241 name = QString("%1 (%2)").arg(newName).arg(idx++);
242 }
243
244 qCDebug(QGCMapEngineManagerLog) << "Renaming tile set" << tileSet->name() << "to" << name;
245 tileSet->setName(name);
246 emit tileSet->nameChanged();
247
248 QGCRenameTileSetTask *task = new QGCRenameTileSetTask(tileSet->id(), name);
249 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
250 if (!getQGCMapEngine()->addTask(task)) {
251 task->deleteLater();
252 }
253}
254
255void QGCMapEngineManager::_tileSetDeleted(quint64 setID)
256{
257 for (qsizetype i = 0; i < _tileSets->count(); i++ ) {
258 QGCCachedTileSet *set = qobject_cast<QGCCachedTileSet*>(_tileSets->get(i));
259 if (set && (set->id() == setID)) {
260 (void) _tileSets->removeAt(i);
261 delete set;
262 emit tileSetsChanged();
263 break;
264 }
265 }
266}
267
269{
270 QString task;
271 switch (type) {
273 task = QStringLiteral("Fetch Tile Set");
274 break;
276 task = QStringLiteral("Create Tile Set");
277 break;
279 task = QStringLiteral("Get Tile Download List");
280 break;
282 task = QStringLiteral("Update Tile Download Status");
283 break;
285 task = QStringLiteral("Delete Tile Set");
286 break;
288 task = QStringLiteral("Reset Tile Sets");
289 break;
291 task = QStringLiteral("Export Tile Sets");
292 break;
293 default:
294 task = QStringLiteral("Database Error");
295 break;
296 }
297
298 QString serror = QStringLiteral("Error in task: ") + task;
299 serror += QStringLiteral("\nError description:\n");
300 serror += error;
301
302 setErrorMessage(serror);
303
304 qCWarning(QGCMapEngineManagerLog) << serror;
305}
306
307void QGCMapEngineManager::_updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize)
308{
309 for (qsizetype i = 0; i < _tileSets->count(); i++) {
310 QGCCachedTileSet* const set = qobject_cast<QGCCachedTileSet*>(_tileSets->get(i));
311 if (set && set->defaultSet()) {
312 set->setSavedTileSize(totalsize);
313 set->setSavedTileCount(totaltiles);
314 set->setTotalTileCount(defaulttiles);
315 set->setTotalTileSize(defaultsize);
316 return;
317 }
318 }
319}
320
321bool QGCMapEngineManager::findName(const QString &name) const
322{
323 for (qsizetype i = 0; i < _tileSets->count(); i++) {
324 const QGCCachedTileSet* const set = qobject_cast<const QGCCachedTileSet*>(_tileSets->get(i));
325 if (set && (set->name() == name)) {
326 return true;
327 }
328 }
329
330 return false;
331}
332
334{
335 for (qsizetype i = 0; i < _tileSets->count(); i++) {
336 QGCCachedTileSet* const set = qobject_cast<QGCCachedTileSet*>(_tileSets->get(i));
337 if (set) {
338 set->setSelected(true);
339 }
340 }
341}
342
344{
345 for (qsizetype i = 0; i < _tileSets->count(); i++) {
346 QGCCachedTileSet* const set = qobject_cast<QGCCachedTileSet*>(_tileSets->get(i));
347 if (set) {
348 set->setSelected(false);
349 }
350 }
351}
352
354{
355 int count = 0;
356
357 for (qsizetype i = 0; i < _tileSets->count(); i++) {
358 const QGCCachedTileSet* const set = qobject_cast<const QGCCachedTileSet*>(_tileSets->get(i));
359 if (set && set->selected()) {
360 count++;
361 }
362 }
363
364 return count;
365}
366
367bool QGCMapEngineManager::importSets(const QString &path)
368{
370
371 if (path.isEmpty()) {
372 return false;
373 }
374
376
377 QGCImportTileTask *task = new QGCImportTileTask(path, _importReplace);
378 (void) connect(task, &QGCImportTileTask::actionCompleted, this, &QGCMapEngineManager::_actionCompleted);
379 (void) connect(task, &QGCImportTileTask::actionProgress, this, &QGCMapEngineManager::_actionProgressHandler);
380 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
381 if (!getQGCMapEngine()->addTask(task)) {
382 task->deleteLater();
383 return false;
384 }
385
386 return true;
387}
388
389bool QGCMapEngineManager::exportSets(const QString &path)
390{
392
393 if (path.isEmpty()) {
394 return false;
395 }
396
397 QList<TileSetRecord> records;
398
399 for (qsizetype i = 0; i < _tileSets->count(); i++) {
400 QGCCachedTileSet* const set = qobject_cast<QGCCachedTileSet*>(_tileSets->get(i));
401 if (set && set->selected()) {
402 TileSetRecord rec;
403 rec.setID = set->id();
404 rec.name = set->name();
405 rec.mapTypeStr = set->mapTypeStr();
406 rec.topleftLat = set->topleftLat();
407 rec.topleftLon = set->topleftLon();
408 rec.bottomRightLat = set->bottomRightLat();
409 rec.bottomRightLon = set->bottomRightLon();
410 rec.minZoom = set->minZoom();
411 rec.maxZoom = set->maxZoom();
413 rec.numTiles = set->totalTileCount();
414 rec.defaultSet = set->defaultSet();
415 rec.date = set->creationDate().toSecsSinceEpoch();
416 records.append(rec);
417 }
418 }
419
420 if (records.isEmpty()) {
421 return false;
422 }
423
425
426 QGCExportTileTask *task = new QGCExportTileTask(records, path);
427 (void) connect(task, &QGCExportTileTask::actionCompleted, this, &QGCMapEngineManager::_actionCompleted);
428 (void) connect(task, &QGCExportTileTask::actionProgress, this, &QGCMapEngineManager::_actionProgressHandler);
429 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
430 if (!getQGCMapEngine()->addTask(task)) {
431 task->deleteLater();
432 return false;
433 }
434
435 return true;
436}
437
438void QGCMapEngineManager::_actionCompleted()
439{
440 const ImportAction oldState = _importAction;
442
443 if (oldState == ImportAction::ActionImporting) {
444 loadTileSets();
445 }
446}
447
449{
450 int count = 1;
451 while (true) {
452 const QString name = QStringLiteral("Tile Set ") + QString::asprintf("%03d", count++);
453 if (!findName(name)) {
454 return name;
455 }
456 }
457}
458
460{
462}
463
465{
466 QStringList mapStringList = mapList();
467 const QStringList elevationStringList = elevationProviderList();
468 for (const QString &elevationProviderName : elevationStringList) {
469 (void) mapStringList.removeAll(elevationProviderName);
470 }
471
472 static const QRegularExpression providerType = QRegularExpression(uR"(^([^\ ]*) (.*)$)"_s);
473 (void) mapStringList.replaceInStrings(providerType, "\\1");
474 (void) mapStringList.removeDuplicates();
475
476 return mapStringList;
477}
478
483
484bool QGCMapEngineManager::importArchive(const QString &archivePath)
485{
486 if (archivePath.isEmpty()) {
487 setErrorMessage(tr("No archive path specified"));
488 return false;
489 }
490
491 if (!QFile::exists(archivePath)) {
492 setErrorMessage(tr("Archive file not found: %1").arg(archivePath));
493 return false;
494 }
495
496 if (!QGCCompression::isArchiveFile(archivePath)) {
497 setErrorMessage(tr("Not a supported archive format: %1").arg(archivePath));
498 return false;
499 }
500
501 if (_importAction == ImportAction::ActionImporting) {
502 setErrorMessage(tr("Import already in progress"));
503 return false;
504 }
505
506 const QString tempPath = QDir::temp().filePath(QStringLiteral("qgc_tiles_") + QString::number(QDateTime::currentMSecsSinceEpoch()));
507 if (!QDir().mkpath(tempPath)) {
508 setErrorMessage(tr("Could not create temporary directory"));
509 return false;
510 }
511
512 _extractionOutputDir = tempPath;
513
514 if (_extractionJob == nullptr) {
515 _extractionJob = new QGCCompressionJob(this);
516 connect(_extractionJob, &QGCCompressionJob::progressChanged,
517 this, &QGCMapEngineManager::_handleExtractionProgress);
518 connect(_extractionJob, &QGCCompressionJob::finished,
519 this, &QGCMapEngineManager::_handleExtractionFinished);
520 }
521
524
525 _extractionJob->extractArchive(archivePath, tempPath);
526 return true;
527}
528
529void QGCMapEngineManager::_handleExtractionProgress(qreal progress)
530{
531 setActionProgress(static_cast<int>(progress * 50.0));
532}
533
534void QGCMapEngineManager::_handleExtractionFinished(bool success)
535{
536 if (!success) {
537 const QString error = _extractionJob != nullptr ? _extractionJob->errorString() : tr("Extraction failed");
540 QDir(_extractionOutputDir).removeRecursively();
541 _extractionOutputDir.clear();
542 return;
543 }
544
545 QString dbPath;
546 QDirIterator it(_extractionOutputDir, {QStringLiteral("*.db"), QStringLiteral("*.sqlite")},
547 QDir::Files, QDirIterator::Subdirectories);
548 if (it.hasNext()) {
549 dbPath = it.next();
550 }
551
552 if (dbPath.isEmpty()) {
553 setErrorMessage(tr("No tile database found in archive"));
555 QDir(_extractionOutputDir).removeRecursively();
556 _extractionOutputDir.clear();
557 return;
558 }
559
560 qCDebug(QGCMapEngineManagerLog) << "Found tile database:" << dbPath;
561
562 QGCImportTileTask *task = new QGCImportTileTask(dbPath, _importReplace);
563 (void) connect(task, &QGCImportTileTask::actionCompleted, this, [this]() {
564 _actionCompleted();
565 QDir(_extractionOutputDir).removeRecursively();
566 _extractionOutputDir.clear();
567 });
568 (void) connect(task, &QGCImportTileTask::actionProgress, this, [this](int percentage) {
569 setActionProgress(50 + (percentage / 2));
570 });
571 (void) connect(task, &QGCMapTask::error, this, &QGCMapEngineManager::taskError);
572 if (!getQGCMapEngine()->addTask(task)) {
573 task->deleteLater();
574 setErrorMessage(tr("Failed to start import task"));
576 QDir(_extractionOutputDir).removeRecursively();
577 _extractionOutputDir.clear();
578 }
579}
QObject wrapper for async compression operations using QtConcurrent/QPromise.
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Q_APPLICATION_STATIC(QGCMapEngineManager, _mapEngineManager)
QGCMapEngine * getQGCMapEngine()
void setTotalTileCount(quint32 num)
bool selected() const
double bottomRightLon() const
void setName(const QString &name)
double topleftLat() const
void setTotalTileSize(quint64 size)
void setSavedTileCount(quint32 num)
void setMinZoom(int zoom)
void setSavedTileSize(quint64 size)
void setMaxZoom(int zoom)
void setTopleftLat(double lat)
const QString & type() const
double topleftLon() const
void setDeleting(bool del)
quint32 totalTileCount() const
Q_INVOKABLE void createDownloadTask()
const QString & name() const
void setBottomRightLon(double lon)
void setType(const QString &type)
const QString & mapTypeStr() const
void setBottomRightLat(double lat)
const QDateTime & creationDate() const
void setSelected(bool sel)
bool defaultSet() const
void setManager(QGCMapEngineManager *mgr)
void setMapTypeStr(const QString &typeStr)
double bottomRightLat() const
quint64 id() const
void setTopleftLon(double lon)
QObject wrapper for compression operations with progress signals.
QString errorString() const
void progressChanged(qreal progress)
Emitted when progress changes (0.0 to 1.0)
void finished(bool success)
void extractArchive(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
void tileSetSaved(QGCCachedTileSet *tileSet)
void tileSetDeleted(quint64 setID)
void actionProgress(int percentage)
void tileSetFetched(QGCCachedTileSet *tileSet)
void actionProgress(int percentage)
static QStringList mapProviderList()
Q_INVOKABLE bool findName(const QString &name) const
void taskError(QGCMapTask::TaskType type, const QString &error)
static Q_INVOKABLE QString loadSetting(const QString &key, const QString &defaultValue)
Q_INVOKABLE bool importArchive(const QString &archivePath)
Q_INVOKABLE void selectAll()
void setActionProgress(int percentage)
QString tileCountStr() const
static QStringList elevationProviderList()
static Q_INVOKABLE void saveSetting(const QString &key, const QString &value)
static QStringList mapList()
static Q_INVOKABLE QStringList mapTypeList(const QString &provider)
QGCMapEngineManager(QObject *parent=nullptr)
Q_INVOKABLE void startDownload(const QString &name, const QString &mapType)
QString tileSizeStr() const
Q_INVOKABLE void renameTileSet(QGCCachedTileSet *tileSet, const QString &newName)
Q_INVOKABLE void deleteTileSet(QGCCachedTileSet *tileSet)
Q_INVOKABLE void selectNone()
Q_INVOKABLE bool importSets(const QString &path=QString())
Q_INVOKABLE bool exportSets(const QString &path=QString())
Q_INVOKABLE QString getUniqueName() const
Q_INVOKABLE void loadTileSets()
void setImportAction(ImportAction action)
void setErrorMessage(const QString &error)
Q_INVOKABLE void updateForCurrentView(double lon0, double lat0, double lon1, double lat1, int minZoom, int maxZoom, const QString &mapName)
bool addTask(QGCMapTask *task)
void updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize)
void error(QGCMapTask::TaskType type, const QString &errorString)
void resetCompleted()
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
Q_INVOKABLE QObject * get(int index)
QObject * removeAt(int index)
int count() const override final
void clear() override final
FlightMapSettings * flightMapSettings() const
static SettingsManager * instance()
static QStringList getProviderTypes()
static QGCTileSet getTileCount(int zoom, double topleftLon, double topleftLat, double bottomRightLon, double bottomRightLat, QStringView mapType)
static bool isElevation(int qtMapId)
static QStringList getElevationProviderTypes()
static int getQtMapIdFromProviderType(QStringView type)
bool isArchiveFile(const QString &filePath)
Check if file path indicates an archive file (.zip, .tar, .tar.gz, etc.)
QString numberToString(quint64 number)
Decimal integer (e.g. "1,234,567").
Definition QGCFormat.cc:15
QString bigSizeToString(quint64 size)
Byte size with unit: B, KB, MB, GB, TB. 1 fractional digit above 1 KB.
Definition QGCFormat.cc:20
void clear()
Definition QGCTileSet.h:15
quint64 tileCount
Definition QGCTileSet.h:29
quint64 tileSize
Definition QGCTileSet.h:30