QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GeoMapFWLandingPatternVisual.qml
Go to the documentation of this file.
1/****************************************************************************
2 *
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 *
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
7 *
8 ****************************************************************************/
9
10import QtQuick
11import QtQuick.Shapes
12import QtPositioning
13
14import QGroundControl
15import QGroundControl.Controls
16import QGroundControl.GeoMap
17
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
21/// shapes are exact
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 {
27 id: root
28
29 readonly property real _landingWidthMeters: 15
30 readonly property real _landingLengthMeters: 100
31 readonly property real _landingAreaBearing: item.landingCoordinate.azimuthTo(item.slopeStartCoordinate)
32
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)
36
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
42
43 GeoMapProjectedPath {
44 id: landingAreaPath
45 scene: root.scene
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]
50 }
51
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
58
59 Shape {
60 visible: root._showExtendedVisuals && landingAreaPath.projected
61 ShapePath {
62 strokeColor: "black"
63 strokeWidth: 1
64 // ShapePath has no fillOpacity; bake the alpha into the fill color
65 fillColor: Qt.alpha("green", 0.5)
66 PathPolyline { path: landingAreaPath.screenPoints }
67 }
68 }
69
70 GeoMapProjectedPath {
71 id: glideSlopePath
72 scene: root.scene
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
78 // approach altitude
79 coordinates: [
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)
89 ]
90 }
91
92 Shape {
93 visible: root._showExtendedVisuals && glideSlopePath.projected
94 ShapePath {
95 strokeColor: "black"
96 strokeWidth: 1
97 fillColor: Qt.alpha(root.item.terrainCollision ? "red" : "orange", 0.5)
98 PathPolyline { path: glideSlopePath.screenPoints }
99 }
100 }
101
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) {
108 adjusted -= 180
109 }
110 adjusted -= 90
111 if (adjusted < 0) {
112 adjusted += 360
113 }
114 return adjusted
115 }
116
117 GeoMapProjectedPath {
118 id: bearingPath
119 scene: root.scene
120 surfaceModel: root.surfaceModel
121 coordinates: [root.item.landingCoordinate, root.item.slopeStartCoordinate]
122 }
123
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) {
128 return 0
129 }
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
133 }
134
135 GeoMapItem {
136 scene: root.scene
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
144
145 QGCLabel {
146 id: landingAreaLabel
147 text: qsTr("Landing Area")
148 color: "white"
149 rotation: root._adjustedBearing(root._screenBearing)
150 }
151 }
152
153 GeoMapItem {
154 scene: root.scene
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
162
163 QGCLabel {
164 id: glideSlopeLabel
165 text: qsTr("Glide Slope")
166 color: "white"
167 rotation: root._adjustedBearing(root._screenBearing)
168 }
169 }
170
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))
179
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)
184
185 GeoMapMissionHeightBadge {
186 scene: root.scene
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
192 }
193
194 GeoMapMissionHeightBadge {
195 scene: root.scene
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
201 }
202
203 GeoMapMissionHeightBadge {
204 scene: root.scene
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
209 }
210}