9import QGroundControl.Controls
10import QGroundControl.FlightMap
12/// QGCMapPolygon map visuals
16 property var mapControl ///< Map control to place item in
17 property var mapPolygon ///< QGCMapPolygon object
18 property bool interactive: mapPolygon.interactive
19 property color interiorColor: "transparent"
20 property color altColor: "transparent"
21 property real interiorOpacity: 1
22 property int borderWidth: 0
23 property color borderColor: "black"
25 property bool _circleMode: false
26 property real _circleRadius
27 property bool _circleRadiusDrag: false
28 property var _circleRadiusDragCoord: QtPositioning.coordinate()
29 property bool _editCircleRadius: false
30 property string _instructionText: _polygonToolsText
31 property var _savedVertices: [ ]
32 property bool _savedCircleMode
33 property bool _isVertexBeingDragged: false
35 property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap
36 property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2
37 property real _zorderCenterHandle: QGroundControl.zOrderMapItems + 1 // Lowest such that drag or split takes precedence
39 readonly property string _polygonToolsText: qsTr("Polygon Tools")
40 readonly property string _traceText: qsTr("Click in the map to add vertices. Click 'Done Tracing' when finished.")
42 function addCommonVisuals() {
43 if (_objMgrCommonVisuals.empty) {
44 _objMgrCommonVisuals.createObject(polygonComponent, mapControl, true)
48 function removeCommonVisuals() {
49 _objMgrCommonVisuals.destroyObjects()
52 function addEditingVisuals() {
53 if (_objMgrEditingVisuals.empty) {
54 _objMgrEditingVisuals.createObjects(
55 [ dragHandlesComponent, splitHandlesComponent, centerDragHandleComponent, edgeLengthHandlesComponent ],
61 function removeEditingVisuals() {
62 _objMgrEditingVisuals.destroyObjects()
65 function addToolbarVisuals() {
66 if (_objMgrToolVisuals.empty) {
67 var toolbar = _objMgrToolVisuals.createObject(toolbarComponent, mapControl)
68 toolbar.z = QGroundControl.zOrderWidgets
72 function removeToolVisuals() {
73 _objMgrToolVisuals.destroyObjects()
76 function addCircleVisuals() {
77 if (_objMgrCircleVisuals.empty) {
78 _objMgrCircleVisuals.createObject(radiusVisualsComponent, mapControl)
82 /// Calculate the default/initial 4 sided polygon
83 function defaultPolygonVertices() {
84 // Initial polygon is inset to take 2/3rds space
85 var rect = Qt.rect(mapControl.centerViewport.x, mapControl.centerViewport.y, mapControl.centerViewport.width, mapControl.centerViewport.height)
86 rect.x += (rect.width * 0.25) / 2
87 rect.y += (rect.height * 0.25) / 2
91 var centerCoord = mapControl.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */)
92 var topLeftCoord = mapControl.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */)
93 var topRightCoord = mapControl.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */)
94 var bottomLeftCoord = mapControl.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */)
95 var bottomRightCoord = mapControl.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */)
97 // Initial polygon has max width and height of 3000 meters
98 var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), 3000) / 2
99 var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), 3000) / 2
100 topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0)
101 topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0)
102 bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180)
103 bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180)
105 return [ topLeftCoord, topRightCoord, bottomRightCoord, bottomLeftCoord ]
108 /// Reset polygon back to initial default
109 function _resetPolygon() {
110 mapPolygon.beginReset()
112 mapPolygon.appendVertices(defaultPolygonVertices())
113 mapPolygon.endReset()
117 function _createCircularPolygon(center, radius) {
118 var unboundCenter = center.atDistanceAndAzimuth(0, 0)
120 var angleIncrement = 360 / segments
122 mapPolygon.beginReset()
124 _circleRadius = radius
125 for (var i=0; i<segments; i++) {
126 var vertex = unboundCenter.atDistanceAndAzimuth(radius, angle)
127 mapPolygon.appendVertex(vertex)
128 angle += angleIncrement
130 mapPolygon.endReset()
134 /// Reset polygon to a circle which fits within initial polygon
135 function _resetCircle() {
136 var initialVertices = defaultPolygonVertices()
137 var width = initialVertices[0].distanceTo(initialVertices[1])
138 var height = initialVertices[1].distanceTo(initialVertices[2])
139 var radius = Math.min(width, height) / 2
140 var center = initialVertices[0].atDistanceAndAzimuth(width / 2, 90).atDistanceAndAzimuth(height / 2, 180)
141 _createCircularPolygon(center, radius)
144 function _handleInteractiveChanged() {
149 mapPolygon.traceMode = false
150 removeEditingVisuals()
155 function _saveCurrentVertices() {
157 _savedCircleMode = _circleMode
158 for (var i=0; i<mapPolygon.count; i++) {
159 _savedVertices.push(mapPolygon.vertexCoordinate(i))
163 function _restorePreviousVertices() {
164 mapPolygon.beginReset()
166 for (var i=0; i<_savedVertices.length; i++) {
167 mapPolygon.appendVertex(_savedVertices[i])
169 mapPolygon.endReset()
170 _circleMode = _savedCircleMode
173 onInteractiveChanged: _handleInteractiveChanged()
175 on_CircleModeChanged: {
179 _objMgrCircleVisuals.destroyObjects()
185 function onTraceModeChanged(traceMode) {
187 _instructionText = _traceText
188 _objMgrTraceVisuals.createObject(traceMouseAreaComponent, mapControl, false)
190 _instructionText = _polygonToolsText
191 _objMgrTraceVisuals.destroyObjects()
196 Component.onCompleted: {
198 _handleInteractiveChanged()
200 Component.onDestruction: mapPolygon.traceMode = false
202 QGCDynamicObjectManager { id: _objMgrCommonVisuals }
203 QGCDynamicObjectManager { id: _objMgrToolVisuals }
204 QGCDynamicObjectManager { id: _objMgrEditingVisuals }
205 QGCDynamicObjectManager { id: _objMgrTraceVisuals }
206 QGCDynamicObjectManager { id: _objMgrCircleVisuals }
208 QGCPalette { id: qgcPal }
211 id: kmlOrSHPLoadDialog
212 title: qsTr("Select Polygon File")
214 onAcceptedForLoad: (file) => {
215 mapPolygon.loadKMLOrSHPFile(file)
216 mapFitFunctions.fitMapViewportToMissionItems()
224 property int _editingVertexIndex: -1
226 function popupVertex(curIndex) {
227 menu._editingVertexIndex = curIndex
228 removeVertexItem.visible = (mapPolygon.count > 3 && menu._editingVertexIndex >= 0)
232 function popupCenter() {
238 visible: !_circleMode
239 text: qsTr("Remove vertex")
241 if (menu._editingVertexIndex >= 0) {
242 mapPolygon.removeVertex(menu._editingVertexIndex)
248 visible: removeVertexItem.visible
252 text: qsTr("Set radius..." )
254 onTriggered: _editCircleRadius = true
258 text: qsTr("Edit position..." )
260 onTriggered: editCenterPositionDialogFactory.open()
264 text: qsTr("Edit position..." )
265 visible: !_circleMode && menu._editingVertexIndex >= 0
266 onTriggered: editVertexPositionDialogFactory.open()
274 color: mapPolygon.showAltColor ? altColor : interiorColor
275 opacity: interiorOpacity
276 border.color: borderColor
277 border.width: borderWidth
278 path: mapPolygon.path
283 id: edgeLengthHandleComponent
287 anchorPoint.x: sourceItem.width / 2
288 anchorPoint.y: sourceItem.height / 2
289 visible: !_circleMode
291 property int vertexIndex
292 property real distance
294 property var _unitsConversion: QGroundControl.unitsConversion
297 text: _unitsConversion.metersToAppSettingsHorizontalDistanceUnits(distance).toFixed(1) + " " +
298 _unitsConversion.appSettingsHorizontalDistanceUnitsString
305 id: edgeLengthHandlesComponent
308 model: _isVertexBeingDragged ? mapPolygon.path : undefined
311 property var _edgeLengthHandle
312 property var _vertices: mapPolygon.path
314 function _setHandlePosition() {
315 var nextIndex = index + 1
316 if (nextIndex > _vertices.length - 1) {
319 var distance = _vertices[index].distanceTo(_vertices[nextIndex])
320 var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex])
321 _edgeLengthHandle.coordinate =_vertices[index].atDistanceAndAzimuth(distance / 3, azimuth)
322 _edgeLengthHandle.distance = distance
325 Component.onCompleted: {
326 _edgeLengthHandle = edgeLengthHandleComponent.createObject(mapControl)
327 _edgeLengthHandle.vertexIndex = index
329 mapControl.addMapItem(_edgeLengthHandle)
332 Component.onDestruction: {
333 if (_edgeLengthHandle) {
334 _edgeLengthHandle.destroy()
342 id: splitHandleComponent
346 anchorPoint.x: sourceItem.width / 2
347 anchorPoint.y: sourceItem.height / 2
348 visible: !_circleMode
350 property int vertexIndex
352 sourceItem: SplitIndicator {
353 z: _zorderSplitHandle
354 onClicked: if(_root.interactive) mapPolygon.splitPolygonSegment(mapQuickItem.vertexIndex)
360 id: splitHandlesComponent
363 model: mapPolygon.path
366 property var _splitHandle
367 property var _vertices: mapPolygon.path
369 function _setHandlePosition() {
370 var nextIndex = index + 1
371 if (nextIndex > _vertices.length - 1) {
374 var distance = _vertices[index].distanceTo(_vertices[nextIndex])
375 var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex])
376 _splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
379 Component.onCompleted: {
380 _splitHandle = splitHandleComponent.createObject(mapControl)
381 _splitHandle.vertexIndex = index
383 mapControl.addMapItem(_splitHandle)
386 Component.onDestruction: {
388 _splitHandle.destroy()
395 // Control which is used to drag polygon vertices
397 id: dragAreaComponent
399 MissionItemIndicatorDrag {
401 mapControl: _root.mapControl
403 visible: !_circleMode
404 onDragStart: _isVertexBeingDragged = true
405 onDragStop: { _isVertexBeingDragged = false; mapPolygon.verifyClockwiseWinding() }
407 property int polygonVertex
409 property bool _creationComplete: false
411 Component.onCompleted: _creationComplete = true
413 onItemCoordinateChanged: {
414 if (_creationComplete) {
415 // During component creation some bad coordinate values got through which screws up draw
416 mapPolygon.adjustVertex(polygonVertex, itemCoordinate)
420 onClicked: if(_root.interactive) menu.popupVertex(polygonVertex)
428 anchorPoint.x: dragHandle.width * 0.5
429 anchorPoint.y: dragHandle.height * 0.5
431 sourceItem: Rectangle {
433 width: ScreenTools.defaultFontPixelHeight * 1.5
436 color: Qt.rgba(1,1,1,0.8)
437 border.color: Qt.rgba(0,0,0,0.25)
442 color: Qt.rgba(0,0,0,1)
444 fillMode: Image.PreserveAspectFit
445 source: "/qmlimages/MapCenter.svg"
446 sourceSize.height: height
447 anchors.centerIn: parent
454 id: dragHandleComponent
458 anchorPoint.x: dragHandle.width / 2
459 anchorPoint.y: dragHandle.height / 2
461 visible: !_circleMode
463 property int polygonVertex
465 sourceItem: Rectangle {
467 width: ScreenTools.defaultFontPixelHeight * 1.5
470 color: Qt.rgba(1,1,1,0.8)
471 border.color: Qt.rgba(0,0,0,0.25)
477 // Add all polygon vertex drag handles to the map
479 id: dragHandlesComponent
482 model: mapPolygon.pathModel
485 property var _visuals: [ ]
487 Component.onCompleted: {
488 var dragHandle = dragHandleComponent.createObject(mapControl)
489 dragHandle.coordinate = Qt.binding(function() { return object.coordinate })
490 dragHandle.polygonVertex = Qt.binding(function() { return index })
491 mapControl.addMapItem(dragHandle)
492 var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate })
493 dragArea.polygonVertex = Qt.binding(function() { return index })
494 _visuals.push(dragHandle)
495 _visuals.push(dragArea)
498 Component.onDestruction: {
499 for (var i=0; i<_visuals.length; i++) {
500 _visuals[i].destroy()
508 QGCPopupDialogFactory {
509 id: editCenterPositionDialogFactory
511 dialogComponent: editCenterPositionDialog
515 id: editCenterPositionDialog
518 title: qsTr("Edit Center Position")
519 coordinate: mapPolygon.center
520 onCoordinateChanged: {
521 // Prevent spamming signals on vertex changes by setting centerDrag = true when changing center position.
522 // This also fixes a bug where Qt gets confused by all the signalling and draws a bad visual.
523 mapPolygon.centerDrag = true
524 mapPolygon.center = coordinate
525 mapPolygon.centerDrag = false
530 QGCPopupDialogFactory {
531 id: editVertexPositionDialogFactory
533 dialogComponent: editVertexPositionDialog
537 id: editVertexPositionDialog
540 title: qsTr("Edit Vertex Position")
541 coordinate: mapPolygon.vertexCoordinate(menu._editingVertexIndex)
542 onCoordinateChanged: {
543 mapPolygon.adjustVertex(menu._editingVertexIndex, coordinate)
544 mapPolygon.verifyClockwiseWinding()
550 id: centerDragAreaComponent
552 MissionItemIndicatorDrag {
553 mapControl: _root.mapControl
554 z: _zorderCenterHandle
555 onItemCoordinateChanged: mapPolygon.center = itemCoordinate
556 onDragStart: mapPolygon.centerDrag = true
557 onDragStop: mapPolygon.centerDrag = false
562 id: centerDragHandleComponent
565 property var dragHandle
566 property var dragArea
568 Component.onCompleted: {
569 dragHandle = centerDragHandle.createObject(mapControl)
570 dragHandle.coordinate = Qt.binding(function() { return mapPolygon.center })
571 mapControl.addMapItem(dragHandle)
572 dragArea = centerDragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": mapPolygon.center })
575 Component.onDestruction: {
586 anchors.horizontalCenter: mapControl.left
587 anchors.horizontalCenterOffset: mapControl.centerViewport.left + (mapControl.centerViewport.width / 2)
588 y: mapControl.centerViewport.top
589 availableWidth: mapControl.centerViewport.width
592 _horizontalPadding: 0
594 visible: !mapPolygon.traceMode
595 onClicked: _resetPolygon()
599 _horizontalPadding: 0
600 text: qsTr("Circular")
601 visible: !mapPolygon.traceMode
602 onClicked: _resetCircle()
606 _horizontalPadding: 0
607 text: mapPolygon.traceMode ? qsTr("Done Tracing") : qsTr("Trace")
609 if (mapPolygon.traceMode) {
610 if (mapPolygon.count < 3) {
611 _restorePreviousVertices()
613 mapPolygon.traceMode = false
615 _saveCurrentVertices()
617 mapPolygon.traceMode = true
624 _horizontalPadding: 0
625 text: qsTr("Load KML/SHP...")
626 onClicked: kmlOrSHPLoadDialog.openForLoad()
627 visible: !mapPolygon.traceMode
632 // Mouse area to capture clicks for tracing a polygon
634 id: traceMouseAreaComponent
637 anchors.fill: mapControl
638 preventStealing: true
639 z: QGroundControl.zOrderMapItems + 1 // Over item indicators
641 onClicked: (mouse) => {
642 if (mouse.button === Qt.LeftButton && _root.interactive) {
643 mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */))
650 id: radiusDragHandleComponent
654 anchorPoint.x: dragHandle.width / 2
655 anchorPoint.y: dragHandle.height / 2
656 z: QGroundControl.zOrderMapItems + 2
658 sourceItem: Rectangle {
660 width: ScreenTools.defaultFontPixelHeight * 1.5
664 opacity: interiorOpacity * .90
670 id: radiusDragDebounceTimer
674 property var pendingCoord: undefined
677 // re-build the circular polygon only once per event loop
679 var coord = pendingCoord
680 pendingCoord = undefined
681 var radius = mapPolygon.center.distanceTo(coord)
682 _createCircularPolygon(mapPolygon.center, radius)
688 id: radiusDragAreaComponent
690 MissionItemIndicatorDrag {
691 mapControl: _root.mapControl
693 onItemCoordinateChanged: {
694 var radius = mapPolygon.center.distanceTo(itemCoordinate)
696 if (Math.abs(radius - _circleRadius) > 0.1) {
697 // De-bounced circular polygon re-drawing
698 radiusDragDebounceTimer.pendingCoord = itemCoordinate
699 radiusDragDebounceTimer.start()
706 id: radiusVisualsComponent
709 property var circleCenterCoord: mapPolygon.center
711 function _calcRadiusDragCoord() {
712 _circleRadiusDragCoord = circleCenterCoord.atDistanceAndAzimuth(_circleRadius, 90)
715 onCircleCenterCoordChanged: {
716 if (!_circleRadiusDrag) {
717 _calcRadiusDragCoord()
721 QGCDynamicObjectManager {
725 Component.onCompleted: {
726 _calcRadiusDragCoord()
727 var radiusDragHandle = _objMgr.createObject(radiusDragHandleComponent, mapControl, true)
728 radiusDragHandle.coordinate = Qt.binding(function() { return _circleRadiusDragCoord })
729 var radiusDragIndicator = radiusDragAreaComponent.createObject(mapControl, { "itemIndicator": radiusDragHandle, "itemCoordinate": _circleRadiusDragCoord })
730 _objMgr.addObject(radiusDragIndicator)