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