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