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
6import Qt.labs.animation
7
8import QGroundControl
9import QGroundControl.Controls
10import QGroundControl.FlightMap
11
12Map {
13 id: _map
14
15 plugin: Plugin { name: "QGroundControl" }
16 opacity: 0.99 // https://bugreports.qt.io/browse/QTBUG-82185
17
18 property string mapName: 'defaultMap'
19 property bool isSatelliteMap: activeMapType.name.indexOf("Satellite") > -1 || activeMapType.name.indexOf("Hybrid") > -1
20 property var gcsPosition: QGroundControl.qgcPositionManger.gcsPosition
21 property real gcsHeading: QGroundControl.qgcPositionManger.gcsHeading
22 property bool allowGCSLocationCenter: false ///< true: map will center/zoom to gcs location one time
23 property bool allowVehicleLocationCenter: false ///< true: map will center/zoom to vehicle location one time
24 property bool firstGCSPositionReceived: false ///< true: first gcs position update was responded to
25 property bool firstVehiclePositionReceived: false ///< true: first vehicle position update was responded to
26 property bool planView: false ///< true: map being using for Plan view, items should be draggable
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 _possiblyCenterToVehiclePosition() {
44 if (!firstVehiclePositionReceived && allowVehicleLocationCenter && _activeVehicleCoordinate.isValid) {
45 firstVehiclePositionReceived = true
46 center = _activeVehicleCoordinate
47 zoomLevel = QGroundControl.flightMapInitialZoom
48 }
49 }
50
51 function centerToSpecifiedLocation() {
52 specifyMapPositionDialogFactory.open()
53 }
54
55 QGCPopupDialogFactory {
56 id: specifyMapPositionDialogFactory
57
58 dialogComponent: specifyMapPositionDialog
59 }
60
61 Component {
62 id: specifyMapPositionDialog
63 EditPositionDialog {
64 title: qsTr("Specify Position")
65 coordinate: center
66 onCoordinateChanged: center = coordinate
67 }
68 }
69
70 // Center map to gcs location
71 onGcsPositionChanged: {
72 if (gcsPosition.isValid && allowGCSLocationCenter && !firstGCSPositionReceived && !firstVehiclePositionReceived) {
73 firstGCSPositionReceived = true
74 //-- Only center on gsc if we have no vehicle (and we are supposed to do so)
75 var _activeVehicleCoordinate = _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
76 if(QGroundControl.settingsManager.flyViewSettings.keepMapCenteredOnVehicle.rawValue || !_activeVehicleCoordinate.isValid)
77 center = gcsPosition
78 }
79 }
80
81 function updateActiveMapType() {
82 var settings = QGroundControl.settingsManager.flightMapSettings
83 var fullMapName = settings.mapProvider.value + " " + settings.mapType.value
84
85 for (var i = 0; i < _map.supportedMapTypes.length; i++) {
86 if (fullMapName === _map.supportedMapTypes[i].name) {
87 _map.activeMapType = _map.supportedMapTypes[i]
88 return
89 }
90 }
91 }
92
93 on_ActiveVehicleCoordinateChanged: _possiblyCenterToVehiclePosition()
94
95 onMapReadyChanged: {
96 if (_map.mapReady) {
97 updateActiveMapType()
98 _possiblyCenterToVehiclePosition()
99 }
100 }
101
102 Connections {
103 target: QGroundControl.settingsManager.flightMapSettings.mapType
104 function onRawValueChanged() { updateActiveMapType() }
105 }
106
107 Connections {
108 target: QGroundControl.settingsManager.flightMapSettings.mapProvider
109 function onRawValueChanged() { updateActiveMapType() }
110 }
111
112 signal mapPanStart
113 signal mapPanStop
114 signal mapClicked(var position)
115 signal mapRightClicked(var position)
116 signal mapPressAndHold(var position)
117
118 PinchHandler {
119 id: pinchHandler
120 target: null
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