/**************************************************************************** * * (c) 2009-2016 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 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 bool _traceMode: 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 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 } } } 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: 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: !_traceMode onClicked: _resetPolygon() } QGCButton { _horizontalPadding: 0 text: qsTr("Circular") visible: !_traceMode onClicked: _resetCircle() } QGCButton { _horizontalPadding: 0 text: _traceMode ? qsTr("Done Tracing") : qsTr("Trace Polygon") onClicked: { if (_traceMode) { if (mapPolygon.count < 3) { _restorePreviousVertices() } _traceMode = false } else { _saveCurrentVertices() _circleMode = false _traceMode = true mapPolygon.clear(); } } } QGCButton { _horizontalPadding: 0 text: qsTr("Load KML/SHP...") onClicked: kmlOrSHPLoadDialog.openForLoad() visible: !_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) { 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: .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) } } } }