Skip to content

Commit e26d490

Browse files
committed
QtLocationPlugin: Qt Integration Improvements & Fixes
1 parent f7cc8de commit e26d490

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4191
-559
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Support for setting individual MAVLink message rates in the Inspector.
1212
- Enabled MAVLink 2 signing.
1313
- **Battery Display**: Dynamic bars with configurable thresholds (100%, Config 1, Config 2, Low, Critical).
14+
- **Offline Maps**: Added pause/resume/retry controls, live download metrics, a Default Cache toggle, and documented cache schema version 2 (existing caches upgrade automatically).
1415

1516
---
1617

docs/en/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
- [MAVLink Logs](qgc-dev-guide/file_formats/mavlink.md)
102102
- [Developer Tools](qgc-dev-guide/tools/index.md)
103103
- [Mock Link](qgc-dev-guide/tools/mock_link.md)
104+
- [Offline Map Cache](qgc-dev-guide/tools/map_cache.md)
104105
- [Command Line Options](qgc-dev-guide/command_line_options.md)
105106
- [Custom Builds](qgc-dev-guide/custom_build/custom_build.md)
106107
- [Initial Repository Setup For Custom Build](qgc-dev-guide/custom_build/fork_repo.md)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Offline map cache internals
2+
3+
QGroundControl stores downloaded map tiles in an SQLite database located under the platform cache directory (for example `~/.cache/QGroundControl/QGCMapCache` on Linux). The file is named `qgcMapCache.db` and is created automatically the first time the QtLocation plugin runs.
4+
5+
## Schema version
6+
7+
The cache schema is versioned through `PRAGMA user_version`. QGroundControl v4.4 introduced schema version **2** (`kCurrentSchemaVersion` in `QGCTileCacheWorker`). On startup we verify the on-disk version and automatically rebuild the cache if it is missing or outdated. If you change the schema, update `kCurrentSchemaVersion`, describe the migration path in this document, and add a release note so that integrators know the cache will be rebuilt.
8+
9+
## Migration behaviour
10+
11+
* If the schema is missing or corrupted, `QGCTileCacheWorker::_verifyDatabaseVersion()` deletes the database and creates a fresh one.
12+
* When importing a cache file, `_checkDatabaseVersion()` ensures the version matches `kCurrentSchemaVersion` and surfaces a user-facing error if it does not.
13+
* Default tile sets are recreated on demand and always use map ID `UrlFactory::defaultSetMapId()`.
14+
15+
## Disabling caching
16+
17+
Two separate switches control caching:
18+
19+
* `setCachingPaused(true/false)` is used internally to stop saving tiles while maintenance tasks such as deletes and resets are in progress. Downloads are paused and resumed automatically.
20+
* `MapsSettings.disableDefaultCache` exposes a user-facing “Default Cache” toggle. When disabled, tiles fetched during normal browsing are not persisted, but manually created offline sets continue to work.
21+
22+
When adding new features that manipulate the cache, prefer to go through `QGCMapEngineManager` so that pause/resume, download bookkeeping, and schema checks remain in sync.

docs/en/qgc-user-guide/settings_view/offline_maps.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ To create a new offline map set, click "Add new set". Which will take you to thi
1212
From here you can name your set as well as specify the zoom levels you want to cache. Move the map to the position you want to cache and then set the zoom levels and click Download to cache the tiles.
1313

1414
To the left you can see previews of the min and max zoom levels you have chosen.
15+
16+
## Managing downloads
17+
18+
Each offline tile set shows live download statistics (pending, active, and error tiles) so you can see whether work is still in progress. You can pause an in-flight download, resume it later, or retry only the tiles that previously failed. Pausing keeps your place in the queue, which is especially useful when you need to temporarily disable connectivity or suspend caching from the main Map Settings page.
19+
20+
## Default cache toggle
21+
22+
The **Default Cache** switch near the top of the Offline Maps page controls whether QGroundControl stores tiles that are fetched during normal map browsing. Leave it enabled if you rely on the automatic cache for day-to-day flying, or disable it to save disk space and rely exclusively on the offline tile sets you create manually. The toggle simply affects the default/system cache—the user-defined offline sets continue to work normally.

