1/****************************************************************************
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
8 ****************************************************************************/
15import QGroundControl.Controls
16import QGroundControl.GeoMap
18/// Fixed Wing landing pattern visual for GeoMap: the shared landing-pattern
19/// base plus the FW-only landing-area/glide-slope polygons, labels and
20/// height annotations (src/PlanView/FWLandingPatternMapVisual.qml). Polygon
22/// geo-math ports (great-circle distance/azimuth, camera-orientation
23/// independent); label rotation uses on-screen bearing instead of geographic
24/// azimuth since GeoMap's 2D camera heading is user-rotatable (see
25/// GeoMapMissionArrow for the same adaptation).
26GeoMapLandingPatternVisuals {
29 readonly property real _landingWidthMeters: 15
30 readonly property real _landingLengthMeters: 100
31 readonly property real _landingAreaBearing: item.landingCoordinate.azimuthTo(item.slopeStartCoordinate)
33 readonly property real _angleRadians: Math.atan((_landingWidthMeters / 2) / (_landingLengthMeters / 2))
34 readonly property real _angleDegrees: _angleRadians * (180 / Math.PI)
35 readonly property real _hypotenuse: (_landingWidthMeters / 2) / Math.sin(_angleRadians)
37 readonly property var _landingAreaCorner1: item.landingCoordinate.atDistanceAndAzimuth(_hypotenuse, _landingAreaBearing - _angleDegrees)
38 readonly property var _landingAreaCorner2: item.landingCoordinate.atDistanceAndAzimuth(_hypotenuse, _landingAreaBearing + _angleDegrees)
39 readonly property var _landingAreaCorner3: item.landingCoordinate.atDistanceAndAzimuth(_hypotenuse, _landingAreaBearing + (180 - _angleDegrees))
40 readonly property var _landingAreaCorner4: item.landingCoordinate.atDistanceAndAzimuth(_hypotenuse, _landingAreaBearing - (180 - _angleDegrees))
41 readonly property var _glideSlopeThirdCoordinate: _useLoiterToAlt ? item.slopeStartCoordinate : item.finalApproachCoordinate
46 surfaceModel: root.surfaceModel
47 // First corner repeated: PathPolyline strokes an open path, so the
48 // outline needs the closing edge spelled out
49 coordinates: [root._landingAreaCorner1, root._landingAreaCorner2, root._landingAreaCorner3, root._landingAreaCorner4, root._landingAreaCorner1]
52 // The source gates these FW-only elements on isCurrentItem (the item
53 // selected in the Plan editor); in the read-only fly view an item is only
54 // "current" while the vehicle is executing that exact sequence number, so
55 // that gate would hide them essentially always. Show them whenever the
56 // pattern is set instead.
57 readonly property bool _showExtendedVisuals: item.landingCoordSet
60 visible: root._showExtendedVisuals && landingAreaPath.projected
64 // ShapePath has no fillOpacity; bake the alpha into the fill color
65 fillColor: Qt.alpha("green", 0.5)
66 PathPolyline { path: landingAreaPath.screenPoints }
73 surfaceModel: root.surfaceModel
74 altitudeMode: GeoMapItem.Absolute
75 // Unlike the flat 2D source, render the slope as a true 3D surface:
76 // base edge at the transition altitude over the landing-area edge
77 // (the slope's actual crossing height there), apex up at the final
80 QtPositioning.coordinate(root._landingAreaCorner1.latitude, root._landingAreaCorner1.longitude,
81 root._landAltAMSL + root._transitionAltitudeMeters),
82 QtPositioning.coordinate(root._landingAreaCorner2.latitude, root._landingAreaCorner2.longitude,
83 root._landAltAMSL + root._transitionAltitudeMeters),
84 QtPositioning.coordinate(root._glideSlopeThirdCoordinate.latitude, root._glideSlopeThirdCoordinate.longitude,
85 root._approachAltAMSL),
86 // Repeat the first base corner: PathPolyline strokes an open path
87 QtPositioning.coordinate(root._landingAreaCorner1.latitude, root._landingAreaCorner1.longitude,
88 root._landAltAMSL + root._transitionAltitudeMeters)
93 visible: root._showExtendedVisuals && glideSlopePath.projected
97 fillColor: Qt.alpha(root.item.terrainCollision ? "red" : "orange", 0.5)
98 PathPolyline { path: glideSlopePath.screenPoints }
102 // Screen-space bearing (0 = up, clockwise) of the landing-area direction,
103 // folded the same way the source folds the geographic azimuth so the
104 // label text never renders upside down
105 function _adjustedBearing(rawBearing) {
106 let adjusted = rawBearing < 0 ? rawBearing + 360 : rawBearing
107 if (adjusted > 180) {
117 GeoMapProjectedPath {
120 surfaceModel: root.surfaceModel
121 coordinates: [root.item.landingCoordinate, root.item.slopeStartCoordinate]
124 // Normalized to [0, 360) to match the source visual's geographic-azimuth
125 // conventions (atan2 alone yields [-180, 180])
126 readonly property real _screenBearing: {
127 if (!bearingPath.projected || bearingPath.screenPoints.length < 2) {
130 const bearing = Math.atan2(bearingPath.screenPoints[1].x - bearingPath.screenPoints[0].x,
131 -(bearingPath.screenPoints[1].y - bearingPath.screenPoints[0].y)) * 180 / Math.PI
132 return bearing < 0 ? bearing + 360 : bearing
137 surfaceModel: root.surfaceModel
138 altitudeMode: GeoMapItem.ClampToGround
139 coordinate: root.item.landingCoordinate
140 visible: root._showExtendedVisuals
141 anchorPoint: Qt.point(landingAreaLabel.width / 2, landingAreaLabel.height / 2)
142 width: landingAreaLabel.width
143 height: landingAreaLabel.height
147 text: qsTr("Landing Area")
149 rotation: root._adjustedBearing(root._screenBearing)
155 surfaceModel: root.surfaceModel
156 altitudeMode: GeoMapItem.ClampToGround
157 coordinate: root.item.landingCoordinate.atDistanceAndAzimuth(root._landingLengthMeters / 2 + 2, root._landingAreaBearing)
158 visible: root._showExtendedVisuals
159 anchorPoint: Qt.point(root._screenBearing > 180 ? glideSlopeLabel.width : 0, glideSlopeLabel.height / 2)
160 width: glideSlopeLabel.width
161 height: glideSlopeLabel.height
165 text: qsTr("Glide Slope")
167 rotation: root._adjustedBearing(root._screenBearing)
171 readonly property real _finalApproachAltitudeMeters: item.finalApproachAltitude.rawValue
172 readonly property real _landingAltitudeMeters: item.landingAltitude.rawValue
173 readonly property real _glideSlopeAdjacent: _useLoiterToAlt ? item.landingCoordinate.distanceTo(item.slopeStartCoordinate)
174 : item.landingCoordinate.distanceTo(item.finalApproachCoordinate)
175 readonly property real _glideSlopeAngleRadians: Math.atan((_finalApproachAltitudeMeters - _landingAltitudeMeters) / _glideSlopeAdjacent)
176 readonly property real _transitionDistance: _landingLengthMeters / 2
177 readonly property real _transitionAltitudeMeters: Math.tan(_glideSlopeAngleRadians) * _transitionDistance
178 readonly property real _midSlopeAltitudeMeters: Math.tan(_glideSlopeAngleRadians) * (_transitionDistance + ((_glideSlopeAdjacent - _transitionDistance) / 2))
180 readonly property int _angleIncrement: _landingAreaBearing > 180 ? -90 : 90
181 readonly property var _transitionCoordinate: item.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2, _landingAreaBearing)
182 readonly property var _midSlopeCenterCoordinate: _transitionCoordinate.atDistanceAndAzimuth(
183 _transitionCoordinate.distanceTo(_glideSlopeThirdCoordinate) / 2, _landingAreaBearing)
185 GeoMapMissionHeightBadge {
187 surfaceModel: root.surfaceModel
188 coordinate: root._transitionCoordinate.atDistanceAndAzimuth(root._landingWidthMeters, root._landingAreaBearing + root._angleIncrement)
189 visible: root._showExtendedVisuals
190 text: Math.floor(QGroundControl.unitsConversion.metersToAppSettingsVerticalDistanceUnits(root._transitionAltitudeMeters)) + " "
191 + QGroundControl.unitsConversion.appSettingsVerticalDistanceUnitsString
194 GeoMapMissionHeightBadge {
196 surfaceModel: root.surfaceModel
197 coordinate: root._midSlopeCenterCoordinate.atDistanceAndAzimuth(root._landingWidthMeters / 2, root._landingAreaBearing + root._angleIncrement)
198 visible: root._showExtendedVisuals
199 text: Math.floor(QGroundControl.unitsConversion.metersToAppSettingsVerticalDistanceUnits(root._midSlopeAltitudeMeters)) + " "
200 + QGroundControl.unitsConversion.appSettingsVerticalDistanceUnitsString
203 GeoMapMissionHeightBadge {
205 surfaceModel: root.surfaceModel
206 coordinate: root.item.slopeStartCoordinate
207 visible: root._showExtendedVisuals
208 text: root.item.finalApproachAltitude.value.toFixed(1) + " " + root.item.finalApproachAltitude.units