地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

709 lines
26 KiB

/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* 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 QtQuick.Layouts 1.11
import QGroundControl 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 _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
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 real _toolsMargin: ScreenTools.defaultFontPixelWidth * 0.75
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 _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
}
}
onCenterChanged: {
QGroundControl.flightMapPosition = center
}
// We track whether the user has panned or not to correctly handle automatic map positioning
Connections {
target: gesture
function onPanStarted() { _disableVehicleTracking = true }
function onFlickStarted() { _disableVehicleTracking = true }
function onPanFinished() { panRecenterTimer.restart() }
function 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()
}
// returns the rectangle formed by the four center insets
// used for checking if vehicle is under ui, and as a target for recentering the view
function _insetCenterRect() {
return Qt.rect(toolInsets.leftEdgeCenterInset,
toolInsets.topEdgeCenterInset,
_root.width - toolInsets.leftEdgeCenterInset - toolInsets.rightEdgeCenterInset,
_root.height - toolInsets.topEdgeCenterInset - toolInsets.bottomEdgeCenterInset)
}
// returns the four rectangles formed by the 8 corner insets
// used for detecting if the vehicle has flown under the instrument panel, virtual joystick etc
function _insetCornerRects() {
var rects = {
"topleft": Qt.rect(0,0,
toolInsets.leftEdgeTopInset,
toolInsets.topEdgeLeftInset),
"topright": Qt.rect(_root.width-toolInsets.rightEdgeTopInset,0,
toolInsets.rightEdgeTopInset,
toolInsets.topEdgeRightInset),
"bottomleft": Qt.rect(0,_root.height-toolInsets.bottomEdgeLeftInset,
toolInsets.leftEdgeBottomInset,
toolInsets.bottomEdgeLeftInset),
"bottomright": Qt.rect(_root.width-toolInsets.rightEdgeBottomInset,_root.height-toolInsets.bottomEdgeRightInset,
toolInsets.rightEdgeBottomInset,
toolInsets.bottomEdgeRightInset)}
return rects
}
function recenterNeeded() {
var vehiclePoint = _root.fromCoordinate(_activeVehicleCoordinate, false /* clipToViewport */)
var centerRect = _insetCenterRect()
//return !pointInRect(vehiclePoint,insetRect)
// If we are outside the center inset rectangle, recenter
if(!pointInRect(vehiclePoint, centerRect)){
return true
}
// if we are inside the center inset rectangle
// then additionally check if we are underneath one of the corner inset rectangles
var cornerRects = _insetCornerRects()
if(pointInRect(vehiclePoint, cornerRects["topleft"])){
return true
} else if(pointInRect(vehiclePoint, cornerRects["topright"])){
return true
} else if(pointInRect(vehiclePoint, cornerRects["bottomleft"])){
return true
} else if(pointInRect(vehiclePoint, cornerRects["bottomright"])){
return true
}
// if we are inside the center inset rectangle, and not under any corner elements
return false
}
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 centerInsetRect = _insetCenterRect()
var centerInsetPoint = Qt.point(centerInsetRect.x + centerInsetRect.width / 2, centerInsetRect.y + centerInsetRect.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
function 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
}
ObstacleDistanceOverlayMap {
id: obstacleDistance
showText: !pipMode
}
// Add trajectory lines to the map
MapPolyline {
id: trajectoryPolyline
line.width: 3
line.color: "red"
z: QGroundControl.zOrderTrajectoryLines
visible: !pipMode
Connections {
target: QGroundControl.multiVehicleManager
function onActiveVehicleChanged(activeVehicle) {
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
size: pipMode ? ScreenTools.defaultFontPixelHeight : ScreenTools.defaultFontPixelHeight * 2.5
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: masterController
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: QGroundControl.multiVehicleManager
function onActiveVehicleChanged(activeVehicle) {
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: QGroundControl.multiVehicleManager
function onActiveVehicleChanged(activeVehicle) {
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: globals.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")
onClicked: (position) => {
var roiEditMenu = popupMenuComponent.createObject(_root, { coord: roiLocationItem.coordinate, contentItemComponent: roiEditMenuComponent })
var clickPoint = mapToItem(_root, position.x, position.y)
roiEditMenu.setPosition(clickPoint.x, clickPoint.y)
roiEditMenu.open()
}
}
//-- Visibilty controlled by actual state
function show(coord) {
roiLocationItem.coordinate = coord
}
}
// 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")
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!globals.guidedControllerFlyView.guidedUIVisible &&
(globals.guidedControllerFlyView.showGotoLocation || globals.guidedControllerFlyView.showOrbit || globals.guidedControllerFlyView.showROI || globals.guidedControllerFlyView.showSetHome || globals.guidedControllerFlyView.showSetEstimatorOrigin)) {
orbitMapCircle.hide()
gotoLocationItem.hide()
var clickCoord = _root.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
var mapClickMenu = popupMenuComponent.createObject(_root, { coord: clickCoord, contentItemComponent: mapClickMenuComponent })
mapClickMenu.setPosition(mouse.x, mouse.y)
mapClickMenu.open()
}
}
}
Component {
id: popupMenuComponent
Popup {
id: mapClickMenu
modal: true
property var coord
property var contentItemComponent
function setPosition(mouseX, mouseY) {
var newX = mouseX
var newY = mouseY
// Filtering coordinates
if (newX + mapClickMenu.width > _root.width) {
newX = _root.width - mapClickMenu.width
}
if (newY + mapClickMenu.height > _root.height) {
newY = _root.height - mapClickMenu.height
}
// Set coordiantes
x = newX
y = newY
}
background: Rectangle {
radius: ScreenTools.defaultFontPixelHeight * 0.5
color: qgcPal.window
border.color: qgcPal.text
}
contentItem: Loader {
sourceComponent: contentItemComponent
property var mapClickCoord: mapClickMenu.coord
property var popup: mapClickMenu
}
}
}
Component {
id: mapClickMenuComponent
ColumnLayout {
id: mainLayout
spacing: ScreenTools.defaultFontPixelWidth / 2
QGCButton {
Layout.fillWidth: true
text: qsTr("Go to location")
visible: globals.guidedControllerFlyView.showGotoLocation
onClicked: {
if (popup.opened) {
popup.close()
}
gotoLocationItem.show(mapClickCoord)
globals.guidedControllerFlyView.confirmAction(globals.guidedControllerFlyView.actionGoto, mapClickCoord, gotoLocationItem)
}
}
QGCButton {
Layout.fillWidth: true
text: qsTr("Orbit at location")
visible: globals.guidedControllerFlyView.showOrbit
onClicked: {
if (popup.opened) {
popup.close()
}
orbitMapCircle.show(mapClickCoord)
globals.guidedControllerFlyView.confirmAction(globals.guidedControllerFlyView.actionOrbit, mapClickCoord, orbitMapCircle)
}
}
QGCButton {
Layout.fillWidth: true
text: qsTr("ROI at location")
visible: globals.guidedControllerFlyView.showROI
onClicked: {
if (popup.opened) {
popup.close()
}
roiLocationItem.show(mapClickCoord)
globals.guidedControllerFlyView.executeAction(globals.guidedControllerFlyView.actionROI, mapClickCoord, 0, false)
}
}
QGCButton {
Layout.fillWidth: true
text: qsTr("Set home here")
visible: globals.guidedControllerFlyView.showSetHome
onClicked: {
if (popup.opened) {
popup.close()
}
globals.guidedControllerFlyView.confirmAction(globals.guidedControllerFlyView.actionSetHome, mapClickCoord)
}
}
}
}
Component {
id: roiEditPositionDialogComponent
EditPositionDialog {
title: qsTr("Edit ROI Position")
coordinate: roiLocationItem.coordinate
onCoordinateChanged: {
roiLocationItem.coordinate = coordinate
_activeVehicle.guidedModeROI(coordinate)
}
}
}
Component {
id: roiEditMenuComponent
ColumnLayout {
id: mainLayout
spacing: ScreenTools.defaultFontPixelWidth / 2
QGCButton {
Layout.fillWidth: true
text: qsTr("Cancel ROI")
onClicked: {
_activeVehicle.stopGuidedModeROI()
popup.close()
}
}
QGCButton {
Layout.fillWidth: true
text: qsTr("Edit Position")
onClicked: {
roiEditPositionDialogComponent.createObject(mainWindow, { showSetPositionFromVehicle: false }).open()
popup.close()
}
}
}
}
MapScale {
id: mapScale
anchors.margins: _toolsMargin
anchors.left: parent.left
anchors.top: parent.top
mapControl: _root
buttonsOnLeft: true
visible: !ScreenTools.isTinyScreen && QGroundControl.corePlugin.options.flyView.showMapScale && mapControl.pipState.state === mapControl.pipState.windowState
property real centerInset: visible ? parent.height - y : 0
}
}