src/QmlControls/OfflineMapEditor.qml

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import QtQuick
1111
import QtQuick.Controls
1212
import QtQuick.Layouts
1313
import QtQuick.Dialogs
14-
import QtQuick.Controls
1514
import QtLocation
1615
import QtPositioning
1716

@@ -35,8 +34,9 @@ FlightMap {
3534

3635
property var _settingsManager: QGroundControl.settingsManager
3736
property var _settings: _settingsManager ? _settingsManager.offlineMapsSettings : null
37+
property var _mapsSettings: _settingsManager ? _settingsManager.mapsSettings : null
3838
property var _fmSettings: _settingsManager ? _settingsManager.flightMapSettings : null
39-
property var _appSettings: _settingsManager.appSettings
39+
property var _appSettings: _settingsManager ? _settingsManager.appSettings : null
4040
property Fact _tiandituFact: _settingsManager ? _settingsManager.appSettings.tiandituToken : null
4141
property Fact _mapboxFact: _settingsManager ? _settingsManager.appSettings.mapboxToken : null
4242
property Fact _mapboxAccountFact: _settingsManager ? _settingsManager.appSettings.mapboxAccount : null
@@ -72,18 +72,26 @@ FlightMap {
7272
QGCPalette { id: qgcPal }
7373

7474
Component.onCompleted: {
75-
QGroundControl.mapEngineManager.loadTileSets()
75+
if (QGroundControl.mapEngineManager.tileSets.count === 0) {
76+
QGroundControl.mapEngineManager.loadTileSets()
77+
}
7678
resetMapToDefaults()
7779
updateMap()
7880
savedCenter = _map.toCoordinate(Qt.point(_map.width / 2, _map.height / 2), false /* clipToViewPort */)
7981
settingsPage.enabled = false // Prevent mouse events from bleeding through to the settings page which is below this in hierarchy
8082
}
8183

82-
Component.onDestruction: settingsPage.enabled = true
84+
Component.onDestruction: {
85+
settingsPage.enabled = true
86+
}
8387

8488
Connections {
8589
target: QGroundControl.mapEngineManager
86-
onErrorMessageChanged: errorDialogComponent.createObject(mainWindow).open()
90+
function onErrorMessageChanged() {
91+
var dialog = errorDialogComponent.createObject(mainWindow)
92+
dialog.closed.connect(function() { dialog.destroy() })
93+
dialog.open()
94+
}
8795
}
8896

8997
function handleChanges() {
@@ -212,12 +220,7 @@ FlightMap {
212220
color: Qt.rgba(qgcPal.window.r, qgcPal.window.g, qgcPal.window.b, 0.85)
213221
radius: ScreenTools.defaultFontPixelWidth * 0.5
214222

215-
property bool _extraButton: {
216-
if(!tileSet)
217-
return false;
218-
var curSel = tileSet;
219-
return !_defaultSet && ((!curSel.complete && !curSel.downloading) || (!curSel.complete && curSel.downloading));
220-
}
223+
property bool _extraButton: tileSet && !_defaultSet && !tileSet.complete
221224

222225
property real _labelWidth: ScreenTools.defaultFontPixelWidth * 10
223226
property real _valueWidth: ScreenTools.defaultFontPixelWidth * 14
@@ -309,36 +312,61 @@ FlightMap {
309312
QGCLabel { text: qsTr("Tile Count:"); width: infoView._labelWidth; }
310313
QGCLabel { text: tileSet ? tileSet.savedTileCountStr : ""; horizontalAlignment: Text.AlignRight; width: infoView._valueWidth; }
311314
}
315+
Row {
316+
spacing: ScreenTools.defaultFontPixelWidth
317+
anchors.horizontalCenter: parent.horizontalCenter
318+
visible: tileSet && !_defaultSet
319+
QGCLabel { text: qsTr("Queue:"); width: infoView._labelWidth; }
320+
QGCLabel {
321+
width: ScreenTools.defaultFontPixelWidth * 24
322+
horizontalAlignment: Text.AlignRight
323+
wrapMode: Text.NoWrap
324+
elide: Text.ElideNone
325+
text: tileSet ? qsTr("%1 pending / %2 active / %3 error")
326+
.arg(tileSet.pendingTiles)
327+
.arg(tileSet.downloadingTiles)
328+
.arg(tileSet.errorTiles) : ""
329+
}
330+
}
312331
Row {
313332
spacing: ScreenTools.defaultFontPixelWidth
314333
anchors.horizontalCenter: parent.horizontalCenter
315334
QGCButton {
316335
text: qsTr("Resume Download")
317-
visible: tileSet && tileSet && !_defaultSet && (!tileSet.complete && !tileSet.downloading)
318-
width: ScreenTools.defaultFontPixelWidth * 16
336+
visible: tileSet && !_defaultSet && (!tileSet.complete && !tileSet.downloading)
337+
width: ScreenTools.defaultFontPixelWidth * 18
319338
onClicked: {
320339
if(tileSet)
321340
tileSet.resumeDownloadTask()
322341
}
323342
}
324343
QGCButton {
325-
text: qsTr("Cancel Download")
326-
visible: tileSet && tileSet && !_defaultSet && (!tileSet.complete && tileSet.downloading)
327-
width: ScreenTools.defaultFontPixelWidth * 16
344+
text: qsTr("Pause Download")
345+
visible: tileSet && !_defaultSet && (!tileSet.complete && tileSet.downloading)
346+
width: ScreenTools.defaultFontPixelWidth * 18
347+
onClicked: {
348+
if(tileSet)
349+
tileSet.pauseDownloadTask()
350+
}
351+
}
352+
QGCButton {
353+
text: qsTr("Retry Failed (%1)").arg(tileSet ? tileSet.errorTiles : 0)
354+
visible: tileSet && !_defaultSet && (tileSet.errorTiles > 0)
355+
width: ScreenTools.defaultFontPixelWidth * 18
328356
onClicked: {
329357
if(tileSet)
330-
tileSet.cancelDownloadTask()
358+
tileSet.retryFailedTiles()
331359
}
332360
}
333361
QGCButton {
334362
text: qsTr("Delete")
335-
width: ScreenTools.defaultFontPixelWidth * (infoView._extraButton ? 6 : 10)
363+
width: ScreenTools.defaultFontPixelWidth * (infoView._extraButton ? 8 : 10)
336364
onClicked: deleteConfirmationDialogComponent.createObject(mainWindow).open()
337-
enabled: tileSet ? (tileSet.savedTileSize > 0) : false
365+
enabled: tileSet ? (!tileSet.deleting && (_defaultSet ? tileSet.savedTileSize > 0 : true)) : false
338366
}
339367
QGCButton {
340368
text: qsTr("Ok")
341-
width: ScreenTools.defaultFontPixelWidth * (infoView._extraButton ? 6 : 10)
369+
width: ScreenTools.defaultFontPixelWidth * (infoView._extraButton ? 8 : 10)
342370
visible: !_defaultSet
343371
enabled: editSetName.text !== ""
344372
onClicked: {
@@ -350,7 +378,7 @@ FlightMap {
350378
}
351379
QGCButton {
352380
text: _defaultSet ? qsTr("Close") : qsTr("Cancel")
353-
width: ScreenTools.defaultFontPixelWidth * (infoView._extraButton ? 6 : 10)
381+
width: ScreenTools.defaultFontPixelWidth * (infoView._extraButton ? 8 : 10)
354382
onClicked: _map.destroy()
355383
}
356384
}
@@ -691,10 +719,13 @@ FlightMap {
691719
} // Rectangle - Zoom info
692720

693721
QGCLabel {
694-
text: qsTr("Too many tiles")
722+
text: qsTr("Too many tiles: %1 exceeds limit of %2").arg(QGroundControl.mapEngineManager.tileCountStr).arg(_settings ? _settings.maxTilesForDownload.valueString : "")
695723
visible: _tooManyTiles
696724
color: qgcPal.warningText
697-
anchors.horizontalCenter: parent.horizontalCenter
725+
wrapMode: Text.WordWrap
726+
anchors.left: parent.left
727+
anchors.right: parent.right
728+
horizontalAlignment: Text.AlignHCenter
698729
}
699730

700731
Row {
@@ -765,4 +796,3 @@ FlightMap {
765796
}
766797
}
767798
}
768-

src/QmlControls/OfflineMapInfo.qml

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,49 @@ RowLayout {
2424

2525
signal clicked
2626

27-
property int _tileCount: tileSet.totalTileCount
27+
property int _tileCount: tileSet ? tileSet.totalTileCount : 0
28+
29+
QGCPalette { id: qgcPal }
2830

2931
QGCLabel {
3032
Layout.fillWidth: true
31-
text: tileSet.name
33+
text: tileSet ? (tileSet.defaultSet ? qsTr("%1 (System Cache)").arg(tileSet.name) : tileSet.name) : ""
34+
font.italic: tileSet && tileSet.defaultSet
3235
}
3336

3437
QGCLabel {
3538
id: sizeLabel
36-
text: tileSet.downloadStatus + (_tileCount > 0 ? " (" + _tileCount + " tiles)" : "")
39+
text: _computeDisplayText()
40+
function _computeDisplayText() {
41+
if (!tileSet) return ""
42+
if (tileSet.defaultSet) {
43+
return tileSet.savedTileSizeStr + " (" + tileSet.savedTileCount + " tiles)"
44+
}
45+
var result = tileSet.downloadStatus
46+
if (_tileCount > 0) result += " (" + _tileCount + " tiles)"
47+
return result + _queueSuffix()
48+
}
49+
function _queueSuffix() {
50+
if (!tileSet || tileSet.defaultSet) {
51+
return ""
52+
}
53+
var parts = []
54+
if (tileSet.pendingTiles > 0)
55+
parts.push(qsTr("%1 pending").arg(tileSet.pendingTiles))
56+
if (tileSet.downloadingTiles > 0)
57+
parts.push(qsTr("%1 active").arg(tileSet.downloadingTiles))
58+
if (tileSet.errorTiles > 0)
59+
parts.push(qsTr("%1 error").arg(tileSet.errorTiles))
60+
return parts.length ? " [" + parts.join(", ") + "]" : ""
61+
}
3762
}
3863

3964
Rectangle {
4065
width: sizeLabel.height * 0.5
4166
height: sizeLabel.height * 0.5
4267
radius: width / 2
43-
color: tileSet.complete ? "#31f55b" : "#fc5656"
44-
opacity: sizeLabel.text.length > 0 ? 1 : 0
68+
color: tileSet && tileSet.defaultSet ? qgcPal.text : (tileSet && tileSet.complete ? qgcPal.colorGreen : qgcPal.colorRed)
69+
opacity: sizeLabel.text.length > 0 ? (tileSet && tileSet.defaultSet ? 0.4 : 1) : 0
4570
}
4671

4772
QGCButton {

src/QtLocationPlugin/CMakeLists.txt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ qt_add_plugin(QGCLocation
5454
# ----------------------------------------------------------------------------
5555
# Platform-Specific Configuration
5656
# ----------------------------------------------------------------------------
57-
# Google Maps not available on iOS
57+
# iOS: Disable Google Maps due to App Store licensing restrictions
58+
# Google Maps requires additional licensing agreements for iOS distribution
59+
# through the App Store that differ from other platforms
5860
if(IOS)
5961
target_compile_definitions(QGCLocation PRIVATE QGC_NO_GOOGLE_MAPS)
6062
endif()
@@ -64,18 +66,19 @@ endif()
6466
# ----------------------------------------------------------------------------
6567
target_link_libraries(QGCLocation
6668
PRIVATE
67-
Qt6::Positioning
68-
Qt6::Sql
69-
PUBLIC
7069
Qt6::Core
7170
Qt6::Location
7271
Qt6::LocationPrivate
7372
Qt6::Network
73+
Qt6::Positioning
74+
Qt6::Sql
7475
)
7576

7677
target_include_directories(QGCLocation
7778
PUBLIC
78-
${CMAKE_CURRENT_SOURCE_DIR}
79+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
80+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Providers>
81+
PRIVATE
7982
${CMAKE_CURRENT_SOURCE_DIR}/..
8083
${CMAKE_CURRENT_SOURCE_DIR}/../FactSystem
8184
${CMAKE_CURRENT_SOURCE_DIR}/../QmlControls

0 commit comments

Comments
 (0)