6import Qt.labs.animation
9import QGroundControl.Controls
10import QGroundControl.FlightMap
15 plugin: Plugin { name: "QGroundControl" }
16 opacity: 0.99 // https://bugreports.qt.io/browse/QTBUG-82185
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
28 property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
29 property var _activeVehicleCoordinate: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
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
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
43 function _possiblyCenterToVehiclePosition() {
44 if (!firstVehiclePositionReceived && allowVehicleLocationCenter && _activeVehicleCoordinate.isValid) {
45 firstVehiclePositionReceived = true
46 center = _activeVehicleCoordinate
47 zoomLevel = QGroundControl.flightMapInitialZoom
51 function centerToSpecifiedLocation() {
52 specifyMapPositionDialogFactory.open()
55 QGCPopupDialogFactory {
56 id: specifyMapPositionDialogFactory
58 dialogComponent: specifyMapPositionDialog
62 id: specifyMapPositionDialog
64 title: qsTr("Specify Position")
66 onCoordinateChanged: center = coordinate
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)
81 function updateActiveMapType() {
82 var settings = QGroundControl.settingsManager.flightMapSettings
83 var fullMapName = settings.mapProvider.value + " " + settings.mapType.value
85 for (var i = 0; i < _map.supportedMapTypes.length; i++) {
86 if (fullMapName === _map.supportedMapTypes[i].name) {
87 _map.activeMapType = _map.supportedMapTypes[i]
93 on_ActiveVehicleCoordinateChanged: _possiblyCenterToVehiclePosition()
98 _possiblyCenterToVehiclePosition()
103 target: QGroundControl.settingsManager.flightMapSettings.mapType
104 function onRawValueChanged() { updateActiveMapType() }
108 target: QGroundControl.settingsManager.flightMapSettings.mapProvider
109 function onRawValueChanged() { updateActiveMapType() }
114 signal mapClicked(var position)
115 signal mapRightClicked(var position)
116 signal mapPressAndHold(var position)
122 property var pinchStartGeoCoord // geo coordinate under centroid at pinch start
123 property var pinchStartScreenPoint // screen point of centroid at pinch start
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)
134 onScaleChanged: (delta) => {
135 _map.zoomLevel = Math.max(_map.zoomLevel + Math.log2(delta), 0)
136 _map.alignCoordinateToPoint(pinchStartGeoCoord, pinchStartScreenPoint)
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
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))
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 {
168 maximumTouchPoints: 1
171 property bool dragActive: false
172 property real lastMouseX
173 property real lastMouseY
174 property bool isPressed: false
175 property bool pressAndHold: false
177 onPressed: (touchPoints) => {
178 lastMouseX = touchPoints[0].x
179 lastMouseY = touchPoints[0].y
182 pressAndHoldTimer.start()
185 onGestureStarted: (gesture) => {
191 onUpdated: (touchPoints) => {
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
203 onReleased: (touchPoints) => {
205 pressAndHoldTimer.stop()
207 _map.pan(lastMouseX - touchPoints[0].x, lastMouseY - touchPoints[0].y)
210 } else if (!pressAndHold) {
211 mapClicked(Qt.point(touchPoints[0].x, touchPoints[0].y))
217 id: pressAndHoldTimer
218 interval: 600 // hold duration in ms
222 if (multiTouchArea.isPressed && !multiTouchArea.dragActive) {
223 multiTouchArea.pressAndHold = true
224 mapPressAndHold(Qt.point(multiTouchArea.lastMouseX, multiTouchArea.lastMouseY))
232 acceptedButtons: Qt.RightButton
233 propagateComposedEvents: true
235 onPressed: (mouseEvent) => {
236 if (mouseEvent.button === Qt.RightButton) {
237 mapRightClicked(Qt.point(mouseEvent.x, mouseEvent.y))
242 /// Ground Station location
244 anchorPoint.x: sourceItem.width / 2
245 anchorPoint.y: sourceItem.height / 2
246 visible: gcsPosition.isValid && !planView
247 coordinate: gcsPosition
251 source: isNaN(gcsHeading) ? "/res/QGCLogoFull.svg" : "/res/QGCLogoArrow.svg"
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