QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCMapPolygonVisuals.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtLocation
4import QtPositioning
5import QtQuick.Dialogs
6import QtQuick.Layouts
7
8import QGroundControl
9import QGroundControl.Controls
10import QGroundControl.FlightMap
11import QGroundControl.PlanView
12
13/// QGCMapPolygon map visuals
14Item {
15 id: _root
16
17 property var mapControl ///< Map control to place item in
18 property var mapPolygon ///< QGCMapPolygon object
19 property bool interactive: mapPolygon.interactive
20 property color interiorColor: "transparent"
21 property color altColor: "transparent"
22 property real interiorOpacity: 1
23 property int borderWidth: 0
24 property color borderColor: "black"
25
26 property bool _circleMode: false
27 property real _circleRadius
28 property bool _circleRadiusDrag: false
29 property var _circleRadiusDragCoord: QtPositioning.coordinate()
30 property string _instructionText: _polygonToolsText
31 property var _savedVertices: [ ]
32 property bool _savedCircleMode
33 property bool _isVertexBeingDragged: false
34
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
38
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.")
41
42 function addCommonVisuals() {
43 if (_objMgrCommonVisuals.empty) {
44 _objMgrCommonVisuals.createObject(polygonComponent, mapControl, true)
45 }
46 }
47
48 function removeCommonVisuals() {
49 _objMgrCommonVisuals.destroyObjects()
50 }
51
52 function addEditingVisuals() {
53 if (_objMgrEditingVisuals.empty) {
54 _objMgrEditingVisuals.createObjects(
55 [ dragHandlesComponent, splitHandlesComponent, centerDragHandleComponent, edgeLengthHandlesComponent ],
56 mapControl,
57 false /* addToMap */)
58 }
59 }
60
61 function removeEditingVisuals() {
62 _objMgrEditingVisuals.destroyObjects()
63 }
64
65 function addToolbarVisuals() {
66 if (_objMgrToolVisuals.empty) {
67 var toolbar = _objMgrToolVisuals.createObject(toolbarComponent, mapControl)
68 toolbar.z = QGroundControl.zOrderWidgets
69 }
70 }
71
72 function removeToolVisuals() {
73 _objMgrToolVisuals.destroyObjects()
74 }
75
76 function addCircleVisuals() {
77 if (_objMgrCircleVisuals.empty) {
78 _objMgrCircleVisuals.createObject(radiusVisualsComponent, mapControl)
79 }
80 }
81
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
88 rect.width *= 0.75
89 rect.height *= 0.75
90
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 */)
96
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)
104
105 return [ topLeftCoord, topRightCoord, bottomRightCoord, bottomLeftCoord ]
106 }
107
108 /// Reset polygon back to initial default
109 function _resetPolygon() {
110 mapPolygon.beginReset()
111 mapPolygon.clear()
112 mapPolygon.appendVertices(defaultPolygonVertices())
113 mapPolygon.endReset()
114 _circleMode = false
115 }
116
117 function _createCircularPolygon(center, radius) {
118 var unboundCenter = center.atDistanceAndAzimuth(0, 0)
119 var segments = 16
120 var angleIncrement = 360 / segments
121 var angle = 0
122 mapPolygon.beginReset()
123 mapPolygon.clear()
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
129 }
130 mapPolygon.endReset()
131 _circleMode = true
132 }
133
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)
142 }
143
144 function _handleInteractiveChanged() {
145 if (interactive) {
146 addEditingVisuals()
147 addToolbarVisuals()
148 } else {
149 mapPolygon.traceMode = false
150 removeEditingVisuals()
151 removeToolVisuals()
152 }
153 }
154
155 function _saveCurrentVertices() {
156 _savedVertices = [ ]
157 _savedCircleMode = _circleMode
158 for (var i=0; i<mapPolygon.count; i++) {
159 _savedVertices.push(mapPolygon.vertexCoordinate(i))
160 }
161 }
162
163 function _restorePreviousVertices() {
164 mapPolygon.beginReset()
165 mapPolygon.clear()
166 for (var i=0; i<_savedVertices.length; i++) {
167 mapPolygon.appendVertex(_savedVertices[i])
168 }
169 mapPolygon.endReset()
170 _circleMode = _savedCircleMode
171 }
172
173 onInteractiveChanged: _handleInteractiveChanged()
174
175 on_CircleModeChanged: {
176 if (_circleMode) {
177 addCircleVisuals()
178 } else {
179 _objMgrCircleVisuals.destroyObjects()
180 }
181 }
182
183 Connections {
184 target: mapPolygon
185 function onTraceModeChanged(traceMode) {
186 if (traceMode) {
187 _instructionText = _traceText
188 _objMgrTraceVisuals.createObject(traceMouseAreaComponent, mapControl, false)
189 } else {
190 _instructionText = _polygonToolsText
191 _objMgrTraceVisuals.destroyObjects()
192 }
193 }
194 }
195
196 Component.onCompleted: {
197 addCommonVisuals()
198 _handleInteractiveChanged()
199 }
200 Component.onDestruction: mapPolygon.traceMode = false
201
202 QGCDynamicObjectManager { id: _objMgrCommonVisuals }
203 QGCDynamicObjectManager { id: _objMgrToolVisuals }
204 QGCDynamicObjectManager { id: _objMgrEditingVisuals }
205 QGCDynamicObjectManager { id: _objMgrTraceVisuals }
206 QGCDynamicObjectManager { id: _objMgrCircleVisuals }
207
208 QGCPalette { id: qgcPal }
209
210 KMLOrSHPFileDialog {
211 id: kmlOrSHPLoadDialog
212 title: qsTr("Select Polygon File")
213
214 onAcceptedForLoad: (file) => {
215 mapPolygon.loadKMLOrSHPFile(file)
216 mapFitFunctions.fitMapViewportToMissionItems()
217 close()
218 }
219 }
220
221 QGCMenu {
222 id: menu
223
224 property int _editingVertexIndex: -1
225
226 function popupVertex(curIndex) {
227 menu._editingVertexIndex = curIndex
228 removeVertexItem.visible = (mapPolygon.count > 3 && menu._editingVertexIndex >= 0)
229 menu.popup()
230 }
231
232 function popupCenter() {
233 menu.popup()
234 }
235
236 QGCMenuItem {
237 id: removeVertexItem
238 visible: !_circleMode
239 text: qsTr("Remove vertex")
240 onTriggered: {
241 if (menu._editingVertexIndex >= 0) {
242 mapPolygon.removeVertex(menu._editingVertexIndex)
243 }
244 }
245 }
246
247 QGCMenuSeparator {
248 visible: removeVertexItem.visible
249 }
250
251 QGCMenuItem {
252 text: qsTr("Set radius..." )
253 visible: _circleMode
254 onTriggered: editCircleRadiusDialogFactory.open()
255 }
256
257 QGCMenuItem {
258 text: qsTr("Edit position..." )
259 visible: _circleMode
260 onTriggered: editCenterPositionDialogFactory.open()
261 }
262
263 QGCMenuItem {
264 text: qsTr("Edit position..." )
265 visible: !_circleMode && menu._editingVertexIndex >= 0
266 onTriggered: editVertexPositionDialogFactory.open()
267 }
268 }
269
270 Component {
271 id: polygonComponent
272
273 MapPolygon {
274 color: mapPolygon.showAltColor ? altColor : interiorColor
275 opacity: interiorOpacity
276 visible: _root.visible
277 border.color: mapPolygon.vertexDrag ? "orange" : borderColor
278 border.width: mapPolygon.vertexDrag ? 3 : borderWidth
279 path: mapPolygon.vertexDrag ? mapPolygon.dragPath : mapPolygon.path
280 }
281 }
282
283 Component {
284 id: edgeLengthHandleComponent
285
286 MapQuickItem {
287 id: mapQuickItem
288 anchorPoint.x: sourceItem.width / 2
289 anchorPoint.y: sourceItem.height / 2
290 visible: !_circleMode
291
292 property int vertexIndex
293 property real distance
294
295 property var _unitsConversion: QGroundControl.unitsConversion
296
297 sourceItem: Text {
298 text: _unitsConversion.metersToAppSettingsHorizontalDistanceUnits(distance).toFixed(1) + " " +
299 _unitsConversion.appSettingsHorizontalDistanceUnitsString
300 color: "white"
301 }
302 }
303 }
304
305 Component {
306 id: edgeLengthHandlesComponent
307
308 Repeater {
309 model: _isVertexBeingDragged ? mapPolygon.path : undefined
310
311 delegate: Item {
312 property var _edgeLengthHandle
313
314 function _setHandlePosition() {
315 var vertices = mapPolygon.vertexDrag ? mapPolygon.dragPath : mapPolygon.path
316 var nextIndex = index + 1
317 if (nextIndex > vertices.length - 1) {
318 nextIndex = 0
319 }
320 var distance = vertices[index].distanceTo(vertices[nextIndex])
321 var azimuth = vertices[index].azimuthTo(vertices[nextIndex])
322 _edgeLengthHandle.coordinate = vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
323 _edgeLengthHandle.distance = distance
324 }
325
326 Connections {
327 target: mapPolygon
328 function onDragPathChanged() { _setHandlePosition() }
329 }
330
331 Component.onCompleted: {
332 _edgeLengthHandle = edgeLengthHandleComponent.createObject(mapControl)
333 _edgeLengthHandle.vertexIndex = index
334 _setHandlePosition()
335 mapControl.addMapItem(_edgeLengthHandle)
336 }
337
338 Component.onDestruction: {
339 if (_edgeLengthHandle) {
340 _edgeLengthHandle.destroy()
341 }
342 }
343 }
344 }
345 }
346
347 Component {
348 id: splitHandleComponent
349
350 MapQuickItem {
351 id: mapQuickItem
352 anchorPoint.x: sourceItem.width / 2
353 anchorPoint.y: sourceItem.height / 2
354 visible: !_circleMode && !_isVertexBeingDragged && !mapPolygon.centerDrag
355
356 property int vertexIndex
357
358 sourceItem: SplitIndicator {
359 z: _zorderSplitHandle
360 onClicked: if(_root.interactive) mapPolygon.splitPolygonSegment(mapQuickItem.vertexIndex)
361 }
362 }
363 }
364
365 Component {
366 id: splitHandlesComponent
367
368 Repeater {
369 model: mapPolygon.path
370
371 delegate: Item {
372 property var _splitHandle
373 property var _vertices: mapPolygon.path
374
375 function _setHandlePosition() {
376 var nextIndex = index + 1
377 if (nextIndex > _vertices.length - 1) {
378 nextIndex = 0
379 }
380 var distance = _vertices[index].distanceTo(_vertices[nextIndex])
381 var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex])
382 _splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
383 }
384
385 Component.onCompleted: {
386 _splitHandle = splitHandleComponent.createObject(mapControl)
387 _splitHandle.vertexIndex = index
388 _setHandlePosition()
389 mapControl.addMapItem(_splitHandle)
390 }
391
392 Component.onDestruction: {
393 if (_splitHandle) {
394 _splitHandle.destroy()
395 }
396 }
397 }
398 }
399 }
400
401 // Control which is used to drag polygon vertices
402 Component {
403 id: dragAreaComponent
404
405 MissionItemIndicatorDrag {
406 id: dragArea
407 mapControl: _root.mapControl
408 z: _zorderDragHandle
409 visible: !_circleMode
410 onDragStart: { _isVertexBeingDragged = true; mapPolygon.vertexDrag = true }
411 onDragStop: { _isVertexBeingDragged = false; mapPolygon.vertexDrag = false; mapPolygon.verifyClockwiseWinding() }
412
413 property int polygonVertex
414
415 property bool _creationComplete: false
416
417 Component.onCompleted: _creationComplete = true
418
419 onItemCoordinateChanged: {
420 if (_creationComplete) {
421 // During component creation some bad coordinate values got through which screws up draw
422 mapPolygon.adjustVertex(polygonVertex, itemCoordinate)
423 }
424 }
425
426 onClicked: if(_root.interactive) menu.popupVertex(polygonVertex)
427 }
428 }
429
430 Component {
431 id: centerDragHandle
432 MapQuickItem {
433 id: mapQuickItem
434 anchorPoint.x: dragHandle.width * 0.5
435 anchorPoint.y: dragHandle.height * 0.5
436 z: _zorderDragHandle
437 visible: !_isVertexBeingDragged
438 sourceItem: Rectangle {
439 id: dragHandle
440 width: ScreenTools.defaultFontPixelHeight * 1.5
441 height: width
442 radius: width * 0.5
443 color: Qt.rgba(1,1,1,0.8)
444 border.color: Qt.rgba(0,0,0,0.25)
445 border.width: 1
446 QGCColoredImage {
447 width: parent.width
448 height: width
449 color: Qt.rgba(0,0,0,1)
450 mipmap: true
451 fillMode: Image.PreserveAspectFit
452 source: "/qmlimages/MapCenter.svg"
453 sourceSize.height: height
454 anchors.centerIn: parent
455 }
456 }
457 }
458 }
459
460 Component {
461 id: dragHandleComponent
462
463 MapQuickItem {
464 id: mapQuickItem
465 anchorPoint.x: dragHandle.width / 2
466 anchorPoint.y: dragHandle.height / 2
467 z: _zorderDragHandle
468 visible: !_circleMode && !mapPolygon.centerDrag
469
470 property int polygonVertex
471
472 sourceItem: Rectangle {
473 id: dragHandle
474 width: ScreenTools.defaultFontPixelHeight * 1.5
475 height: width
476 radius: width * 0.5
477 color: Qt.rgba(1,1,1,0.8)
478 border.color: Qt.rgba(0,0,0,0.25)
479 border.width: 1
480 }
481 }
482 }
483
484 // Add all polygon vertex drag handles to the map
485 Component {
486 id: dragHandlesComponent
487
488 Repeater {
489 model: mapPolygon.pathModel
490
491 delegate: Item {
492 property var _visuals: [ ]
493
494 Component.onCompleted: {
495 var dragHandle = dragHandleComponent.createObject(mapControl)
496 dragHandle.coordinate = Qt.binding(function() { return object.coordinate })
497 dragHandle.polygonVertex = Qt.binding(function() { return index })
498 mapControl.addMapItem(dragHandle)
499 var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate })
500 dragArea.polygonVertex = Qt.binding(function() { return index })
501 _visuals.push(dragHandle)
502 _visuals.push(dragArea)
503 }
504
505 Component.onDestruction: {
506 for (var i=0; i<_visuals.length; i++) {
507 _visuals[i].destroy()
508 }
509 _visuals = [ ]
510 }
511 }
512 }
513 }
514
515 QGCPopupDialogFactory {
516 id: editCircleRadiusDialogFactory
517
518 dialogComponent: editCircleRadiusDialog
519 }
520
521 Component {
522 id: editCircleRadiusDialog
523
524 QGCPopupDialog {
525 id: popupDialog
526 title: qsTr("Set Radius")
527 buttons: Dialog.Save | Dialog.Cancel
528
529 onAccepted: {
530 const appRadius = Number(radiusField.text)
531 if (!isNaN(appRadius) && appRadius > 0) {
532 const radiusMeters = QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsToMeters(appRadius)
533 _createCircularPolygon(mapPolygon.center, radiusMeters)
534 } else {
535 preventClose = true
536 }
537 }
538
539 ColumnLayout {
540 width: ScreenTools.defaultFontPixelWidth * 30
541 spacing: ScreenTools.defaultFontPixelHeight
542
543 QGCLabel {
544 Layout.fillWidth: true
545 text: qsTr("Enter circle radius.")
546 wrapMode: Text.WordWrap
547 }
548
549 QGCTextField {
550 id: radiusField
551 Layout.fillWidth: true
552 text: QGroundControl.unitsConversion.metersToAppSettingsHorizontalDistanceUnits(_circleRadius).toFixed(1)
553 validator: DoubleValidator { bottom: 0.1; notation: DoubleValidator.StandardNotation }
554 inputMethodHints: Qt.ImhFormattedNumbersOnly
555 }
556
557 QGCLabel {
558 Layout.fillWidth: true
559 text: QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString
560 }
561 }
562 }
563 }
564
565 QGCPopupDialogFactory {
566 id: editCenterPositionDialogFactory
567
568 dialogComponent: editCenterPositionDialog
569 }
570
571 Component {
572 id: editCenterPositionDialog
573
574 EditPositionDialog {
575 title: qsTr("Edit Center Position")
576 coordinate: mapPolygon.center
577 onCoordinateChanged: {
578 // Prevent spamming signals on vertex changes by setting centerDrag = true when changing center position.
579 // This also fixes a bug where Qt gets confused by all the signalling and draws a bad visual.
580 mapPolygon.centerDrag = true
581 mapPolygon.center = coordinate
582 mapPolygon.centerDrag = false
583 }
584 }
585 }
586
587 QGCPopupDialogFactory {
588 id: editVertexPositionDialogFactory
589
590 dialogComponent: editVertexPositionDialog
591 }
592
593 Component {
594 id: editVertexPositionDialog
595
596 EditPositionDialog {
597 title: qsTr("Edit Vertex Position")
598 coordinate: mapPolygon.vertexCoordinate(menu._editingVertexIndex)
599 onCoordinateChanged: {
600 mapPolygon.adjustVertex(menu._editingVertexIndex, coordinate)
601 mapPolygon.verifyClockwiseWinding()
602 }
603 }
604 }
605
606 Component {
607 id: centerDragAreaComponent
608
609 MissionItemIndicatorDrag {
610 mapControl: _root.mapControl
611 z: _zorderCenterHandle
612 onItemCoordinateChanged: mapPolygon.center = itemCoordinate
613 onDragStart: { mapPolygon.centerDrag = true; mapPolygon.vertexDrag = true }
614 onDragStop: { mapPolygon.centerDrag = false; mapPolygon.vertexDrag = false }
615 onClicked: if(_root.interactive) menu.popupCenter()
616 }
617 }
618
619 Component {
620 id: centerDragHandleComponent
621
622 Item {
623 property var dragHandle
624 property var dragArea
625
626 Component.onCompleted: {
627 dragHandle = centerDragHandle.createObject(mapControl)
628 dragHandle.coordinate = Qt.binding(function() { return mapPolygon.centerDrag ? mapPolygon.dragCenter : mapPolygon.center })
629 mapControl.addMapItem(dragHandle)
630 dragArea = centerDragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": mapPolygon.center })
631 }
632
633 Component.onDestruction: {
634 dragHandle.destroy()
635 dragArea.destroy()
636 }
637 }
638 }
639
640 Component {
641 id: toolbarComponent
642
643 PlanEditToolbar {
644 anchors.horizontalCenter: mapControl.left
645 anchors.horizontalCenterOffset: mapControl.centerViewport.left + (mapControl.centerViewport.width / 2)
646 y: mapControl.centerViewport.top
647 availableWidth: mapControl.centerViewport.width
648
649 QGCButton {
650 _horizontalPadding: 0
651 text: qsTr("Basic")
652 visible: !mapPolygon.traceMode
653 onClicked: _resetPolygon()
654 }
655
656 QGCButton {
657 _horizontalPadding: 0
658 text: qsTr("Circular")
659 visible: !mapPolygon.traceMode
660 onClicked: _resetCircle()
661 }
662
663 QGCButton {
664 _horizontalPadding: 0
665 text: mapPolygon.traceMode ? qsTr("Done Tracing") : qsTr("Trace")
666 onClicked: {
667 if (mapPolygon.traceMode) {
668 if (mapPolygon.count < 3) {
669 _restorePreviousVertices()
670 }
671 mapPolygon.traceMode = false
672 } else {
673 _saveCurrentVertices()
674 _circleMode = false
675 mapPolygon.traceMode = true
676 mapPolygon.clear();
677 }
678 }
679 }
680
681 QGCButton {
682 _horizontalPadding: 0
683 text: qsTr("Load KML/SHP...")
684 onClicked: kmlOrSHPLoadDialog.openForLoad()
685 visible: !mapPolygon.traceMode
686 }
687 }
688 }
689
690 // Mouse area to capture clicks for tracing a polygon
691 Component {
692 id: traceMouseAreaComponent
693
694 MouseArea {
695 anchors.fill: mapControl
696 preventStealing: true
697 z: QGroundControl.zOrderMapItems + 1 // Over item indicators
698
699 onClicked: (mouse) => {
700 if (mouse.button === Qt.LeftButton && _root.interactive) {
701 mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */))
702 }
703 }
704 }
705 }
706
707 Component {
708 id: radiusDragHandleComponent
709
710 MapQuickItem {
711 id: mapQuickItem
712 anchorPoint.x: dragHandle.width / 2
713 anchorPoint.y: dragHandle.height / 2
714 z: QGroundControl.zOrderMapItems + 2
715 visible: !mapPolygon.centerDrag
716
717 sourceItem: Rectangle {
718 id: dragHandle
719 width: ScreenTools.defaultFontPixelHeight * 1.5
720 height: width
721 radius: width / 2
722 color: "white"
723 opacity: interiorOpacity * .90
724 }
725 }
726 }
727
728 Timer {
729 id: radiusDragDebounceTimer
730 interval: 0
731 repeat: false
732
733 property var pendingCoord: undefined
734
735 onTriggered: {
736 // re-build the circular polygon only once per event loop
737 if (pendingCoord) {
738 var coord = pendingCoord
739 pendingCoord = undefined
740 var radius = mapPolygon.center.distanceTo(coord)
741 _createCircularPolygon(mapPolygon.center, radius)
742 }
743 }
744 }
745
746 Component {
747 id: radiusDragAreaComponent
748
749 MissionItemIndicatorDrag {
750 mapControl: _root.mapControl
751 onDragStart: {
752 _circleRadiusDrag = true
753 mapPolygon.vertexDrag = true
754 }
755 onDragStop: {
756 _circleRadiusDrag = false
757 mapPolygon.vertexDrag = false
758 }
759
760 onItemCoordinateChanged: {
761 // Keep the handle visually attached to the cursor while radius updates are de-bounced.
762 _circleRadiusDragCoord = itemCoordinate
763
764 var radius = mapPolygon.center.distanceTo(itemCoordinate)
765
766 if (Math.abs(radius - _circleRadius) > 0.1) {
767 // De-bounced circular polygon re-drawing
768 radiusDragDebounceTimer.pendingCoord = itemCoordinate
769 radiusDragDebounceTimer.start()
770 }
771 }
772 }
773 }
774
775 Component {
776 id: radiusVisualsComponent
777
778 Item {
779 property var circleCenterCoord: mapPolygon.center
780
781 function _calcRadiusDragCoord() {
782 _circleRadiusDragCoord = circleCenterCoord.atDistanceAndAzimuth(_circleRadius, 90)
783 }
784
785 onCircleCenterCoordChanged: {
786 if (!_circleRadiusDrag) {
787 _calcRadiusDragCoord()
788 }
789 }
790
791 QGCDynamicObjectManager {
792 id: _objMgr
793 }
794
795 Component.onCompleted: {
796 _calcRadiusDragCoord()
797 var radiusDragHandle = _objMgr.createObject(radiusDragHandleComponent, mapControl, true)
798 radiusDragHandle.coordinate = Qt.binding(function() { return _circleRadiusDragCoord })
799 var radiusDragIndicator = radiusDragAreaComponent.createObject(mapControl, { "itemIndicator": radiusDragHandle, "itemCoordinate": _circleRadiusDragCoord })
800 _objMgr.addObject(radiusDragIndicator)
801 }
802 }
803 }
804}