/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ import QtQuick 2.11 import QtQuick.Controls 2.4 import QtLocation 5.3 import QtPositioning 5.3 import QtQuick.Dialogs 1.2 import QGroundControl 1.0 import QGroundControl.Airspace 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.Controls 1.0 import QGroundControl.FlightDisplay 1.0 import QGroundControl.FlightMap 1.0 import QGroundControl.Palette 1.0 import QGroundControl.ScreenTools 1.0 import QGroundControl.Vehicle 1.0 FlightMap { id: _root allowGCSLocationCenter: true allowVehicleLocationCenter: !_keepVehicleCentered planView: false zoomLevel: QGroundControl.flightMapZoom center: QGroundControl.flightMapPosition property Item pipState: _pipState QGCPipState { id: _pipState pipOverlay: _pipOverlay isDark: _isFullWindowItemDark } property var rightPanelWidth property var planMasterController property bool pipMode: false // true: map is shown in a small pip mode property var toolInsets // Insets for the center viewport area property var _planMasterController: planMasterController property var _geoFenceController: planMasterController.geoFenceController property var _rallyPointController: planMasterController.rallyPointController property var _activeVehicleCoordinate: activeVehicle ? activeVehicle.coordinate : QtPositioning.coordinate() property real _toolButtonTopMargin: parent.height - mainWindow.height + (ScreenTools.defaultFontPixelHeight / 2) property bool _airspaceEnabled: QGroundControl.airmapSupported ? (QGroundControl.settingsManager.airMapSettings.enableAirMap.rawValue && QGroundControl.airspaceManager.connected): false property var _flyViewSettings: QGroundControl.settingsManager.flyViewSettings property bool _keepMapCenteredOnVehicle: _flyViewSettings.keepMapCenteredOnVehicle.rawValue property bool _disableVehicleTracking: false property bool _keepVehicleCentered: pipMode ? true : false property bool _saveZoomLevelSetting: true function updateAirspace(reset) { if(_airspaceEnabled) { var coordinateNW = _root.toCoordinate(Qt.point(0,0), false /* clipToViewPort */) var coordinateSE = _root.toCoordinate(Qt.point(width,height), false /* clipToViewPort */) if(coordinateNW.isValid && coordinateSE.isValid) { QGroundControl.airspaceManager.setROI(coordinateNW, coordinateSE, false /*planView*/, reset) } } } function _adjustMapZoomForPipMode() { _saveZoomLevelSetting = false if (pipMode) { if (QGroundControl.flightMapZoom > 3) { zoomLevel = QGroundControl.flightMapZoom - 3 } } else { zoomLevel = QGroundControl.flightMapZoom } _saveZoomLevelSetting = true } onPipModeChanged: _adjustMapZoomForPipMode() onVisibleChanged: { if (visible) { // Synchronize center position with Plan View center = QGroundControl.flightMapPosition } } onZoomLevelChanged: { if (_saveZoomLevelSetting) { QGroundControl.flightMapZoom = zoomLevel updateAirspace(false) } } onCenterChanged: { QGroundControl.flightMapPosition = center updateAirspace(false) } on_AirspaceEnabledChanged: { updateAirspace(true) } // We track whether the user has panned or not to correctly handle automatic map positioning Connections { target: gesture onPanStarted: _disableVehicleTracking = true onFlickStarted: _disableVehicleTracking = true onPanFinished: panRecenterTimer.restart() onFlickFinished: panRecenterTimer.restart() } function pointInRect(point, rect) { return point.x > rect.x && point.x < rect.x + rect.width && point.y > rect.y && point.y < rect.y + rect.height; } property real _animatedLatitudeStart property real _animatedLatitudeStop property real _animatedLongitudeStart property real _animatedLongitudeStop property real animatedLatitude property real animatedLongitude onAnimatedLatitudeChanged: _root.center = QtPositioning.coordinate(animatedLatitude, animatedLongitude) onAnimatedLongitudeChanged: _root.center = QtPositioning.coordinate(animatedLatitude, animatedLongitude) NumberAnimation on animatedLatitude { id: animateLat; from: _animatedLatitudeStart; to: _animatedLatitudeStop; duration: 1000 } NumberAnimation on animatedLongitude { id: animateLong; from: _animatedLongitudeStart; to: _animatedLongitudeStop; duration: 1000 } function animatedMapRecenter(fromCoord, toCoord) { _animatedLatitudeStart = fromCoord.latitude _animatedLongitudeStart = fromCoord.longitude _animatedLatitudeStop = toCoord.latitude _animatedLongitudeStop = toCoord.longitude animateLat.start() animateLong.start() } function _insetRect() { return Qt.rect(toolInsets.leftEdgeCenterInset, toolInsets.topEdgeCenterInset, _root.width - toolInsets.leftEdgeCenterInset - toolInsets.rightEdgeCenterInset, _root.height - toolInsets.topEdgeCenterInset - toolInsets.bottomEdgeCenterInset) } function recenterNeeded() { var vehiclePoint = _root.fromCoordinate(_activeVehicleCoordinate, false /* clipToViewport */) var insetRect = _insetRect() return !pointInRect(vehiclePoint, insetRect) } function updateMapToVehiclePosition() { if (animateLat.running || animateLong.running) { return } // We let FlightMap handle first vehicle position if (!_keepMapCenteredOnVehicle && firstVehiclePositionReceived && _activeVehicleCoordinate.isValid && !_disableVehicleTracking) { if (_keepVehicleCentered) { _root.center = _activeVehicleCoordinate } else { if (firstVehiclePositionReceived && recenterNeeded()) { // Move the map such that the vehicle is centered within the inset area var vehiclePoint = _root.fromCoordinate(_activeVehicleCoordinate, false /* clipToViewport */) var insetRect = _insetRect() var centerInsetPoint = Qt.point(insetRect.x + insetRect.width / 2, insetRect.y + insetRect.height / 2) var centerOffset = Qt.point((_root.width / 2) - centerInsetPoint.x, (_root.height / 2) - centerInsetPoint.y) var vehicleOffsetPoint = Qt.point(vehiclePoint.x + centerOffset.x, vehiclePoint.y + centerOffset.y) var vehicleOffsetCoord = _root.toCoordinate(vehicleOffsetPoint, false /* clipToViewport */) animatedMapRecenter(_root.center, vehicleOffsetCoord) } } } } on_ActiveVehicleCoordinateChanged: { if (_keepMapCenteredOnVehicle && _activeVehicleCoordinate.isValid && !_disableVehicleTracking) { _root.center = _activeVehicleCoordinate } } Timer { id: panRecenterTimer interval: 10000 running: false onTriggered: { _disableVehicleTracking = false updateMapToVehiclePosition() } } Timer { interval: 500 running: true repeat: true onTriggered: updateMapToVehiclePosition() } QGCMapPalette { id: mapPal; lightColors: isSatelliteMap } Connections { target: _missionController ignoreUnknownSignals: true onNewItemsFromVehicle: { var visualItems = _missionController.visualItems if (visualItems && visualItems.count !== 1) { mapFitFunctions.fitMapViewportToMissionItems() firstVehiclePositionReceived = true } } } MapFitFunctions { id: mapFitFunctions // The name for this id cannot be changed without breaking references outside of this code. Beware! map: _root usePlannedHomePosition: false planMasterController: _planMasterController } // Add trajectory lines to the map MapPolyline { id: trajectoryPolyline line.width: 3 line.color: "red" z: QGroundControl.zOrderTrajectoryLines visible: !pipMode Connections { target: QGroundControl.multiVehicleManager onActiveVehicleChanged: trajectoryPolyline.path = activeVehicle ? activeVehicle.trajectoryPoints.list() : [] } Connections { target: activeVehicle ? activeVehicle.trajectoryPoints : null onPointAdded: trajectoryPolyline.addCoordinate(coordinate) onUpdateLastPoint: trajectoryPolyline.replaceCoordinate(trajectoryPolyline.pathLength() - 1, coordinate) onPointsCleared: trajectoryPolyline.path = [] } } // Add the vehicles to the map MapItemView { model: QGroundControl.multiVehicleManager.vehicles delegate: VehicleMapItem { vehicle: object coordinate: object.coordinate map: _root size: pipMode ? ScreenTools.defaultFontPixelHeight : ScreenTools.defaultFontPixelHeight * 3 z: QGroundControl.zOrderVehicles } } // Add distance sensor view MapItemView{ model: QGroundControl.multiVehicleManager.vehicles delegate: ProximityRadarMapView { vehicle: object coordinate: object.coordinate map: _root z: QGroundControl.zOrderVehicles } } // Add ADSB vehicles to the map MapItemView { model: QGroundControl.adsbVehicleManager.adsbVehicles delegate: VehicleMapItem { coordinate: object.coordinate altitude: object.altitude callsign: object.callsign heading: object.heading alert: object.alert map: _root z: QGroundControl.zOrderVehicles } } // Add the items associated with each vehicles flight plan to the map Repeater { model: QGroundControl.multiVehicleManager.vehicles PlanMapItems { map: _root largeMapView: !pipMode planMasterController: _planMasterController vehicle: _vehicle property var _vehicle: object PlanMasterController { id: masterController Component.onCompleted: startStaticActiveVehicle(object) } } } MapItemView { model: pipMode ? undefined : _missionController.directionArrows delegate: MapLineArrow { fromCoord: object ? object.coordinate1 : undefined toCoord: object ? object.coordinate2 : undefined arrowPosition: 2 z: QGroundControl.zOrderWaypointLines } } // Allow custom builds to add map items CustomMapItems { map: _root largeMapView: !pipMode } GeoFenceMapVisuals { map: _root myGeoFenceController: _geoFenceController interactive: false planView: false homePosition: activeVehicle && activeVehicle.homePosition.isValid ? activeVehicle.homePosition : QtPositioning.coordinate() } // Rally points on map MapItemView { model: _rallyPointController.points delegate: MapQuickItem { id: itemIndicator anchorPoint.x: sourceItem.anchorPointX anchorPoint.y: sourceItem.anchorPointY coordinate: object.coordinate z: QGroundControl.zOrderMapItems sourceItem: MissionItemIndexLabel { id: itemIndexLabel label: qsTr("R", "rally point map item label") } } } // Camera trigger points MapItemView { model: activeVehicle ? activeVehicle.cameraTriggerPoints : 0 delegate: CameraTriggerIndicator { coordinate: object.coordinate z: QGroundControl.zOrderTopMost } } // GoTo Location visuals MapQuickItem { id: gotoLocationItem visible: false z: QGroundControl.zOrderMapItems anchorPoint.x: sourceItem.anchorPointX anchorPoint.y: sourceItem.anchorPointY sourceItem: MissionItemIndexLabel { checked: true index: -1 label: qsTr("Go here", "Go to location waypoint") } property bool inGotoFlightMode: activeVehicle ? activeVehicle.flightMode === activeVehicle.gotoFlightMode : false onInGotoFlightModeChanged: { if (!inGotoFlightMode && gotoLocationItem.visible) { // Hide goto indicator when vehicle falls out of guided mode gotoLocationItem.visible = false } } Connections { target: mainWindow onActiveVehicleChanged: { if (!activeVehicle) { gotoLocationItem.visible = false } } } function show(coord) { gotoLocationItem.coordinate = coord gotoLocationItem.visible = true } function hide() { gotoLocationItem.visible = false } function actionConfirmed() { // We leave the indicator visible. The handling for onInGuidedModeChanged will hide it. } function actionCancelled() { hide() } } // Orbit editing visuals QGCMapCircleVisuals { id: orbitMapCircle mapControl: parent mapCircle: _mapCircle visible: false property alias center: _mapCircle.center property alias clockwiseRotation: _mapCircle.clockwiseRotation readonly property real defaultRadius: 30 Connections { target: mainWindow onActiveVehicleChanged: { if (!activeVehicle) { orbitMapCircle.visible = false } } } function show(coord) { _mapCircle.radius.rawValue = defaultRadius orbitMapCircle.center = coord orbitMapCircle.visible = true } function hide() { orbitMapCircle.visible = false } function actionConfirmed() { // Live orbit status is handled by telemetry so we hide here and telemetry will show again. hide() } function actionCancelled() { hide() } function radius() { return _mapCircle.radius.rawValue } Component.onCompleted: mainWindow.guidedControllerFlyView.orbitMapCircle = orbitMapCircle QGCMapCircle { id: _mapCircle interactive: true radius.rawValue: 30 showRotation: true clockwiseRotation: true } } // ROI Location visuals MapQuickItem { id: roiLocationItem visible: activeVehicle && activeVehicle.isROIEnabled z: QGroundControl.zOrderMapItems anchorPoint.x: sourceItem.anchorPointX anchorPoint.y: sourceItem.anchorPointY sourceItem: MissionItemIndexLabel { checked: true index: -1 label: qsTr("ROI here", "Make this a Region Of Interest") } //-- Visibilty controlled by actual state function show(coord) { roiLocationItem.coordinate = coord } function hide() { } function actionConfirmed() { } function actionCancelled() { } } // Orbit telemetry visuals QGCMapCircleVisuals { id: orbitTelemetryCircle mapControl: parent mapCircle: activeVehicle ? activeVehicle.orbitMapCircle : null visible: activeVehicle ? activeVehicle.orbitActive : false } MapQuickItem { id: orbitCenterIndicator anchorPoint.x: sourceItem.anchorPointX anchorPoint.y: sourceItem.anchorPointY coordinate: activeVehicle ? activeVehicle.orbitMapCircle.center : QtPositioning.coordinate() visible: orbitTelemetryCircle.visible sourceItem: MissionItemIndexLabel { checked: true index: -1 label: qsTr("Orbit", "Orbit waypoint") } } // Handle guided mode clicks MouseArea { anchors.fill: parent QGCMenu { id: clickMenu property var coord QGCMenuItem { text: qsTr("Go to location") visible: mainWindow.guidedControllerFlyView.showGotoLocation onTriggered: { gotoLocationItem.show(clickMenu.coord) mainWindow.guidedControllerFlyView.confirmAction(mainWindow.guidedControllerFlyView.actionGoto, clickMenu.coord, gotoLocationItem) } } QGCMenuItem { text: qsTr("Orbit at location") visible: mainWindow.guidedControllerFlyView.showOrbit onTriggered: { orbitMapCircle.show(clickMenu.coord) mainWindow.guidedControllerFlyView.confirmAction(mainWindow.guidedControllerFlyView.actionOrbit, clickMenu.coord, orbitMapCircle) } } QGCMenuItem { text: qsTr("ROI at location") visible: mainWindow.guidedControllerFlyView.showROI onTriggered: { roiLocationItem.show(clickMenu.coord) mainWindow.guidedControllerFlyView.confirmAction(mainWindow.guidedControllerFlyView.actionROI, clickMenu.coord, roiLocationItem) } } } onClicked: { if (!mainWindow.guidedControllerFlyView.guidedUIVisible && (mainWindow.guidedControllerFlyView.showGotoLocation || mainWindow.guidedControllerFlyView.showOrbit || mainWindow.guidedControllerFlyView.showROI)) { orbitMapCircle.hide() gotoLocationItem.hide() var clickCoord = _root.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */) clickMenu.coord = clickCoord clickMenu.popup() } } } // Airspace overlap support MapItemView { model: _airspaceEnabled && QGroundControl.settingsManager.airMapSettings.enableAirspace && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.circles : [] delegate: MapCircle { center: object.center radius: object.radius color: object.color border.color: object.lineColor border.width: object.lineWidth } } MapItemView { model: _airspaceEnabled && QGroundControl.settingsManager.airMapSettings.enableAirspace && QGroundControl.airspaceManager.airspaceVisible ? QGroundControl.airspaceManager.airspaces.polygons : [] delegate: MapPolygon { path: object.polygon color: object.color border.color: object.lineColor border.width: object.lineWidth } } }