QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FlightMap.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtLocation
4import QtPositioning
5import QtQuick.Dialogs
6
7import QGroundControl
8import QGroundControl.Controls
9import QGroundControl.FlightMap
10
11Map {
12 id: _map
13
14 plugin: Plugin { name: "QGroundControl" }
15 opacity: 0.99 // https://bugreports.qt.io/browse/QTBUG-82185
16
17 property string mapName: 'defaultMap'
18 property bool isSatelliteMap: activeMapType.name.indexOf("Satellite") > -1 || activeMapType.name.indexOf("Hybrid") > -1
19 property var gcsPosition: QGroundControl.qgcPositionManger.gcsPosition
20 property real gcsHeading: QGroundControl.qgcPositionManger.gcsHeading
21 property alias allowGCSLocationCenter: _positionTracker.allowGCSLocationCenter ///< true: map will center to gcs location one time
22 property alias allowVehicleLocationCenter: _positionTracker.allowVehicleLocationCenter ///< true: map will center/zoom to vehicle location one time
23 property alias firstVehiclePositionReceived: _positionTracker.firstVehiclePositionReceived ///< true: first vehicle position update was responded to
24 property alias positionTracker: _positionTracker ///< Auto-centering policy, for derived maps to layer follow behavior on
25 property bool planView: false ///< true: map being using for Plan view, items should be draggable
26 property bool pinchZoomDisabledByVirtualJoysticks: false ///< true: disable pinch-to-zoom while virtual joystick thumbs are down
27
28 property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
29 property var _activeVehicleCoordinate: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
30
31 function setVisibleRegion(region) {
32 // This works around a bug on Qt where if you set a visibleRegion and then the user moves or zooms the map
33 // and then you set the same visibleRegion the map will not move/scale appropriately since it thinks there
34 // is nothing to do.
35 let maxZoomLevel = 20
36 _map.visibleRegion = QtPositioning.rectangle(QtPositioning.coordinate(0, 0), QtPositioning.coordinate(0, 0))
37 _map.visibleRegion = region
38 if (_map.zoomLevel > maxZoomLevel) {
39 _map.zoomLevel = maxZoomLevel
40 }
41 }
42
43 function centerToSpecifiedLocation() {
44 specifyMapPositionDialogFactory.open()
45 }
46
47 MapPositionTracker {
48 id: _positionTracker
49
50 active: _map.mapReady
51 gcsPosition: _map.gcsPosition
52 vehicleCoordinate: _map._activeVehicleCoordinate
53 centerGCSWhenVehicleValid: QGroundControl.settingsManager.flyViewSettings.keepMapCenteredOnVehicle.rawValue
54
55 onCenterMap: (coordinate, firstPosition) => {
56 _map.center = coordinate
57 if (firstPosition) {
58 _map.zoomLevel = QGroundControl.flightMapInitialZoom
59 }
60 }
61 }
62
63 QGCPopupDialogFactory {
64 id: specifyMapPositionDialogFactory
65
66 dialogComponent: specifyMapPositionDialog
67 }
68
69 Component {
70 id: specifyMapPositionDialog
71 EditPositionDialog {
72 title: qsTr("Specify Position")
73 coordinate: center
74 onCoordinateChanged: center = coordinate
75 }
76 }
77
78 function updateActiveMapType() {
79 var settings = QGroundControl.settingsManager.flightMapSettings
80 var fullMapName = settings.mapProvider.value + " " + settings.mapType.value
81
82 for (var i = 0; i < _map.supportedMapTypes.length; i++) {
83 if (fullMapName === _map.supportedMapTypes[i].name) {
84 _map.activeMapType = _map.supportedMapTypes[i]
85 return
86 }
87 }
88 }
89
90 onMapReadyChanged: {
91 if (_map.mapReady) {
92 updateActiveMapType()
93 }
94 }
95
96 Connections {
97 target: QGroundControl.settingsManager.flightMapSettings.mapType
98 function onRawValueChanged() { updateActiveMapType() }
99 }
100
101 Connections {
102 target: QGroundControl.settingsManager.flightMapSettings.mapProvider
103 function onRawValueChanged() { updateActiveMapType() }
104 }
105
106 signal mapPanStart
107 signal mapPanStop
108 signal mapClicked(var position)
109 signal mapRightClicked(var position)
110 signal mapPressAndHold(var position)
111
112 PinchHandler {
113 id: pinchHandler
114 target: null
115 // Disabled while virtual joystick thumbs are down. The default grabPermissions include
116 // CanTakeOverFromItems, which lets this handler steal touch grabs from the joystick pads'
117 // MultiPointTouchAreas and zoom the map instead. The permission can't be removed since
118 // pinch on the map itself must take over from the panning MultiPointTouchArea below.
119 // See GitHub issue #13450.
120 enabled: !_map.pinchZoomDisabledByVirtualJoysticks
121
122 property var pinchStartGeoCoord // geo coordinate under centroid at pinch start
123 property var pinchStartScreenPoint // screen point of centroid at pinch start
124
125 onActiveChanged: {
126 if (active) {
127 // Capture both the screen point and its geo coordinate once at pinch start.
128 // alignCoordinateToPoint requires a fixed screen anchor; using the live
129 // centroid.position causes the map to pan as fingers drift.
130 pinchStartScreenPoint = pinchHandler.centroid.position
131 pinchStartGeoCoord = _map.toCoordinate(pinchStartScreenPoint, false)
132 }
133 }
134 onScaleChanged: (delta) => {
135 _map.zoomLevel = Math.max(_map.zoomLevel + Math.log2(delta), 0)
136 _map.alignCoordinateToPoint(pinchStartGeoCoord, pinchStartScreenPoint)
137 }
138 }
139
140 WheelHandler {
141 // WheelHandler's default acceptedDevices=Mouse silently drops trackpad scroll events on
142 // multiple platforms:
143 // - Linux/Wayland (QTBUG-112394 / QTBUG-112432): the Wayland
144 // protocol exposes no way to distinguish a mouse from a trackpad, so Qt registers all
145 // pointer devices as TouchPad.
146 // - xcb / XWayland: Wayland pointer events are translated back to X11 and device-type
147 // metadata is lost — physical mouse scroll events arrive as PointerDevice.TouchPad.
148 // - macOS (cocoa): trackpad scroll events are correctly reported as PointerDevice.TouchPad
149 // but are excluded by the Mouse-only default.
150 // Accepting both Mouse and TouchPad on all platforms is harmless and covers every case.
151 acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
152 rotationScale: 1 / 120
153
154 onWheel: (event) => {
155 const zoomDelta = event.angleDelta.y * rotationScale
156 const mouseGeoPos = _map.toCoordinate(Qt.point(event.x, event.y), false)
157 _map.zoomLevel = Math.max(_map.zoomLevel + zoomDelta, 0)
158 _map.alignCoordinateToPoint(mouseGeoPos, Qt.point(event.x, event.y))
159 }
160 }
161
162 // We specifically do not use a DragHandler for panning. It just causes too many problems if you overlay anything else like a Flickable above it.
163 // Causes all sorts of crazy problems where dragging/scrolling no longerr works on items above in the hierarchy.
164 // Since we are using a MouseArea we also can't use TapHandler for clicks. So we handle that here as well.
165 MultiPointTouchArea {
166 id: multiTouchArea
167 anchors.fill: parent
168 maximumTouchPoints: 1
169 mouseEnabled: true
170
171 property bool dragActive: false
172 property real lastMouseX
173 property real lastMouseY
174 property bool isPressed: false
175 property bool pressAndHold: false
176
177 onPressed: (touchPoints) => {
178 lastMouseX = touchPoints[0].x
179 lastMouseY = touchPoints[0].y
180 isPressed = true
181 pressAndHold = false
182 pressAndHoldTimer.start()
183 }
184
185 onGestureStarted: (gesture) => {
186 dragActive = true
187 gesture.grab()
188 mapPanStart()
189 }
190
191 onUpdated: (touchPoints) => {
192 if (dragActive) {
193 let deltaX = touchPoints[0].x - lastMouseX
194 let deltaY = touchPoints[0].y - lastMouseY
195 if (Math.abs(deltaX) >= 1.0 || Math.abs(deltaY) >= 1.0) {
196 _map.pan(lastMouseX - touchPoints[0].x, lastMouseY - touchPoints[0].y)
197 lastMouseX = touchPoints[0].x
198 lastMouseY = touchPoints[0].y
199 }
200 }
201 }
202
203 onReleased: (touchPoints) => {
204 isPressed = false
205 pressAndHoldTimer.stop()
206 if (dragActive) {
207 _map.pan(lastMouseX - touchPoints[0].x, lastMouseY - touchPoints[0].y)
208 dragActive = false
209 mapPanStop()
210 } else if (!pressAndHold) {
211 mapClicked(Qt.point(touchPoints[0].x, touchPoints[0].y))
212 }
213 pressAndHold = false
214 }
215
216 Timer {
217 id: pressAndHoldTimer
218 interval: 600 // hold duration in ms
219 repeat: false
220
221 onTriggered: {
222 if (multiTouchArea.isPressed && !multiTouchArea.dragActive) {
223 multiTouchArea.pressAndHold = true
224 mapPressAndHold(Qt.point(multiTouchArea.lastMouseX, multiTouchArea.lastMouseY))
225 }
226 }
227 }
228 }
229
230 MouseArea {
231 anchors.fill: parent
232 acceptedButtons: Qt.RightButton
233 propagateComposedEvents: true
234
235 onPressed: (mouseEvent) => {
236 if (mouseEvent.button === Qt.RightButton) {
237 mapRightClicked(Qt.point(mouseEvent.x, mouseEvent.y))
238 }
239 }
240 }
241
242 /// Ground Station location
243 MapQuickItem {
244 anchorPoint.x: sourceItem.width / 2
245 anchorPoint.y: sourceItem.height / 2
246 visible: gcsPosition.isValid && !planView
247 coordinate: gcsPosition
248
249 sourceItem: Image {
250 id: mapItemImage
251 source: isNaN(gcsHeading) ? "/res/QGCLogoFull.svg" : "/res/QGCLogoArrow.svg"
252 mipmap: true
253 antialiasing: true
254 fillMode: Image.PreserveAspectFit
255 height: ScreenTools.defaultFontPixelHeight * (isNaN(gcsHeading) ? 1.75 : 2.5 )
256 sourceSize.height: height
257 transform: Rotation {
258 origin.x: mapItemImage.width / 2
259 origin.y: mapItemImage.height / 2
260 angle: isNaN(gcsHeading) ? 0 : gcsHeading
261 }
262 }
263 }
264} // Map