QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FlyViewMap.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtLocation
4import QtPositioning
5import QtQuick.Dialogs
6import QtQuick.Layouts
7
8import QGroundControl
9import QGroundControl.Controls
10import QGroundControl.FlyView
11import QGroundControl.FlightMap
12import QGroundControl.PlanView
13
14FlightMap {
15 id: _root
16 allowGCSLocationCenter: true
17 allowVehicleLocationCenter: !_keepVehicleCentered
18 planView: false
19 zoomLevel: QGroundControl.flightMapZoom
20 center: QGroundControl.flightMapPosition
21
22 property Item pipView
23 property Item pipState: _pipState
24 property var rightPanelWidth
25 property var planMasterController
26 property bool pipMode: false // true: map is shown in a small pip mode
27 property rect viewportRect: Qt.rect(0, 0, width, height) // Map area the vehicle is kept within
28 property var occluders: [] // Rects within the viewport covered by UI
29
30 property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
31 property var _planMasterController: planMasterController
32 property var _geoFenceController: planMasterController.geoFenceController
33 property var _rallyPointController: planMasterController.rallyPointController
34 property var _activeVehicleCoordinate: _activeVehicle ? _activeVehicle.coordinate : QtPositioning.coordinate()
35 property real _toolButtonTopMargin: parent.height - mainWindow.height + (ScreenTools.defaultFontPixelHeight / 2)
36 property real _toolsMargin: ScreenTools.defaultFontPixelWidth * 0.75
37 property var _flyViewSettings: QGroundControl.settingsManager.flyViewSettings
38 property bool _keepMapCenteredOnVehicle: _flyViewSettings.keepMapCenteredOnVehicle.rawValue
39
40 property bool _keepVehicleCentered: pipMode ? true : false
41 property bool _saveZoomLevelSetting: true
42
43 function _adjustMapZoomForPipMode() {
44 _saveZoomLevelSetting = false
45 if (pipMode) {
46 if (QGroundControl.flightMapZoom > 3) {
47 zoomLevel = QGroundControl.flightMapZoom - 3
48 }
49 } else {
50 zoomLevel = QGroundControl.flightMapZoom
51 }
52 _saveZoomLevelSetting = true
53 }
54
55 onPipModeChanged: _adjustMapZoomForPipMode()
56
57 onVisibleChanged: {
58 if (visible) {
59 // Synchronize center position with Plan View
60 center = QGroundControl.flightMapPosition
61 }
62 }
63
64 onZoomLevelChanged: {
65 if (_saveZoomLevelSetting) {
66 QGroundControl.flightMapZoom = _root.zoomLevel
67 }
68 }
69 onCenterChanged: {
70 QGroundControl.flightMapPosition = _root.center
71 }
72
73 // We track whether the user has panned or not to correctly handle automatic map positioning
74 onMapPanStart: positionTracker.userInteracting = true
75 onMapPanStop: positionTracker.userInteracting = false
76
77 // Follow behavior on top of FlightMap's one-shot centering: hard follow while
78 // the keep-centered setting or pip mode is active, occluder follow otherwise
79 Binding {
80 target: positionTracker
81 property: "keepVehicleCentered"
82 value: _keepMapCenteredOnVehicle || _keepVehicleCentered
83 }
84
85 Binding {
86 target: positionTracker
87 property: "animating"
88 value: animateLat.running || animateLong.running
89 }
90
91 Connections {
92 target: positionTracker
93 function onRecenterVehicleTo(screenPoint) {
94 // Move the map such that the vehicle lands on screenPoint
95 let vehiclePoint = _root.fromCoordinate(_activeVehicleCoordinate, false /* clipToViewport */)
96 let centerOffset = Qt.point((_root.width / 2) - screenPoint.x, (_root.height / 2) - screenPoint.y)
97 let vehicleOffsetPoint = Qt.point(vehiclePoint.x + centerOffset.x, vehiclePoint.y + centerOffset.y)
98 let vehicleOffsetCoord = _root.toCoordinate(vehicleOffsetPoint, false /* clipToViewport */)
99 animatedMapRecenter(_root.center, vehicleOffsetCoord)
100 }
101 }
102
103 property real _animatedLatitudeStart
104 property real _animatedLatitudeStop
105 property real _animatedLongitudeStart
106 property real _animatedLongitudeStop
107 property real animatedLatitude
108 property real animatedLongitude
109
110 onAnimatedLatitudeChanged: _root.center = QtPositioning.coordinate(animatedLatitude, animatedLongitude)
111 onAnimatedLongitudeChanged: _root.center = QtPositioning.coordinate(animatedLatitude, animatedLongitude)
112
113 NumberAnimation on animatedLatitude { id: animateLat; from: _animatedLatitudeStart; to: _animatedLatitudeStop; duration: 1000 }
114 NumberAnimation on animatedLongitude { id: animateLong; from: _animatedLongitudeStart; to: _animatedLongitudeStop; duration: 1000 }
115
116 function animatedMapRecenter(fromCoord, toCoord) {
117 _animatedLatitudeStart = fromCoord.latitude
118 _animatedLongitudeStart = fromCoord.longitude
119 _animatedLatitudeStop = toCoord.latitude
120 _animatedLongitudeStop = toCoord.longitude
121 animateLat.start()
122 animateLong.start()
123 }
124
125 PipState {
126 id: _pipState
127 pipView: _root.pipView
128 isDark: _isFullWindowItemDark
129 }
130
131 Timer {
132 interval: 500
133 running: true
134 repeat: true
135 onTriggered: {
136 let vehiclePoint = _root.fromCoordinate(_activeVehicleCoordinate, false /* clipToViewport */)
137 positionTracker.evaluateOccluderFollow(vehiclePoint, viewportRect, occluders)
138 }
139 }
140
141 QGCMapPalette { id: mapPal; lightColors: isSatelliteMap }
142
143 Connections {
144 target: _missionController
145 ignoreUnknownSignals: true
146 function onNewItemsFromVehicle() {
147 var visualItems = _missionController.visualItems
148 if (visualItems && visualItems.count !== 1) {
149 mapFitFunctions.fitMapViewportToMissionItems()
150 firstVehiclePositionReceived = true
151 }
152 }
153 }
154
155 MapFitFunctions {
156 id: mapFitFunctions // The name for this id cannot be changed without breaking references outside of this code. Beware!
157 map: _root
158 usePlannedHomePosition: false
159 planMasterController: _planMasterController
160 }
161
162 ObstacleDistanceOverlayMap {
163 id: obstacleDistance
164 mapControl: _root
165 showText: !pipMode
166 }
167
168 // Add trajectory lines to the map
169 MapPolyline {
170 id: trajectoryPolyline
171 line.width: 3
172 line.color: "red"
173 z: QGroundControl.zOrderTrajectoryLines
174 visible: !pipMode
175
176 Connections {
177 target: QGroundControl.multiVehicleManager
178 function onActiveVehicleChanged(activeVehicle) {
179 trajectoryPolyline.path = _activeVehicle ? _activeVehicle.trajectoryPoints.list() : []
180 }
181 }
182
183 Connections {
184 target: _activeVehicle ? _activeVehicle.trajectoryPoints : null
185 function onPointAdded(coordinate) { trajectoryPolyline.addCoordinate(coordinate) }
186 function onUpdateLastPoint(coordinate) { trajectoryPolyline.replaceCoordinate(trajectoryPolyline.pathLength() - 1, coordinate) }
187 function onPointsCleared() { trajectoryPolyline.path = [] }
188 }
189 }
190
191 // Add the vehicles to the map
192 MapItemView {
193 model: QGroundControl.multiVehicleManager.vehicles
194 delegate: VehicleMapItem {
195 vehicle: object
196 coordinate: object.coordinate
197 map: _root
198 size: pipMode ? ScreenTools.defaultFontPixelHeight : ScreenTools.defaultFontPixelHeight * 3
199 z: QGroundControl.zOrderVehicles
200 }
201 }
202 // Add distance sensor view
203 MapItemView{
204 model: QGroundControl.multiVehicleManager.vehicles
205 delegate: ProximityRadarMapView {
206 vehicle: object
207 coordinate: object.coordinate
208 map: _root
209 z: QGroundControl.zOrderVehicles
210 }
211 }
212 // Add ADSB vehicles to the map
213 MapItemView {
214 model: QGroundControl.adsbVehicleManager.adsbVehicles
215 delegate: ADSBVehicleMapItem {
216 coordinate: object.coordinate
217 altitude: object.altitude
218 callsign: object.callsign
219 heading: object.heading
220 alert: object.alert
221 map: _root
222 size: pipMode ? ScreenTools.defaultFontPixelHeight : ScreenTools.defaultFontPixelHeight * 2.5
223 z: QGroundControl.zOrderVehicles
224 }
225 }
226
227 // Add the items associated with each vehicles flight plan to the map
228 Repeater {
229 model: QGroundControl.multiVehicleManager.vehicles
230
231 PlanMapItems {
232 map: _root
233 largeMapView: !pipMode
234 planMasterController: masterController
235 vehicle: _vehicle
236
237 property var _vehicle: object
238
239 PlanMasterController {
240 id: masterController
241 Component.onCompleted: startStaticActiveVehicle(object)
242 }
243 }
244 }
245
246 // Allow custom builds to add map items
247 CustomMapItems {
248 map: _root
249 largeMapView: !pipMode
250 }
251
252 GeoFenceMapVisuals {
253 map: _root
254 myGeoFenceController: _geoFenceController
255 interactive: false
256 planView: false
257 homePosition: _activeVehicle && _activeVehicle.homePosition.isValid ? _activeVehicle.homePosition : QtPositioning.coordinate()
258 }
259
260 // Rally points on map
261 MapItemView {
262 model: _rallyPointController.points
263
264 delegate: MapQuickItem {
265 id: itemIndicator
266 anchorPoint.x: sourceItem.anchorPointX
267 anchorPoint.y: sourceItem.anchorPointY
268 coordinate: object.coordinate
269 z: QGroundControl.zOrderMapItems
270
271 sourceItem: MissionItemIndexLabel {
272 id: itemIndexLabel
273 label: qsTr("R", "rally point map item label")
274 }
275 }
276 }
277
278 // Camera trigger points
279 MapItemView {
280 model: _activeVehicle ? _activeVehicle.cameraTriggerPoints : 0
281
282 delegate: CameraTriggerIndicator {
283 coordinate: object.coordinate
284 z: QGroundControl.zOrderTopMost
285 }
286 }
287
288 // GoTo Location forward flight circle visuals
289 QGCMapCircleVisuals {
290 id: fwdFlightGotoMapCircle
291 mapControl: parent
292 mapCircle: _fwdFlightGotoMapCircle
293 radiusLabelVisible: true
294 // PX4 ignores the commanded loiter radius (flies NAV_LOITER_RAD), so the circle size is unknown
295 visible: gotoLocationItem.visible && _activeVehicle &&
296 !_activeVehicle.px4Firmware &&
297 _activeVehicle.inFwdFlight &&
298 !_activeVehicle.orbitActive
299
300 property alias coordinate: _fwdFlightGotoMapCircle.center
301 property alias radius: _fwdFlightGotoMapCircle.radius
302 property alias clockwiseRotation: _fwdFlightGotoMapCircle.clockwiseRotation
303
304 Component.onCompleted: {
305 // Only allow editing the radius, not the position
306 centerDragHandleVisible = false
307
308 globals.guidedControllerFlyView.fwdFlightGotoMapCircle = this
309 }
310
311 Binding {
312 target: _fwdFlightGotoMapCircle
313 property: "center"
314 value: gotoLocationItem.coordinate
315 }
316
317 function startLoiterRadiusEdit() {
318 _fwdFlightGotoMapCircle.interactive = true
319 }
320
321 // Called when loiter edit is confirmed
322 function actionConfirmed() {
323 _fwdFlightGotoMapCircle.interactive = false
324 _fwdFlightGotoMapCircle._commitRadius()
325 }
326
327 // Called when loiter edit is cancelled
328 function actionCancelled() {
329 _fwdFlightGotoMapCircle.interactive = false
330 _fwdFlightGotoMapCircle._restoreRadius()
331 }
332
333 QGCMapCircle {
334 id: _fwdFlightGotoMapCircle
335 interactive: false
336 showRotation: true
337 clockwiseRotation: true
338
339 property real _defaultLoiterRadius: _flyViewSettings.forwardFlightGoToLocationLoiterRad.rawValue
340 property real _committedRadius;
341
342 onCenterChanged: {
343 radius.rawValue = _defaultLoiterRadius
344 // Don't commit the radius in case this operation is undone
345 }
346
347 Component.onCompleted: {
348 radius.rawValue = _defaultLoiterRadius
349 _commitRadius()
350 }
351
352 function _commitRadius() {
353 _committedRadius = radius.rawValue
354 }
355
356 function _restoreRadius() {
357 radius.rawValue = _committedRadius
358 }
359 }
360 }
361
362 // GoTo Location visuals
363 MapQuickItem {
364 id: gotoLocationItem
365 visible: false
366 z: QGroundControl.zOrderMapItems
367 anchorPoint.x: sourceItem.anchorPointX
368 anchorPoint.y: sourceItem.anchorPointY
369 sourceItem: MissionItemIndexLabel {
370 checked: true
371 index: -1
372 label: qsTr("Go here", "Go to location waypoint")
373 }
374
375 property bool inGotoFlightMode: _activeVehicle ? _activeVehicle.flightMode === _activeVehicle.gotoFlightMode : false
376
377 property var _committedCoordinate: null
378
379 onInGotoFlightModeChanged: {
380 if (!inGotoFlightMode && gotoLocationItem.visible) {
381 // Hide goto indicator when vehicle falls out of guided mode
382 hide()
383 }
384 }
385
386 function show(coord) {
387 gotoLocationItem.coordinate = coord
388 gotoLocationItem.visible = true
389 }
390
391 function hide() {
392 gotoLocationItem.visible = false
393 }
394
395 function actionConfirmed() {
396 _commitCoordinate()
397
398 // Commit the new radius which possibly changed
399 fwdFlightGotoMapCircle.actionConfirmed()
400
401 // We leave the indicator visible. The handling for onInGuidedModeChanged will hide it.
402 }
403
404 function actionCancelled() {
405 _restoreCoordinate()
406
407 // Also restore the loiter radius
408 fwdFlightGotoMapCircle.actionCancelled()
409 }
410
411 function _commitCoordinate() {
412 // Must deep copy
413 _committedCoordinate = QtPositioning.coordinate(
414 coordinate.latitude,
415 coordinate.longitude
416 );
417 }
418
419 function _restoreCoordinate() {
420 if (_committedCoordinate) {
421 coordinate = _committedCoordinate
422 } else {
423 hide()
424 }
425 }
426 }
427
428 // Orbit editing visuals
429 QGCMapCircleVisuals {
430 id: orbitMapCircle
431 mapControl: parent
432 mapCircle: _mapCircle
433 visible: false
434
435 property alias center: _mapCircle.center
436 property alias clockwiseRotation: _mapCircle.clockwiseRotation
437 readonly property real defaultRadius: 30
438
439 Connections {
440 target: QGroundControl.multiVehicleManager
441 function onActiveVehicleChanged(activeVehicle) {
442 if (!activeVehicle) {
443 orbitMapCircle.visible = false
444 }
445 }
446 }
447
448 function show(coord) {
449 _mapCircle.radius.rawValue = defaultRadius
450 orbitMapCircle.center = coord
451 orbitMapCircle.visible = true
452 }
453
454 function hide() {
455 orbitMapCircle.visible = false
456 }
457
458 function actionConfirmed() {
459 // Live orbit status is handled by telemetry so we hide here and telemetry will show again.
460 hide()
461 }
462
463 function actionCancelled() {
464 hide()
465 }
466
467 function radius() {
468 return _mapCircle.radius.rawValue
469 }
470
471 Component.onCompleted: globals.guidedControllerFlyView.orbitMapCircle = orbitMapCircle
472
473 QGCMapCircle {
474 id: _mapCircle
475 interactive: true
476 radius.rawValue: 30
477 showRotation: true
478 clockwiseRotation: true
479 }
480 }
481
482 // ROI Location visuals
483 MapQuickItem {
484 id: roiLocationItem
485 visible: _activeVehicle && _activeVehicle.isROIEnabled
486 z: QGroundControl.zOrderMapItems
487 anchorPoint.x: sourceItem.anchorPointX
488 anchorPoint.y: sourceItem.anchorPointY
489
490 Connections {
491 target: _activeVehicle
492 function onRoiCoordChanged(centerCoord) {
493 roiLocationItem.show(centerCoord)
494 }
495 }
496
497 MouseArea {
498 anchors.fill: parent
499 onClicked: (position) => {
500 position = Qt.point(position.x, position.y)
501 var clickCoord = _root.toCoordinate(position, false /* clipToViewPort */)
502 // For some strange reason using mainWindow in mapToItem doesn't work, so we use globals.parent instead which also gets us mainWindow
503 position = mapToItem(globals.parent, position)
504 var dropPanel = roiEditDropPanelComponent.createObject(mainWindow, { clickRect: Qt.rect(position.x, position.y, 0, 0) })
505 dropPanel.open()
506 }
507 }
508
509 sourceItem: MissionItemIndexLabel {
510 checked: true
511 index: -1
512 label: qsTr("ROI here", "Make this a Region Of Interest")
513 }
514
515 //-- Visibilty controlled by actual state
516 function show(coord) {
517 roiLocationItem.coordinate = coord
518 }
519 }
520
521 // Orbit telemetry visuals
522 QGCMapCircleVisuals {
523 id: orbitTelemetryCircle
524 mapControl: parent
525 mapCircle: _activeVehicle ? _activeVehicle.orbitMapCircle : null
526 visible: _activeVehicle ? _activeVehicle.orbitActive : false
527 }
528
529 MapQuickItem {
530 id: orbitCenterIndicator
531 anchorPoint.x: sourceItem.anchorPointX
532 anchorPoint.y: sourceItem.anchorPointY
533 coordinate: _activeVehicle ? _activeVehicle.orbitMapCircle.center : QtPositioning.coordinate()
534 visible: orbitTelemetryCircle.visible && !gotoLocationItem.visible
535
536 sourceItem: MissionItemIndexLabel {
537 checked: true
538 index: -1
539 label: qsTr("Orbit", "Orbit waypoint")
540 }
541 }
542
543 QGCPopupDialogFactory {
544 id: roiEditPositionDialogFactory
545
546 dialogComponent: roiEditPositionDialogComponent
547 }
548
549 Component {
550 id: roiEditPositionDialogComponent
551
552 EditPositionDialog {
553 title: qsTr("Edit ROI Position")
554 coordinate: roiLocationItem.coordinate
555
556 readonly property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
557
558 // The ROI belongs to the vehicle the dialog was opened for; close
559 // if that vehicle goes away or the active vehicle changes
560 on_ActiveVehicleChanged: close()
561
562 onCoordinateChanged: {
563 roiLocationItem.coordinate = coordinate
564 _activeVehicle.guidedModeROI(coordinate, _activeVehicle.roiRelativeAltitudeMeters)
565 }
566 }
567 }
568
569 Component {
570 id: roiEditDropPanelComponent
571
572 DropPanel {
573 id: roiEditDropPanel
574
575 readonly property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
576
577 // The ROI belongs to the vehicle the panel was opened for; close
578 // if that vehicle goes away or the active vehicle changes
579 on_ActiveVehicleChanged: close()
580
581 // Created dynamically per ROI click; close() alone would leak the panel
582 onClosed: destroy()
583
584 sourceComponent: Component {
585 ColumnLayout {
586 spacing: ScreenTools.defaultFontPixelWidth / 2
587
588 QGCButton {
589 Layout.fillWidth: true
590 text: qsTr("Cancel ROI")
591 onClicked: {
592 _activeVehicle.stopGuidedModeROI()
593 roiEditDropPanel.close()
594 }
595 }
596
597 QGCButton {
598 Layout.fillWidth: true
599 text: qsTr("Edit Position")
600 onClicked: {
601 roiEditPositionDialogFactory.open()
602 roiEditDropPanel.close()
603 }
604 }
605 }
606 }
607 }
608 }
609
610 Component {
611 id: mapClickDropPanelComponent
612
613 FlyViewMapClickDropPanel {
614 gotoIndicator: gotoLocationItem
615 orbitIndicator: orbitMapCircle
616 }
617 }
618
619 onMapClicked: (position) => {
620 if (!globals.guidedControllerFlyView.guidedUIVisible &&
621 (globals.guidedControllerFlyView.showGotoLocation || globals.guidedControllerFlyView.showOrbit ||
622 globals.guidedControllerFlyView.showROI || globals.guidedControllerFlyView.showSetHome ||
623 globals.guidedControllerFlyView.showSetEstimatorOrigin)) {
624
625 position = Qt.point(position.x, position.y)
626 var clickCoord = _root.toCoordinate(position, false /* clipToViewPort */)
627 // For some strange reason using mainWindow in mapToItem doesn't work, so we use globals.parent instead which also gets us mainWindow
628 position = _root.mapToItem(globals.parent, position)
629 var dropPanel = mapClickDropPanelComponent.createObject(mainWindow, { mapClickCoord: clickCoord, clickRect: Qt.rect(position.x, position.y, 0, 0) })
630 dropPanel.open()
631 }
632 }
633
634 MapScale {
635 id: mapScale
636 anchors.margins: _toolsMargin
637 anchors.left: parent.left
638 anchors.top: parent.top
639 mapControl: _root
640 visible: !ScreenTools.isTinyScreen && QGroundControl.corePlugin.options.flyView.showMapScale && mapControl.pipState.state === mapControl.pipState.windowState
641 }
642}