8import QGroundControl.Controls
9import QGroundControl.FlightMap
11/// Fixed Wing Landing Pattern map visuals
15 property var map ///< Map control to place item in
16 property var vehicle ///< Vehicle associated with this item
17 property bool interactive: true
19 signal clicked(int sequenceNumber)
21 readonly property real _landingWidthMeters: 15
22 readonly property real _landingLengthMeters: 100
24 property var _missionItem: object
25 property var _mouseArea
26 property var _dragAreas: [ ]
27 property var _flightPath
28 property var _loiterPointObject
29 property var _landingPointObject
30 property real _transitionAltitudeMeters
31 property real _midSlopeAltitudeMeters
32 property real _landingAltitudeMeters: _missionItem.landingAltitude.rawValue
33 property real _finalApproachAltitudeMeters: _missionItem.finalApproachAltitude.rawValue
34 property bool _useLoiterToAlt: _missionItem.useLoiterToAlt.rawValue
35 property real _landingAreaBearing: _missionItem.landingCoordinate.azimuthTo(_missionItem.slopeStartCoordinate)
37 function _calcGlideSlopeHeights() {
39 if (_useLoiterToAlt) {
40 adjacent = _missionItem.landingCoordinate.distanceTo(_missionItem.slopeStartCoordinate)
42 adjacent = _missionItem.landingCoordinate.distanceTo(_missionItem.finalApproachCoordinate)
44 var opposite = _finalApproachAltitudeMeters - _landingAltitudeMeters
45 var angleRadians = Math.atan(opposite / adjacent)
46 var transitionDistance = _landingLengthMeters / 2
47 var glideSlopeDistance = adjacent - transitionDistance
49 _transitionAltitudeMeters = Math.tan(angleRadians) * (transitionDistance)
50 _midSlopeAltitudeMeters = Math.tan(angleRadians) * (transitionDistance + (glideSlopeDistance / 2))
53 function hideItemVisuals() {
54 objMgr.destroyObjects()
57 function showItemVisuals() {
58 if (objMgr.rgDynamicObjects.length === 0) {
59 _loiterPointObject = objMgr.createObject(finalApproachComponent, map, true /* parentObjectIsMap */)
60 _landingPointObject = objMgr.createObject(landingPointComponent, map, true /* parentObjectIsMap */)
62 var rgComponents = [ flightPathComponent, loiterRadiusComponent, landingAreaComponent, landingAreaLabelComponent,
63 glideSlopeComponent, glideSlopeLabelComponent, transitionHeightComponent, midGlideSlopeHeightComponent,
64 approachHeightComponent ]
65 objMgr.createObjects(rgComponents, map, true /* parentObjectIsMap */)
69 function hideMouseArea() {
72 _mouseArea = undefined
76 function showMouseArea() {
78 _mouseArea = mouseAreaComponent.createObject(map)
82 function hideDragAreas() {
83 for (var i=0; i<_dragAreas.length; i++) {
84 _dragAreas[i].destroy()
89 function showDragAreas() {
90 if (_dragAreas.length === 0) {
91 _dragAreas.push(finalApproachDragAreaComponent.createObject(map))
92 _dragAreas.push(landDragAreaComponent.createObject(map))
96 function _setFlightPath() {
97 if (_useLoiterToAlt) {
98 _flightPath = [ _missionItem.slopeStartCoordinate, _missionItem.landingCoordinate ]
100 _flightPath = [ _missionItem.finalApproachCoordinate, _missionItem.landingCoordinate ]
104 QGCDynamicObjectManager {
108 Component.onCompleted: {
109 if (_missionItem.landingCoordSet) {
111 if (!_missionItem.flyView && _missionItem.isCurrentItem) {
115 } else if (!_missionItem.flyView && _missionItem.isCurrentItem) {
120 Component.onDestruction: {
126 on_LandingAltitudeMetersChanged: _calcGlideSlopeHeights()
127 on_FinalApproachAltitudeMetersChanged: _calcGlideSlopeHeights()
128 on_UseLoiterToAltChanged: { _calcGlideSlopeHeights(); _setFlightPath() }
133 onIsCurrentItemChanged: {
134 if (_missionItem.flyView) {
137 if (_missionItem.isCurrentItem) {
138 if (_missionItem.landingCoordSet) {
149 onLandingCoordSetChanged: {
150 if (_missionItem.flyView) {
153 if (_missionItem.landingCoordSet) {
158 } else if (_missionItem.isCurrentItem) {
164 onLandingCoordinateChanged: {
165 _calcGlideSlopeHeights()
169 onSlopeStartCoordinateChanged: {
170 _calcGlideSlopeHeights()
174 onFinalApproachCoordinateChanged: {
175 _calcGlideSlopeHeights()
180 // Mouse area to capture landing point coordindate
182 id: mouseAreaComponent
186 z: QGroundControl.zOrderMapItems + 1 // Over item indicators
187 visible: _root.interactive
189 readonly property int _decimalPlaces: 8
191 onClicked: (mouse) => {
192 var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)
193 coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
194 coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
195 coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces)
196 _missionItem.landingCoordinate = coordinate
197 _missionItem.setLandingHeadingToTakeoffHeading()
202 // Control which is used to drag the final approach point
204 id: finalApproachDragAreaComponent
206 MissionItemIndicatorDrag {
207 mapControl: _root.map
208 itemIndicator: _loiterPointObject
209 itemCoordinate: _missionItem.finalApproachCoordinate
210 visible: _root.interactive
212 property bool _preventReentrancy: false
214 onItemCoordinateChanged: {
215 if (!_preventReentrancy) {
217 _preventReentrancy = true
218 var angle = _missionItem.landingCoordinate.azimuthTo(itemCoordinate)
219 var distance = _missionItem.landingCoordinate.distanceTo(_missionItem.finalApproachCoordinate)
220 _missionItem.finalApproachCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(distance, angle)
221 _preventReentrancy = false
228 // Control which is used to drag the landing point
230 id: landDragAreaComponent
232 MissionItemIndicatorDrag {
233 mapControl: _root.map
234 itemIndicator: _landingPointObject
235 itemCoordinate: _missionItem.landingCoordinate
236 visible: _root.interactive
238 onItemCoordinateChanged: _missionItem.coordinate = itemCoordinate
244 id: flightPathComponent
247 z: QGroundControl.zOrderMapItems - 1 // Under item indicators
248 line.color: "#be781c"
254 // Final approach point
256 id: finalApproachComponent
259 anchorPoint.x: sourceItem.anchorPointX
260 anchorPoint.y: sourceItem.anchorPointY
261 z: QGroundControl.zOrderMapItems
262 coordinate: _missionItem.finalApproachCoordinate
265 MissionItemIndexLabel {
266 index: _missionItem.sequenceNumber
267 label: _useLoiterToAlt ? qsTr("Loiter") : qsTr("Approach")
268 checked: _missionItem.isCurrentItem
270 onClicked: _root.clicked(_missionItem.sequenceNumber)
277 id: landingPointComponent
280 anchorPoint.x: sourceItem.anchorPointX
281 anchorPoint.y: sourceItem.anchorPointY
282 z: QGroundControl.zOrderMapItems
283 coordinate: _missionItem.landingCoordinate
286 MissionItemIndexLabel {
287 index: _missionItem.lastSequenceNumber
288 checked: _missionItem.isCurrentItem
290 onClicked: _root.clicked(_missionItem.sequenceNumber)
296 id: loiterRadiusComponent
299 z: QGroundControl.zOrderMapItems
300 center: _missionItem.finalApproachCoordinate
301 radius: _missionItem.loiterRadius.rawValue
303 border.color: "green"
305 visible: _useLoiterToAlt
310 id: landingAreaLabelComponent
313 anchorPoint.x: sourceItem.contentWidth / 2
314 anchorPoint.y: sourceItem.contentHeight / 2
315 z: QGroundControl.zOrderMapItems
316 coordinate: _missionItem.landingCoordinate
317 visible: _missionItem.isCurrentItem
319 sourceItem: QGCLabel {
321 text: qsTr("Landing Area")
324 property real _rawBearing: _landingAreaBearing
325 property real _adjustedBearing
327 on_RawBearingChanged: {
328 _adjustedBearing = _rawBearing
329 if (_adjustedBearing > 180) {
330 _adjustedBearing -= 180
332 _adjustedBearing -= 90
333 if (_adjustedBearing < 0) {
334 _adjustedBearing += 360
338 transform: Rotation {
339 origin.x: landingAreaLabel.width / 2
340 origin.y: landingAreaLabel.height / 2
341 angle: landingAreaLabel._adjustedBearing
348 id: glideSlopeLabelComponent
351 anchorPoint.x: sourceItem._rawBearing > 180 ? sourceItem.contentWidth : 0
352 anchorPoint.y: sourceItem.contentHeight / 2
353 z: QGroundControl.zOrderMapItems
354 visible: _missionItem.isCurrentItem
357 sourceItem: QGCLabel {
359 text: qsTr("Glide Slope")
362 property real _rawBearing: _landingAreaBearing
363 property real _adjustedBearing
365 on_RawBearingChanged: {
366 _adjustedBearing = _rawBearing
367 if (_adjustedBearing > 180) {
368 _adjustedBearing -= 180
370 _adjustedBearing -= 90
371 if (_adjustedBearing < 0) {
372 _adjustedBearing += 360
376 transform: Rotation {
377 origin.x: sourceItem._rawBearing > 180 ? sourceItem.contentWidth : 0
378 origin.y: glideSlopeLabel.contentHeight / 2
379 angle: glideSlopeLabel._adjustedBearing
384 coordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2 + 2, _landingAreaBearing)
387 Component.onCompleted: recalc()
391 onLandingCoordinateChanged: recalc()
392 onSlopeStartCoordinateChanged: recalc()
393 onFinalApproachCoordinateChanged: recalc()
399 id: landingAreaComponent
402 z: QGroundControl.zOrderMapItems
404 border.color: "black"
408 readonly property real angleRadians: Math.atan((_landingWidthMeters / 2) / (_landingLengthMeters / 2))
409 readonly property real angleDegrees: (angleRadians * (180 / Math.PI))
410 readonly property real hypotenuse: (_landingWidthMeters / 2) / Math.sin(angleRadians)
414 addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - angleDegrees))
415 addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + angleDegrees))
416 addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + (180 - angleDegrees)))
417 addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - (180 - angleDegrees)))
420 Component.onCompleted: recalc()
424 onLandingCoordinateChanged: recalc()
425 onSlopeStartCoordinateChanged: recalc()
426 onFinalApproachCoordinateChanged: recalc()
432 id: glideSlopeComponent
435 z: QGroundControl.zOrderMapItems
437 border.color: "black"
438 color: _missionItem.terrainCollision ? "red" : "orange"
441 readonly property real angleRadians: Math.atan((_landingWidthMeters / 2) / (_landingLengthMeters / 2))
442 readonly property real angleDegrees: (angleRadians * (180 / Math.PI))
443 readonly property real hypotenuse: (_landingWidthMeters / 2) / Math.sin(angleRadians)
447 addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - angleDegrees))
448 addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + angleDegrees))
449 addCoordinate(_useLoiterToAlt ? _missionItem.slopeStartCoordinate : _missionItem.finalApproachCoordinate)
452 Component.onCompleted: recalc()
456 onLandingCoordinateChanged: recalc()
457 onSlopeStartCoordinateChanged: recalc()
458 onFinalApproachCoordinateChanged: recalc()
462 target: _missionItem.useLoiterToAlt
463 onRawValueChanged: recalc()
469 id: transitionHeightComponent
472 anchorPoint.x: sourceItem.width / 2
474 z: QGroundControl.zOrderMapItems
475 visible: _missionItem.isCurrentItem
477 sourceItem: HeightIndicator {
479 heightText: Math.floor(QGroundControl.unitsConversion.metersToAppSettingsVerticalDistanceUnits(_transitionAltitudeMeters)) +
480 QGroundControl.unitsConversion.appSettingsVerticalDistanceUnitsString + "<sup>*</sup>"
484 var centeredCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2, _landingAreaBearing)
485 var angleIncrement = _landingAreaBearing > 180 ? -90 : 90
486 coordinate = centeredCoordinate.atDistanceAndAzimuth(_landingWidthMeters, _landingAreaBearing + angleIncrement)
489 Component.onCompleted: recalc()
493 onLandingCoordinateChanged: recalc()
494 onSlopeStartCoordinateChanged: recalc()
495 onFinalApproachCoordinateChanged: recalc()
501 id: midGlideSlopeHeightComponent
504 anchorPoint.x: sourceItem.width / 2
506 z: QGroundControl.zOrderMapItems
507 visible: _missionItem.isCurrentItem
509 sourceItem: HeightIndicator {
511 heightText: Math.floor(QGroundControl.unitsConversion.metersToAppSettingsVerticalDistanceUnits(_midSlopeAltitudeMeters)) +
512 QGroundControl.unitsConversion.appSettingsVerticalDistanceUnitsString + "<sup>*</sup>"
516 var transitionCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2, _landingAreaBearing)
517 var halfDistance = transitionCoordinate.distanceTo(_useLoiterToAlt ? _missionItem.slopeStartCoordinate : _missionItem.finalApproachCoordinate) / 2
518 var centeredCoordinate = transitionCoordinate.atDistanceAndAzimuth(halfDistance, _landingAreaBearing)
519 var angleIncrement = _landingAreaBearing > 180 ? -90 : 90
520 coordinate = centeredCoordinate.atDistanceAndAzimuth(_landingWidthMeters / 2, _landingAreaBearing + angleIncrement)
523 Component.onCompleted: recalc()
527 onLandingCoordinateChanged: recalc()
528 onSlopeStartCoordinateChanged: recalc()
529 onFinalApproachCoordinateChanged: recalc()
533 target: _missionItem.useLoiterToAlt
534 onRawValueChanged: recalc()
540 id: approachHeightComponent
543 anchorPoint.x: sourceItem.width / 2
545 z: QGroundControl.zOrderMapItems
546 visible: _missionItem.isCurrentItem
547 coordinate: _missionItem.slopeStartCoordinate
549 sourceItem: HeightIndicator {
551 heightText: _missionItem.finalApproachAltitude.value.toFixed(1) + QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString