QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCMapPolylineVisuals.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtLocation
4import QtPositioning
5import QtQuick.Dialogs
6
7import QGroundControl
8import QGroundControl.Controls
9import QGroundControl.FlightMap
10import QGroundControl.PlanView
11
12/// QGCMapPolyline map visuals
13Item {
14 id: _root
15
16 property var mapControl ///< Map control to place item in
17 property var mapPolyline ///< QGCMapPolyline object
18 property bool interactive: mapPolyline.interactive
19 property int lineWidth: 3
20 property color lineColor: "#be781c"
21
22 property var _dragHandlesComponent
23 property var _splitHandlesComponent
24 property string _instructionText: _corridorToolsText
25 property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap
26 property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2
27 property var _savedVertices: [ ]
28 property bool _isVertexBeingDragged: false
29 property bool dragging: _isVertexBeingDragged
30
31 readonly property string _corridorToolsText: qsTr("Polyline Tools")
32 readonly property string _traceText: qsTr("Click in the map to add vertices. Click 'Done Tracing' when finished.")
33
34 function _addCommonVisuals() {
35 if (_objMgrCommonVisuals.empty) {
36 _objMgrCommonVisuals.createObject(polylineComponent, mapControl, true)
37 }
38 }
39
40 function _addInteractiveVisuals() {
41 if (_objMgrInteractiveVisuals.empty) {
42 _objMgrInteractiveVisuals.createObjects([ dragHandlesComponent, splitHandlesComponent, edgeLengthHandlesComponent, toolbarComponent ], mapControl)
43 }
44 }
45
46 /// Calculate the default/initial polyline
47 function _defaultPolylineVertices() {
48 var x = mapControl.centerViewport.x + (mapControl.centerViewport.width / 2)
49 var yInset = mapControl.centerViewport.height / 4
50 var topPointCoord = mapControl.toCoordinate(Qt.point(x, mapControl.centerViewport.y + yInset), false /* clipToViewPort */)
51 var bottomPointCoord = mapControl.toCoordinate(Qt.point(x, mapControl.centerViewport.y + mapControl.centerViewport.height - yInset), false /* clipToViewPort */)
52 return [ topPointCoord, bottomPointCoord ]
53 }
54
55 /// Reset polyline back to initial default
56 function _resetPolyline() {
57 mapPolyline.beginReset()
58 mapPolyline.clear()
59 var initialVertices = _defaultPolylineVertices()
60 mapPolyline.appendVertex(initialVertices[0])
61 mapPolyline.appendVertex(initialVertices[1])
62 mapPolyline.endReset()
63 }
64
65 function _saveCurrentVertices() {
66 _savedVertices = [ ]
67 for (var i=0; i<mapPolyline.count; i++) {
68 _savedVertices.push(mapPolyline.vertexCoordinate(i))
69 }
70 }
71
72 function _restorePreviousVertices() {
73 mapPolyline.beginReset()
74 mapPolyline.clear()
75 for (var i=0; i<_savedVertices.length; i++) {
76 mapPolyline.appendVertex(_savedVertices[i])
77 }
78 mapPolyline.endReset()
79 }
80
81 onInteractiveChanged: {
82 if (interactive) {
83 _addInteractiveVisuals()
84 } else {
85 _objMgrInteractiveVisuals.destroyObjects()
86 }
87 }
88
89 Connections {
90 target: mapPolyline
91 function onTraceModeChanged(traceMode) {
92 if (traceMode) {
93 _instructionText = _traceText
94 _objMgrTraceVisuals.createObject(traceMouseAreaComponent, mapControl, false)
95 } else {
96 _instructionText = _corridorToolsText
97 _objMgrTraceVisuals.destroyObjects()
98 }
99 }
100 }
101
102 Component.onCompleted: {
103 _addCommonVisuals()
104 if (interactive) {
105 _addInteractiveVisuals()
106 }
107 }
108 Component.onDestruction: mapPolyline.traceMode = false
109
110 QGCDynamicObjectManager { id: _objMgrCommonVisuals }
111 QGCDynamicObjectManager { id: _objMgrInteractiveVisuals }
112 QGCDynamicObjectManager { id: _objMgrTraceVisuals }
113
114 QGCPalette { id: qgcPal }
115
116 KMLOrSHPFileDialog {
117 id: kmlOrSHPLoadDialog
118 title: qsTr("Select Polyline File")
119
120 onAcceptedForLoad: (file) => {
121 mapPolyline.loadKMLOrSHPFile(file)
122 mapFitFunctions.fitMapViewportToMissionItems()
123 close()
124 }
125 }
126
127 QGCMenu {
128 id: menu
129 property int _removeVertexIndex
130
131 function popUpWithIndex(curIndex) {
132 _removeVertexIndex = curIndex
133 removeVertexItem.visible = mapPolyline.count > 2
134 menu.popup()
135 }
136
137 QGCMenuItem {
138 id: removeVertexItem
139 text: qsTr("Remove vertex" )
140 onTriggered: mapPolyline.removeVertex(menu._removeVertexIndex)
141 }
142
143 QGCMenuItem {
144 text: qsTr("Edit position..." )
145 onTriggered: editPositionDialogFactory.open({ coordinate: mapPolyline.path[menu._removeVertexIndex] })
146 }
147 }
148
149 QGCPopupDialogFactory {
150 id: editPositionDialogFactory
151
152 dialogComponent: editPositionDialog
153 }
154
155 Component {
156 id: editPositionDialog
157
158 EditPositionDialog {
159 onCoordinateChanged: mapPolyline.adjustVertex(menu._removeVertexIndex,coordinate)
160 }
161 }
162
163 Component {
164 id: polylineComponent
165
166 MapPolyline {
167 line.width: mapPolyline.vertexDrag ? 3 : lineWidth
168 line.color: mapPolyline.vertexDrag ? "orange" : lineColor
169 path: mapPolyline.vertexDrag ? mapPolyline.dragPath : mapPolyline.path
170 visible: _root.visible
171 opacity: _root.opacity
172 }
173 }
174
175 Component {
176 id: splitHandleComponent
177
178 MapQuickItem {
179 id: mapQuickItem
180 anchorPoint.x: sourceItem.width / 2
181 anchorPoint.y: sourceItem.height / 2
182 z: _zorderSplitHandle
183 opacity: _root.opacity
184 visible: !mapPolyline.vertexDrag
185
186 property int vertexIndex
187
188 sourceItem: SplitIndicator {
189 onClicked: mapPolyline.splitSegment(mapQuickItem.vertexIndex)
190 }
191 }
192 }
193
194 Component {
195 id: splitHandlesComponent
196
197 Repeater {
198 model: mapPolyline.path
199
200 delegate: Item {
201 property var _splitHandle
202 property var _vertices: mapPolyline.path
203
204 opacity: _root.opacity
205
206 function _setHandlePosition() {
207 var nextIndex = index + 1
208 var distance = _vertices[index].distanceTo(_vertices[nextIndex])
209 var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex])
210 _splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
211 }
212
213 Component.onCompleted: {
214 if (index + 1 <= _vertices.length - 1) {
215 _splitHandle = splitHandleComponent.createObject(mapControl)
216 _splitHandle.vertexIndex = index
217 _setHandlePosition()
218 mapControl.addMapItem(_splitHandle)
219 }
220 }
221
222 Component.onDestruction: {
223 if (_splitHandle) {
224 _splitHandle.destroy()
225 }
226 }
227 }
228 }
229 }
230
231 Component {
232 id: edgeLengthHandleComponent
233
234 MapQuickItem {
235 id: edgeLengthMapItem
236 anchorPoint.x: sourceItem.width / 2
237 anchorPoint.y: sourceItem.height / 2
238
239 property int vertexIndex
240 property real distance
241
242 property var _unitsConversion: QGroundControl.unitsConversion
243
244 sourceItem: Text {
245 text: _unitsConversion.metersToAppSettingsHorizontalDistanceUnits(distance).toFixed(1) + " " +
246 _unitsConversion.appSettingsHorizontalDistanceUnitsString
247 color: "white"
248 }
249 }
250 }
251
252 Component {
253 id: edgeLengthHandlesComponent
254
255 Repeater {
256 model: _isVertexBeingDragged ? mapPolyline.path : undefined
257
258 delegate: Item {
259 property var _edgeLengthHandle
260
261 function _setHandlePosition() {
262 var vertices = mapPolyline.vertexDrag ? mapPolyline.dragPath : mapPolyline.path
263 var nextIndex = index + 1
264 if (nextIndex > vertices.length - 1) {
265 return
266 }
267 var distance = vertices[index].distanceTo(vertices[nextIndex])
268 var azimuth = vertices[index].azimuthTo(vertices[nextIndex])
269 _edgeLengthHandle.coordinate = vertices[index].atDistanceAndAzimuth(distance / 2, azimuth)
270 _edgeLengthHandle.distance = distance
271 }
272
273 Connections {
274 target: mapPolyline
275 function onDragPathChanged() { _setHandlePosition() }
276 }
277
278 Component.onCompleted: {
279 if (index + 1 <= mapPolyline.path.length - 1) {
280 _edgeLengthHandle = edgeLengthHandleComponent.createObject(mapControl)
281 _edgeLengthHandle.vertexIndex = index
282 _setHandlePosition()
283 mapControl.addMapItem(_edgeLengthHandle)
284 }
285 }
286
287 Component.onDestruction: {
288 if (_edgeLengthHandle) {
289 _edgeLengthHandle.destroy()
290 }
291 }
292 }
293 }
294 }
295
296 // Control which is used to drag polygon vertices
297 Component {
298 id: dragAreaComponent
299
300 MissionItemIndicatorDrag {
301 mapControl: _root.mapControl
302 id: dragArea
303 z: _zorderDragHandle
304 opacity: _root.opacity
305 onDragStart: { _isVertexBeingDragged = true; mapPolyline.vertexDrag = true }
306 onDragStop: { _isVertexBeingDragged = false; mapPolyline.vertexDrag = false }
307
308 property int polylineVertex
309
310 property bool _creationComplete: false
311
312 Component.onCompleted: _creationComplete = true
313
314 onItemCoordinateChanged: {
315 if (_creationComplete) {
316 // During component creation some bad coordinate values got through which screws up draw
317 mapPolyline.adjustVertex(polylineVertex, itemCoordinate)
318 }
319 }
320
321 onClicked: {
322 menu.popUpWithIndex(polylineVertex)
323 }
324
325 }
326 }
327
328 Component {
329 id: dragHandleComponent
330
331 MapQuickItem {
332 id: mapQuickItem
333 anchorPoint.x: dragHandle.width / 2
334 anchorPoint.y: dragHandle.height / 2
335 z: _zorderDragHandle
336 opacity: _root.opacity
337
338 property int polylineVertex
339
340 sourceItem: Rectangle {
341 id: dragHandle
342 width: ScreenTools.defaultFontPixelHeight * 1.5
343 height: width
344 radius: width * 0.5
345 color: Qt.rgba(1,1,1,0.8)
346 border.color: Qt.rgba(0,0,0,0.25)
347 border.width: 1
348 }
349 }
350 }
351
352 // Add all polygon vertex drag handles to the map
353 Component {
354 id: dragHandlesComponent
355
356 Repeater {
357 model: mapPolyline.pathModel
358
359 delegate: Item {
360 property var _visuals: [ ]
361
362 opacity: _root.opacity
363
364 Component.onCompleted: {
365 var dragHandle = dragHandleComponent.createObject(mapControl)
366 dragHandle.coordinate = Qt.binding(function() { return object.coordinate })
367 dragHandle.polylineVertex = Qt.binding(function() { return index })
368 mapControl.addMapItem(dragHandle)
369 var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate })
370 dragArea.polylineVertex = Qt.binding(function() { return index })
371 _visuals.push(dragHandle)
372 _visuals.push(dragArea)
373 }
374
375 Component.onDestruction: {
376 for (var i=0; i<_visuals.length; i++) {
377 _visuals[i].destroy()
378 }
379 _visuals = [ ]
380 }
381 }
382 }
383 }
384
385 Component {
386 id: toolbarComponent
387
388 PlanEditToolbar {
389 anchors.horizontalCenter: mapControl.left
390 anchors.horizontalCenterOffset: mapControl.centerViewport.left + (mapControl.centerViewport.width / 2)
391 y: mapControl.centerViewport.top
392 z: QGroundControl.zOrderMapItems + 2
393 availableWidth: mapControl.centerViewport.width
394
395 QGCButton {
396 _horizontalPadding: 0
397 text: qsTr("Basic")
398 visible: !mapPolyline.traceMode
399 onClicked: _resetPolyline()
400 }
401
402 QGCButton {
403 _horizontalPadding: 0
404 text: mapPolyline.traceMode ? qsTr("Done Tracing") : qsTr("Trace")
405 onClicked: {
406 if (mapPolyline.traceMode) {
407 if (mapPolyline.count < 2) {
408 _restorePreviousVertices()
409 }
410 mapPolyline.traceMode = false
411 } else {
412 _saveCurrentVertices()
413 mapPolyline.traceMode = true
414 mapPolyline.clear();
415 }
416 }
417 }
418
419 QGCButton {
420 _horizontalPadding: 0
421 text: qsTr("Load KML/SHP...")
422 onClicked: kmlOrSHPLoadDialog.openForLoad()
423 visible: !mapPolyline.traceMode
424 }
425 }
426 }
427
428 // Mouse area to capture clicks for tracing a polyline
429 Component {
430 id: traceMouseAreaComponent
431
432 MouseArea {
433 anchors.fill: mapControl
434 preventStealing: true
435 z: QGroundControl.zOrderMapItems + 1 // Over item indicators
436
437 onClicked: (mouse) => {
438 if (mouse.button === Qt.LeftButton && _root.interactive) {
439 mapPolyline.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */))
440 }
441 }
442 }
443 }
444}