/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ #include "LandingComplexItem.h" #include "JsonHelper.h" #include "MissionController.h" #include "QGCGeo.h" #include "SimpleMissionItem.h" #include "PlanMasterController.h" #include "FlightPathSegment.h" #include "TakeoffMissionItem.h" #include QGC_LOGGING_CATEGORY(LandingComplexItemLog, "LandingComplexItemLog") const char* LandingComplexItem::_jsonFinalApproachCoordinateKey = "landingApproachCoordinate"; const char* LandingComplexItem::_jsonLoiterRadiusKey = "loiterRadius"; const char* LandingComplexItem::_jsonLoiterClockwiseKey = "loiterClockwise"; const char* LandingComplexItem::_jsonLandingCoordinateKey = "landCoordinate"; const char* LandingComplexItem::_jsonAltitudesAreRelativeKey = "altitudesAreRelative"; const char* LandingComplexItem::_jsonUseLoiterToAltKey = "stopTakingPhotos"; const char* LandingComplexItem::_jsonStopTakingPhotosKey = "stopTakingPhotos"; const char* LandingComplexItem::_jsonStopTakingVideoKey = "stopVideoPhotos"; const char* LandingComplexItem::finalApproachToLandDistanceName = "LandingDistance"; const char* LandingComplexItem::landingHeadingName = "LandingHeading"; const char* LandingComplexItem::finalApproachAltitudeName = "FinalApproachAltitude"; const char* LandingComplexItem::loiterRadiusName = "LoiterRadius"; const char* LandingComplexItem::loiterClockwiseName = "LoiterClockwise"; const char* LandingComplexItem::landingAltitudeName = "LandingAltitude"; const char* LandingComplexItem::useLoiterToAltName = "UseLoiterToAlt"; const char* LandingComplexItem::stopTakingPhotosName = "StopTakingPhotos"; const char* LandingComplexItem::stopTakingVideoName = "StopTakingVideo"; // Deprecated keys // Support for separate relative alt settings for land/loiter was removed. It now only has a single // relative alt setting stored in _jsonAltitudesAreRelativeKey. const char* LandingComplexItem::_jsonDeprecatedLandingAltitudeRelativeKey = "landAltitudeRelative"; const char* LandingComplexItem::_jsonDeprecatedLoiterAltitudeRelativeKey = "loiterAltitudeRelative"; // Name changed from _jsonDeprecatedLoiterCoordinateKey to _jsonFinalApproachCoordinateKey to reflect // the new support for using either a loiter or just a waypoint as the approach entry point. const char* LandingComplexItem::_jsonDeprecatedLoiterCoordinateKey = "loiterCoordinate"; LandingComplexItem::LandingComplexItem(PlanMasterController* masterController, bool flyView, QObject* parent) : ComplexMissionItem (masterController, flyView, parent) { _isIncomplete = false; // The following is used to compress multiple recalc calls in a row to into a single call. connect(this, &LandingComplexItem::_updateFlightPathSegmentsSignal, this, &LandingComplexItem::_updateFlightPathSegmentsDontCallDirectly, Qt::QueuedConnection); qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&LandingComplexItem::_updateFlightPathSegmentsSignal)); } void LandingComplexItem::_init(void) { if (_masterController->controllerVehicle()->apmFirmware()) { // ArduPilot does not support camera commands stopTakingVideo()->setRawValue(false); stopTakingPhotos()->setRawValue(false); } connect(landingDistance(), &Fact::valueChanged, this, &LandingComplexItem::_recalcFromHeadingAndDistanceChange); connect(landingHeading(), &Fact::valueChanged, this, &LandingComplexItem::_recalcFromHeadingAndDistanceChange); connect(loiterRadius(), &Fact::valueChanged, this, &LandingComplexItem::_recalcFromRadiusChange); connect(this, &LandingComplexItem::loiterClockwiseChanged, this, &LandingComplexItem::_recalcFromRadiusChange); connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::_recalcFromCoordinateChange); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::_recalcFromCoordinateChange); connect(this, &LandingComplexItem::useLoiterToAltChanged, this, &LandingComplexItem::_recalcFromCoordinateChange); connect(finalApproachAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(landingAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(landingDistance(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(landingHeading(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(loiterRadius(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(loiterClockwise(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(useLoiterToAlt(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(stopTakingPhotos(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(stopTakingVideo(), &Fact::valueChanged, this, &LandingComplexItem::_setDirty); connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::_setDirty); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::_setDirty); connect(this, &LandingComplexItem::loiterClockwiseChanged, this, &LandingComplexItem::_setDirty); connect(this, &LandingComplexItem::altitudesAreRelativeChanged, this, &LandingComplexItem::_setDirty); connect(this, &LandingComplexItem::useLoiterToAltChanged, this, &LandingComplexItem::_setDirty); connect(stopTakingPhotos(), &Fact::valueChanged, this, &LandingComplexItem::_signalLastSequenceNumberChanged); connect(stopTakingVideo(), &Fact::valueChanged, this, &LandingComplexItem::_signalLastSequenceNumberChanged); connect(this, &LandingComplexItem::altitudesAreRelativeChanged, this, &LandingComplexItem::_amslEntryAltChanged); connect(this, &LandingComplexItem::altitudesAreRelativeChanged, this, &LandingComplexItem::_amslExitAltChanged); connect(_missionController, &MissionController::plannedHomePositionChanged, this, &LandingComplexItem::_amslEntryAltChanged); connect(_missionController, &MissionController::plannedHomePositionChanged, this, &LandingComplexItem::_amslExitAltChanged); connect(finalApproachAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_amslEntryAltChanged); connect(landingAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_amslExitAltChanged); connect(this, &LandingComplexItem::amslEntryAltChanged, this, &LandingComplexItem::maxAMSLAltitudeChanged); connect(this, &LandingComplexItem::amslExitAltChanged, this, &LandingComplexItem::minAMSLAltitudeChanged); connect(this, &LandingComplexItem::landingCoordSetChanged, this, &LandingComplexItem::readyForSaveStateChanged); connect(this, &LandingComplexItem::wizardModeChanged, this, &LandingComplexItem::readyForSaveStateChanged); connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::complexDistanceChanged); connect(this, &LandingComplexItem::loiterTangentCoordinateChanged,this, &LandingComplexItem::complexDistanceChanged); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::complexDistanceChanged); connect(this, &LandingComplexItem::loiterTangentCoordinateChanged,this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(this, &LandingComplexItem::finalApproachCoordinateChanged,this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(this, &LandingComplexItem::landingCoordinateChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(finalApproachAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(landingAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(this, &LandingComplexItem::altitudesAreRelativeChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(_missionController, &MissionController::plannedHomePositionChanged, this, &LandingComplexItem::_updateFlightPathSegmentsSignal); connect(finalApproachAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_updateFinalApproachCoodinateAltitudeFromFact); connect(landingAltitude(), &Fact::valueChanged, this, &LandingComplexItem::_updateLandingCoodinateAltitudeFromFact); } void LandingComplexItem::setLandingHeadingToTakeoffHeading() { TakeoffMissionItem* takeoffMissionItem = _missionController->takeoffMissionItem(); if (takeoffMissionItem && takeoffMissionItem->specifiesCoordinate()) { qreal heading = takeoffMissionItem->launchCoordinate().azimuthTo(takeoffMissionItem->coordinate()); landingHeading()->setRawValue(heading); } } double LandingComplexItem::complexDistance(void) const { return finalApproachCoordinate().distanceTo(loiterTangentCoordinate()) + loiterTangentCoordinate().distanceTo(landingCoordinate()); } // Never call this method directly. If you want to update the flight segments you emit _updateFlightPathSegmentsSignal() void LandingComplexItem::_updateFlightPathSegmentsDontCallDirectly(void) { if (_cTerrainCollisionSegments != 0) { _cTerrainCollisionSegments = 0; emit terrainCollisionChanged(false); } _flightPathSegments.beginReset(); _flightPathSegments.clearAndDeleteContents(); _appendFlightPathSegment(finalApproachCoordinate(), amslEntryAlt(), loiterTangentCoordinate(), amslEntryAlt()); _appendFlightPathSegment(loiterTangentCoordinate(), amslEntryAlt(), landingCoordinate(), amslEntryAlt()); _appendFlightPathSegment(landingCoordinate(), amslEntryAlt(), landingCoordinate(), amslExitAlt()); _flightPathSegments.endReset(); if (_cTerrainCollisionSegments != 0) { emit terrainCollisionChanged(true); } _masterController->missionController()->recalcTerrainProfile(); } void LandingComplexItem::setLandingCoordinate(const QGeoCoordinate& coordinate) { if (coordinate != _landingCoordinate) { _landingCoordinate = coordinate; if (_landingCoordSet) { emit exitCoordinateChanged(coordinate); emit landingCoordinateChanged(coordinate); } else { _ignoreRecalcSignals = true; emit exitCoordinateChanged(coordinate); emit landingCoordinateChanged(coordinate); _ignoreRecalcSignals = false; _landingCoordSet = true; _recalcFromHeadingAndDistanceChange(); emit landingCoordSetChanged(true); } } } void LandingComplexItem::setFinalApproachCoordinate(const QGeoCoordinate& coordinate) { if (coordinate != _finalApproachCoordinate) { _finalApproachCoordinate = coordinate; emit coordinateChanged(coordinate); emit finalApproachCoordinateChanged(coordinate); } } QPointF LandingComplexItem::_rotatePoint(const QPointF& point, const QPointF& origin, double angle) { QPointF rotated; double radians = (M_PI / 180.0) * angle; rotated.setX(((point.x() - origin.x()) * cos(radians)) - ((point.y() - origin.y()) * sin(radians)) + origin.x()); rotated.setY(((point.x() - origin.x()) * sin(radians)) + ((point.y() - origin.y()) * cos(radians)) + origin.y()); return rotated; } void LandingComplexItem::_recalcFromHeadingAndDistanceChange(void) { // Fixed: // land // heading // distance // radius // Adjusted: // loiter // loiter tangent // glide slope if (!_ignoreRecalcSignals && _landingCoordSet) { // These are our known values double radius = loiterRadius()->rawValue().toDouble(); double landToTangentDistance = landingDistance()->rawValue().toDouble(); double heading = landingHeading()->rawValue().toDouble(); // Heading is from loiter to land, hence +180 _loiterTangentCoordinate = _landingCoordinate.atDistanceAndAzimuth(landToTangentDistance, heading + 180); // Loiter coord is 90 degrees counter clockwise from tangent coord _finalApproachCoordinate = _loiterTangentCoordinate.atDistanceAndAzimuth(radius, heading - 180 - 90); _finalApproachCoordinate.setAltitude(finalApproachAltitude()->rawValue().toDouble()); _ignoreRecalcSignals = true; emit loiterTangentCoordinateChanged(_loiterTangentCoordinate); emit finalApproachCoordinateChanged(_finalApproachCoordinate); emit coordinateChanged(_finalApproachCoordinate); _calcGlideSlope(); _ignoreRecalcSignals = false; } } void LandingComplexItem::_recalcFromRadiusChange(void) { // Fixed: // land // loiter tangent // distance // radius // heading // Adjusted: // loiter if (!_ignoreRecalcSignals) { // These are our known values double radius = loiterRadius()->rawValue().toDouble(); double landToTangentDistance = landingDistance()->rawValue().toDouble(); double heading = landingHeading()->rawValue().toDouble(); double landToLoiterDistance = _landingCoordinate.distanceTo(_finalApproachCoordinate); if (landToLoiterDistance < radius) { // Degnenerate case: Move tangent to loiter point _loiterTangentCoordinate = _finalApproachCoordinate; double heading = _landingCoordinate.azimuthTo(_loiterTangentCoordinate); _ignoreRecalcSignals = true; landingHeading()->setRawValue(heading); emit loiterTangentCoordinateChanged(_loiterTangentCoordinate); _ignoreRecalcSignals = false; } else { double landToLoiterDistance = qSqrt(qPow(radius, 2) + qPow(landToTangentDistance, 2)); double angleLoiterToTangent = qRadiansToDegrees(qAsin(radius/landToLoiterDistance)) * (_loiterClockwise()->rawValue().toBool() ? -1 : 1); _finalApproachCoordinate = _landingCoordinate.atDistanceAndAzimuth(landToLoiterDistance, heading + 180 + angleLoiterToTangent); _finalApproachCoordinate.setAltitude(finalApproachAltitude()->rawValue().toDouble()); _ignoreRecalcSignals = true; emit finalApproachCoordinateChanged(_finalApproachCoordinate); emit coordinateChanged(_finalApproachCoordinate); _ignoreRecalcSignals = false; } } } void LandingComplexItem::_recalcFromCoordinateChange(void) { // Fixed: // land // loiter // radius // Adjusted: // loiter tangent // heading // distance // glide slope if (!_ignoreRecalcSignals && _landingCoordSet) { // These are our known values double radius = loiterRadius()->rawValue().toDouble(); double landToLoiterDistance = _landingCoordinate.distanceTo(_finalApproachCoordinate); double landToLoiterHeading = _landingCoordinate.azimuthTo(_finalApproachCoordinate); double landToTangentDistance; if (landToLoiterDistance < radius) { // Degenerate case, set tangent to loiter coordinate _loiterTangentCoordinate = _finalApproachCoordinate; landToTangentDistance = _landingCoordinate.distanceTo(_loiterTangentCoordinate); } else { double loiterToTangentAngle = qRadiansToDegrees(qAsin(radius/landToLoiterDistance)) * (_loiterClockwise()->rawValue().toBool() ? 1 : -1); landToTangentDistance = qSqrt(qPow(landToLoiterDistance, 2) - qPow(radius, 2)); _loiterTangentCoordinate = _landingCoordinate.atDistanceAndAzimuth(landToTangentDistance, landToLoiterHeading + loiterToTangentAngle); } double heading = _loiterTangentCoordinate.azimuthTo(_landingCoordinate); _ignoreRecalcSignals = true; landingHeading()->setRawValue(heading); landingDistance()->setRawValue(landToTangentDistance); emit loiterTangentCoordinateChanged(_loiterTangentCoordinate); _calcGlideSlope(); _ignoreRecalcSignals = false; } } int LandingComplexItem::lastSequenceNumber(void) const { // Fixed items are: // land start, loiter, land // Optional items are: // stop photos/video return _sequenceNumber + 2 + (stopTakingPhotos()->rawValue().toBool() ? 2 : 0) + (stopTakingVideo()->rawValue().toBool() ? 1 : 0); } void LandingComplexItem::appendMissionItems(QList& items, QObject* missionItemParent) { int seqNum = _sequenceNumber; // IMPORTANT NOTE: Any changes here must also be taken into account in scanForItem MissionItem* item = _createDoLandStartItem(seqNum++, missionItemParent); items.append(item); if (stopTakingPhotos()->rawValue().toBool()) { CameraSection::appendStopTakingPhotos(items, seqNum, missionItemParent); } if (stopTakingVideo()->rawValue().toBool()) { CameraSection::appendStopTakingVideo(items, seqNum, missionItemParent); } item = _createFinalApproachItem(seqNum++, missionItemParent); items.append(item); item = _createLandItem(seqNum++, _altitudesAreRelative, _landingCoordinate.latitude(), _landingCoordinate.longitude(), landingAltitude()->rawValue().toDouble(), missionItemParent); items.append(item); } MissionItem* LandingComplexItem::_createDoLandStartItem(int seqNum, QObject* parent) { return new MissionItem(seqNum, // sequence number MAV_CMD_DO_LAND_START, // MAV_CMD MAV_FRAME_MISSION, // MAV_FRAME 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, // param 1-7 true, // autoContinue false, // isCurrentItem parent); } MissionItem* LandingComplexItem::_createFinalApproachItem(int seqNum, QObject* parent) { if (useLoiterToAlt()->rawValue().toBool()) { return new MissionItem(seqNum, MAV_CMD_NAV_LOITER_TO_ALT, _altitudesAreRelative ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL, 1.0, // Heading required = true loiterRadius()->rawValue().toDouble() * (_loiterClockwise()->rawValue().toBool() ? 1.0 : -1.0), 0.0, // param 3 - unused 1.0, // Exit crosstrack - tangent of loiter to land point _finalApproachCoordinate.latitude(), _finalApproachCoordinate.longitude(), _finalApproachAltitude()->rawValue().toFloat(), true, // autoContinue false, // isCurrentItem parent); } else { return new MissionItem(seqNum, MAV_CMD_NAV_WAYPOINT, _altitudesAreRelative ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL, 0, // No hold time 0, // Use default acceptance radius 0, // Pass through waypoint qQNaN(), // Yaw not specified _finalApproachCoordinate.latitude(), _finalApproachCoordinate.longitude(), _finalApproachAltitude()->rawValue().toFloat(), true, // autoContinue false, // isCurrentItem parent); } } bool LandingComplexItem::_scanForItem(QmlObjectListModel* visualItems, bool flyView, PlanMasterController* masterController, IsLandItemFunc isLandItemFunc, CreateItemFunc createItemFunc) { qCDebug(LandingComplexItemLog) << "VTOLLandingComplexItem::scanForItem count" << visualItems->count(); if (visualItems->count() < 3) { return false; } // A valid landing pattern is comprised of the follow commands in this order at the end of the item list: // MAV_CMD_DO_LAND_START - required // Stop taking photos sequence - optional // Stop taking video sequence - optional // MAV_CMD_NAV_LOITER_TO_ALT or MAV_CMD_NAV_WAYPOINT // MAV_CMD_NAV_LAND or MAV_CMS_NAV_VTOL_LAND // Start looking for the commands in reverse order from the end of the list int scanIndex = visualItems->count() - 1; if (scanIndex < 0 || scanIndex > visualItems->count() - 1) { return false; } SimpleMissionItem* item = visualItems->value(scanIndex--); if (!item) { return false; } MissionItem& missionItemLand = item->missionItem(); if (!isLandItemFunc(missionItemLand)) { return false; } MAV_FRAME landPointFrame = missionItemLand.frame(); if (scanIndex < 0 || scanIndex > visualItems->count() - 1) { return false; } item = visualItems->value(scanIndex); if (!item) { return false; } bool useLoiterToAlt = true; MissionItem& missionItemFinalApproach = item->missionItem(); if (missionItemFinalApproach.command() == MAV_CMD_NAV_LOITER_TO_ALT) { if (missionItemFinalApproach.frame() != landPointFrame || missionItemFinalApproach.param1() != 1.0 || missionItemFinalApproach.param3() != 0 || missionItemFinalApproach.param4() != 1.0) { return false; } } else if (missionItemFinalApproach.command() == MAV_CMD_NAV_WAYPOINT) { if (missionItemFinalApproach.frame() != landPointFrame || missionItemFinalApproach.param1() != 0 || missionItemFinalApproach.param2() != 0 || missionItemFinalApproach.param3() != 0 || !qIsNaN(missionItemFinalApproach.param4()) || qIsNaN(missionItemFinalApproach.param5()) || qIsNaN(missionItemFinalApproach.param6()) || qIsNaN(missionItemFinalApproach.param6())) { return false; } useLoiterToAlt = false; } else { return false; } scanIndex -= CameraSection::stopTakingVideoCommandCount(); bool stopTakingVideo = CameraSection::scanStopTakingVideo(visualItems, scanIndex, false /* removeScannedItems */); if (!stopTakingVideo) { scanIndex += CameraSection::stopTakingVideoCommandCount(); } scanIndex -= CameraSection::stopTakingPhotosCommandCount(); bool stopTakingPhotos = CameraSection::scanStopTakingPhotos(visualItems, scanIndex, false /* removeScannedItems */); if (!stopTakingPhotos) { scanIndex += CameraSection::stopTakingPhotosCommandCount(); } scanIndex--; if (scanIndex < 0 || scanIndex > visualItems->count() - 1) { return false; } item = visualItems->value(scanIndex); if (!item) { return false; } MissionItem& missionItemDoLandStart = item->missionItem(); if (missionItemDoLandStart.command() != MAV_CMD_DO_LAND_START || missionItemDoLandStart.frame() != MAV_FRAME_MISSION || missionItemDoLandStart.param1() != 0 || missionItemDoLandStart.param2() != 0 || missionItemDoLandStart.param3() != 0 || missionItemDoLandStart.param4() != 0 || missionItemDoLandStart.param5() != 0 || missionItemDoLandStart.param6() != 0 || missionItemDoLandStart.param7() != 0) { return false; } // We made it this far so we do have a Fixed Wing Landing Pattern item at the end of the mission. // Since we have scanned it we need to remove the items for it fromt the list int deleteCount = 3; if (stopTakingPhotos) { deleteCount += CameraSection::stopTakingPhotosCommandCount(); } if (stopTakingVideo) { deleteCount += CameraSection::stopTakingVideoCommandCount(); } int firstItem = visualItems->count() - deleteCount; while (deleteCount--) { visualItems->removeAt(firstItem)->deleteLater(); } // Now stuff all the scanned information into the item LandingComplexItem* complexItem = createItemFunc(masterController, flyView, visualItems /* parent */); complexItem->_ignoreRecalcSignals = true; complexItem->_altitudesAreRelative = landPointFrame == MAV_FRAME_GLOBAL_RELATIVE_ALT; complexItem->setFinalApproachCoordinate(QGeoCoordinate(missionItemFinalApproach.param5(), missionItemFinalApproach.param6())); complexItem->finalApproachAltitude()->setRawValue(missionItemFinalApproach.param7()); complexItem->useLoiterToAlt()->setRawValue(useLoiterToAlt); if (useLoiterToAlt) { complexItem->loiterRadius()->setRawValue(qAbs(missionItemFinalApproach.param2())); complexItem->loiterClockwise()->setRawValue(missionItemFinalApproach.param2() > 0); } complexItem->_landingCoordinate.setLatitude(missionItemLand.param5()); complexItem->_landingCoordinate.setLongitude(missionItemLand.param6()); complexItem->landingAltitude()->setRawValue(missionItemLand.param7()); complexItem->stopTakingPhotos()->setRawValue(stopTakingPhotos); complexItem->stopTakingVideo()->setRawValue(stopTakingVideo); complexItem->_landingCoordSet = true; complexItem->_ignoreRecalcSignals = false; complexItem->_recalcFromCoordinateChange(); complexItem->setDirty(false); visualItems->append(complexItem); return true; } void LandingComplexItem::applyNewAltitude(double newAltitude) { finalApproachAltitude()->setRawValue(newAltitude); } LandingComplexItem::ReadyForSaveState LandingComplexItem::readyForSaveState(void) const { return _landingCoordSet && !_wizardMode ? ReadyForSave : NotReadyForSaveData; } void LandingComplexItem::setDirty(bool dirty) { if (_dirty != dirty) { _dirty = dirty; emit dirtyChanged(_dirty); } } void LandingComplexItem::_setDirty(void) { setDirty(true); } void LandingComplexItem::setSequenceNumber(int sequenceNumber) { if (_sequenceNumber != sequenceNumber) { _sequenceNumber = sequenceNumber; emit sequenceNumberChanged(sequenceNumber); emit lastSequenceNumberChanged(lastSequenceNumber()); } } double LandingComplexItem::amslEntryAlt(void) const { return finalApproachAltitude()->rawValue().toDouble() + (_altitudesAreRelative ? _missionController->plannedHomePosition().altitude() : 0); } double LandingComplexItem::amslExitAlt(void) const { return landingAltitude()->rawValue().toDouble() + (_altitudesAreRelative ? _missionController->plannedHomePosition().altitude() : 0); } void LandingComplexItem::_signalLastSequenceNumberChanged(void) { emit lastSequenceNumberChanged(lastSequenceNumber()); } void LandingComplexItem::_updateFinalApproachCoodinateAltitudeFromFact(void) { _finalApproachCoordinate.setAltitude(finalApproachAltitude()->rawValue().toDouble()); emit finalApproachCoordinateChanged(_finalApproachCoordinate); emit coordinateChanged(_finalApproachCoordinate); } void LandingComplexItem::_updateLandingCoodinateAltitudeFromFact(void) { _landingCoordinate.setAltitude(landingAltitude()->rawValue().toDouble()); emit landingCoordinateChanged(_landingCoordinate); } double LandingComplexItem::greatestDistanceTo(const QGeoCoordinate &other) const { return qMax(_finalApproachCoordinate.distanceTo(other),_landingCoordinate.distanceTo(other)); } QJsonObject LandingComplexItem::_save(void) { QJsonObject saveObject; QGeoCoordinate coordinate; QJsonValue jsonCoordinate; coordinate = _finalApproachCoordinate; coordinate.setAltitude(finalApproachAltitude()->rawValue().toDouble()); JsonHelper::saveGeoCoordinate(coordinate, true /* writeAltitude */, jsonCoordinate); saveObject[_jsonFinalApproachCoordinateKey] = jsonCoordinate; coordinate = _landingCoordinate; coordinate.setAltitude(landingAltitude()->rawValue().toDouble()); JsonHelper::saveGeoCoordinate(coordinate, true /* writeAltitude */, jsonCoordinate); saveObject[_jsonLandingCoordinateKey] = jsonCoordinate; saveObject[_jsonLoiterRadiusKey] = loiterRadius()->rawValue().toDouble(); saveObject[_jsonStopTakingPhotosKey] = stopTakingPhotos()->rawValue().toBool(); saveObject[_jsonStopTakingVideoKey] = stopTakingVideo()->rawValue().toBool(); saveObject[_jsonLoiterClockwiseKey] = loiterClockwise()->rawValue().toBool(); saveObject[_jsonUseLoiterToAltKey] = useLoiterToAlt()->rawValue().toBool(); saveObject[_jsonAltitudesAreRelativeKey] = _altitudesAreRelative; return saveObject; } bool LandingComplexItem::_load(const QJsonObject& complexObject, int sequenceNumber, const QString& jsonComplexItemTypeValue, bool useDeprecatedRelAltKeys, QString& errorString) { QList keyInfoList = { { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, { _jsonDeprecatedLoiterCoordinateKey, QJsonValue::Array, false }, // Loiter changed to Final Approach { _jsonFinalApproachCoordinateKey, QJsonValue::Array, false }, { _jsonLoiterRadiusKey, QJsonValue::Double, true }, { _jsonLoiterClockwiseKey, QJsonValue::Bool, true }, { _jsonLandingCoordinateKey, QJsonValue::Array, true }, { _jsonStopTakingPhotosKey, QJsonValue::Bool, false }, { _jsonStopTakingVideoKey, QJsonValue::Bool, false }, { _jsonUseLoiterToAltKey, QJsonValue::Bool, false }, }; if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) { return false; } if (!complexObject.contains(_jsonDeprecatedLoiterCoordinateKey) && !complexObject.contains(_jsonFinalApproachCoordinateKey)) { QList keyInfoList = { { _jsonFinalApproachCoordinateKey, QJsonValue::Array, true }, }; if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) { return false; } } QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString(); QString complexType = complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString(); if (itemType != VisualMissionItem::jsonTypeComplexItemValue || complexType != jsonComplexItemTypeValue) { errorString = tr("%1 does not support loading this complex mission item type: %2:%3").arg(qgcApp()->applicationName()).arg(itemType).arg(complexType); return false; } setSequenceNumber(sequenceNumber); _ignoreRecalcSignals = true; if (useDeprecatedRelAltKeys) { QList v1KeyInfoList = { { _jsonDeprecatedLoiterAltitudeRelativeKey, QJsonValue::Bool, true }, { _jsonDeprecatedLandingAltitudeRelativeKey, QJsonValue::Bool, true }, }; if (!JsonHelper::validateKeys(complexObject, v1KeyInfoList, errorString)) { return false; } bool loiterAltitudeRelative = complexObject[_jsonDeprecatedLoiterAltitudeRelativeKey].toBool(); bool landingAltitudeRelative = complexObject[_jsonDeprecatedLandingAltitudeRelativeKey].toBool(); if (loiterAltitudeRelative != landingAltitudeRelative) { qgcApp()->showAppMessage(tr("Fixed Wing Landing Pattern: " "Setting the loiter and landing altitudes with different settings for altitude relative is no longer supported. " "Both have been set to relative altitude. Be sure to adjust/check your plan prior to flight.")); _altitudesAreRelative = true; } else { _altitudesAreRelative = loiterAltitudeRelative; } } else { QList v2KeyInfoList = { { _jsonAltitudesAreRelativeKey, QJsonValue::Bool, true }, }; if (!JsonHelper::validateKeys(complexObject, v2KeyInfoList, errorString)) { _ignoreRecalcSignals = false; return false; } _altitudesAreRelative = complexObject[_jsonAltitudesAreRelativeKey].toBool(); } QGeoCoordinate coordinate; QString finalApproachKey = complexObject.contains(_jsonFinalApproachCoordinateKey) ? _jsonFinalApproachCoordinateKey : _jsonDeprecatedLoiterCoordinateKey; if (!JsonHelper::loadGeoCoordinate(complexObject[finalApproachKey], true /* altitudeRequired */, coordinate, errorString)) { return false; } _finalApproachCoordinate = coordinate; finalApproachAltitude()->setRawValue(coordinate.altitude()); if (!JsonHelper::loadGeoCoordinate(complexObject[_jsonLandingCoordinateKey], true /* altitudeRequired */, coordinate, errorString)) { return false; } _landingCoordinate = coordinate; landingAltitude()->setRawValue(coordinate.altitude()); loiterRadius()->setRawValue(complexObject[_jsonLoiterRadiusKey].toDouble()); loiterClockwise()->setRawValue(complexObject[_jsonLoiterClockwiseKey].toBool()); useLoiterToAlt()->setRawValue(complexObject[_jsonUseLoiterToAltKey].toBool(true)); stopTakingPhotos()->setRawValue(complexObject[_jsonStopTakingPhotosKey].toBool(false)); stopTakingVideo()->setRawValue(complexObject[_jsonStopTakingVideoKey].toBool(false)); _calcGlideSlope(); _landingCoordSet = true; _ignoreRecalcSignals = false; _recalcFromCoordinateChange(); emit coordinateChanged(this->coordinate()); // This will kick off terrain query return true; } void LandingComplexItem::setAltitudesAreRelative(bool altitudesAreRelative) { if (altitudesAreRelative != _altitudesAreRelative) { _altitudesAreRelative = altitudesAreRelative; emit altitudesAreRelativeChanged(_altitudesAreRelative); } }