地面站终端 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.

659 lines
23 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.ScreenTools 1.0
import QGroundControl.Palette 1.0
import QGroundControl.Controls 1.0
import QGroundControl.FlightMap 1.0
import QGroundControl.ShapeFileHelper 1.0
/// QGCMapPolygon map visuals
Item {
id: _root
property var mapControl ///< Map control to place item in
property var mapPolygon ///< QGCMapPolygon object
property bool interactive: mapPolygon.interactive
property color interiorColor: "transparent"
property real interiorOpacity: 1
property int borderWidth: 0
property color borderColor: "black"
property bool _circleMode: false
property real _circleRadius
property bool _circleRadiusDrag: false
property var _circleRadiusDragCoord: QtPositioning.coordinate()
property bool _editCircleRadius: false
property string _instructionText: _polygonToolsText
property var _savedVertices: [ ]
property bool _savedCircleMode
property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap
property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2
property real _zorderCenterHandle: QGroundControl.zOrderMapItems + 1 // Lowest such that drag or split takes precedence
readonly property string _polygonToolsText: qsTr("Polygon Tools")
readonly property string _traceText: qsTr("Click in the map to add vertices. Click 'Done Tracing' when finished.")
function addCommonVisuals() {
if (_objMgrCommonVisuals.empty) {
_objMgrCommonVisuals.createObject(polygonComponent, mapControl, true)
}
}
function removeCommonVisuals() {
_objMgrCommonVisuals.destroyObjects()
}
function addEditingVisuals() {
if (_objMgrEditingVisuals.empty) {
_objMgrEditingVisuals.createObjects([ dragHandlesComponent, splitHandlesComponent, centerDragHandleComponent ], mapControl, false /* addToMap */)
}
}
function removeEditingVisuals() {
_objMgrEditingVisuals.destroyObjects()
}
function addToolbarVisuals() {
if (_objMgrToolVisuals.empty) {
_objMgrToolVisuals.createObject(toolbarComponent, mapControl)
}
}
function removeToolVisuals() {
_objMgrToolVisuals.destroyObjects()
}
function addCircleVisuals() {
if (_objMgrCircleVisuals.empty) {
_objMgrCircleVisuals.createObject(radiusVisualsComponent, mapControl)
}
}
/// Calculate the default/initial 4 sided polygon
function defaultPolygonVertices() {
// Initial polygon is inset to take 2/3rds space
var rect = Qt.rect(mapControl.centerViewport.x, mapControl.centerViewport.y, mapControl.centerViewport.width, mapControl.centerViewport.height)
rect.x += (rect.width * 0.25) / 2
rect.y += (rect.height * 0.25) / 2
rect.width *= 0.75
rect.height *= 0.75
var centerCoord = mapControl.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */)
var topLeftCoord = mapControl.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */)
var topRightCoord = mapControl.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */)
var bottomLeftCoord = mapControl.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */)
var bottomRightCoord = mapControl.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */)
// Initial polygon has max width and height of 3000 meters
var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), 3000) / 2
var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), 3000) / 2
topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0)
topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0)
bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180)
bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180)
return [ topLeftCoord, topRightCoord, bottomRightCoord, bottomLeftCoord, centerCoord ]
}
/// Reset polygon back to initial default
function _resetPolygon() {
mapPolygon.beginReset()
mapPolygon.clear()
var initialVertices = defaultPolygonVertices()
for (var i=0; i<4; i++) {
mapPolygon.appendVertex(initialVertices[i])
}
mapPolygon.endReset()
_circleMode = false
}
function _createCircularPolygon(center, radius) {
var unboundCenter = center.atDistanceAndAzimuth(0, 0)
var segments = 16
var angleIncrement = 360 / segments
var angle = 0
mapPolygon.beginReset()
mapPolygon.clear()
_circleRadius = radius
for (var i=0; i<segments; i++) {
var vertex = unboundCenter.atDistanceAndAzimuth(radius, angle)
mapPolygon.appendVertex(vertex)
angle += angleIncrement
}
mapPolygon.endReset()
_circleMode = true
}
/// Reset polygon to a circle which fits within initial polygon
function _resetCircle() {
var initialVertices = defaultPolygonVertices()
var width = initialVertices[0].distanceTo(initialVertices[1])
var height = initialVertices[1].distanceTo(initialVertices[2])
var radius = Math.min(width, height) / 2
var center = initialVertices[4]
_createCircularPolygon(center, radius)
}
function _handleInteractiveChanged() {
if (interactive) {
addEditingVisuals()
addToolbarVisuals()
} else {
mapPolygon.traceMode = false
removeEditingVisuals()
removeToolVisuals()
}
}
function _saveCurrentVertices() {
_savedVertices = [ ]
_savedCircleMode = _circleMode
for (var i=0; i<mapPolygon.count; i++) {
_savedVertices.push(mapPolygon.vertexCoordinate(i))
}
}
function _restorePreviousVertices() {
mapPolygon.beginReset()
mapPolygon.clear()
for (var i=0; i<_savedVertices.length; i++) {
mapPolygon.appendVertex(_savedVertices[i])
}
mapPolygon.endReset()
_circleMode = _savedCircleMode
}
onInteractiveChanged: _handleInteractiveChanged()
on_CircleModeChanged: {
if (_circleMode) {
addCircleVisuals()
} else {
_objMgrCircleVisuals.destroyObjects()
}
}
Connections {
target: mapPolygon
onTraceModeChanged: {
if (mapPolygon.traceMode) {
_instructionText = _traceText
_objMgrTraceVisuals.createObject(traceMouseAreaComponent, mapControl, false)
} else {
_instructionText = _polygonToolsText
_objMgrTraceVisuals.destroyObjects()
}
}
}
Component.onCompleted: {
addCommonVisuals()
_handleInteractiveChanged()
}
Component.onDestruction: mapPolygon.traceMode = false
QGCDynamicObjectManager { id: _objMgrCommonVisuals }
QGCDynamicObjectManager { id: _objMgrToolVisuals }
QGCDynamicObjectManager { id: _objMgrEditingVisuals }
QGCDynamicObjectManager { id: _objMgrTraceVisuals }
QGCDynamicObjectManager { id: _objMgrCircleVisuals }
QGCPalette { id: qgcPal }
KMLOrSHPFileDialog {
id: kmlOrSHPLoadDialog
title: qsTr("Select Polygon File")
selectExisting: true
onAcceptedForLoad: {
mapPolygon.loadKMLOrSHPFile(file)
mapFitFunctions.fitMapViewportToMissionItems()
close()
}
}
QGCMenu {
id: menu
property int _editingVertexIndex: -1
function popupVertex(curIndex) {
menu._editingVertexIndex = curIndex
removeVertexItem.visible = (mapPolygon.count > 3 && menu._editingVertexIndex >= 0)
menu.popup()
}
function popupCenter() {
menu.popup()
}
QGCMenuItem {
id: removeVertexItem
visible: !_circleMode
text: qsTr("Remove vertex")
onTriggered: {
if (menu._editingVertexIndex >= 0) {
mapPolygon.removeVertex(menu._editingVertexIndex)
}
}
}
QGCMenuSeparator {
visible: removeVertexItem.visible
}
QGCMenuItem {
text: qsTr("Set radius..." )
visible: _circleMode
onTriggered: _editCircleRadius = true
}
QGCMenuItem {
text: qsTr("Edit position..." )
visible: _circleMode
onTriggered: mainWindow.showComponentDialog(editCenterPositionDialog, qsTr("Edit Center Position"), mainWindow.showDialogDefaultWidth, StandardButton.Close)
}
QGCMenuItem {
text: qsTr("Edit position..." )
visible: !_circleMode && menu._editingVertexIndex >= 0
onTriggered: mainWindow.showComponentDialog(editVertexPositionDialog, qsTr("Edit Vertex Position"), mainWindow.showDialogDefaultWidth, StandardButton.Close)
}
}
Component {
id: polygonComponent
MapPolygon {
color: interiorColor
opacity: interiorOpacity
border.color: borderColor
border.width: borderWidth
path: mapPolygon.path
}
}
Component {
id: splitHandleComponent
MapQuickItem {
id: mapQuickItem
anchorPoint.x: sourceItem.width / 2
anchorPoint.y: sourceItem.height / 2
visible: !_circleMode
property int vertexIndex
sourceItem: SplitIndicator {
z: _zorderSplitHandle
onClicked: if(_root.interactive) mapPolygon.splitPolygonSegment(mapQuickItem.vertexIndex)
}
}
}
Component {
id: splitHandlesComponent
Repeater {
model: mapPolygon.path
delegate: Item {
property var _splitHandle
property var _vertices: mapPolygon.path
function _setHandlePosition() {
var nextIndex = index + 1
if (nextIndex > _vertices.length - 1) {
nextIndex = 0
}
var distance = _vertices[index].distanceTo(_vertices[nextIndex])
var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex])
_splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
}
Component.onCompleted: {
_splitHandle = splitHandleComponent.createObject(mapControl)
_splitHandle.vertexIndex = index
_setHandlePosition()
mapControl.addMapItem(_splitHandle)
}
Component.onDestruction: {
if (_splitHandle) {
_splitHandle.destroy()
}
}
}
}
}
// Control which is used to drag polygon vertices
Component {
id: dragAreaComponent
MissionItemIndicatorDrag {
id: dragArea
mapControl: _root.mapControl
z: _zorderDragHandle
visible: !_circleMode
onDragStop: mapPolygon.verifyClockwiseWinding()
property int polygonVertex
property bool _creationComplete: false
Component.onCompleted: _creationComplete = true
onItemCoordinateChanged: {
if (_creationComplete) {
// During component creation some bad coordinate values got through which screws up draw
mapPolygon.adjustVertex(polygonVertex, itemCoordinate)
}
}
onClicked: if(_root.interactive) menu.popupVertex(polygonVertex)
}
}
Component {
id: centerDragHandle
MapQuickItem {
id: mapQuickItem
anchorPoint.x: dragHandle.width * 0.5
anchorPoint.y: dragHandle.height * 0.5
z: _zorderDragHandle
sourceItem: Rectangle {
id: dragHandle
width: ScreenTools.defaultFontPixelHeight * 1.5
height: width
radius: width * 0.5
color: Qt.rgba(1,1,1,0.8)
border.color: Qt.rgba(0,0,0,0.25)
border.width: 1
QGCColoredImage {
width: parent.width
height: width
color: Qt.rgba(0,0,0,1)
mipmap: true
fillMode: Image.PreserveAspectFit
source: "/qmlimages/MapCenter.svg"
sourceSize.height: height
anchors.centerIn: parent
}
}
}
}
Component {
id: dragHandleComponent
MapQuickItem {
id: mapQuickItem
anchorPoint.x: dragHandle.width / 2
anchorPoint.y: dragHandle.height / 2
z: _zorderDragHandle
visible: !_circleMode
property int polygonVertex
sourceItem: Rectangle {
id: dragHandle
width: ScreenTools.defaultFontPixelHeight * 1.5
height: width
radius: width * 0.5
color: Qt.rgba(1,1,1,0.8)
border.color: Qt.rgba(0,0,0,0.25)
border.width: 1
}
}
}
// Add all polygon vertex drag handles to the map
Component {
id: dragHandlesComponent
Repeater {
model: mapPolygon.pathModel
delegate: Item {
property var _visuals: [ ]
Component.onCompleted: {
var dragHandle = dragHandleComponent.createObject(mapControl)
dragHandle.coordinate = Qt.binding(function() { return object.coordinate })
dragHandle.polygonVertex = Qt.binding(function() { return index })
mapControl.addMapItem(dragHandle)
var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate })
dragArea.polygonVertex = Qt.binding(function() { return index })
_visuals.push(dragHandle)
_visuals.push(dragArea)
}
Component.onDestruction: {
for (var i=0; i<_visuals.length; i++) {
_visuals[i].destroy()
}
_visuals = [ ]
}
}
}
}
Component {
id: editCenterPositionDialog
EditPositionDialog {
coordinate: mapPolygon.center
onCoordinateChanged: {
// Prevent spamming signals on vertex changes by setting centerDrag = true when changing center position.
// This also fixes a bug where Qt gets confused by all the signalling and draws a bad visual.
mapPolygon.centerDrag = true
mapPolygon.center = coordinate
mapPolygon.centerDrag = false
}
}
}
Component {
id: editVertexPositionDialog
EditPositionDialog {
coordinate: mapPolygon.vertexCoordinate(menu._editingVertexIndex)
onCoordinateChanged: {
mapPolygon.adjustVertex(menu._editingVertexIndex, coordinate)
mapPolygon.verifyClockwiseWinding()
}
}
}
Component {
id: centerDragAreaComponent
MissionItemIndicatorDrag {
mapControl: _root.mapControl
z: _zorderCenterHandle
onItemCoordinateChanged: mapPolygon.center = itemCoordinate
onDragStart: mapPolygon.centerDrag = true
onDragStop: mapPolygon.centerDrag = false
}
}
Component {
id: centerDragHandleComponent
Item {
property var dragHandle
property var dragArea
Component.onCompleted: {
dragHandle = centerDragHandle.createObject(mapControl)
dragHandle.coordinate = Qt.binding(function() { return mapPolygon.center })
mapControl.addMapItem(dragHandle)
dragArea = centerDragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": mapPolygon.center })
}
Component.onDestruction: {
dragHandle.destroy()
dragArea.destroy()
}
}
}
Component {
id: toolbarComponent
PlanEditToolbar {
anchors.horizontalCenter: mapControl.left
anchors.horizontalCenterOffset: mapControl.centerViewport.left + (mapControl.centerViewport.width / 2)
y: mapControl.centerViewport.top
z: QGroundControl.zOrderMapItems + 2
availableWidth: mapControl.centerViewport.width
QGCButton {
_horizontalPadding: 0
text: qsTr("Basic")
visible: !mapPolygon.traceMode
onClicked: _resetPolygon()
}
QGCButton {
_horizontalPadding: 0
text: qsTr("Circular")
visible: !mapPolygon.traceMode
onClicked: _resetCircle()
}
QGCButton {
_horizontalPadding: 0
text: mapPolygon.traceMode ? qsTr("Done Tracing") : qsTr("Trace")
onClicked: {
if (mapPolygon.traceMode) {
if (mapPolygon.count < 3) {
_restorePreviousVertices()
}
mapPolygon.traceMode = false
} else {
_saveCurrentVertices()
_circleMode = false
mapPolygon.traceMode = true
mapPolygon.clear();
}
}
}
QGCButton {
_horizontalPadding: 0
text: qsTr("Load KML/SHP...")
onClicked: kmlOrSHPLoadDialog.openForLoad()
visible: !mapPolygon.traceMode
}
}
}
// Mouse area to capture clicks for tracing a polygon
Component {
id: traceMouseAreaComponent
MouseArea {
anchors.fill: mapControl
preventStealing: true
z: QGroundControl.zOrderMapItems + 1 // Over item indicators
onClicked: {
if (mouse.button === Qt.LeftButton && _root.interactive) {
mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */))
}
}
}
}
Component {
id: radiusDragHandleComponent
MapQuickItem {
id: mapQuickItem
anchorPoint.x: dragHandle.width / 2
anchorPoint.y: dragHandle.height / 2
z: QGroundControl.zOrderMapItems + 2
sourceItem: Rectangle {
id: dragHandle
width: ScreenTools.defaultFontPixelHeight * 1.5
height: width
radius: width / 2
color: "white"
opacity: interiorOpacity * .90
}
}
}
Component {
id: radiusDragAreaComponent
MissionItemIndicatorDrag {
mapControl: _root.mapControl
property real _lastRadius
onItemCoordinateChanged: {
var radius = mapPolygon.center.distanceTo(itemCoordinate)
// Prevent signalling re-entrancy
if (!_circleRadiusDrag && Math.abs(radius - _lastRadius) > 0.1) {
_circleRadiusDrag = true
_createCircularPolygon(mapPolygon.center, radius)
_circleRadiusDragCoord = itemCoordinate
_circleRadiusDrag = false
_lastRadius = radius
}
}
}
}
Component {
id: radiusVisualsComponent
Item {
property var circleCenterCoord: mapPolygon.center
function _calcRadiusDragCoord() {
_circleRadiusDragCoord = circleCenterCoord.atDistanceAndAzimuth(_circleRadius, 90)
}
onCircleCenterCoordChanged: {
if (!_circleRadiusDrag) {
_calcRadiusDragCoord()
}
}
QGCDynamicObjectManager {
id: _objMgr
}
Component.onCompleted: {
_calcRadiusDragCoord()
var radiusDragHandle = _objMgr.createObject(radiusDragHandleComponent, mapControl, true)
radiusDragHandle.coordinate = Qt.binding(function() { return _circleRadiusDragCoord })
var radiusDragIndicator = radiusDragAreaComponent.createObject(mapControl, { "itemIndicator": radiusDragHandle, "itemCoordinate": _circleRadiusDragCoord })
_objMgr.addObject(radiusDragIndicator)
}
}
}
}