You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
535 lines
19 KiB
535 lines
19 KiB
/**************************************************************************** |
|
* |
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> |
|
* |
|
* QGroundControl is licensed according to the terms in the file |
|
* COPYING.md in the root of the source code directory. |
|
* |
|
****************************************************************************/ |
|
|
|
import QtQuick 2.3 |
|
import QtQuick.Controls 1.2 |
|
import QtLocation 5.3 |
|
import QtPositioning 5.3 |
|
import QtQuick.Layouts 1.11 |
|
|
|
import QGroundControl 1.0 |
|
import QGroundControl.ScreenTools 1.0 |
|
import QGroundControl.Palette 1.0 |
|
import QGroundControl.Controls 1.0 |
|
import QGroundControl.FlightMap 1.0 |
|
|
|
/// Fixed Wing Landing Pattern map visuals |
|
Item { |
|
id: _root |
|
|
|
property var map ///< Map control to place item in |
|
property bool interactive: true |
|
|
|
signal clicked(int sequenceNumber) |
|
|
|
readonly property real _landingWidthMeters: 15 |
|
readonly property real _landingLengthMeters: 100 |
|
|
|
property var _missionItem: object |
|
property var _mouseArea |
|
property var _dragAreas: [ ] |
|
property var _flightPath |
|
property real _landingAreaBearing: _missionItem.landingCoordinate.azimuthTo(_missionItem.loiterTangentCoordinate) |
|
property var _loiterPointObject |
|
property var _landingPointObject |
|
property real _transitionAltitudeMeters |
|
property real _midSlopeAltitudeMeters |
|
property real _landingAltitudeMeters: _missionItem.landingAltitude.rawValue |
|
property real _loiterAltitudeMeters: _missionItem.loiterAltitude.rawValue |
|
|
|
function _calcGlideSlopeHeights() { |
|
var adjacent = _missionItem.landingCoordinate.distanceTo(_missionItem.loiterTangentCoordinate) |
|
var opposite = _loiterAltitudeMeters - _landingAltitudeMeters |
|
var angleRadians = Math.atan(opposite / adjacent) |
|
var transitionDistance = _landingLengthMeters / 2 |
|
var glideSlopeDistance = adjacent - transitionDistance |
|
|
|
_transitionAltitudeMeters = Math.tan(angleRadians) * (transitionDistance) |
|
_midSlopeAltitudeMeters = Math.tan(angleRadians) * (transitionDistance + (glideSlopeDistance / 2)) |
|
} |
|
|
|
function hideItemVisuals() { |
|
objMgr.destroyObjects() |
|
} |
|
|
|
function showItemVisuals() { |
|
if (objMgr.rgDynamicObjects.length === 0) { |
|
_loiterPointObject = objMgr.createObject(loiterPointComponent, map, true /* parentObjectIsMap */) |
|
_landingPointObject = objMgr.createObject(landingPointComponent, map, true /* parentObjectIsMap */) |
|
|
|
var rgComponents = [ flightPathComponent, loiterRadiusComponent, landingAreaComponent, landingAreaLabelComponent, |
|
glideSlopeComponent, glideSlopeLabelComponent, transitionHeightComponent, midGlideSlopeHeightComponent, |
|
approachHeightComponent ] |
|
objMgr.createObjects(rgComponents, map, true /* parentObjectIsMap */) |
|
} |
|
} |
|
|
|
function hideMouseArea() { |
|
if (_mouseArea) { |
|
_mouseArea.destroy() |
|
_mouseArea = undefined |
|
} |
|
} |
|
|
|
function showMouseArea() { |
|
if (!_mouseArea) { |
|
_mouseArea = mouseAreaComponent.createObject(map) |
|
map.addMapItem(_mouseArea) |
|
} |
|
} |
|
|
|
function hideDragAreas() { |
|
for (var i=0; i<_dragAreas.length; i++) { |
|
_dragAreas[i].destroy() |
|
} |
|
_dragAreas = [ ] |
|
} |
|
|
|
function showDragAreas() { |
|
if (_dragAreas.length === 0) { |
|
_dragAreas.push(loiterDragAreaComponent.createObject(map)) |
|
_dragAreas.push(landDragAreaComponent.createObject(map)) |
|
} |
|
} |
|
|
|
function _setFlightPath() { |
|
_flightPath = [ _missionItem.loiterTangentCoordinate, _missionItem.landingCoordinate ] |
|
} |
|
|
|
QGCDynamicObjectManager { |
|
id: objMgr |
|
} |
|
|
|
Component.onCompleted: { |
|
if (_missionItem.landingCoordSet) { |
|
showItemVisuals() |
|
if (!_missionItem.flyView && _missionItem.isCurrentItem) { |
|
showDragAreas() |
|
} |
|
_setFlightPath() |
|
} else if (!_missionItem.flyView && _missionItem.isCurrentItem) { |
|
showMouseArea() |
|
} |
|
} |
|
|
|
Component.onDestruction: { |
|
hideDragAreas() |
|
hideMouseArea() |
|
hideItemVisuals() |
|
} |
|
|
|
on_LandingAltitudeMetersChanged: _calcGlideSlopeHeights() |
|
on_LoiterAltitudeMetersChanged: _calcGlideSlopeHeights() |
|
|
|
Connections { |
|
target: _missionItem |
|
|
|
onIsCurrentItemChanged: { |
|
if (_missionItem.flyView) { |
|
return |
|
} |
|
if (_missionItem.isCurrentItem) { |
|
if (_missionItem.landingCoordSet) { |
|
showDragAreas() |
|
} else { |
|
showMouseArea() |
|
} |
|
} else { |
|
hideMouseArea() |
|
hideDragAreas() |
|
} |
|
} |
|
|
|
onLandingCoordSetChanged: { |
|
if (_missionItem.flyView) { |
|
return |
|
} |
|
if (_missionItem.landingCoordSet) { |
|
hideMouseArea() |
|
showItemVisuals() |
|
showDragAreas() |
|
_setFlightPath() |
|
} else if (_missionItem.isCurrentItem) { |
|
hideDragAreas() |
|
showMouseArea() |
|
} |
|
} |
|
|
|
onLandingCoordinateChanged: { |
|
_calcGlideSlopeHeights() |
|
_setFlightPath() |
|
} |
|
|
|
onLoiterTangentCoordinateChanged: { |
|
_calcGlideSlopeHeights() |
|
_setFlightPath() |
|
} |
|
} |
|
|
|
// Mouse area to capture landing point coordindate |
|
Component { |
|
id: mouseAreaComponent |
|
|
|
MouseArea { |
|
anchors.fill: map |
|
z: QGroundControl.zOrderMapItems + 1 // Over item indicators |
|
visible: _root.interactive |
|
|
|
readonly property int _decimalPlaces: 8 |
|
|
|
onClicked: { |
|
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */) |
|
coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) |
|
coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) |
|
coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) |
|
_missionItem.landingCoordinate = coordinate |
|
_missionItem.setLandingHeadingToTakeoffHeading() |
|
} |
|
} |
|
} |
|
|
|
// Control which is used to drag the loiter point |
|
Component { |
|
id: loiterDragAreaComponent |
|
|
|
MissionItemIndicatorDrag { |
|
mapControl: _root.map |
|
itemIndicator: _loiterPointObject |
|
itemCoordinate: _missionItem.loiterCoordinate |
|
visible: _root.interactive |
|
|
|
property bool _preventReentrancy: false |
|
|
|
onItemCoordinateChanged: { |
|
if (!_preventReentrancy) { |
|
if (Drag.active) { |
|
_preventReentrancy = true |
|
var angle = _missionItem.landingCoordinate.azimuthTo(itemCoordinate) |
|
var distance = _missionItem.landingCoordinate.distanceTo(_missionItem.loiterCoordinate) |
|
_missionItem.loiterCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(distance, angle) |
|
_preventReentrancy = false |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Control which is used to drag the landing point |
|
Component { |
|
id: landDragAreaComponent |
|
|
|
MissionItemIndicatorDrag { |
|
mapControl: _root.map |
|
itemIndicator: _landingPointObject |
|
itemCoordinate: _missionItem.landingCoordinate |
|
visible: _root.interactive |
|
|
|
onItemCoordinateChanged: _missionItem.moveLandingPosition(itemCoordinate) |
|
} |
|
} |
|
|
|
// Flight path |
|
Component { |
|
id: flightPathComponent |
|
|
|
MapPolyline { |
|
z: QGroundControl.zOrderMapItems - 1 // Under item indicators |
|
line.color: "#be781c" |
|
line.width: 2 |
|
path: _flightPath |
|
} |
|
} |
|
|
|
// Loiter point |
|
Component { |
|
id: loiterPointComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem.anchorPointX |
|
anchorPoint.y: sourceItem.anchorPointY |
|
z: QGroundControl.zOrderMapItems |
|
coordinate: _missionItem.loiterCoordinate |
|
|
|
sourceItem: |
|
MissionItemIndexLabel { |
|
index: _missionItem.sequenceNumber |
|
label: qsTr("Loiter") |
|
checked: _missionItem.isCurrentItem |
|
|
|
onClicked: _root.clicked(_missionItem.sequenceNumber) |
|
} |
|
} |
|
} |
|
|
|
// Landing point |
|
Component { |
|
id: landingPointComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem.anchorPointX |
|
anchorPoint.y: sourceItem.anchorPointY |
|
z: QGroundControl.zOrderMapItems |
|
coordinate: _missionItem.landingCoordinate |
|
|
|
sourceItem: |
|
MissionItemIndexLabel { |
|
index: _missionItem.lastSequenceNumber |
|
checked: _missionItem.isCurrentItem |
|
|
|
onClicked: _root.clicked(_missionItem.sequenceNumber) |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: loiterRadiusComponent |
|
|
|
MapCircle { |
|
z: QGroundControl.zOrderMapItems |
|
center: _missionItem.loiterCoordinate |
|
radius: _missionItem.loiterRadius.rawValue |
|
border.width: 2 |
|
border.color: "green" |
|
color: "transparent" |
|
} |
|
} |
|
|
|
Component { |
|
id: landingAreaLabelComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem.contentWidth / 2 |
|
anchorPoint.y: sourceItem.contentHeight / 2 |
|
z: QGroundControl.zOrderMapItems |
|
coordinate: _missionItem.landingCoordinate |
|
visible: _missionItem.isCurrentItem |
|
|
|
sourceItem: QGCLabel { |
|
id: landingAreaLabel |
|
text: qsTr("Landing Area") |
|
color: "white" |
|
|
|
property real _rawBearing: _landingAreaBearing |
|
property real _adjustedBearing |
|
|
|
on_RawBearingChanged: { |
|
_adjustedBearing = _rawBearing |
|
if (_adjustedBearing > 180) { |
|
_adjustedBearing -= 180 |
|
} |
|
_adjustedBearing -= 90 |
|
if (_adjustedBearing < 0) { |
|
_adjustedBearing += 360 |
|
} |
|
} |
|
|
|
transform: Rotation { |
|
origin.x: landingAreaLabel.width / 2 |
|
origin.y: landingAreaLabel.height / 2 |
|
angle: landingAreaLabel._adjustedBearing |
|
} |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: glideSlopeLabelComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem._rawBearing > 180 ? sourceItem.contentWidth : 0 |
|
anchorPoint.y: sourceItem.contentHeight / 2 |
|
z: QGroundControl.zOrderMapItems |
|
visible: _missionItem.isCurrentItem |
|
|
|
sourceItem: QGCLabel { |
|
id: glideSlopeLabel |
|
text: qsTr("Glide Slope") |
|
color: "white" |
|
|
|
property real _rawBearing: _landingAreaBearing |
|
property real _adjustedBearing |
|
|
|
on_RawBearingChanged: { |
|
_adjustedBearing = _rawBearing |
|
if (_adjustedBearing > 180) { |
|
_adjustedBearing -= 180 |
|
} |
|
_adjustedBearing -= 90 |
|
if (_adjustedBearing < 0) { |
|
_adjustedBearing += 360 |
|
} |
|
} |
|
|
|
transform: Rotation { |
|
origin.x: sourceItem._rawBearing > 180 ? sourceItem.contentWidth : 0 |
|
origin.y: glideSlopeLabel.contentHeight / 2 |
|
angle: glideSlopeLabel._adjustedBearing |
|
} |
|
} |
|
|
|
function recalc() { |
|
coordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2 + 2, _landingAreaBearing) |
|
} |
|
|
|
Component.onCompleted: recalc() |
|
|
|
Connections { |
|
target: _missionItem |
|
onLandingCoordinateChanged: recalc() |
|
onLoiterTangentCoordinateChanged: recalc() |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: landingAreaComponent |
|
|
|
MapPolygon { |
|
z: QGroundControl.zOrderMapItems |
|
border.width: 1 |
|
border.color: "black" |
|
color: "green" |
|
opacity: 0.5 |
|
|
|
readonly property real angleRadians: Math.atan((_landingWidthMeters / 2) / (_landingLengthMeters / 2)) |
|
readonly property real angleDegrees: (angleRadians * (180 / Math.PI)) |
|
readonly property real hypotenuse: (_landingWidthMeters / 2) / Math.sin(angleRadians) |
|
|
|
function recalc() { |
|
path = [ ] |
|
addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - angleDegrees)) |
|
addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + angleDegrees)) |
|
addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + (180 - angleDegrees))) |
|
addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - (180 - angleDegrees))) |
|
} |
|
|
|
Component.onCompleted: recalc() |
|
|
|
Connections { |
|
target: _missionItem |
|
onLandingCoordinateChanged: recalc() |
|
onLoiterTangentCoordinateChanged: recalc() |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: glideSlopeComponent |
|
|
|
MapPolygon { |
|
z: QGroundControl.zOrderMapItems |
|
border.width: 1 |
|
border.color: "black" |
|
color: _missionItem.terrainCollision ? "red" : "orange" |
|
opacity: 0.5 |
|
|
|
readonly property real angleRadians: Math.atan((_landingWidthMeters / 2) / (_landingLengthMeters / 2)) |
|
readonly property real angleDegrees: (angleRadians * (180 / Math.PI)) |
|
readonly property real hypotenuse: (_landingWidthMeters / 2) / Math.sin(angleRadians) |
|
|
|
function recalc() { |
|
path = [ ] |
|
addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing - angleDegrees)) |
|
addCoordinate(_missionItem.landingCoordinate.atDistanceAndAzimuth(hypotenuse, _landingAreaBearing + angleDegrees)) |
|
addCoordinate(_missionItem.loiterTangentCoordinate) |
|
} |
|
|
|
Component.onCompleted: recalc() |
|
|
|
Connections { |
|
target: _missionItem |
|
onLandingCoordinateChanged: recalc() |
|
onLoiterTangentCoordinateChanged: recalc() |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: transitionHeightComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem.width / 2 |
|
anchorPoint.y: 0 |
|
z: QGroundControl.zOrderMapItems |
|
visible: _missionItem.isCurrentItem |
|
|
|
sourceItem: HeightIndicator { |
|
map: _root.map |
|
heightText: Math.floor(QGroundControl.unitsConversion.metersToAppSettingsHorizontalDistanceUnits(_transitionAltitudeMeters)) + |
|
QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString + "<sup>*</sup>" |
|
} |
|
|
|
function recalc() { |
|
var centeredCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2, _landingAreaBearing) |
|
var angleIncrement = _landingAreaBearing > 180 ? -90 : 90 |
|
coordinate = centeredCoordinate.atDistanceAndAzimuth(_landingWidthMeters, _landingAreaBearing + angleIncrement) |
|
} |
|
|
|
Component.onCompleted: recalc() |
|
|
|
Connections { |
|
target: _missionItem |
|
onLandingCoordinateChanged: recalc() |
|
onLoiterTangentCoordinateChanged: recalc() |
|
} |
|
|
|
} |
|
} |
|
|
|
Component { |
|
id: midGlideSlopeHeightComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem.width / 2 |
|
anchorPoint.y: 0 |
|
z: QGroundControl.zOrderMapItems |
|
visible: _missionItem.isCurrentItem |
|
|
|
sourceItem: HeightIndicator { |
|
map: _root.map |
|
heightText: Math.floor(QGroundControl.unitsConversion.metersToAppSettingsHorizontalDistanceUnits(_midSlopeAltitudeMeters)) + |
|
QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString + "<sup>*</sup>" |
|
} |
|
|
|
function recalc() { |
|
var transitionCoordinate = _missionItem.landingCoordinate.atDistanceAndAzimuth(_landingLengthMeters / 2, _landingAreaBearing) |
|
var halfDistance = transitionCoordinate.distanceTo(_missionItem.loiterTangentCoordinate) / 2 |
|
var centeredCoordinate = transitionCoordinate.atDistanceAndAzimuth(halfDistance, _landingAreaBearing) |
|
var angleIncrement = _landingAreaBearing > 180 ? -90 : 90 |
|
coordinate = centeredCoordinate.atDistanceAndAzimuth(_landingWidthMeters / 2, _landingAreaBearing + angleIncrement) |
|
} |
|
|
|
Component.onCompleted: recalc() |
|
|
|
Connections { |
|
target: _missionItem |
|
onLandingCoordinateChanged: recalc() |
|
onLoiterTangentCoordinateChanged: recalc() |
|
} |
|
|
|
} |
|
} |
|
|
|
Component { |
|
id: approachHeightComponent |
|
|
|
MapQuickItem { |
|
anchorPoint.x: sourceItem.width / 2 |
|
anchorPoint.y: 0 |
|
z: QGroundControl.zOrderMapItems |
|
visible: _missionItem.isCurrentItem |
|
coordinate: _missionItem.loiterTangentCoordinate |
|
|
|
sourceItem: HeightIndicator { |
|
map: _root.map |
|
heightText: _missionItem.loiterAltitude.value.toFixed(1) + QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString |
|
} |
|
} |
|
} |
|
}
|
|
|