diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index ba496589697f2ec2212d1228bbba7b1e1048ece9..22be4fd428c741aa8b70e82075bf57a7248d217f 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -421,6 +421,8 @@ INCLUDEPATH += \ src/Vehicle \ src/Audio \ src/comm \ + src/RouteMissionItem \ + src/RouteMissionItem/geometry \ src/comm/ros_bridge \ src/input \ src/lib/qmapcontrol \ @@ -445,7 +447,11 @@ contains (DEFINES, QGC_ENABLE_PAIRING) { HEADERS += \ src/QmlControls/QmlUnitsConversion.h \ + src/RouteMissionItem/geometry/GeoArea.h \ + src/RouteMissionItem/geometry/MeasurementArea.h \ + src/RouteMissionItem/geometry/SafeArea.h \ src/Vehicle/VehicleEscStatusFactGroup.h \ + src/RouteMissionItem/AreaData.h \ src/RouteMissionItem/RouteComplexItem.h \ src/RouteMissionItem/GenericSingelton.h \ src/RouteMissionItem/geometry/GenericCircle.h \ @@ -492,12 +498,6 @@ HEADERS += \ src/api/QGCSettings.h \ src/api/QmlComponentInfo.h \ src/GPS/Drivers/src/base_station.h \ - src/RouteMissionItem/geometry/WimaArea.h \ - src/RouteMissionItem/geometry/WimaServiceArea.h \ - src/RouteMissionItem/geometry/WimaMeasurementArea.h \ - src/RouteMissionItem/geometry/PlanimetryCalculus.h \ - src/RouteMissionItem/geometry/PolygonCalculus.h \ - src/RouteMissionItem/OptimisationTools.h \ src/Settings/WimaSettings.h \ src/comm/ros_bridge/include/RosBridgeClient.h \ src/comm/ros_bridge/include/com_private.h \ @@ -522,6 +522,9 @@ contains (DEFINES, QGC_ENABLE_PAIRING) { } SOURCES += \ + src/RouteMissionItem/geometry/GeoArea.cc \ + src/RouteMissionItem/geometry/MeasurementArea.cc \ + src/RouteMissionItem/geometry/SafeArea.cc \ src/Vehicle/VehicleEscStatusFactGroup.cc \ src/RouteMissionItem/AreaData.cc \ src/api/QGCCorePlugin.cc \ @@ -554,12 +557,6 @@ SOURCES += \ src/comm/ros_bridge/include/server.cpp \ src/comm/ros_bridge/include/topic_publisher.cpp \ src/comm/ros_bridge/include/topic_subscriber.cpp \ - src/RouteMissionItem/geometry/WimaArea.cc \ - src/RouteMissionItem/geometry/WimaServiceArea.cc \ - src/RouteMissionItem/geometry/WimaMeasurementArea.cc \ - src/RouteMissionItem/geometry/PlanimetryCalculus.cc \ - src/RouteMissionItem/OptimisationTools.cc \ - src/RouteMissionItem/geometry/PolygonCalculus.cc \ src/Settings/WimaSettings.cc \ src/comm/ros_bridge/src/ros_bridge.cpp diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 9b4e3f387b2ab5c0ef272fc5f841f0350124bea9..3020e5ccdf9657d13cb766aa42568860f109f489 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -261,7 +261,6 @@ src/QmlControls/QmlTest.qml src/AutoPilotPlugins/Common/RadioComponent.qml src/ui/preferences/SerialSettings.qml - src/Wima/Snake/WimaAreaNoVisual.qml src/WimaView/CircularGeneratorEditor.qml src/WimaView/CircularGeneratorMapVisual.qml src/WimaView/CircularSurveyItemEditor.qml @@ -273,21 +272,14 @@ src/WimaView/ProgressIndicator.qml src/WimaView/Wima.qmldir src/WimaView/WimaAreaMapVisual.qml - src/WimaView/WimaCorridorDataVisual.qml src/WimaView/WimaCorridorEditor.qml - src/WimaView/WimaCorridorMapVisual.qml src/WimaView/WimaItemEditor.qml - src/WimaView/WimaJoinedAreaDataVisual.qml - src/WimaView/WimaJoinedAreaMapVisual.qml src/WimaView/WimaMapPolygonVisuals.qml - src/WimaView/WimaMapPolylineVisuals.qml src/WimaView/WimaMapVisual.qml - src/WimaView/WimaMeasurementAreaDataVisual.qml - src/WimaView/WimaMeasurementAreaEditor.qml - src/WimaView/WimaMeasurementAreaMapVisual.qml - src/WimaView/WimaServiceAreaDataVisual.qml - src/WimaView/WimaServiceAreaEditor.qml - src/WimaView/WimaServiceAreaMapVisual.qml + src/WimaView/MeasurementAreaEditor.qml + src/WimaView/MeasurementAreaMapVisual.qml + src/WimaView/SafeAreaEditor.qml + src/WimaView/SafeAreaMapVisual.qml src/WimaView/WimaToolBar.qml src/VehicleSetup/SetupParameterEditor.qml src/VehicleSetup/SetupView.qml @@ -360,9 +352,9 @@ src/Settings/Video.SettingsGroup.json src/MissionManager/VTOLLandingPattern.FactMetaData.json src/Settings/Wima.SettingsGroup.json - src/Wima/json/CircularSurvey.SettingsGroup.json - src/Wima/Snake/json/LinearGenerator.SettingsGroup.json - src/Wima/Snake/json/CircularGenerator.SettingsGroup.json + src/RouteMissionItem/json/RouteComplexItem.SettingsGroup.json + src/RouteMissionItem/json/LinearGenerator.SettingsGroup.json + src/RouteMissionItem/json/CircularGenerator.SettingsGroup.json src/comm/APMArduSubMockLink.params diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index 9713e547583b8f7f1ee12e84310a53039f1e35bd..d5c78e4038932f64ee95b905543f3b532cd82936 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -7,2654 +7,3051 @@ * ****************************************************************************/ -#include "MissionCommandUIInfo.h" #include "MissionController.h" -#include "MultiVehicleManager.h" -#include "MissionManager.h" -#include "FlightPathSegment.h" +#include "AppSettings.h" +#include "CorridorScanComplexItem.h" #include "FirmwarePlugin.h" -#include "QGCApplication.h" -#include "SimpleMissionItem.h" -#include "SurveyComplexItem.h" #include "FixedWingLandingComplexItem.h" -#include "VTOLLandingComplexItem.h" -#include "StructureScanComplexItem.h" -#include "Wima/CircularSurvey.h" -#include "CorridorScanComplexItem.h" +#include "FlightPathSegment.h" #include "JsonHelper.h" -#include "ParameterManager.h" -#include "QGroundControlQmlGlobal.h" -#include "SettingsManager.h" -#include "AppSettings.h" +#include "KMLPlanDomDocument.h" +#include "MissionCommandUIInfo.h" +#include "MissionManager.h" #include "MissionSettingsItem.h" -#include "QGCQGeoCoordinate.h" +#include "MultiVehicleManager.h" +#include "ParameterManager.h" #include "PlanMasterController.h" -#include "KMLPlanDomDocument.h" +#include "PlanViewSettings.h" +#include "QGCApplication.h" #include "QGCCorePlugin.h" +#include "QGCQGeoCoordinate.h" +#include "QGroundControlQmlGlobal.h" +#include "RouteComplexItem.h" +#include "SettingsManager.h" +#include "SimpleMissionItem.h" +#include "StructureScanComplexItem.h" +#include "SurveyComplexItem.h" #include "TakeoffMissionItem.h" -#include "PlanViewSettings.h" - -#include "src/Wima/CircularSurvey.h" - -#define UPDATE_TIMEOUT 5000 ///< How often we check for bounding box changes - -QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog") - -const char* MissionController::_settingsGroup = "MissionController"; -const char* MissionController::_jsonFileTypeValue = "Mission"; -const char* MissionController::_jsonItemsKey = "items"; -const char* MissionController::_jsonPlannedHomePositionKey = "plannedHomePosition"; -const char* MissionController::_jsonFirmwareTypeKey = "firmwareType"; -const char* MissionController::_jsonVehicleTypeKey = "vehicleType"; -const char* MissionController::_jsonCruiseSpeedKey = "cruiseSpeed"; -const char* MissionController::_jsonHoverSpeedKey = "hoverSpeed"; -const char* MissionController::_jsonParamsKey = "params"; -const char* MissionController::_jsonGlobalPlanAltitudeModeKey = "globalPlanAltitudeMode"; - -// Deprecated V1 format keys -const char* MissionController::_jsonComplexItemsKey = "complexItems"; -const char* MissionController::_jsonMavAutopilotKey = "MAV_AUTOPILOT"; - -const int MissionController::_missionFileVersion = 2; - -MissionController::MissionController(PlanMasterController* masterController, QObject *parent) - : PlanElementController (masterController, parent) - , _controllerVehicle (masterController->controllerVehicle()) - , _managerVehicle (masterController->managerVehicle()) - , _missionManager (masterController->managerVehicle()->missionManager()) - , _visualItems (new QmlObjectListModel(this)) - , _planViewSettings (qgcApp()->toolbox()->settingsManager()->planViewSettings()) - , _appSettings (qgcApp()->toolbox()->settingsManager()->appSettings()) -{ - _resetMissionFlightStatus(); - - _updateTimer.setSingleShot(true); - - connect(&_updateTimer, &QTimer::timeout, this, &MissionController::_updateTimeout); - connect(_planViewSettings->takeoffItemNotRequired(), &Fact::rawValueChanged, this, &MissionController::_takeoffItemNotRequiredChanged); - connect(this, &MissionController::missionDistanceChanged, this, &MissionController::recalcTerrainProfile); - - // The follow is used to compress multiple recalc calls in a row to into a single call. - connect(this, &MissionController::_recalcMissionFlightStatusSignal, this, &MissionController::_recalcMissionFlightStatus, Qt::QueuedConnection); - connect(this, &MissionController::_recalcFlightPathSegmentsSignal, this, &MissionController::_recalcFlightPathSegments, Qt::QueuedConnection); - qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&MissionController::_recalcMissionFlightStatusSignal)); - qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&MissionController::_recalcFlightPathSegmentsSignal)); - qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&MissionController::recalcTerrainProfile)); -} - -MissionController::~MissionController() -{ - -} - -void MissionController::_resetMissionFlightStatus(void) -{ - _missionFlightStatus.totalDistance = 0.0; - _missionFlightStatus.maxTelemetryDistance = 0.0; - _missionFlightStatus.totalTime = 0.0; - _missionFlightStatus.hoverTime = 0.0; - _missionFlightStatus.cruiseTime = 0.0; - _missionFlightStatus.hoverDistance = 0.0; - _missionFlightStatus.cruiseDistance = 0.0; - _missionFlightStatus.cruiseSpeed = _controllerVehicle->defaultCruiseSpeed(); - _missionFlightStatus.hoverSpeed = _controllerVehicle->defaultHoverSpeed(); - _missionFlightStatus.vehicleSpeed = _controllerVehicle->multiRotor() || _managerVehicle->vtol() ? _missionFlightStatus.hoverSpeed : _missionFlightStatus.cruiseSpeed; - _missionFlightStatus.vehicleYaw = qQNaN(); - _missionFlightStatus.gimbalYaw = qQNaN(); - _missionFlightStatus.gimbalPitch = qQNaN(); - _missionFlightStatus.mAhBattery = 0; - _missionFlightStatus.hoverAmps = 0; - _missionFlightStatus.cruiseAmps = 0; - _missionFlightStatus.ampMinutesAvailable = 0; - _missionFlightStatus.hoverAmpsTotal = 0; - _missionFlightStatus.cruiseAmpsTotal = 0; - _missionFlightStatus.batteryChangePoint = -1; - _missionFlightStatus.batteriesRequired = -1; - _missionFlightStatus.vtolMode = _missionContainsVTOLTakeoff ? QGCMAVLink::VehicleClassMultiRotor : QGCMAVLink::VehicleClassFixedWing; - - _controllerVehicle->firmwarePlugin()->batteryConsumptionData(_controllerVehicle, _missionFlightStatus.mAhBattery, _missionFlightStatus.hoverAmps, _missionFlightStatus.cruiseAmps); - if (_missionFlightStatus.mAhBattery != 0) { - double batteryPercentRemainingAnnounce = qgcApp()->toolbox()->settingsManager()->appSettings()->batteryPercentRemainingAnnounce()->rawValue().toDouble(); - _missionFlightStatus.ampMinutesAvailable = static_cast(_missionFlightStatus.mAhBattery) / 1000.0 * 60.0 * ((100.0 - batteryPercentRemainingAnnounce) / 100.0); - } - - emit missionDistanceChanged(_missionFlightStatus.totalDistance); - emit missionTimeChanged(); - emit missionHoverDistanceChanged(_missionFlightStatus.hoverDistance); - emit missionCruiseDistanceChanged(_missionFlightStatus.cruiseDistance); - emit missionHoverTimeChanged(); - emit missionCruiseTimeChanged(); - emit missionMaxTelemetryChanged(_missionFlightStatus.maxTelemetryDistance); - emit batteryChangePointChanged(_missionFlightStatus.batteryChangePoint); - emit batteriesRequiredChanged(_missionFlightStatus.batteriesRequired); - -} - -void MissionController::start(bool flyView) -{ - qCDebug(MissionControllerLog) << "start flyView" << flyView; - - _managerVehicleChanged(_managerVehicle); - connect(_masterController, &PlanMasterController::managerVehicleChanged, this, &MissionController::_managerVehicleChanged); - - PlanElementController::start(flyView); - _init(); -} - -void MissionController::_init(void) -{ - // We start with an empty mission - _addMissionSettings(_visualItems); - _initAllVisualItems(); -} - -// Called when new mission items have completed downloading from Vehicle -void MissionController::_newMissionItemsAvailableFromVehicle(bool removeAllRequested) -{ - qCDebug(MissionControllerLog) << "_newMissionItemsAvailableFromVehicle flyView:count" << _flyView << _missionManager->missionItems().count(); - - // Fly view always reloads on _loadComplete - // Plan view only reloads if: - // - Load was specifically requested - // - There is no current Plan - if (_flyView || removeAllRequested || _itemsRequested || isEmpty()) { - // Fly Mode (accept if): - // - Always accepts new items from the vehicle so Fly view is kept up to date - // Edit Mode (accept if): - // - Remove all was requested from Fly view (clear mission on flight end) - // - A load from vehicle was manually requested - // - The initial automatic load from a vehicle completed and the current editor is empty - - _deinitAllVisualItems(); - _visualItems->deleteLater(); - _visualItems = nullptr; - _settingsItem = nullptr; - _takeoffMissionItem = nullptr; - _updateContainsItems(); // This will clear containsItems which will be set again below. This will re-pop Start Mission confirmation. - - QmlObjectListModel* newControllerMissionItems = new QmlObjectListModel(this); - const QList& newMissionItems = _missionManager->missionItems(); - qCDebug(MissionControllerLog) << "loading from vehicle: count"<< newMissionItems.count(); - - _missionItemCount = newMissionItems.count(); - emit missionItemCountChanged(_missionItemCount); - - MissionSettingsItem* settingsItem = _addMissionSettings(newControllerMissionItems); - - int i=0; - if (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() && newMissionItems.count() != 0) { - // First item is fake home position - MissionItem* fakeHomeItem = newMissionItems[0]; - if (fakeHomeItem->coordinate().latitude() != 0 || fakeHomeItem->coordinate().longitude() != 0) { - settingsItem->setInitialHomePosition(fakeHomeItem->coordinate()); - } - i = 1; - } - - for (; i < newMissionItems.count(); i++) { - const MissionItem* missionItem = newMissionItems[i]; - SimpleMissionItem* simpleItem = new SimpleMissionItem(_masterController, _flyView, *missionItem, this); - if (TakeoffMissionItem::isTakeoffCommand(static_cast(simpleItem->command()))) { - // This needs to be a TakeoffMissionItem - _takeoffMissionItem = new TakeoffMissionItem(*missionItem, _masterController, _flyView, settingsItem, this); - simpleItem->deleteLater(); - simpleItem = _takeoffMissionItem; - } - newControllerMissionItems->append(simpleItem); - } - - _visualItems = newControllerMissionItems; - _settingsItem = settingsItem; - - MissionController::_scanForAdditionalSettings(_visualItems, _masterController); - - _initAllVisualItems(); - _updateContainsItems(); - emit newItemsFromVehicle(); - } - _itemsRequested = false; -} - -void MissionController::loadFromVehicle(void) -{ - if (_masterController->offline()) { - qCWarning(MissionControllerLog) << "MissionControllerLog::loadFromVehicle called while offline"; - } else if (syncInProgress()) { - qCWarning(MissionControllerLog) << "MissionControllerLog::loadFromVehicle called while syncInProgress"; - } else { - _itemsRequested = true; - _managerVehicle->missionManager()->loadFromVehicle(); - } -} - -void MissionController::sendToVehicle(void) -{ - if (_masterController->offline()) { - qCWarning(MissionControllerLog) << "MissionControllerLog::sendToVehicle called while offline"; - } else if (syncInProgress()) { - qCWarning(MissionControllerLog) << "MissionControllerLog::sendToVehicle called while syncInProgress"; - } else { - qCDebug(MissionControllerLog) << "MissionControllerLog::sendToVehicle"; - if (_visualItems->count() == 1) { - // This prevents us from sending a possibly bogus home position to the vehicle - QmlObjectListModel emptyModel; - sendItemsToVehicle(_managerVehicle, &emptyModel); - } else { - sendItemsToVehicle(_managerVehicle, _visualItems); - } - setDirty(false); - } -} - -/// Converts from visual items to MissionItems -/// @param missionItemParent QObject parent for newly allocated MissionItems -/// @return true: Mission end action was added to end of list -bool MissionController::_convertToMissionItems(QmlObjectListModel* visualMissionItems, QList& rgMissionItems, QObject* missionItemParent) -{ - if (visualMissionItems->count() == 0) { - return false; - } - - bool endActionSet = false; - int lastSeqNum = 0; - - for (int i=0; icount(); i++) { - VisualMissionItem* visualItem = qobject_cast(visualMissionItems->get(i)); - - lastSeqNum = visualItem->lastSequenceNumber(); - visualItem->appendMissionItems(rgMissionItems, missionItemParent); - - qCDebug(MissionControllerLog) << "_convertToMissionItems seqNum:lastSeqNum:command" - << visualItem->sequenceNumber() - << lastSeqNum - << visualItem->commandName(); - } - - // Mission settings has a special case for end mission action - MissionSettingsItem* settingsItem = visualMissionItems->value(0); - if (settingsItem) { - endActionSet = settingsItem->addMissionEndAction(rgMissionItems, lastSeqNum + 1, missionItemParent); - } - - return endActionSet; -} - -void MissionController::addMissionToKML(KMLPlanDomDocument& planKML) -{ - QObject* deleteParent = new QObject(); - QList rgMissionItems; - - _convertToMissionItems(_visualItems, rgMissionItems, deleteParent); - planKML.addMission(_controllerVehicle, _visualItems, rgMissionItems); - deleteParent->deleteLater(); -} - -void MissionController::sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems) -{ - if (vehicle) { - QList rgMissionItems; - - _convertToMissionItems(visualMissionItems, rgMissionItems, vehicle); - - // PlanManager takes control of MissionItems so no need to delete - vehicle->missionManager()->writeMissionItems(rgMissionItems); - } -} - -int MissionController::_nextSequenceNumber(void) -{ - if (_visualItems->count() == 0) { - qWarning() << "Internal error: Empty visual item list"; - return 0; - } else { - VisualMissionItem* lastItem = _visualItems->value(_visualItems->count() - 1); - return lastItem->lastSequenceNumber() + 1; - } -} - -VisualMissionItem* MissionController::_insertSimpleMissionItemWorker(QGeoCoordinate coordinate, MAV_CMD command, int visualItemIndex, bool makeCurrentItem) -{ - int sequenceNumber = _nextSequenceNumber(); - SimpleMissionItem * newItem = new SimpleMissionItem(_masterController, _flyView, false /* forLoad */, this); - newItem->setSequenceNumber(sequenceNumber); - newItem->setCoordinate(coordinate); - newItem->setCommand(command); - _initVisualItem(newItem); - - if (newItem->specifiesAltitude()) { - if (!qgcApp()->toolbox()->missionCommandTree()->isLandCommand(command)) { - double prevAltitude; - int prevAltitudeMode; - - if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) { - newItem->altitude()->setRawValue(prevAltitude); - if (globalAltitudeMode() == QGroundControlQmlGlobal::AltitudeModeNone) { - // We are in mixed altitude modes, so copy from previous. Otherwise alt mode will be set from global setting. - newItem->setAltitudeMode(static_cast(prevAltitudeMode)); - } - } - } - } - if (visualItemIndex == -1) { - _visualItems->append(newItem); - } else { - _visualItems->insert(visualItemIndex, newItem); - } - - // We send the click coordinate through here to be able to set the planned home position from the user click location if needed - _recalcAllWithCoordinate(coordinate); - - if (makeCurrentItem) { - setCurrentPlanViewSeqNum(newItem->sequenceNumber(), true); - } - - _firstItemAdded(); - - return newItem; -} - - -VisualMissionItem* MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem) -{ - return _insertSimpleMissionItemWorker(coordinate, MAV_CMD_NAV_WAYPOINT, visualItemIndex, makeCurrentItem); -} - -VisualMissionItem* MissionController::insertTakeoffItem(QGeoCoordinate /*coordinate*/, int visualItemIndex, bool makeCurrentItem) -{ - int sequenceNumber = _nextSequenceNumber(); - _takeoffMissionItem = new TakeoffMissionItem(_controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_TAKEOFF : MAV_CMD_NAV_TAKEOFF, _masterController, _flyView, _settingsItem, this); - _takeoffMissionItem->setSequenceNumber(sequenceNumber); - _initVisualItem(_takeoffMissionItem); - - if (_takeoffMissionItem->specifiesAltitude()) { - double prevAltitude; - int prevAltitudeMode; - - if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) { - _takeoffMissionItem->altitude()->setRawValue(prevAltitude); - _takeoffMissionItem->setAltitudeMode(static_cast(prevAltitudeMode)); - } - } - if (visualItemIndex == -1) { - _visualItems->append(_takeoffMissionItem); - } else { - _visualItems->insert(visualItemIndex, _takeoffMissionItem); - } - - _recalcAll(); - - if (makeCurrentItem) { - setCurrentPlanViewSeqNum(_takeoffMissionItem->sequenceNumber(), true); - } - - _firstItemAdded(); - - return _takeoffMissionItem; -} - -VisualMissionItem* MissionController::insertLandItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem) -{ - if (_controllerVehicle->fixedWing()) { - FixedWingLandingComplexItem* fwLanding = qobject_cast(insertComplexMissionItem(FixedWingLandingComplexItem::name, coordinate, visualItemIndex, makeCurrentItem)); - return fwLanding; - } else if (_controllerVehicle->vtol()) { - VTOLLandingComplexItem* vtolLanding = qobject_cast(insertComplexMissionItem(VTOLLandingComplexItem::name, coordinate, visualItemIndex, makeCurrentItem)); - return vtolLanding; - } else { - return _insertSimpleMissionItemWorker(coordinate, _controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_LAND : MAV_CMD_NAV_RETURN_TO_LAUNCH, visualItemIndex, makeCurrentItem); - } -} - -VisualMissionItem* MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem) -{ - SimpleMissionItem* simpleItem = qobject_cast(_insertSimpleMissionItemWorker(coordinate, MAV_CMD_DO_SET_ROI_LOCATION, visualItemIndex, makeCurrentItem)); - - if (!_controllerVehicle->firmwarePlugin()->supportedMissionCommands(QGCMAVLink::VehicleClassGeneric).contains(MAV_CMD_DO_SET_ROI_LOCATION)) { - simpleItem->setCommand(MAV_CMD_DO_SET_ROI) ; - simpleItem->missionItem().setParam1(MAV_ROI_LOCATION); - } - _recalcROISpecialVisuals(); - return simpleItem; -} - -VisualMissionItem* MissionController::insertCancelROIMissionItem(int visualItemIndex, bool makeCurrentItem) -{ - SimpleMissionItem* simpleItem = qobject_cast(_insertSimpleMissionItemWorker(QGeoCoordinate(), MAV_CMD_DO_SET_ROI_NONE, visualItemIndex, makeCurrentItem)); - - if (!_controllerVehicle->firmwarePlugin()->supportedMissionCommands(QGCMAVLink::VehicleClassGeneric).contains(MAV_CMD_DO_SET_ROI_NONE)) { - simpleItem->setCommand(MAV_CMD_DO_SET_ROI) ; - simpleItem->missionItem().setParam1(MAV_ROI_NONE); - } - _recalcROISpecialVisuals(); - return simpleItem; -} - -VisualMissionItem* MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int visualItemIndex, bool makeCurrentItem) -{ - ComplexMissionItem* newItem = nullptr; - - if (itemName == SurveyComplexItem::name) { - newItem = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */); - newItem->setCoordinate(mapCenterCoordinate); - } else if (itemName == FixedWingLandingComplexItem::name) { - newItem = new FixedWingLandingComplexItem(_masterController, _flyView, _visualItems /* parent */); - } else if (itemName == VTOLLandingComplexItem::name) { - newItem = new VTOLLandingComplexItem(_masterController, _flyView, _visualItems /* parent */); - } else if (itemName == StructureScanComplexItem::name) { - newItem = new StructureScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */); - } else if (itemName == CorridorScanComplexItem::name) { - newItem = new CorridorScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */); - } else if (itemName == CircularSurvey::name) { - newItem = new CircularSurvey(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */); - } else { - qWarning() << "Internal error: Unknown complex item:" << itemName; - return nullptr; - } - - _insertComplexMissionItemWorker(mapCenterCoordinate, newItem, visualItemIndex, makeCurrentItem); - - return newItem; -} - -VisualMissionItem* MissionController::insertComplexMissionItemFromKMLOrSHP(QString itemName, QString file, int visualItemIndex, bool makeCurrentItem) -{ - ComplexMissionItem* newItem = nullptr; - - if (itemName == SurveyComplexItem::name) { - newItem = new SurveyComplexItem(_masterController, _flyView, file, _visualItems); - } else if (itemName == StructureScanComplexItem::name) { - newItem = new StructureScanComplexItem(_masterController, _flyView, file, _visualItems); - } else if (itemName == CorridorScanComplexItem::name) { - newItem = new CorridorScanComplexItem(_masterController, _flyView, file, _visualItems); - } else { - qWarning() << "Internal error: Unknown complex item:" << itemName; - return nullptr; - } - - _insertComplexMissionItemWorker(QGeoCoordinate(), newItem, visualItemIndex, makeCurrentItem); - - return newItem; -} - -void MissionController::_insertComplexMissionItemWorker(const QGeoCoordinate& mapCenterCoordinate, ComplexMissionItem* complexItem, int visualItemIndex, bool makeCurrentItem) -{ - int sequenceNumber = _nextSequenceNumber(); - bool surveyStyleItem = qobject_cast(complexItem) || - qobject_cast(complexItem) || - qobject_cast(complexItem) || - qobject_cast(complexItem) ; - - if (surveyStyleItem) { - bool rollSupported = false; - bool pitchSupported = false; - bool yawSupported = false; - - // If the vehicle is known to have a gimbal then we automatically point the gimbal straight down if not already set - - MissionSettingsItem* settingsItem = _visualItems->value(0); - CameraSection* cameraSection = settingsItem->cameraSection(); - - // Set camera to photo mode (leave alone if user already specified) - if (cameraSection->cameraModeSupported() && !cameraSection->specifyCameraMode()) { - cameraSection->setSpecifyCameraMode(true); - cameraSection->cameraMode()->setRawValue(CAMERA_MODE_IMAGE_SURVEY); - } - - // Point gimbal straight down - if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) { - // If the user already specified a gimbal angle leave it alone - if (!cameraSection->specifyGimbal()) { - cameraSection->setSpecifyGimbal(true); - cameraSection->gimbalPitch()->setRawValue(-90.0); - } - } - } - - complexItem->setSequenceNumber(sequenceNumber); - complexItem->setWizardMode(true); - _initVisualItem(complexItem); - - if (visualItemIndex == -1) { - _visualItems->append(complexItem); - } else { - _visualItems->insert(visualItemIndex, complexItem); - } - - //-- Keep track of bounding box changes in complex items - if(!complexItem->isSimpleItem()) { - connect(complexItem, &ComplexMissionItem::boundingCubeChanged, this, &MissionController::_complexBoundingBoxChanged); - } - _recalcAllWithCoordinate(mapCenterCoordinate); - - if (makeCurrentItem) { - setCurrentPlanViewSeqNum(complexItem->sequenceNumber(), true); - } - _firstItemAdded(); -} - -void MissionController::removeVisualItem(int viIndex) -{ - if (viIndex <= 0 || viIndex >= _visualItems->count()) { - qWarning() << "MissionController::removeVisualItem called with bad index - count:index" << _visualItems->count() << viIndex; - return; - } - - bool removeSurveyStyle = _visualItems->value(viIndex) || - _visualItems->value(viIndex) || - _visualItems->value(viIndex); - VisualMissionItem* item = qobject_cast(_visualItems->removeAt(viIndex)); - - if (item == _takeoffMissionItem) { - _takeoffMissionItem = nullptr; - } - - _deinitVisualItem(item); - item->deleteLater(); - - if (removeSurveyStyle) { - // Determine if the mission still has another survey style item in it - bool foundSurvey = false; - for (int i=1; i<_visualItems->count(); i++) { - if (_visualItems->value(i) - || _visualItems->value(i) - || _visualItems->value(i) ) { - foundSurvey = true; - break; - } - } - - // If there is no longer a survey item in the mission remove added commands - if (!foundSurvey) { - bool rollSupported = false; - bool pitchSupported = false; - bool yawSupported = false; - CameraSection* cameraSection = _settingsItem->cameraSection(); - if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) { - if (cameraSection->specifyGimbal() && cameraSection->gimbalPitch()->rawValue().toDouble() == -90.0 && cameraSection->gimbalYaw()->rawValue().toDouble() == 0.0) { - cameraSection->setSpecifyGimbal(false); - } - } - if (cameraSection->cameraModeSupported() && cameraSection->specifyCameraMode() && cameraSection->cameraMode()->rawValue().toInt() == 0) { - cameraSection->setSpecifyCameraMode(false); - } - } - } - - _recalcAll(); - - // Adjust current item - int newVIIndex; - if (viIndex >= _visualItems->count()) { - newVIIndex = _visualItems->count() - 1; - } else { - newVIIndex = viIndex; - } - setCurrentPlanViewSeqNum(_visualItems->value(newVIIndex)->sequenceNumber(), true); - - setDirty(true); - - if (_visualItems->count() == 1) { - _allItemsRemoved(); - } -} - -void MissionController::removeAll(void) -{ - if (_visualItems) { - _deinitAllVisualItems(); - _visualItems->clearAndDeleteContents(); - _visualItems->deleteLater(); - _settingsItem = nullptr; - _takeoffMissionItem = nullptr; - _visualItems = new QmlObjectListModel(this); - _addMissionSettings(_visualItems); - _initAllVisualItems(); - setDirty(true); - _resetMissionFlightStatus(); - _allItemsRemoved(); - } -} - -bool MissionController::_loadJsonMissionFileV1(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString) -{ - // Validate root object keys - QList rootKeyInfoList = { - { _jsonPlannedHomePositionKey, QJsonValue::Object, true }, - { _jsonItemsKey, QJsonValue::Array, true }, - { _jsonMavAutopilotKey, QJsonValue::Double, true }, - { _jsonComplexItemsKey, QJsonValue::Array, true }, - }; - if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) { - return false; - } - - setGlobalAltitudeMode(QGroundControlQmlGlobal::AltitudeModeNone); // Mixed mode - - // Read complex items - QList surveyItems; - QJsonArray complexArray(json[_jsonComplexItemsKey].toArray()); - qCDebug(MissionControllerLog) << "Json load: complex item count" << complexArray.count(); - for (int i=0; iload(itemObject, itemObject["id"].toInt(), errorString)) { - surveyItems.append(item); - } else { - return false; - } - } - - // Read simple items, interspersing complex items into the full list - - int nextSimpleItemIndex= 0; - int nextComplexItemIndex= 0; - int nextSequenceNumber = 1; // Start with 1 since home is in 0 - QJsonArray itemArray(json[_jsonItemsKey].toArray()); - - MissionSettingsItem* settingsItem = _addMissionSettings(visualItems); - if (json.contains(_jsonPlannedHomePositionKey)) { - SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems); - if (item->load(json[_jsonPlannedHomePositionKey].toObject(), 0, errorString)) { - settingsItem->setInitialHomePositionFromUser(item->coordinate()); - item->deleteLater(); - } else { - return false; - } - } - - qCDebug(MissionControllerLog) << "Json load: simple item loop start simpleItemCount:ComplexItemCount" << itemArray.count() << surveyItems.count(); - do { - qCDebug(MissionControllerLog) << "Json load: simple item loop nextSimpleItemIndex:nextComplexItemIndex:nextSequenceNumber" << nextSimpleItemIndex << nextComplexItemIndex << nextSequenceNumber; - - // If there is a complex item that should be next in sequence add it in - if (nextComplexItemIndex < surveyItems.count()) { - SurveyComplexItem* complexItem = surveyItems[nextComplexItemIndex]; - - if (complexItem->sequenceNumber() == nextSequenceNumber) { - qCDebug(MissionControllerLog) << "Json load: injecting complex item expectedSequence:actualSequence:" << nextSequenceNumber << complexItem->sequenceNumber(); - visualItems->append(complexItem); - nextSequenceNumber = complexItem->lastSequenceNumber() + 1; - nextComplexItemIndex++; - continue; - } - } - - // Add the next available simple item - if (nextSimpleItemIndex < itemArray.count()) { - const QJsonValue& itemValue = itemArray[nextSimpleItemIndex++]; - - if (!itemValue.isObject()) { - errorString = QStringLiteral("Mission item is not an object"); - return false; - } - - const QJsonObject itemObject = itemValue.toObject(); - SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems); - if (item->load(itemObject, itemObject["id"].toInt(), errorString)) { - if (TakeoffMissionItem::isTakeoffCommand(item->mavCommand())) { - // This needs to be a TakeoffMissionItem - TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, visualItems); - takeoffItem->load(itemObject, itemObject["id"].toInt(), errorString); - item->deleteLater(); - item = takeoffItem; - } - qCDebug(MissionControllerLog) << "Json load: adding simple item expectedSequence:actualSequence" << nextSequenceNumber << item->sequenceNumber(); - nextSequenceNumber = item->lastSequenceNumber() + 1; - visualItems->append(item); - } else { - return false; - } - } - } while (nextSimpleItemIndex < itemArray.count() || nextComplexItemIndex < surveyItems.count()); - - return true; -} - -bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString) -{ - // Validate root object keys - QList rootKeyInfoList = { - { _jsonPlannedHomePositionKey, QJsonValue::Array, true }, - { _jsonItemsKey, QJsonValue::Array, true }, - { _jsonFirmwareTypeKey, QJsonValue::Double, true }, - { _jsonVehicleTypeKey, QJsonValue::Double, false }, - { _jsonCruiseSpeedKey, QJsonValue::Double, false }, - { _jsonHoverSpeedKey, QJsonValue::Double, false }, - { _jsonGlobalPlanAltitudeModeKey, QJsonValue::Double, false }, - }; - if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) { - return false; - } - - setGlobalAltitudeMode(QGroundControlQmlGlobal::AltitudeModeNone); // Mixed mode - - qCDebug(MissionControllerLog) << "MissionController::_loadJsonMissionFileV2 itemCount:" << json[_jsonItemsKey].toArray().count(); - - AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings(); - - // Get the firmware/vehicle type from the plan file - MAV_AUTOPILOT planFileFirmwareType = static_cast(json[_jsonFirmwareTypeKey].toInt()); - MAV_TYPE planFileVehicleType = static_cast (QGCMAVLink::vehicleClassToMavType(appSettings->offlineEditingVehicleClass()->rawValue().toInt())); - if (json.contains(_jsonVehicleTypeKey)) { - planFileVehicleType = static_cast(json[_jsonVehicleTypeKey].toInt()); - } - - // Update firmware/vehicle offline settings if we aren't connect to a vehicle - if (_masterController->offline()) { - appSettings->offlineEditingFirmwareClass()->setRawValue(QGCMAVLink::firmwareClass(static_cast(json[_jsonFirmwareTypeKey].toInt()))); - if (json.contains(_jsonVehicleTypeKey)) { - appSettings->offlineEditingVehicleClass()->setRawValue(QGCMAVLink::vehicleClass(planFileVehicleType)); - } - } - - // The controller vehicle always tracks the Plan file firmware/vehicle types so update it - _controllerVehicle->stopTrackingFirmwareVehicleTypeChanges(); - _controllerVehicle->_offlineFirmwareTypeSettingChanged(planFileFirmwareType); - _controllerVehicle->_offlineVehicleTypeSettingChanged(planFileVehicleType); - - if (json.contains(_jsonCruiseSpeedKey)) { - appSettings->offlineEditingCruiseSpeed()->setRawValue(json[_jsonCruiseSpeedKey].toDouble()); - } - if (json.contains(_jsonHoverSpeedKey)) { - appSettings->offlineEditingHoverSpeed()->setRawValue(json[_jsonHoverSpeedKey].toDouble()); - } - if (json.contains(_jsonGlobalPlanAltitudeModeKey)) { - setGlobalAltitudeMode(json[_jsonGlobalPlanAltitudeModeKey].toVariant().value()); - } - - QGeoCoordinate homeCoordinate; - if (!JsonHelper::loadGeoCoordinate(json[_jsonPlannedHomePositionKey], true /* altitudeRequired */, homeCoordinate, errorString)) { - return false; - } - MissionSettingsItem* settingsItem = new MissionSettingsItem(_masterController, _flyView, visualItems); - settingsItem->setCoordinate(homeCoordinate); - visualItems->insert(0, settingsItem); - qCDebug(MissionControllerLog) << "plannedHomePosition" << homeCoordinate; - - // Read mission items - - int nextSequenceNumber = 1; // Start with 1 since home is in 0 - const QJsonArray rgMissionItems(json[_jsonItemsKey].toArray()); - for (int i=0; i itemKeyInfoList = { - { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, - }; - if (!JsonHelper::validateKeys(itemObject, itemKeyInfoList, errorString)) { - return false; - } - QString itemType = itemObject[VisualMissionItem::jsonTypeKey].toString(); - - if (itemType == VisualMissionItem::jsonTypeSimpleItemValue) { - SimpleMissionItem* simpleItem = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems); - if (simpleItem->load(itemObject, nextSequenceNumber, errorString)) { - if (TakeoffMissionItem::isTakeoffCommand(static_cast(simpleItem->command()))) { - // This needs to be a TakeoffMissionItem - TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, this); - takeoffItem->load(itemObject, nextSequenceNumber, errorString); - simpleItem->deleteLater(); - simpleItem = takeoffItem; - } - qCDebug(MissionControllerLog) << "Loading simple item: nextSequenceNumber:command" << nextSequenceNumber << simpleItem->command(); - nextSequenceNumber = simpleItem->lastSequenceNumber() + 1; - visualItems->append(simpleItem); - } else { - return false; - } - } else if (itemType == VisualMissionItem::jsonTypeComplexItemValue) { - QList complexItemKeyInfoList = { - { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, - }; - if (!JsonHelper::validateKeys(itemObject, complexItemKeyInfoList, errorString)) { - return false; - } - QString complexItemType = itemObject[ComplexMissionItem::jsonComplexItemTypeKey].toString(); - - if (complexItemType == SurveyComplexItem::jsonComplexItemTypeValue) { - qCDebug(MissionControllerLog) << "Loading Survey: nextSequenceNumber" << nextSequenceNumber; - SurveyComplexItem* surveyItem = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems); - if (!surveyItem->load(itemObject, nextSequenceNumber++, errorString)) { - return false; - } - nextSequenceNumber = surveyItem->lastSequenceNumber() + 1; - qCDebug(MissionControllerLog) << "Survey load complete: nextSequenceNumber" << nextSequenceNumber; - visualItems->append(surveyItem); - } else if (complexItemType == FixedWingLandingComplexItem::jsonComplexItemTypeValue) { - qCDebug(MissionControllerLog) << "Loading Fixed Wing Landing Pattern: nextSequenceNumber" << nextSequenceNumber; - FixedWingLandingComplexItem* landingItem = new FixedWingLandingComplexItem(_masterController, _flyView, visualItems); - if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) { - return false; - } - nextSequenceNumber = landingItem->lastSequenceNumber() + 1; - qCDebug(MissionControllerLog) << "FW Landing Pattern load complete: nextSequenceNumber" << nextSequenceNumber; - visualItems->append(landingItem); - } else if (complexItemType == VTOLLandingComplexItem::jsonComplexItemTypeValue) { - qCDebug(MissionControllerLog) << "Loading VTOL Landing Pattern: nextSequenceNumber" << nextSequenceNumber; - VTOLLandingComplexItem* landingItem = new VTOLLandingComplexItem(_masterController, _flyView, visualItems); - if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) { - return false; - } - nextSequenceNumber = landingItem->lastSequenceNumber() + 1; - qCDebug(MissionControllerLog) << "VTOL Landing Pattern load complete: nextSequenceNumber" << nextSequenceNumber; - visualItems->append(landingItem); - } else if (complexItemType == StructureScanComplexItem::jsonComplexItemTypeValue) { - qCDebug(MissionControllerLog) << "Loading Structure Scan: nextSequenceNumber" << nextSequenceNumber; - StructureScanComplexItem* structureItem = new StructureScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems); - if (!structureItem->load(itemObject, nextSequenceNumber++, errorString)) { - return false; - } - nextSequenceNumber = structureItem->lastSequenceNumber() + 1; - qCDebug(MissionControllerLog) << "Structure Scan load complete: nextSequenceNumber" << nextSequenceNumber; - visualItems->append(structureItem); - } else if (complexItemType == CorridorScanComplexItem::jsonComplexItemTypeValue) { - qCDebug(MissionControllerLog) << "Loading Corridor Scan: nextSequenceNumber" << nextSequenceNumber; - CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems); - if (!corridorItem->load(itemObject, nextSequenceNumber++, errorString)) { - return false; - } - nextSequenceNumber = corridorItem->lastSequenceNumber() + 1; - qCDebug(MissionControllerLog) << "Corridor Scan load complete: nextSequenceNumber" << nextSequenceNumber; - visualItems->append(corridorItem); - } else if (complexItemType == CircularSurvey::jsonComplexItemTypeValue) { - qCDebug(MissionControllerLog) << "Loading Circular Survey: nextSequenceNumber" << nextSequenceNumber; - CircularSurvey* survey = new CircularSurvey(_masterController, _flyView, QString() /* kmlFile */, visualItems); - if (!survey->load(itemObject, nextSequenceNumber++, errorString)) { - return false; - } - nextSequenceNumber = survey->lastSequenceNumber() + 1; - qCDebug(MissionControllerLog) << "Ciruclar Survey load complete: nextSequenceNumber" << nextSequenceNumber; - visualItems->append(survey); - } else { - errorString = tr("Unsupported complex item type: %1").arg(complexItemType); - } - } else { - errorString = tr("Unknown item type: %1").arg(itemType); - return false; - } - } - - // Fix up the DO_JUMP commands jump sequence number by finding the item with the matching doJumpId - for (int i=0; icount(); i++) { - if (visualItems->value(i)->isSimpleItem()) { - SimpleMissionItem* doJumpItem = visualItems->value(i); - if (doJumpItem->command() == MAV_CMD_DO_JUMP) { - bool found = false; - int findDoJumpId = static_cast(doJumpItem->missionItem().param1()); - for (int j=0; jcount(); j++) { - if (visualItems->value(j)->isSimpleItem()) { - SimpleMissionItem* targetItem = visualItems->value(j); - if (targetItem->missionItem().doJumpId() == findDoJumpId) { - doJumpItem->missionItem().setParam1(targetItem->sequenceNumber()); - found = true; - break; - } - } - } - if (!found) { - errorString = tr("Could not find doJumpId: %1").arg(findDoJumpId); - return false; - } - } - } - } - - return true; -} - -bool MissionController::_loadItemsFromJson(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString) -{ - // V1 file format has no file type key and version key is string. Convert to new format. - if (!json.contains(JsonHelper::jsonFileTypeKey)) { - json[JsonHelper::jsonFileTypeKey] = _jsonFileTypeValue; - } - - int fileVersion; - JsonHelper::validateExternalQGCJsonFile(json, - _jsonFileTypeValue, // expected file type - 1, // minimum supported version - 2, // maximum supported version - fileVersion, - errorString); - - if (fileVersion == 1) { - return _loadJsonMissionFileV1(json, visualItems, errorString); - } else { - return _loadJsonMissionFileV2(json, visualItems, errorString); - } -} - -bool MissionController::_loadTextMissionFile(QTextStream& stream, QmlObjectListModel* visualItems, QString& errorString) -{ - bool firstItem = true; - bool plannedHomePositionInFile = false; - - QString firstLine = stream.readLine(); - const QStringList& version = firstLine.split(" "); - - bool versionOk = false; - if (version.size() == 3 && version[0] == "QGC" && version[1] == "WPL") { - if (version[2] == "110") { - // ArduPilot file, planned home position is already in position 0 - versionOk = true; - plannedHomePositionInFile = true; - } else if (version[2] == "120") { - // Old QGC file, no planned home position - versionOk = true; - plannedHomePositionInFile = false; - } - } - - if (versionOk) { - MissionSettingsItem* settingsItem = _addMissionSettings(visualItems); - - while (!stream.atEnd()) { - SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems); - if (item->load(stream)) { - if (firstItem && plannedHomePositionInFile) { - settingsItem->setInitialHomePositionFromUser(item->coordinate()); - } else { - if (TakeoffMissionItem::isTakeoffCommand(static_cast(item->command()))) { - // This needs to be a TakeoffMissionItem - TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, visualItems); - takeoffItem->load(stream); - item->deleteLater(); - item = takeoffItem; - } - visualItems->append(item); - } - firstItem = false; - } else { - errorString = tr("The mission file is corrupted."); - return false; - } - } - } else { - errorString = tr("The mission file is not compatible with this version of %1.").arg(qgcApp()->applicationName()); - return false; - } - - if (!plannedHomePositionInFile) { - // Update sequence numbers in DO_JUMP commands to take into account added home position in index 0 - for (int i=1; icount(); i++) { - SimpleMissionItem* item = qobject_cast(visualItems->get(i)); - if (item && item->command() == MAV_CMD_DO_JUMP) { - item->missionItem().setParam1(static_cast(item->missionItem().param1()) + 1); - } - } - } - - return true; -} - -void MissionController::_initLoadedVisualItems(QmlObjectListModel* loadedVisualItems) -{ - if (_visualItems) { - _deinitAllVisualItems(); - _visualItems->deleteLater(); - } - _settingsItem = nullptr; - _takeoffMissionItem = nullptr; - - _visualItems = loadedVisualItems; - - if (_visualItems->count() == 0) { - _addMissionSettings(_visualItems); - } else { - _settingsItem = _visualItems->value(0); - } - - MissionController::_scanForAdditionalSettings(_visualItems, _masterController); - - _initAllVisualItems(); - - if (_visualItems->count() > 1) { - _firstItemAdded(); - } else { - _allItemsRemoved(); - } -} - -bool MissionController::load(const QJsonObject& json, QString& errorString) -{ - QString errorStr; - QString errorMessage = tr("Mission: %1"); - QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this); - - if (!_loadJsonMissionFileV2(json, loadedVisualItems, errorStr)) { - errorString = errorMessage.arg(errorStr); - return false; - } - _initLoadedVisualItems(loadedVisualItems); - - return true; -} - -bool MissionController::loadJsonFile(QFile& file, QString& errorString) -{ - QString errorStr; - QString errorMessage = tr("Mission: %1"); - QJsonDocument jsonDoc; - QByteArray bytes = file.readAll(); - - if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorStr)) { - errorString = errorMessage.arg(errorStr); - return false; - } - - QJsonObject json = jsonDoc.object(); - QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this); - if (!_loadItemsFromJson(json, loadedVisualItems, errorStr)) { - errorString = errorMessage.arg(errorStr); - return false; - } - - _initLoadedVisualItems(loadedVisualItems); - - return true; -} - -bool MissionController::loadTextFile(QFile& file, QString& errorString) -{ - QString errorStr; - QString errorMessage = tr("Mission: %1"); - QByteArray bytes = file.readAll(); - QTextStream stream(bytes); - - setGlobalAltitudeMode(QGroundControlQmlGlobal::AltitudeModeNone); // Mixed mode - - QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this); - if (!_loadTextMissionFile(stream, loadedVisualItems, errorStr)) { - errorString = errorMessage.arg(errorStr); - return false; - } - - _initLoadedVisualItems(loadedVisualItems); - - return true; -} - -int MissionController::readyForSaveState(void) const -{ - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i)); - if (visualItem->readyForSaveState() != VisualMissionItem::ReadyForSave) { - return visualItem->readyForSaveState(); - } - } - - return VisualMissionItem::ReadyForSave; -} - -void MissionController::save(QJsonObject& json) -{ - json[JsonHelper::jsonVersionKey] = _missionFileVersion; - - // Mission settings - - MissionSettingsItem* settingsItem = _visualItems->value(0); - if (!settingsItem) { - qWarning() << "First item is not MissionSettingsItem"; - return; - } - QJsonValue coordinateValue; - JsonHelper::saveGeoCoordinate(settingsItem->coordinate(), true /* writeAltitude */, coordinateValue); - json[_jsonPlannedHomePositionKey] = coordinateValue; - json[_jsonFirmwareTypeKey] = _controllerVehicle->firmwareType(); - json[_jsonVehicleTypeKey] = _controllerVehicle->vehicleType(); - json[_jsonCruiseSpeedKey] = _controllerVehicle->defaultCruiseSpeed(); - json[_jsonHoverSpeedKey] = _controllerVehicle->defaultHoverSpeed(); - json[_jsonGlobalPlanAltitudeModeKey] = _globalAltMode; - - // Save the visual items - - QJsonArray rgJsonMissionItems; - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i)); - - visualItem->save(rgJsonMissionItems); - } - - // Mission settings has a special case for end mission action - if (settingsItem) { - QList rgMissionItems; - - if (_convertToMissionItems(_visualItems, rgMissionItems, this /* missionItemParent */)) { - QJsonObject saveObject; - MissionItem* missionItem = rgMissionItems[rgMissionItems.count() - 1]; - missionItem->save(saveObject); - rgJsonMissionItems.append(saveObject); - } - for (int i=0; ideleteLater(); - } - } - - json[_jsonItemsKey] = rgJsonMissionItems; -} - -void MissionController::_calcPrevWaypointValues(VisualMissionItem* currentItem, VisualMissionItem* prevItem, double* azimuth, double* distance, double* altDifference) -{ - QGeoCoordinate currentCoord = currentItem->coordinate(); - QGeoCoordinate prevCoord = prevItem->exitCoordinate(); - - // Convert to fixed altitudes - - *altDifference = currentItem->amslEntryAlt() - prevItem->amslExitAlt(); - *distance = prevCoord.distanceTo(currentCoord); - *azimuth = prevCoord.azimuthTo(currentCoord); -} - -double MissionController::_calcDistanceToHome(VisualMissionItem* currentItem, VisualMissionItem* homeItem) -{ - QGeoCoordinate currentCoord = currentItem->coordinate(); - QGeoCoordinate homeCoord = homeItem->exitCoordinate(); - bool distanceOk = false; - - distanceOk = true; - - return distanceOk ? homeCoord.distanceTo(currentCoord) : 0.0; -} - -FlightPathSegment* MissionController::_createFlightPathSegmentWorker(VisualItemPair& pair) -{ - QGeoCoordinate coord1 = pair.first->isSimpleItem() ? pair.first->coordinate() : pair.first->exitCoordinate(); - QGeoCoordinate coord2 = pair.second->coordinate(); - double coord1Alt = pair.first->isSimpleItem() ? pair.first->amslEntryAlt() : pair.first->amslExitAlt(); - double coord2Alt = pair.second->amslEntryAlt(); - - FlightPathSegment* segment = new FlightPathSegment(coord1, coord1Alt, coord2, coord2Alt, !_flyView /* queryTerrainData */, this); - - auto coord1Notifier = pair.first->isSimpleItem() ? &VisualMissionItem::coordinateChanged : &VisualMissionItem::exitCoordinateChanged; - auto coord2Notifier = &VisualMissionItem::coordinateChanged; - auto coord1AltNotifier = pair.first->isSimpleItem() ? &VisualMissionItem::amslEntryAltChanged : &VisualMissionItem::amslExitAltChanged; - auto coord2AltNotifier = &VisualMissionItem::amslEntryAltChanged; - - connect(pair.first, coord1Notifier, segment, &FlightPathSegment::setCoordinate1); - connect(pair.second, coord2Notifier, segment, &FlightPathSegment::setCoordinate2); - connect(pair.first, coord1AltNotifier, segment, &FlightPathSegment::setCoord1AMSLAlt); - connect(pair.second, coord2AltNotifier, segment, &FlightPathSegment::setCoord2AMSLAlt); - - connect(pair.second, &VisualMissionItem::coordinateChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - - connect(segment, &FlightPathSegment::totalDistanceChanged, this, &MissionController::recalcTerrainProfile, Qt::QueuedConnection); - connect(segment, &FlightPathSegment::coord1AMSLAltChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(segment, &FlightPathSegment::coord2AMSLAltChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(segment, &FlightPathSegment::amslTerrainHeightsChanged, this, &MissionController::recalcTerrainProfile, Qt::QueuedConnection); - connect(segment, &FlightPathSegment::terrainCollisionChanged, this, &MissionController::recalcTerrainProfile, Qt::QueuedConnection); - - return segment; -} - -FlightPathSegment* MissionController::_addFlightPathSegment(FlightPathSegmentHashTable& prevItemPairHashTable, VisualItemPair& pair) -{ - FlightPathSegment* segment = nullptr; - - if (prevItemPairHashTable.contains(pair)) { - // Pair already exists and connected, just re-use - _flightPathSegmentHashTable[pair] = segment = prevItemPairHashTable.take(pair); - } else { - segment = _createFlightPathSegmentWorker(pair); - _flightPathSegmentHashTable[pair] = segment; - } - - _simpleFlightPathSegments.append(segment); - - return segment; -} - -void MissionController::_recalcROISpecialVisuals(void) -{ - return; - VisualMissionItem* lastCoordinateItem = qobject_cast(_visualItems->get(0)); - bool roiActive = false; - - for (int i=1; i<_visualItems->count(); i++) { - VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i)); - SimpleMissionItem* simpleItem = qobject_cast(visualItem); - VisualItemPair viPair; - - if (simpleItem) { - if (roiActive) { - if (_isROICancelItem(simpleItem)) { - roiActive = false; - } - } else { - if (_isROIBeginItem(simpleItem)) { - roiActive = true; - } - } - } - - if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) { - viPair = VisualItemPair(lastCoordinateItem, visualItem); - if (_flightPathSegmentHashTable.contains(viPair)) { - _flightPathSegmentHashTable[viPair]->setSpecialVisual(roiActive); - } - lastCoordinateItem = visualItem; - } - } -} - -void MissionController::_recalcFlightPathSegments(void) -{ - VisualItemPair lastSegmentVisualItemPair; - int segmentCount = 0; - bool firstCoordinateNotFound = true; - VisualMissionItem* lastFlyThroughVI = qobject_cast(_visualItems->get(0)); - bool linkEndToHome = false; - bool linkStartToHome = _controllerVehicle->rover() ? true : false; - bool foundRTL = false; - bool homePositionValid = _settingsItem->coordinate().isValid(); - bool roiActive = false; - bool previousItemIsIncomplete = false; - - qCDebug(MissionControllerLog) << "_recalcFlightPathSegments homePositionValid" << homePositionValid; - - FlightPathSegmentHashTable oldSegmentTable = _flightPathSegmentHashTable; - - _missionContainsVTOLTakeoff = false; - _flightPathSegmentHashTable.clear(); - _waypointPath.clear(); - - // Note: Although visual support _incompleteComplexItemLines is still in the codebase. The support for populating the list is not. - // This is due to the initial implementation being buggy and incomplete with respect to correctly generating the line set. - // So for now we leave the code for displaying them in, but none are ever added until we have time to implement the correct support. - - _simpleFlightPathSegments.beginReset(); - _directionArrows.beginReset(); - _incompleteComplexItemLines.beginReset(); - - _simpleFlightPathSegments.clear(); - _directionArrows.clear(); - _incompleteComplexItemLines.clearAndDeleteContents(); - - // Mission Settings item needs to start with no segment - lastFlyThroughVI->setSimpleFlighPathSegment(nullptr); - - // Grovel through the list of items keeping track of things needed to correctly draw waypoints lines - - for (int i=1; i<_visualItems->count(); i++) { - VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i)); - SimpleMissionItem* simpleItem = qobject_cast(visualItem); - ComplexMissionItem* complexItem = qobject_cast(visualItem); - - visualItem->setSimpleFlighPathSegment(nullptr); - - if (simpleItem) { - if (roiActive) { - if (_isROICancelItem(simpleItem)) { - roiActive = false; - } - } else { - if (_isROIBeginItem(simpleItem)) { - roiActive = true; - } - } - - MAV_CMD command = simpleItem->mavCommand(); - switch (command) { - case MAV_CMD_NAV_TAKEOFF: - case MAV_CMD_NAV_VTOL_TAKEOFF: - _missionContainsVTOLTakeoff = command == MAV_CMD_NAV_VTOL_TAKEOFF; - if (!linkEndToHome) { - // If we still haven't found the first coordinate item and we hit a takeoff command this means the mission starts from the ground. - // Link the first item back to home to show that. - if (firstCoordinateNotFound) { - linkStartToHome = true; - } - } - break; - case MAV_CMD_NAV_RETURN_TO_LAUNCH: - linkEndToHome = true; - foundRTL = true; - break; - default: - break; - } - } - - // No need to add waypoint segments after an RTL. - if (foundRTL) { - break; - } - - if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) { - // Incomplete items are complex items which are waiting for the user to complete setup before there visuals can become valid. - // They may not yet have valid entry/exit coordinates associated with them while in the incomplete state. - // For examples a Survey item which has no polygon set yet. - if (complexItem && complexItem->isIncomplete()) { - // We don't link lines from a valid item to an incomplete item - previousItemIsIncomplete = true; - } else if (previousItemIsIncomplete) { - // We also don't link lines from an incomplete item to a valid item. - previousItemIsIncomplete = false; - firstCoordinateNotFound = false; - lastFlyThroughVI = visualItem; - } else { - if (lastFlyThroughVI != _settingsItem || (homePositionValid && linkStartToHome)) { - bool addDirectionArrow = false; - if (i != 1) { - // Direction arrows are added to the second segment and every 5 segments thereafter. - // The reason for start with second segment is to prevent an arrow being added in between the home position - // and a takeoff item which may be right over each other. In that case the arrow points in a random direction. - if (firstCoordinateNotFound || !lastFlyThroughVI->isSimpleItem() || !visualItem->isSimpleItem()) { - addDirectionArrow = true; - } else if (segmentCount > 5) { - segmentCount = 0; - addDirectionArrow = true; - } - segmentCount++; - } - - lastSegmentVisualItemPair = VisualItemPair(lastFlyThroughVI, visualItem); - if (!_flyView || addDirectionArrow) { - FlightPathSegment* segment = _addFlightPathSegment(oldSegmentTable, lastSegmentVisualItemPair); - segment->setSpecialVisual(roiActive); - if (addDirectionArrow) { - _directionArrows.append(segment); - } - lastFlyThroughVI->setSimpleFlighPathSegment(segment); - } - } - firstCoordinateNotFound = false; - _waypointPath.append(QVariant::fromValue(visualItem->coordinate())); - lastFlyThroughVI = visualItem; - } - } - } - - if (linkStartToHome && homePositionValid) { - _waypointPath.prepend(QVariant::fromValue(_settingsItem->coordinate())); - } - - if (linkEndToHome && lastFlyThroughVI != _settingsItem && homePositionValid) { - lastSegmentVisualItemPair = VisualItemPair(lastFlyThroughVI, _settingsItem); - if (_flyView) { - _waypointPath.append(QVariant::fromValue(_settingsItem->coordinate())); - } - FlightPathSegment* segment = _addFlightPathSegment(oldSegmentTable, lastSegmentVisualItemPair); - segment->setSpecialVisual(roiActive); - lastFlyThroughVI->setSimpleFlighPathSegment(segment); - } - - // Add direction arrow to last segment - if (lastSegmentVisualItemPair.first) { - FlightPathSegment* coordVector = nullptr; - - // The pair may not be in the hash, this can happen in the fly view where only segments with arrows on them are added to hash. - // check for that first and add if needed - - if (_flightPathSegmentHashTable.contains(lastSegmentVisualItemPair)) { - // Pair exists in the new table already just reuse it - coordVector = _flightPathSegmentHashTable[lastSegmentVisualItemPair]; - } else if (oldSegmentTable.contains(lastSegmentVisualItemPair)) { - // Pair already exists in old table, pull from old to new and reuse - _flightPathSegmentHashTable[lastSegmentVisualItemPair] = coordVector = oldSegmentTable.take(lastSegmentVisualItemPair); - } else { - // Create a new segment. Since this is the fly view there is no need to wire change signals. - coordVector = new FlightPathSegment(lastSegmentVisualItemPair.first->isSimpleItem() ? lastSegmentVisualItemPair.first->coordinate() : lastSegmentVisualItemPair.first->exitCoordinate(), - lastSegmentVisualItemPair.first->isSimpleItem() ? lastSegmentVisualItemPair.first->amslEntryAlt() : lastSegmentVisualItemPair.first->amslExitAlt(), - lastSegmentVisualItemPair.second->coordinate(), - lastSegmentVisualItemPair.second->amslEntryAlt(), - !_flyView /* queryTerrainData */, - this); - _flightPathSegmentHashTable[lastSegmentVisualItemPair] = coordVector; - } - - _directionArrows.append(coordVector); - } - - _simpleFlightPathSegments.endReset(); - _directionArrows.endReset(); - _incompleteComplexItemLines.endReset(); - - // Anything left in the old table is an obsolete line object that can go - qDeleteAll(oldSegmentTable); - - emit _recalcMissionFlightStatusSignal(); - - if (_waypointPath.count() == 0) { - // MapPolyLine has a bug where if you change from a path which has elements to an empty path the line drawn - // is not cleared from the map. This hack works around that since it causes the previous lines to be remove - // as then doesn't draw anything on the map. - _waypointPath.append(QVariant::fromValue(QGeoCoordinate(0, 0))); - _waypointPath.append(QVariant::fromValue(QGeoCoordinate(0, 0))); - } - - emit waypointPathChanged(); - emit recalcTerrainProfile(); -} - -void MissionController::_updateBatteryInfo(int waypointIndex) -{ - if (_missionFlightStatus.mAhBattery != 0) { - _missionFlightStatus.hoverAmpsTotal = (_missionFlightStatus.hoverTime / 60.0) * _missionFlightStatus.hoverAmps; - _missionFlightStatus.cruiseAmpsTotal = (_missionFlightStatus.cruiseTime / 60.0) * _missionFlightStatus.cruiseAmps; - _missionFlightStatus.batteriesRequired = ceil((_missionFlightStatus.hoverAmpsTotal + _missionFlightStatus.cruiseAmpsTotal) / _missionFlightStatus.ampMinutesAvailable); - // FIXME: Battery change point code pretty much doesn't work. The reason is that is treats complex items as a black box. It needs to be able to look - // inside complex items in order to determine a swap point that is interior to a complex item. Current the swap point display in PlanToolbar is - // disabled to do this problem. - if (waypointIndex != -1 && _missionFlightStatus.batteriesRequired == 2 && _missionFlightStatus.batteryChangePoint == -1) { - _missionFlightStatus.batteryChangePoint = waypointIndex - 1; - } - } -} - -void MissionController::_addHoverTime(double hoverTime, double hoverDistance, int waypointIndex) -{ - _missionFlightStatus.totalTime += hoverTime; - _missionFlightStatus.hoverTime += hoverTime; - _missionFlightStatus.hoverDistance += hoverDistance; - _missionFlightStatus.totalDistance += hoverDistance; - _updateBatteryInfo(waypointIndex); -} - -void MissionController::_addCruiseTime(double cruiseTime, double cruiseDistance, int waypointIndex) -{ - _missionFlightStatus.totalTime += cruiseTime; - _missionFlightStatus.cruiseTime += cruiseTime; - _missionFlightStatus.cruiseDistance += cruiseDistance; - _missionFlightStatus.totalDistance += cruiseDistance; - _updateBatteryInfo(waypointIndex); -} - -/// Adds the specified time to the appropriate hover or cruise time values. -/// @param vtolInHover true: vtol is currrent in hover mode -/// @param hoverTime Amount of time tp add to hover -/// @param cruiseTime Amount of time to add to cruise -/// @param extraTime Amount of additional time to add to hover/cruise -/// @param seqNum Sequence number of waypoint for these values, -1 for no waypoint associated -void MissionController::_addTimeDistance(bool vtolInHover, double hoverTime, double cruiseTime, double extraTime, double distance, int seqNum) -{ - if (_controllerVehicle->vtol()) { - if (vtolInHover) { - _addHoverTime(hoverTime, distance, seqNum); - _addHoverTime(extraTime, 0, -1); - } else { - _addCruiseTime(cruiseTime, distance, seqNum); - _addCruiseTime(extraTime, 0, -1); - } - } else { - if (_controllerVehicle->multiRotor()) { - _addHoverTime(hoverTime, distance, seqNum); - _addHoverTime(extraTime, 0, -1); - } else { - _addCruiseTime(cruiseTime, distance, seqNum); - _addCruiseTime(extraTime, 0, -1); - } - } -} - -void MissionController::_recalcMissionFlightStatus() -{ - if (!_visualItems->count()) { - return; - } - - bool firstCoordinateItem = true; - VisualMissionItem* lastFlyThroughVI = qobject_cast(_visualItems->get(0)); +#include "VTOLLandingComplexItem.h" - bool homePositionValid = _settingsItem->coordinate().isValid(); +#define UPDATE_TIMEOUT 5000 ///< How often we check for bounding box changes - qCDebug(MissionControllerLog) << "_recalcMissionFlightStatus"; +QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog") - // If home position is valid we can calculate distances between all waypoints. - // If home position is not valid we can only calculate distances between waypoints which are - // both relative altitude. +const char *MissionController::_settingsGroup = "MissionController"; +const char *MissionController::_jsonFileTypeValue = "Mission"; +const char *MissionController::_jsonItemsKey = "items"; +const char *MissionController::_jsonPlannedHomePositionKey = + "plannedHomePosition"; +const char *MissionController::_jsonFirmwareTypeKey = "firmwareType"; +const char *MissionController::_jsonVehicleTypeKey = "vehicleType"; +const char *MissionController::_jsonCruiseSpeedKey = "cruiseSpeed"; +const char *MissionController::_jsonHoverSpeedKey = "hoverSpeed"; +const char *MissionController::_jsonParamsKey = "params"; +const char *MissionController::_jsonGlobalPlanAltitudeModeKey = + "globalPlanAltitudeMode"; - // No values for first item - lastFlyThroughVI->setAltDifference(0); - lastFlyThroughVI->setAzimuth(0); - lastFlyThroughVI->setDistance(0); - lastFlyThroughVI->setDistanceFromStart(0); +// Deprecated V1 format keys +const char *MissionController::_jsonComplexItemsKey = "complexItems"; +const char *MissionController::_jsonMavAutopilotKey = "MAV_AUTOPILOT"; + +const int MissionController::_missionFileVersion = 2; + +MissionController::MissionController(PlanMasterController *masterController, + QObject *parent) + : PlanElementController(masterController, parent), + _controllerVehicle(masterController->controllerVehicle()), + _managerVehicle(masterController->managerVehicle()), + _missionManager(masterController->managerVehicle()->missionManager()), + _visualItems(new QmlObjectListModel(this)), + _planViewSettings( + qgcApp()->toolbox()->settingsManager()->planViewSettings()), + _appSettings(qgcApp()->toolbox()->settingsManager()->appSettings()) { + _resetMissionFlightStatus(); + + _updateTimer.setSingleShot(true); + + connect(&_updateTimer, &QTimer::timeout, this, + &MissionController::_updateTimeout); + connect(_planViewSettings->takeoffItemNotRequired(), &Fact::rawValueChanged, + this, &MissionController::_takeoffItemNotRequiredChanged); + connect(this, &MissionController::missionDistanceChanged, this, + &MissionController::recalcTerrainProfile); + + // The follow is used to compress multiple recalc calls in a row to into a + // single call. + connect(this, &MissionController::_recalcMissionFlightStatusSignal, this, + &MissionController::_recalcMissionFlightStatus, Qt::QueuedConnection); + connect(this, &MissionController::_recalcFlightPathSegmentsSignal, this, + &MissionController::_recalcFlightPathSegments, Qt::QueuedConnection); + qgcApp()->addCompressedSignal(QMetaMethod::fromSignal( + &MissionController::_recalcMissionFlightStatusSignal)); + qgcApp()->addCompressedSignal(QMetaMethod::fromSignal( + &MissionController::_recalcFlightPathSegmentsSignal)); + qgcApp()->addCompressedSignal( + QMetaMethod::fromSignal(&MissionController::recalcTerrainProfile)); +} + +MissionController::~MissionController() {} + +void MissionController::_resetMissionFlightStatus(void) { + _missionFlightStatus.totalDistance = 0.0; + _missionFlightStatus.maxTelemetryDistance = 0.0; + _missionFlightStatus.totalTime = 0.0; + _missionFlightStatus.hoverTime = 0.0; + _missionFlightStatus.cruiseTime = 0.0; + _missionFlightStatus.hoverDistance = 0.0; + _missionFlightStatus.cruiseDistance = 0.0; + _missionFlightStatus.cruiseSpeed = _controllerVehicle->defaultCruiseSpeed(); + _missionFlightStatus.hoverSpeed = _controllerVehicle->defaultHoverSpeed(); + _missionFlightStatus.vehicleSpeed = + _controllerVehicle->multiRotor() || _managerVehicle->vtol() + ? _missionFlightStatus.hoverSpeed + : _missionFlightStatus.cruiseSpeed; + _missionFlightStatus.vehicleYaw = qQNaN(); + _missionFlightStatus.gimbalYaw = qQNaN(); + _missionFlightStatus.gimbalPitch = qQNaN(); + _missionFlightStatus.mAhBattery = 0; + _missionFlightStatus.hoverAmps = 0; + _missionFlightStatus.cruiseAmps = 0; + _missionFlightStatus.ampMinutesAvailable = 0; + _missionFlightStatus.hoverAmpsTotal = 0; + _missionFlightStatus.cruiseAmpsTotal = 0; + _missionFlightStatus.batteryChangePoint = -1; + _missionFlightStatus.batteriesRequired = -1; + _missionFlightStatus.vtolMode = _missionContainsVTOLTakeoff + ? QGCMAVLink::VehicleClassMultiRotor + : QGCMAVLink::VehicleClassFixedWing; + + _controllerVehicle->firmwarePlugin()->batteryConsumptionData( + _controllerVehicle, _missionFlightStatus.mAhBattery, + _missionFlightStatus.hoverAmps, _missionFlightStatus.cruiseAmps); + if (_missionFlightStatus.mAhBattery != 0) { + double batteryPercentRemainingAnnounce = + qgcApp() + ->toolbox() + ->settingsManager() + ->appSettings() + ->batteryPercentRemainingAnnounce() + ->rawValue() + .toDouble(); + _missionFlightStatus.ampMinutesAvailable = + static_cast(_missionFlightStatus.mAhBattery) / 1000.0 * 60.0 * + ((100.0 - batteryPercentRemainingAnnounce) / 100.0); + } + + emit missionDistanceChanged(_missionFlightStatus.totalDistance); + emit missionTimeChanged(); + emit missionHoverDistanceChanged(_missionFlightStatus.hoverDistance); + emit missionCruiseDistanceChanged(_missionFlightStatus.cruiseDistance); + emit missionHoverTimeChanged(); + emit missionCruiseTimeChanged(); + emit missionMaxTelemetryChanged(_missionFlightStatus.maxTelemetryDistance); + emit batteryChangePointChanged(_missionFlightStatus.batteryChangePoint); + emit batteriesRequiredChanged(_missionFlightStatus.batteriesRequired); +} + +void MissionController::start(bool flyView) { + qCDebug(MissionControllerLog) << "start flyView" << flyView; + + _managerVehicleChanged(_managerVehicle); + connect(_masterController, &PlanMasterController::managerVehicleChanged, this, + &MissionController::_managerVehicleChanged); + + PlanElementController::start(flyView); + _init(); +} + +void MissionController::_init(void) { + // We start with an empty mission + _addMissionSettings(_visualItems); + _initAllVisualItems(); +} - _minAMSLAltitude = _maxAMSLAltitude = _settingsItem->coordinate().altitude(); +// Called when new mission items have completed downloading from Vehicle +void MissionController::_newMissionItemsAvailableFromVehicle( + bool removeAllRequested) { + qCDebug(MissionControllerLog) + << "_newMissionItemsAvailableFromVehicle flyView:count" << _flyView + << _missionManager->missionItems().count(); + + // Fly view always reloads on _loadComplete + // Plan view only reloads if: + // - Load was specifically requested + // - There is no current Plan + if (_flyView || removeAllRequested || _itemsRequested || isEmpty()) { + // Fly Mode (accept if): + // - Always accepts new items from the vehicle so Fly view is kept up + // to date + // Edit Mode (accept if): + // - Remove all was requested from Fly view (clear mission on flight + // end) + // - A load from vehicle was manually requested + // - The initial automatic load from a vehicle completed and the + // current editor is empty + + _deinitAllVisualItems(); + _visualItems->deleteLater(); + _visualItems = nullptr; + _settingsItem = nullptr; + _takeoffMissionItem = nullptr; + _updateContainsItems(); // This will clear containsItems which will be set + // again below. This will re-pop Start Mission + // confirmation. + + QmlObjectListModel *newControllerMissionItems = + new QmlObjectListModel(this); + const QList &newMissionItems = + _missionManager->missionItems(); + qCDebug(MissionControllerLog) + << "loading from vehicle: count" << newMissionItems.count(); + + _missionItemCount = newMissionItems.count(); + emit missionItemCountChanged(_missionItemCount); + + MissionSettingsItem *settingsItem = + _addMissionSettings(newControllerMissionItems); + + int i = 0; + if (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() && + newMissionItems.count() != 0) { + // First item is fake home position + MissionItem *fakeHomeItem = newMissionItems[0]; + if (fakeHomeItem->coordinate().latitude() != 0 || + fakeHomeItem->coordinate().longitude() != 0) { + settingsItem->setInitialHomePosition(fakeHomeItem->coordinate()); + } + i = 1; + } + + for (; i < newMissionItems.count(); i++) { + const MissionItem *missionItem = newMissionItems[i]; + SimpleMissionItem *simpleItem = new SimpleMissionItem( + _masterController, _flyView, *missionItem, this); + if (TakeoffMissionItem::isTakeoffCommand( + static_cast(simpleItem->command()))) { + // This needs to be a TakeoffMissionItem + _takeoffMissionItem = new TakeoffMissionItem( + *missionItem, _masterController, _flyView, settingsItem, this); + simpleItem->deleteLater(); + simpleItem = _takeoffMissionItem; + } + newControllerMissionItems->append(simpleItem); + } + + _visualItems = newControllerMissionItems; + _settingsItem = settingsItem; + + MissionController::_scanForAdditionalSettings(_visualItems, + _masterController); - _resetMissionFlightStatus(); + _initAllVisualItems(); + _updateContainsItems(); + emit newItemsFromVehicle(); + } + _itemsRequested = false; +} + +void MissionController::loadFromVehicle(void) { + if (_masterController->offline()) { + qCWarning(MissionControllerLog) + << "MissionControllerLog::loadFromVehicle called while offline"; + } else if (syncInProgress()) { + qCWarning(MissionControllerLog) + << "MissionControllerLog::loadFromVehicle called while syncInProgress"; + } else { + _itemsRequested = true; + _managerVehicle->missionManager()->loadFromVehicle(); + } +} + +void MissionController::sendToVehicle(void) { + if (_masterController->offline()) { + qCWarning(MissionControllerLog) + << "MissionControllerLog::sendToVehicle called while offline"; + } else if (syncInProgress()) { + qCWarning(MissionControllerLog) + << "MissionControllerLog::sendToVehicle called while syncInProgress"; + } else { + qCDebug(MissionControllerLog) << "MissionControllerLog::sendToVehicle"; + if (_visualItems->count() == 1) { + // This prevents us from sending a possibly bogus home position to the + // vehicle + QmlObjectListModel emptyModel; + sendItemsToVehicle(_managerVehicle, &emptyModel); + } else { + sendItemsToVehicle(_managerVehicle, _visualItems); + } + setDirty(false); + } +} - bool linkStartToHome = false; - bool foundRTL = false; - double totalHorizontalDistance = 0; +/// Converts from visual items to MissionItems +/// @param missionItemParent QObject parent for newly allocated MissionItems +/// @return true: Mission end action was added to end of list +bool MissionController::_convertToMissionItems( + QmlObjectListModel *visualMissionItems, + QList &rgMissionItems, QObject *missionItemParent) { + if (visualMissionItems->count() == 0) { + return false; + } + + bool endActionSet = false; + int lastSeqNum = 0; + + for (int i = 0; i < visualMissionItems->count(); i++) { + VisualMissionItem *visualItem = + qobject_cast(visualMissionItems->get(i)); + + lastSeqNum = visualItem->lastSequenceNumber(); + visualItem->appendMissionItems(rgMissionItems, missionItemParent); + + qCDebug(MissionControllerLog) + << "_convertToMissionItems seqNum:lastSeqNum:command" + << visualItem->sequenceNumber() << lastSeqNum + << visualItem->commandName(); + } + + // Mission settings has a special case for end mission action + MissionSettingsItem *settingsItem = + visualMissionItems->value(0); + if (settingsItem) { + endActionSet = settingsItem->addMissionEndAction( + rgMissionItems, lastSeqNum + 1, missionItemParent); + } + + return endActionSet; +} + +void MissionController::addMissionToKML(KMLPlanDomDocument &planKML) { + QObject *deleteParent = new QObject(); + QList rgMissionItems; + + _convertToMissionItems(_visualItems, rgMissionItems, deleteParent); + planKML.addMission(_controllerVehicle, _visualItems, rgMissionItems); + deleteParent->deleteLater(); +} + +void MissionController::sendItemsToVehicle( + Vehicle *vehicle, QmlObjectListModel *visualMissionItems) { + if (vehicle) { + QList rgMissionItems; + + _convertToMissionItems(visualMissionItems, rgMissionItems, vehicle); + + // PlanManager takes control of MissionItems so no need to delete + vehicle->missionManager()->writeMissionItems(rgMissionItems); + } +} + +int MissionController::_nextSequenceNumber(void) { + if (_visualItems->count() == 0) { + qWarning() << "Internal error: Empty visual item list"; + return 0; + } else { + VisualMissionItem *lastItem = + _visualItems->value(_visualItems->count() - 1); + return lastItem->lastSequenceNumber() + 1; + } +} + +VisualMissionItem *MissionController::_insertSimpleMissionItemWorker( + QGeoCoordinate coordinate, MAV_CMD command, int visualItemIndex, + bool makeCurrentItem) { + int sequenceNumber = _nextSequenceNumber(); + SimpleMissionItem *newItem = new SimpleMissionItem( + _masterController, _flyView, false /* forLoad */, this); + newItem->setSequenceNumber(sequenceNumber); + newItem->setCoordinate(coordinate); + newItem->setCommand(command); + _initVisualItem(newItem); + + if (newItem->specifiesAltitude()) { + if (!qgcApp()->toolbox()->missionCommandTree()->isLandCommand(command)) { + double prevAltitude; + int prevAltitudeMode; + + if (_findPreviousAltitude(visualItemIndex, &prevAltitude, + &prevAltitudeMode)) { + newItem->altitude()->setRawValue(prevAltitude); + if (globalAltitudeMode() == QGroundControlQmlGlobal::AltitudeModeNone) { + // We are in mixed altitude modes, so copy from previous. Otherwise + // alt mode will be set from global setting. + newItem->setAltitudeMode( + static_cast( + prevAltitudeMode)); + } + } + } + } + if (visualItemIndex == -1) { + _visualItems->append(newItem); + } else { + _visualItems->insert(visualItemIndex, newItem); + } + + // We send the click coordinate through here to be able to set the planned + // home position from the user click location if needed + _recalcAllWithCoordinate(coordinate); + + if (makeCurrentItem) { + setCurrentPlanViewSeqNum(newItem->sequenceNumber(), true); + } + + _firstItemAdded(); + + return newItem; +} + +VisualMissionItem *MissionController::insertSimpleMissionItem( + QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem) { + return _insertSimpleMissionItemWorker(coordinate, MAV_CMD_NAV_WAYPOINT, + visualItemIndex, makeCurrentItem); +} + +VisualMissionItem *MissionController::insertTakeoffItem( + QGeoCoordinate /*coordinate*/, int visualItemIndex, bool makeCurrentItem) { + int sequenceNumber = _nextSequenceNumber(); + _takeoffMissionItem = new TakeoffMissionItem( + _controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_TAKEOFF + : MAV_CMD_NAV_TAKEOFF, + _masterController, _flyView, _settingsItem, this); + _takeoffMissionItem->setSequenceNumber(sequenceNumber); + _initVisualItem(_takeoffMissionItem); + + if (_takeoffMissionItem->specifiesAltitude()) { + double prevAltitude; + int prevAltitudeMode; + + if (_findPreviousAltitude(visualItemIndex, &prevAltitude, + &prevAltitudeMode)) { + _takeoffMissionItem->altitude()->setRawValue(prevAltitude); + _takeoffMissionItem->setAltitudeMode( + static_cast(prevAltitudeMode)); + } + } + if (visualItemIndex == -1) { + _visualItems->append(_takeoffMissionItem); + } else { + _visualItems->insert(visualItemIndex, _takeoffMissionItem); + } + + _recalcAll(); + + if (makeCurrentItem) { + setCurrentPlanViewSeqNum(_takeoffMissionItem->sequenceNumber(), true); + } + + _firstItemAdded(); + + return _takeoffMissionItem; +} + +VisualMissionItem *MissionController::insertLandItem(QGeoCoordinate coordinate, + int visualItemIndex, + bool makeCurrentItem) { + if (_controllerVehicle->fixedWing()) { + FixedWingLandingComplexItem *fwLanding = + qobject_cast(insertComplexMissionItem( + FixedWingLandingComplexItem::name, coordinate, visualItemIndex, + makeCurrentItem)); + return fwLanding; + } else if (_controllerVehicle->vtol()) { + VTOLLandingComplexItem *vtolLanding = + qobject_cast( + insertComplexMissionItem(VTOLLandingComplexItem::name, coordinate, + visualItemIndex, makeCurrentItem)); + return vtolLanding; + } else { + return _insertSimpleMissionItemWorker(coordinate, + _controllerVehicle->vtol() + ? MAV_CMD_NAV_VTOL_LAND + : MAV_CMD_NAV_RETURN_TO_LAUNCH, + visualItemIndex, makeCurrentItem); + } +} + +VisualMissionItem *MissionController::insertROIMissionItem( + QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem) { + SimpleMissionItem *simpleItem = qobject_cast( + _insertSimpleMissionItemWorker(coordinate, MAV_CMD_DO_SET_ROI_LOCATION, + visualItemIndex, makeCurrentItem)); + + if (!_controllerVehicle->firmwarePlugin() + ->supportedMissionCommands(QGCMAVLink::VehicleClassGeneric) + .contains(MAV_CMD_DO_SET_ROI_LOCATION)) { + simpleItem->setCommand(MAV_CMD_DO_SET_ROI); + simpleItem->missionItem().setParam1(MAV_ROI_LOCATION); + } + _recalcROISpecialVisuals(); + return simpleItem; +} + +VisualMissionItem * +MissionController::insertCancelROIMissionItem(int visualItemIndex, + bool makeCurrentItem) { + SimpleMissionItem *simpleItem = qobject_cast( + _insertSimpleMissionItemWorker(QGeoCoordinate(), MAV_CMD_DO_SET_ROI_NONE, + visualItemIndex, makeCurrentItem)); + + if (!_controllerVehicle->firmwarePlugin() + ->supportedMissionCommands(QGCMAVLink::VehicleClassGeneric) + .contains(MAV_CMD_DO_SET_ROI_NONE)) { + simpleItem->setCommand(MAV_CMD_DO_SET_ROI); + simpleItem->missionItem().setParam1(MAV_ROI_NONE); + } + _recalcROISpecialVisuals(); + return simpleItem; +} + +VisualMissionItem *MissionController::insertComplexMissionItem( + QString itemName, QGeoCoordinate mapCenterCoordinate, int visualItemIndex, + bool makeCurrentItem) { + ComplexMissionItem *newItem = nullptr; + + if (itemName == SurveyComplexItem::name) { + newItem = new SurveyComplexItem(_masterController, _flyView, + QString() /* kmlFile */, + _visualItems /* parent */); + newItem->setCoordinate(mapCenterCoordinate); + } else if (itemName == FixedWingLandingComplexItem::name) { + newItem = new FixedWingLandingComplexItem(_masterController, _flyView, + _visualItems /* parent */); + } else if (itemName == VTOLLandingComplexItem::name) { + newItem = new VTOLLandingComplexItem(_masterController, _flyView, + _visualItems /* parent */); + } else if (itemName == StructureScanComplexItem::name) { + newItem = new StructureScanComplexItem(_masterController, _flyView, + QString() /* kmlFile */, + _visualItems /* parent */); + } else if (itemName == CorridorScanComplexItem::name) { + newItem = new CorridorScanComplexItem(_masterController, _flyView, + QString() /* kmlFile */, + _visualItems /* parent */); + } else if (itemName == RouteComplexItem::name) { + newItem = new RouteComplexItem(_masterController, _flyView, + QString() /* kmlFile */, + _visualItems /* parent */); + } else { + qWarning() << "Internal error: Unknown complex item:" << itemName; + return nullptr; + } + + _insertComplexMissionItemWorker(mapCenterCoordinate, newItem, visualItemIndex, + makeCurrentItem); + + return newItem; +} + +VisualMissionItem *MissionController::insertComplexMissionItemFromKMLOrSHP( + QString itemName, QString file, int visualItemIndex, bool makeCurrentItem) { + ComplexMissionItem *newItem = nullptr; + + if (itemName == SurveyComplexItem::name) { + newItem = + new SurveyComplexItem(_masterController, _flyView, file, _visualItems); + } else if (itemName == StructureScanComplexItem::name) { + newItem = new StructureScanComplexItem(_masterController, _flyView, file, + _visualItems); + } else if (itemName == CorridorScanComplexItem::name) { + newItem = new CorridorScanComplexItem(_masterController, _flyView, file, + _visualItems); + } else { + qWarning() << "Internal error: Unknown complex item:" << itemName; + return nullptr; + } + + _insertComplexMissionItemWorker(QGeoCoordinate(), newItem, visualItemIndex, + makeCurrentItem); + + return newItem; +} + +void MissionController::_insertComplexMissionItemWorker( + const QGeoCoordinate &mapCenterCoordinate, ComplexMissionItem *complexItem, + int visualItemIndex, bool makeCurrentItem) { + int sequenceNumber = _nextSequenceNumber(); + bool surveyStyleItem = + qobject_cast(complexItem) || + qobject_cast(complexItem) || + qobject_cast(complexItem) || + qobject_cast(complexItem); + + if (surveyStyleItem) { + bool rollSupported = false; + bool pitchSupported = false; + bool yawSupported = false; + + // If the vehicle is known to have a gimbal then we automatically point the + // gimbal straight down if not already set + + MissionSettingsItem *settingsItem = + _visualItems->value(0); + CameraSection *cameraSection = settingsItem->cameraSection(); + + // Set camera to photo mode (leave alone if user already specified) + if (cameraSection->cameraModeSupported() && + !cameraSection->specifyCameraMode()) { + cameraSection->setSpecifyCameraMode(true); + cameraSection->cameraMode()->setRawValue(CAMERA_MODE_IMAGE_SURVEY); + } + + // Point gimbal straight down + if (_controllerVehicle->firmwarePlugin()->hasGimbal( + _controllerVehicle, rollSupported, pitchSupported, yawSupported) && + pitchSupported) { + // If the user already specified a gimbal angle leave it alone + if (!cameraSection->specifyGimbal()) { + cameraSection->setSpecifyGimbal(true); + cameraSection->gimbalPitch()->setRawValue(-90.0); + } + } + } + + complexItem->setSequenceNumber(sequenceNumber); + complexItem->setWizardMode(true); + _initVisualItem(complexItem); + + if (visualItemIndex == -1) { + _visualItems->append(complexItem); + } else { + _visualItems->insert(visualItemIndex, complexItem); + } + + //-- Keep track of bounding box changes in complex items + if (!complexItem->isSimpleItem()) { + connect(complexItem, &ComplexMissionItem::boundingCubeChanged, this, + &MissionController::_complexBoundingBoxChanged); + } + _recalcAllWithCoordinate(mapCenterCoordinate); + + if (makeCurrentItem) { + setCurrentPlanViewSeqNum(complexItem->sequenceNumber(), true); + } + _firstItemAdded(); +} + +void MissionController::removeVisualItem(int viIndex) { + if (viIndex <= 0 || viIndex >= _visualItems->count()) { + qWarning() << "MissionController::removeVisualItem called with bad index - " + "count:index" + << _visualItems->count() << viIndex; + return; + } - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* item = qobject_cast(_visualItems->get(i)); - SimpleMissionItem* simpleItem = qobject_cast(item); - ComplexMissionItem* complexItem = qobject_cast(item); + bool removeSurveyStyle = + _visualItems->value(viIndex) || + _visualItems->value(viIndex) || + _visualItems->value(viIndex); + VisualMissionItem *item = + qobject_cast(_visualItems->removeAt(viIndex)); - if (simpleItem && simpleItem->mavCommand() == MAV_CMD_NAV_RETURN_TO_LAUNCH) { - foundRTL = true; - } + if (item == _takeoffMissionItem) { + _takeoffMissionItem = nullptr; + } - // Assume the worst - item->setAzimuth(0); - item->setDistance(0); - item->setDistanceFromStart(0); + _deinitVisualItem(item); + item->deleteLater(); - // Gimbal states reflect the state AFTER executing the item + if (removeSurveyStyle) { + // Determine if the mission still has another survey style item in it + bool foundSurvey = false; + for (int i = 1; i < _visualItems->count(); i++) { + if (_visualItems->value(i) || + _visualItems->value(i) || + _visualItems->value(i)) { + foundSurvey = true; + break; + } + } + + // If there is no longer a survey item in the mission remove added commands + if (!foundSurvey) { + bool rollSupported = false; + bool pitchSupported = false; + bool yawSupported = false; + CameraSection *cameraSection = _settingsItem->cameraSection(); + if (_controllerVehicle->firmwarePlugin()->hasGimbal( + _controllerVehicle, rollSupported, pitchSupported, + yawSupported) && + pitchSupported) { + if (cameraSection->specifyGimbal() && + cameraSection->gimbalPitch()->rawValue().toDouble() == -90.0 && + cameraSection->gimbalYaw()->rawValue().toDouble() == 0.0) { + cameraSection->setSpecifyGimbal(false); + } + } + if (cameraSection->cameraModeSupported() && + cameraSection->specifyCameraMode() && + cameraSection->cameraMode()->rawValue().toInt() == 0) { + cameraSection->setSpecifyCameraMode(false); + } + } + } + + _recalcAll(); + + // Adjust current item + int newVIIndex; + if (viIndex >= _visualItems->count()) { + newVIIndex = _visualItems->count() - 1; + } else { + newVIIndex = viIndex; + } + setCurrentPlanViewSeqNum( + _visualItems->value(newVIIndex)->sequenceNumber(), + true); + + setDirty(true); + + if (_visualItems->count() == 1) { + _allItemsRemoved(); + } +} + +void MissionController::removeAll(void) { + if (_visualItems) { + _deinitAllVisualItems(); + _visualItems->clearAndDeleteContents(); + _visualItems->deleteLater(); + _settingsItem = nullptr; + _takeoffMissionItem = nullptr; + _visualItems = new QmlObjectListModel(this); + _addMissionSettings(_visualItems); + _initAllVisualItems(); + setDirty(true); + _resetMissionFlightStatus(); + _allItemsRemoved(); + } +} + +bool MissionController::_loadJsonMissionFileV1(const QJsonObject &json, + QmlObjectListModel *visualItems, + QString &errorString) { + // Validate root object keys + QList rootKeyInfoList = { + {_jsonPlannedHomePositionKey, QJsonValue::Object, true}, + {_jsonItemsKey, QJsonValue::Array, true}, + {_jsonMavAutopilotKey, QJsonValue::Double, true}, + {_jsonComplexItemsKey, QJsonValue::Array, true}, + }; + if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) { + return false; + } + + setGlobalAltitudeMode( + QGroundControlQmlGlobal::AltitudeModeNone); // Mixed mode + + // Read complex items + QList surveyItems; + QJsonArray complexArray(json[_jsonComplexItemsKey].toArray()); + qCDebug(MissionControllerLog) + << "Json load: complex item count" << complexArray.count(); + for (int i = 0; i < complexArray.count(); i++) { + const QJsonValue &itemValue = complexArray[i]; + + if (!itemValue.isObject()) { + errorString = QStringLiteral("Mission item is not an object"); + return false; + } + + SurveyComplexItem *item = new SurveyComplexItem(_masterController, _flyView, + QString() /* kmlFile */, + visualItems /* parent */); + const QJsonObject itemObject = itemValue.toObject(); + if (item->load(itemObject, itemObject["id"].toInt(), errorString)) { + surveyItems.append(item); + } else { + return false; + } + } - // ROI commands cancel out previous gimbal yaw/pitch - if (simpleItem) { - switch (simpleItem->command()) { - case MAV_CMD_NAV_ROI: - case MAV_CMD_DO_SET_ROI_LOCATION: - case MAV_CMD_DO_SET_ROI_WPNEXT_OFFSET: - _missionFlightStatus.gimbalYaw = qQNaN(); - _missionFlightStatus.gimbalPitch = qQNaN(); - break; - default: - break; - } - } + // Read simple items, interspersing complex items into the full list - // Look for specific gimbal changes - double gimbalYaw = item->specifiedGimbalYaw(); - if (!qIsNaN(gimbalYaw) || _planViewSettings->showGimbalOnlyWhenSet()->rawValue().toBool()) { - _missionFlightStatus.gimbalYaw = gimbalYaw; - } - double gimbalPitch = item->specifiedGimbalPitch(); - if (!qIsNaN(gimbalPitch) || _planViewSettings->showGimbalOnlyWhenSet()->rawValue().toBool()) { - _missionFlightStatus.gimbalPitch = gimbalPitch; - } + int nextSimpleItemIndex = 0; + int nextComplexItemIndex = 0; + int nextSequenceNumber = 1; // Start with 1 since home is in 0 + QJsonArray itemArray(json[_jsonItemsKey].toArray()); - // We don't need to do any more processing if: - // Mission Settings Item - // We are after an RTL command - if (i != 0 && !foundRTL) { - // We must set the mission flight status prior to querying for any values from the item. This is because things like - // current speed, gimbal, vtol state impact the values. - item->setMissionFlightStatus(_missionFlightStatus); - - // Link back to home if first item is takeoff and we have home position - if (firstCoordinateItem && simpleItem && (simpleItem->mavCommand() == MAV_CMD_NAV_TAKEOFF || simpleItem->mavCommand() == MAV_CMD_NAV_VTOL_TAKEOFF)) { - if (homePositionValid) { - linkStartToHome = true; - if (_controllerVehicle->multiRotor() || _controllerVehicle->vtol()) { - // We have to special case takeoff, assuming vehicle takes off straight up to specified altitude - double azimuth, distance, altDifference; - _calcPrevWaypointValues(_settingsItem, simpleItem, &azimuth, &distance, &altDifference); - double takeoffTime = qAbs(altDifference) / _appSettings->offlineEditingAscentSpeed()->rawValue().toDouble(); - _addHoverTime(takeoffTime, 0, -1); - } - } + MissionSettingsItem *settingsItem = _addMissionSettings(visualItems); + if (json.contains(_jsonPlannedHomePositionKey)) { + SimpleMissionItem *item = new SimpleMissionItem( + _masterController, _flyView, true /* forLoad */, visualItems); + if (item->load(json[_jsonPlannedHomePositionKey].toObject(), 0, + errorString)) { + settingsItem->setInitialHomePositionFromUser(item->coordinate()); + item->deleteLater(); + } else { + return false; + } + } + + qCDebug(MissionControllerLog) + << "Json load: simple item loop start simpleItemCount:ComplexItemCount" + << itemArray.count() << surveyItems.count(); + do { + qCDebug(MissionControllerLog) + << "Json load: simple item loop " + "nextSimpleItemIndex:nextComplexItemIndex:nextSequenceNumber" + << nextSimpleItemIndex << nextComplexItemIndex << nextSequenceNumber; + + // If there is a complex item that should be next in sequence add it in + if (nextComplexItemIndex < surveyItems.count()) { + SurveyComplexItem *complexItem = surveyItems[nextComplexItemIndex]; + + if (complexItem->sequenceNumber() == nextSequenceNumber) { + qCDebug(MissionControllerLog) + << "Json load: injecting complex item " + "expectedSequence:actualSequence:" + << nextSequenceNumber << complexItem->sequenceNumber(); + visualItems->append(complexItem); + nextSequenceNumber = complexItem->lastSequenceNumber() + 1; + nextComplexItemIndex++; + continue; + } + } + + // Add the next available simple item + if (nextSimpleItemIndex < itemArray.count()) { + const QJsonValue &itemValue = itemArray[nextSimpleItemIndex++]; + + if (!itemValue.isObject()) { + errorString = QStringLiteral("Mission item is not an object"); + return false; + } + + const QJsonObject itemObject = itemValue.toObject(); + SimpleMissionItem *item = new SimpleMissionItem( + _masterController, _flyView, true /* forLoad */, visualItems); + if (item->load(itemObject, itemObject["id"].toInt(), errorString)) { + if (TakeoffMissionItem::isTakeoffCommand(item->mavCommand())) { + // This needs to be a TakeoffMissionItem + TakeoffMissionItem *takeoffItem = + new TakeoffMissionItem(_masterController, _flyView, settingsItem, + true /* forLoad */, visualItems); + takeoffItem->load(itemObject, itemObject["id"].toInt(), errorString); + item->deleteLater(); + item = takeoffItem; + } + qCDebug(MissionControllerLog) + << "Json load: adding simple item expectedSequence:actualSequence" + << nextSequenceNumber << item->sequenceNumber(); + nextSequenceNumber = item->lastSequenceNumber() + 1; + visualItems->append(item); + } else { + return false; + } + } + } while (nextSimpleItemIndex < itemArray.count() || + nextComplexItemIndex < surveyItems.count()); + + return true; +} + +bool MissionController::_loadJsonMissionFileV2(const QJsonObject &json, + QmlObjectListModel *visualItems, + QString &errorString) { + // Validate root object keys + QList rootKeyInfoList = { + {_jsonPlannedHomePositionKey, QJsonValue::Array, true}, + {_jsonItemsKey, QJsonValue::Array, true}, + {_jsonFirmwareTypeKey, QJsonValue::Double, true}, + {_jsonVehicleTypeKey, QJsonValue::Double, false}, + {_jsonCruiseSpeedKey, QJsonValue::Double, false}, + {_jsonHoverSpeedKey, QJsonValue::Double, false}, + {_jsonGlobalPlanAltitudeModeKey, QJsonValue::Double, false}, + }; + if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) { + return false; + } + + setGlobalAltitudeMode( + QGroundControlQmlGlobal::AltitudeModeNone); // Mixed mode + + qCDebug(MissionControllerLog) + << "MissionController::_loadJsonMissionFileV2 itemCount:" + << json[_jsonItemsKey].toArray().count(); + + AppSettings *appSettings = + qgcApp()->toolbox()->settingsManager()->appSettings(); + + // Get the firmware/vehicle type from the plan file + MAV_AUTOPILOT planFileFirmwareType = + static_cast(json[_jsonFirmwareTypeKey].toInt()); + MAV_TYPE planFileVehicleType = + static_cast(QGCMAVLink::vehicleClassToMavType( + appSettings->offlineEditingVehicleClass()->rawValue().toInt())); + if (json.contains(_jsonVehicleTypeKey)) { + planFileVehicleType = + static_cast(json[_jsonVehicleTypeKey].toInt()); + } + + // Update firmware/vehicle offline settings if we aren't connect to a vehicle + if (_masterController->offline()) { + appSettings->offlineEditingFirmwareClass()->setRawValue( + QGCMAVLink::firmwareClass( + static_cast(json[_jsonFirmwareTypeKey].toInt()))); + if (json.contains(_jsonVehicleTypeKey)) { + appSettings->offlineEditingVehicleClass()->setRawValue( + QGCMAVLink::vehicleClass(planFileVehicleType)); + } + } + + // The controller vehicle always tracks the Plan file firmware/vehicle types + // so update it + _controllerVehicle->stopTrackingFirmwareVehicleTypeChanges(); + _controllerVehicle->_offlineFirmwareTypeSettingChanged(planFileFirmwareType); + _controllerVehicle->_offlineVehicleTypeSettingChanged(planFileVehicleType); + + if (json.contains(_jsonCruiseSpeedKey)) { + appSettings->offlineEditingCruiseSpeed()->setRawValue( + json[_jsonCruiseSpeedKey].toDouble()); + } + if (json.contains(_jsonHoverSpeedKey)) { + appSettings->offlineEditingHoverSpeed()->setRawValue( + json[_jsonHoverSpeedKey].toDouble()); + } + if (json.contains(_jsonGlobalPlanAltitudeModeKey)) { + setGlobalAltitudeMode(json[_jsonGlobalPlanAltitudeModeKey] + .toVariant() + .value()); + } + + QGeoCoordinate homeCoordinate; + if (!JsonHelper::loadGeoCoordinate(json[_jsonPlannedHomePositionKey], + true /* altitudeRequired */, + homeCoordinate, errorString)) { + return false; + } + MissionSettingsItem *settingsItem = + new MissionSettingsItem(_masterController, _flyView, visualItems); + settingsItem->setCoordinate(homeCoordinate); + visualItems->insert(0, settingsItem); + qCDebug(MissionControllerLog) << "plannedHomePosition" << homeCoordinate; + + // Read mission items + + int nextSequenceNumber = 1; // Start with 1 since home is in 0 + const QJsonArray rgMissionItems(json[_jsonItemsKey].toArray()); + for (int i = 0; i < rgMissionItems.count(); i++) { + // Convert to QJsonObject + const QJsonValue &itemValue = rgMissionItems[i]; + if (!itemValue.isObject()) { + errorString = tr("Mission item %1 is not an object").arg(i); + return false; + } + const QJsonObject itemObject = itemValue.toObject(); + + // Load item based on type + + QList itemKeyInfoList = { + {VisualMissionItem::jsonTypeKey, QJsonValue::String, true}, + }; + if (!JsonHelper::validateKeys(itemObject, itemKeyInfoList, errorString)) { + return false; + } + QString itemType = itemObject[VisualMissionItem::jsonTypeKey].toString(); + + if (itemType == VisualMissionItem::jsonTypeSimpleItemValue) { + SimpleMissionItem *simpleItem = new SimpleMissionItem( + _masterController, _flyView, true /* forLoad */, visualItems); + if (simpleItem->load(itemObject, nextSequenceNumber, errorString)) { + if (TakeoffMissionItem::isTakeoffCommand( + static_cast(simpleItem->command()))) { + // This needs to be a TakeoffMissionItem + TakeoffMissionItem *takeoffItem = + new TakeoffMissionItem(_masterController, _flyView, settingsItem, + true /* forLoad */, this); + takeoffItem->load(itemObject, nextSequenceNumber, errorString); + simpleItem->deleteLater(); + simpleItem = takeoffItem; + } + qCDebug(MissionControllerLog) + << "Loading simple item: nextSequenceNumber:command" + << nextSequenceNumber << simpleItem->command(); + nextSequenceNumber = simpleItem->lastSequenceNumber() + 1; + visualItems->append(simpleItem); + } else { + return false; + } + } else if (itemType == VisualMissionItem::jsonTypeComplexItemValue) { + QList complexItemKeyInfoList = { + {ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, + true}, + }; + if (!JsonHelper::validateKeys(itemObject, complexItemKeyInfoList, + errorString)) { + return false; + } + QString complexItemType = + itemObject[ComplexMissionItem::jsonComplexItemTypeKey].toString(); + + if (complexItemType == SurveyComplexItem::jsonComplexItemTypeValue) { + qCDebug(MissionControllerLog) + << "Loading Survey: nextSequenceNumber" << nextSequenceNumber; + SurveyComplexItem *surveyItem = new SurveyComplexItem( + _masterController, _flyView, QString() /* kmlFile */, visualItems); + if (!surveyItem->load(itemObject, nextSequenceNumber++, errorString)) { + return false; + } + nextSequenceNumber = surveyItem->lastSequenceNumber() + 1; + qCDebug(MissionControllerLog) + << "Survey load complete: nextSequenceNumber" << nextSequenceNumber; + visualItems->append(surveyItem); + } else if (complexItemType == + FixedWingLandingComplexItem::jsonComplexItemTypeValue) { + qCDebug(MissionControllerLog) + << "Loading Fixed Wing Landing Pattern: nextSequenceNumber" + << nextSequenceNumber; + FixedWingLandingComplexItem *landingItem = + new FixedWingLandingComplexItem(_masterController, _flyView, + visualItems); + if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) { + return false; + } + nextSequenceNumber = landingItem->lastSequenceNumber() + 1; + qCDebug(MissionControllerLog) + << "FW Landing Pattern load complete: nextSequenceNumber" + << nextSequenceNumber; + visualItems->append(landingItem); + } else if (complexItemType == + VTOLLandingComplexItem::jsonComplexItemTypeValue) { + qCDebug(MissionControllerLog) + << "Loading VTOL Landing Pattern: nextSequenceNumber" + << nextSequenceNumber; + VTOLLandingComplexItem *landingItem = new VTOLLandingComplexItem( + _masterController, _flyView, visualItems); + if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) { + return false; + } + nextSequenceNumber = landingItem->lastSequenceNumber() + 1; + qCDebug(MissionControllerLog) + << "VTOL Landing Pattern load complete: nextSequenceNumber" + << nextSequenceNumber; + visualItems->append(landingItem); + } else if (complexItemType == + StructureScanComplexItem::jsonComplexItemTypeValue) { + qCDebug(MissionControllerLog) + << "Loading Structure Scan: nextSequenceNumber" + << nextSequenceNumber; + StructureScanComplexItem *structureItem = new StructureScanComplexItem( + _masterController, _flyView, QString() /* kmlFile */, visualItems); + if (!structureItem->load(itemObject, nextSequenceNumber++, + errorString)) { + return false; + } + nextSequenceNumber = structureItem->lastSequenceNumber() + 1; + qCDebug(MissionControllerLog) + << "Structure Scan load complete: nextSequenceNumber" + << nextSequenceNumber; + visualItems->append(structureItem); + } else if (complexItemType == + CorridorScanComplexItem::jsonComplexItemTypeValue) { + qCDebug(MissionControllerLog) + << "Loading Corridor Scan: nextSequenceNumber" + << nextSequenceNumber; + CorridorScanComplexItem *corridorItem = new CorridorScanComplexItem( + _masterController, _flyView, QString() /* kmlFile */, visualItems); + if (!corridorItem->load(itemObject, nextSequenceNumber++, + errorString)) { + return false; + } + nextSequenceNumber = corridorItem->lastSequenceNumber() + 1; + qCDebug(MissionControllerLog) + << "Corridor Scan load complete: nextSequenceNumber" + << nextSequenceNumber; + visualItems->append(corridorItem); + } else if (complexItemType == + RouteComplexItem::jsonComplexItemTypeValue) { + qCDebug(MissionControllerLog) + << "Loading Circular Survey: nextSequenceNumber" + << nextSequenceNumber; + RouteComplexItem *survey = new RouteComplexItem( + _masterController, _flyView, QString() /* kmlFile */, visualItems); + if (!survey->load(itemObject, nextSequenceNumber++, errorString)) { + return false; + } + nextSequenceNumber = survey->lastSequenceNumber() + 1; + qCDebug(MissionControllerLog) + << "Ciruclar Survey load complete: nextSequenceNumber" + << nextSequenceNumber; + visualItems->append(survey); + } else { + errorString = + tr("Unsupported complex item type: %1").arg(complexItemType); + } + } else { + errorString = tr("Unknown item type: %1").arg(itemType); + return false; + } + } + + // Fix up the DO_JUMP commands jump sequence number by finding the item with + // the matching doJumpId + for (int i = 0; i < visualItems->count(); i++) { + if (visualItems->value(i)->isSimpleItem()) { + SimpleMissionItem *doJumpItem = + visualItems->value(i); + if (doJumpItem->command() == MAV_CMD_DO_JUMP) { + bool found = false; + int findDoJumpId = static_cast(doJumpItem->missionItem().param1()); + for (int j = 0; j < visualItems->count(); j++) { + if (visualItems->value(j)->isSimpleItem()) { + SimpleMissionItem *targetItem = + visualItems->value(j); + if (targetItem->missionItem().doJumpId() == findDoJumpId) { + doJumpItem->missionItem().setParam1(targetItem->sequenceNumber()); + found = true; + break; } + } + } + if (!found) { + errorString = tr("Could not find doJumpId: %1").arg(findDoJumpId); + return false; + } + } + } + } + + return true; +} + +bool MissionController::_loadItemsFromJson(const QJsonObject &json, + QmlObjectListModel *visualItems, + QString &errorString) { + // V1 file format has no file type key and version key is string. Convert to + // new format. + if (!json.contains(JsonHelper::jsonFileTypeKey)) { + json[JsonHelper::jsonFileTypeKey] = _jsonFileTypeValue; + } + + int fileVersion; + JsonHelper::validateExternalQGCJsonFile( + json, + _jsonFileTypeValue, // expected file type + 1, // minimum supported version + 2, // maximum supported version + fileVersion, errorString); + + if (fileVersion == 1) { + return _loadJsonMissionFileV1(json, visualItems, errorString); + } else { + return _loadJsonMissionFileV2(json, visualItems, errorString); + } +} + +bool MissionController::_loadTextMissionFile(QTextStream &stream, + QmlObjectListModel *visualItems, + QString &errorString) { + bool firstItem = true; + bool plannedHomePositionInFile = false; + + QString firstLine = stream.readLine(); + const QStringList &version = firstLine.split(" "); + + bool versionOk = false; + if (version.size() == 3 && version[0] == "QGC" && version[1] == "WPL") { + if (version[2] == "110") { + // ArduPilot file, planned home position is already in position 0 + versionOk = true; + plannedHomePositionInFile = true; + } else if (version[2] == "120") { + // Old QGC file, no planned home position + versionOk = true; + plannedHomePositionInFile = false; + } + } + + if (versionOk) { + MissionSettingsItem *settingsItem = _addMissionSettings(visualItems); + + while (!stream.atEnd()) { + SimpleMissionItem *item = new SimpleMissionItem( + _masterController, _flyView, true /* forLoad */, visualItems); + if (item->load(stream)) { + if (firstItem && plannedHomePositionInFile) { + settingsItem->setInitialHomePositionFromUser(item->coordinate()); + } else { + if (TakeoffMissionItem::isTakeoffCommand( + static_cast(item->command()))) { + // This needs to be a TakeoffMissionItem + TakeoffMissionItem *takeoffItem = new TakeoffMissionItem( + _masterController, _flyView, settingsItem, true /* forLoad */, + visualItems); + takeoffItem->load(stream); + item->deleteLater(); + item = takeoffItem; + } + visualItems->append(item); + } + firstItem = false; + } else { + errorString = tr("The mission file is corrupted."); + return false; + } + } + } else { + errorString = + tr("The mission file is not compatible with this version of %1.") + .arg(qgcApp()->applicationName()); + return false; + } + + if (!plannedHomePositionInFile) { + // Update sequence numbers in DO_JUMP commands to take into account added + // home position in index 0 + for (int i = 1; i < visualItems->count(); i++) { + SimpleMissionItem *item = + qobject_cast(visualItems->get(i)); + if (item && item->command() == MAV_CMD_DO_JUMP) { + item->missionItem().setParam1( + static_cast(item->missionItem().param1()) + 1); + } + } + } + + return true; +} + +void MissionController::_initLoadedVisualItems( + QmlObjectListModel *loadedVisualItems) { + if (_visualItems) { + _deinitAllVisualItems(); + _visualItems->deleteLater(); + } + _settingsItem = nullptr; + _takeoffMissionItem = nullptr; + + _visualItems = loadedVisualItems; + + if (_visualItems->count() == 0) { + _addMissionSettings(_visualItems); + } else { + _settingsItem = _visualItems->value(0); + } - _addTimeDistance(_missionFlightStatus.vtolMode == QGCMAVLink::VehicleClassMultiRotor, 0, 0, item->additionalTimeDelay(), 0, -1); - - if (item->specifiesCoordinate()) { - - // Keep track of the min/max AMSL altitude for entire mission so we can calculate altitude percentages in terrain status display - if (simpleItem) { - double amslAltitude = item->amslEntryAlt(); - _minAMSLAltitude = std::min(_minAMSLAltitude, amslAltitude); - _maxAMSLAltitude = std::max(_maxAMSLAltitude, amslAltitude); - } else { - // Complex item - double complexMinAMSLAltitude = complexItem->minAMSLAltitude(); - double complexMaxAMSLAltitude = complexItem->maxAMSLAltitude(); - _minAMSLAltitude = std::min(_minAMSLAltitude, complexMinAMSLAltitude); - _maxAMSLAltitude = std::max(_maxAMSLAltitude, complexMaxAMSLAltitude); - } + MissionController::_scanForAdditionalSettings(_visualItems, + _masterController); - if (!item->isStandaloneCoordinate()) { - firstCoordinateItem = false; - - // Update vehicle yaw assuming direction to next waypoint and/or mission item change - if (simpleItem) { - double newVehicleYaw = simpleItem->specifiedVehicleYaw(); - if (qIsNaN(newVehicleYaw)) { - // No specific vehicle yaw set. Current vehicle yaw is determined from flight path segment direction. - if (simpleItem != lastFlyThroughVI) { - _missionFlightStatus.vehicleYaw = lastFlyThroughVI->exitCoordinate().azimuthTo(simpleItem->coordinate()); - } - } else { - _missionFlightStatus.vehicleYaw = newVehicleYaw; - } - simpleItem->setMissionVehicleYaw(_missionFlightStatus.vehicleYaw); - } - - if (lastFlyThroughVI != _settingsItem || linkStartToHome) { - // This is a subsequent waypoint or we are forcing the first waypoint back to home - double azimuth, distance, altDifference; - - _calcPrevWaypointValues(item, lastFlyThroughVI, &azimuth, &distance, &altDifference); - totalHorizontalDistance += distance; - item->setAltDifference(altDifference); - item->setAzimuth(azimuth); - item->setDistance(distance); - item->setDistanceFromStart(totalHorizontalDistance); - - _missionFlightStatus.maxTelemetryDistance = qMax(_missionFlightStatus.maxTelemetryDistance, _calcDistanceToHome(item, _settingsItem)); - - // Calculate time/distance - double hoverTime = distance / _missionFlightStatus.hoverSpeed; - double cruiseTime = distance / _missionFlightStatus.cruiseSpeed; - _addTimeDistance(_missionFlightStatus.vtolMode == QGCMAVLink::VehicleClassMultiRotor, hoverTime, cruiseTime, 0, distance, item->sequenceNumber()); - } - - if (complexItem) { - // Add in distance/time inside complex items as well - double distance = complexItem->complexDistance(); - _missionFlightStatus.maxTelemetryDistance = qMax(_missionFlightStatus.maxTelemetryDistance, complexItem->greatestDistanceTo(complexItem->exitCoordinate())); - - double hoverTime = distance / _missionFlightStatus.hoverSpeed; - double cruiseTime = distance / _missionFlightStatus.cruiseSpeed; - _addTimeDistance(_missionFlightStatus.vtolMode == QGCMAVLink::VehicleClassMultiRotor, hoverTime, cruiseTime, 0, distance, item->sequenceNumber()); - - totalHorizontalDistance += distance; - } - - - lastFlyThroughVI = item; - } - } - } + _initAllVisualItems(); - // Speed, VTOL states changes are processed last since they take affect on the next item - - double newSpeed = item->specifiedFlightSpeed(); - if (!qIsNaN(newSpeed)) { - if (_controllerVehicle->multiRotor()) { - _missionFlightStatus.hoverSpeed = newSpeed; - } else if (_controllerVehicle->vtol()) { - if (_missionFlightStatus.vtolMode == QGCMAVLink::VehicleClassMultiRotor) { - _missionFlightStatus.hoverSpeed = newSpeed; - } else { - _missionFlightStatus.cruiseSpeed = newSpeed; - } - } else { - _missionFlightStatus.cruiseSpeed = newSpeed; - } - _missionFlightStatus.vehicleSpeed = newSpeed; - } + if (_visualItems->count() > 1) { + _firstItemAdded(); + } else { + _allItemsRemoved(); + } +} - // Update VTOL state - if (simpleItem && _controllerVehicle->vtol()) { - switch (simpleItem->command()) { - case MAV_CMD_NAV_TAKEOFF: // This will do a fixed wing style takeoff - case MAV_CMD_NAV_VTOL_TAKEOFF: // Vehicle goes straight up and then transitions to FW - case MAV_CMD_NAV_LAND: - _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassFixedWing; - break; - case MAV_CMD_NAV_VTOL_LAND: - _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassMultiRotor; - break; - case MAV_CMD_DO_VTOL_TRANSITION: - { - int transitionState = simpleItem->missionItem().param1(); - if (transitionState == MAV_VTOL_STATE_MC) { - _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassMultiRotor; - } else if (transitionState == MAV_VTOL_STATE_FW) { - _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassFixedWing; - } - } - break; - default: - break; - } - } - } - lastFlyThroughVI->setMissionVehicleYaw(_missionFlightStatus.vehicleYaw); - - // Add the information for the final segment back to home - if (foundRTL && lastFlyThroughVI != _settingsItem && homePositionValid) { - double azimuth, distance, altDifference; - _calcPrevWaypointValues(lastFlyThroughVI, _settingsItem, &azimuth, &distance, &altDifference); - - // Calculate time/distance - double hoverTime = distance / _missionFlightStatus.hoverSpeed; - double cruiseTime = distance / _missionFlightStatus.cruiseSpeed; - double landTime = qAbs(altDifference) / _appSettings->offlineEditingDescentSpeed()->rawValue().toDouble(); - _addTimeDistance(_missionFlightStatus.vtolMode == QGCMAVLink::VehicleClassMultiRotor, hoverTime, cruiseTime, distance, landTime, -1); - } - - if (_missionFlightStatus.mAhBattery != 0 && _missionFlightStatus.batteryChangePoint == -1) { - _missionFlightStatus.batteryChangePoint = 0; - } - - emit missionMaxTelemetryChanged (_missionFlightStatus.maxTelemetryDistance); - emit missionDistanceChanged (_missionFlightStatus.totalDistance); - emit missionHoverDistanceChanged (_missionFlightStatus.hoverDistance); - emit missionCruiseDistanceChanged (_missionFlightStatus.cruiseDistance); - emit missionTimeChanged (); - emit missionHoverTimeChanged (); - emit missionCruiseTimeChanged (); - emit batteryChangePointChanged (_missionFlightStatus.batteryChangePoint); - emit batteriesRequiredChanged (_missionFlightStatus.batteriesRequired); - emit minAMSLAltitudeChanged (_minAMSLAltitude); - emit maxAMSLAltitudeChanged (_maxAMSLAltitude); - - // Walk the list again calculating altitude percentages - double altRange = _maxAMSLAltitude - _minAMSLAltitude; - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* item = qobject_cast(_visualItems->get(i)); - - if (item->specifiesCoordinate()) { - double amslAlt = item->amslEntryAlt(); - if (altRange == 0.0) { - item->setAltPercent(0.0); - item->setTerrainPercent(qQNaN()); - item->setTerrainCollision(false); - } else { - item->setAltPercent((amslAlt - _minAMSLAltitude) / altRange); - double terrainAltitude = item->terrainAltitude(); - if (qIsNaN(terrainAltitude)) { - item->setTerrainPercent(qQNaN()); - item->setTerrainCollision(false); - } else { - item->setTerrainPercent((terrainAltitude - _minAMSLAltitude) / altRange); - item->setTerrainCollision(amslAlt < terrainAltitude); - } - } - } - } +bool MissionController::load(const QJsonObject &json, QString &errorString) { + QString errorStr; + QString errorMessage = tr("Mission: %1"); + QmlObjectListModel *loadedVisualItems = new QmlObjectListModel(this); - _updateTimer.start(UPDATE_TIMEOUT); + if (!_loadJsonMissionFileV2(json, loadedVisualItems, errorStr)) { + errorString = errorMessage.arg(errorStr); + return false; + } + _initLoadedVisualItems(loadedVisualItems); - emit recalcTerrainProfile(); + return true; } -// This will update the sequence numbers to be sequential starting from 0 -void MissionController::_recalcSequence(void) -{ - if (_inRecalcSequence) { - // Don't let this call recurse due to signalling - return; - } +bool MissionController::loadJsonFile(QFile &file, QString &errorString) { + QString errorStr; + QString errorMessage = tr("Mission: %1"); + QJsonDocument jsonDoc; + QByteArray bytes = file.readAll(); - // Setup ascending sequence numbers for all visual items + if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorStr)) { + errorString = errorMessage.arg(errorStr); + return false; + } - _inRecalcSequence = true; - int sequenceNumber = 0; - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* item = qobject_cast(_visualItems->get(i)); - item->setSequenceNumber(sequenceNumber); - sequenceNumber = item->lastSequenceNumber() + 1; - } - _inRecalcSequence = false; -} + QJsonObject json = jsonDoc.object(); + QmlObjectListModel *loadedVisualItems = new QmlObjectListModel(this); + if (!_loadItemsFromJson(json, loadedVisualItems, errorStr)) { + errorString = errorMessage.arg(errorStr); + return false; + } -// This will update the child item hierarchy -void MissionController::_recalcChildItems(void) -{ - VisualMissionItem* currentParentItem = qobject_cast(_visualItems->get(0)); - - currentParentItem->childItems()->clear(); - - for (int i=1; i<_visualItems->count(); i++) { - VisualMissionItem* item = _visualItems->value(i); - - item->setParentItem(nullptr); - item->setHasCurrentChildItem(false); - - // Set up non-coordinate item child hierarchy - if (item->specifiesCoordinate()) { - item->childItems()->clear(); - currentParentItem = item; - } else if (item->isSimpleItem()) { - item->setParentItem(currentParentItem); - currentParentItem->childItems()->append(item); - if (item->isCurrentItem()) { - currentParentItem->setHasCurrentChildItem(true); - } - } - } -} + _initLoadedVisualItems(loadedVisualItems); -void MissionController::_setPlannedHomePositionFromFirstCoordinate(const QGeoCoordinate& clickCoordinate) -{ - bool foundFirstCoordinate = false; - QGeoCoordinate firstCoordinate; + return true; +} - if (_settingsItem->coordinate().isValid()) { - return; - } +bool MissionController::loadTextFile(QFile &file, QString &errorString) { + QString errorStr; + QString errorMessage = tr("Mission: %1"); + QByteArray bytes = file.readAll(); + QTextStream stream(bytes); - // Set the planned home position to be a delta from first coordinate - for (int i=1; i<_visualItems->count(); i++) { - VisualMissionItem* item = _visualItems->value(i); + setGlobalAltitudeMode( + QGroundControlQmlGlobal::AltitudeModeNone); // Mixed mode - if (item->specifiesCoordinate() && item->coordinate().isValid()) { - foundFirstCoordinate = true; - firstCoordinate = item->coordinate(); - break; - } - } + QmlObjectListModel *loadedVisualItems = new QmlObjectListModel(this); + if (!_loadTextMissionFile(stream, loadedVisualItems, errorStr)) { + errorString = errorMessage.arg(errorStr); + return false; + } - // No item specifying a coordinate was found, in this case it we have a clickCoordinate use that - if (!foundFirstCoordinate) { - firstCoordinate = clickCoordinate; - } + _initLoadedVisualItems(loadedVisualItems); - if (firstCoordinate.isValid()) { - QGeoCoordinate plannedHomeCoord = firstCoordinate.atDistanceAndAzimuth(30, 0); - plannedHomeCoord.setAltitude(0); - _settingsItem->setInitialHomePositionFromUser(plannedHomeCoord); - } + return true; } -void MissionController::_recalcAllWithCoordinate(const QGeoCoordinate& coordinate) -{ - if (!_flyView) { - _setPlannedHomePositionFromFirstCoordinate(coordinate); +int MissionController::readyForSaveState(void) const { + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *visualItem = + qobject_cast(_visualItems->get(i)); + if (visualItem->readyForSaveState() != VisualMissionItem::ReadyForSave) { + return visualItem->readyForSaveState(); } - _recalcSequence(); - _recalcChildItems(); - emit _recalcFlightPathSegmentsSignal(); - _updateTimer.start(UPDATE_TIMEOUT); -} + } -void MissionController::_recalcAll(void) -{ - QGeoCoordinate emptyCoord; - _recalcAllWithCoordinate(emptyCoord); + return VisualMissionItem::ReadyForSave; } -/// Initializes a new set of mission items -void MissionController::_initAllVisualItems(void) -{ - // Setup home position at index 0 - - if (!_settingsItem) { - _settingsItem = qobject_cast(_visualItems->get(0)); - if (!_settingsItem) { - qWarning() << "First item not MissionSettingsItem"; - return; - } - } - - connect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, &MissionController::_recalcAll); - connect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, &MissionController::plannedHomePositionChanged); +void MissionController::save(QJsonObject &json) { + json[JsonHelper::jsonVersionKey] = _missionFileVersion; - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* item = qobject_cast(_visualItems->get(i)); - _initVisualItem(item); + // Mission settings - TakeoffMissionItem* takeoffItem = qobject_cast(item); - if (takeoffItem) { - _takeoffMissionItem = takeoffItem; + MissionSettingsItem *settingsItem = + _visualItems->value(0); + if (!settingsItem) { + qWarning() << "First item is not MissionSettingsItem"; + return; + } + QJsonValue coordinateValue; + JsonHelper::saveGeoCoordinate(settingsItem->coordinate(), + true /* writeAltitude */, coordinateValue); + json[_jsonPlannedHomePositionKey] = coordinateValue; + json[_jsonFirmwareTypeKey] = _controllerVehicle->firmwareType(); + json[_jsonVehicleTypeKey] = _controllerVehicle->vehicleType(); + json[_jsonCruiseSpeedKey] = _controllerVehicle->defaultCruiseSpeed(); + json[_jsonHoverSpeedKey] = _controllerVehicle->defaultHoverSpeed(); + json[_jsonGlobalPlanAltitudeModeKey] = _globalAltMode; + + // Save the visual items + + QJsonArray rgJsonMissionItems; + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *visualItem = + qobject_cast(_visualItems->get(i)); + + visualItem->save(rgJsonMissionItems); + } + + // Mission settings has a special case for end mission action + if (settingsItem) { + QList rgMissionItems; + + if (_convertToMissionItems(_visualItems, rgMissionItems, + this /* missionItemParent */)) { + QJsonObject saveObject; + MissionItem *missionItem = rgMissionItems[rgMissionItems.count() - 1]; + missionItem->save(saveObject); + rgJsonMissionItems.append(saveObject); + } + for (int i = 0; i < rgMissionItems.count(); i++) { + rgMissionItems[i]->deleteLater(); + } + } + + json[_jsonItemsKey] = rgJsonMissionItems; +} + +void MissionController::_calcPrevWaypointValues(VisualMissionItem *currentItem, + VisualMissionItem *prevItem, + double *azimuth, + double *distance, + double *altDifference) { + QGeoCoordinate currentCoord = currentItem->coordinate(); + QGeoCoordinate prevCoord = prevItem->exitCoordinate(); + + // Convert to fixed altitudes + + *altDifference = currentItem->amslEntryAlt() - prevItem->amslExitAlt(); + *distance = prevCoord.distanceTo(currentCoord); + *azimuth = prevCoord.azimuthTo(currentCoord); +} + +double MissionController::_calcDistanceToHome(VisualMissionItem *currentItem, + VisualMissionItem *homeItem) { + QGeoCoordinate currentCoord = currentItem->coordinate(); + QGeoCoordinate homeCoord = homeItem->exitCoordinate(); + bool distanceOk = false; + + distanceOk = true; + + return distanceOk ? homeCoord.distanceTo(currentCoord) : 0.0; +} + +FlightPathSegment * +MissionController::_createFlightPathSegmentWorker(VisualItemPair &pair) { + QGeoCoordinate coord1 = pair.first->isSimpleItem() + ? pair.first->coordinate() + : pair.first->exitCoordinate(); + QGeoCoordinate coord2 = pair.second->coordinate(); + double coord1Alt = pair.first->isSimpleItem() ? pair.first->amslEntryAlt() + : pair.first->amslExitAlt(); + double coord2Alt = pair.second->amslEntryAlt(); + + FlightPathSegment *segment = + new FlightPathSegment(coord1, coord1Alt, coord2, coord2Alt, + !_flyView /* queryTerrainData */, this); + + auto coord1Notifier = pair.first->isSimpleItem() + ? &VisualMissionItem::coordinateChanged + : &VisualMissionItem::exitCoordinateChanged; + auto coord2Notifier = &VisualMissionItem::coordinateChanged; + auto coord1AltNotifier = pair.first->isSimpleItem() + ? &VisualMissionItem::amslEntryAltChanged + : &VisualMissionItem::amslExitAltChanged; + auto coord2AltNotifier = &VisualMissionItem::amslEntryAltChanged; + + connect(pair.first, coord1Notifier, segment, + &FlightPathSegment::setCoordinate1); + connect(pair.second, coord2Notifier, segment, + &FlightPathSegment::setCoordinate2); + connect(pair.first, coord1AltNotifier, segment, + &FlightPathSegment::setCoord1AMSLAlt); + connect(pair.second, coord2AltNotifier, segment, + &FlightPathSegment::setCoord2AMSLAlt); + + connect(pair.second, &VisualMissionItem::coordinateChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + + connect(segment, &FlightPathSegment::totalDistanceChanged, this, + &MissionController::recalcTerrainProfile, Qt::QueuedConnection); + connect(segment, &FlightPathSegment::coord1AMSLAltChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(segment, &FlightPathSegment::coord2AMSLAltChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(segment, &FlightPathSegment::amslTerrainHeightsChanged, this, + &MissionController::recalcTerrainProfile, Qt::QueuedConnection); + connect(segment, &FlightPathSegment::terrainCollisionChanged, this, + &MissionController::recalcTerrainProfile, Qt::QueuedConnection); + + return segment; +} + +FlightPathSegment *MissionController::_addFlightPathSegment( + FlightPathSegmentHashTable &prevItemPairHashTable, VisualItemPair &pair) { + FlightPathSegment *segment = nullptr; + + if (prevItemPairHashTable.contains(pair)) { + // Pair already exists and connected, just re-use + _flightPathSegmentHashTable[pair] = segment = + prevItemPairHashTable.take(pair); + } else { + segment = _createFlightPathSegmentWorker(pair); + _flightPathSegmentHashTable[pair] = segment; + } + + _simpleFlightPathSegments.append(segment); + + return segment; +} + +void MissionController::_recalcROISpecialVisuals(void) { + return; + VisualMissionItem *lastCoordinateItem = + qobject_cast(_visualItems->get(0)); + bool roiActive = false; + + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem *visualItem = + qobject_cast(_visualItems->get(i)); + SimpleMissionItem *simpleItem = + qobject_cast(visualItem); + VisualItemPair viPair; + + if (simpleItem) { + if (roiActive) { + if (_isROICancelItem(simpleItem)) { + roiActive = false; + } + } else { + if (_isROIBeginItem(simpleItem)) { + roiActive = true; + } + } + } + + if (visualItem->specifiesCoordinate() && + !visualItem->isStandaloneCoordinate()) { + viPair = VisualItemPair(lastCoordinateItem, visualItem); + if (_flightPathSegmentHashTable.contains(viPair)) { + _flightPathSegmentHashTable[viPair]->setSpecialVisual(roiActive); + } + lastCoordinateItem = visualItem; + } + } +} + +void MissionController::_recalcFlightPathSegments(void) { + VisualItemPair lastSegmentVisualItemPair; + int segmentCount = 0; + bool firstCoordinateNotFound = true; + VisualMissionItem *lastFlyThroughVI = + qobject_cast(_visualItems->get(0)); + bool linkEndToHome = false; + bool linkStartToHome = _controllerVehicle->rover() ? true : false; + bool foundRTL = false; + bool homePositionValid = _settingsItem->coordinate().isValid(); + bool roiActive = false; + bool previousItemIsIncomplete = false; + + qCDebug(MissionControllerLog) + << "_recalcFlightPathSegments homePositionValid" << homePositionValid; + + FlightPathSegmentHashTable oldSegmentTable = _flightPathSegmentHashTable; + + _missionContainsVTOLTakeoff = false; + _flightPathSegmentHashTable.clear(); + _waypointPath.clear(); + + // Note: Although visual support _incompleteComplexItemLines is still in the + // codebase. The support for populating the list is not. This is due to the + // initial implementation being buggy and incomplete with respect to correctly + // generating the line set. So for now we leave the code for displaying them + // in, but none are ever added until we have time to implement the correct + // support. + + _simpleFlightPathSegments.beginReset(); + _directionArrows.beginReset(); + _incompleteComplexItemLines.beginReset(); + + _simpleFlightPathSegments.clear(); + _directionArrows.clear(); + _incompleteComplexItemLines.clearAndDeleteContents(); + + // Mission Settings item needs to start with no segment + lastFlyThroughVI->setSimpleFlighPathSegment(nullptr); + + // Grovel through the list of items keeping track of things needed to + // correctly draw waypoints lines + + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem *visualItem = + qobject_cast(_visualItems->get(i)); + SimpleMissionItem *simpleItem = + qobject_cast(visualItem); + ComplexMissionItem *complexItem = + qobject_cast(visualItem); + + visualItem->setSimpleFlighPathSegment(nullptr); + + if (simpleItem) { + if (roiActive) { + if (_isROICancelItem(simpleItem)) { + roiActive = false; + } + } else { + if (_isROIBeginItem(simpleItem)) { + roiActive = true; + } + } + + MAV_CMD command = simpleItem->mavCommand(); + switch (command) { + case MAV_CMD_NAV_TAKEOFF: + case MAV_CMD_NAV_VTOL_TAKEOFF: + _missionContainsVTOLTakeoff = command == MAV_CMD_NAV_VTOL_TAKEOFF; + if (!linkEndToHome) { + // If we still haven't found the first coordinate item and we hit a + // takeoff command this means the mission starts from the ground. Link + // the first item back to home to show that. + if (firstCoordinateNotFound) { + linkStartToHome = true; + } + } + break; + case MAV_CMD_NAV_RETURN_TO_LAUNCH: + linkEndToHome = true; + foundRTL = true; + break; + default: + break; + } + } + + // No need to add waypoint segments after an RTL. + if (foundRTL) { + break; + } + + if (visualItem->specifiesCoordinate() && + !visualItem->isStandaloneCoordinate()) { + // Incomplete items are complex items which are waiting for the user to + // complete setup before there visuals can become valid. They may not yet + // have valid entry/exit coordinates associated with them while in the + // incomplete state. For examples a Survey item which has no polygon set + // yet. + if (complexItem && complexItem->isIncomplete()) { + // We don't link lines from a valid item to an incomplete item + previousItemIsIncomplete = true; + } else if (previousItemIsIncomplete) { + // We also don't link lines from an incomplete item to a valid item. + previousItemIsIncomplete = false; + firstCoordinateNotFound = false; + lastFlyThroughVI = visualItem; + } else { + if (lastFlyThroughVI != _settingsItem || + (homePositionValid && linkStartToHome)) { + bool addDirectionArrow = false; + if (i != 1) { + // Direction arrows are added to the second segment and every 5 + // segments thereafter. The reason for start with second segment is + // to prevent an arrow being added in between the home position and + // a takeoff item which may be right over each other. In that case + // the arrow points in a random direction. + if (firstCoordinateNotFound || !lastFlyThroughVI->isSimpleItem() || + !visualItem->isSimpleItem()) { + addDirectionArrow = true; + } else if (segmentCount > 5) { + segmentCount = 0; + addDirectionArrow = true; + } + segmentCount++; + } + + lastSegmentVisualItemPair = + VisualItemPair(lastFlyThroughVI, visualItem); + if (!_flyView || addDirectionArrow) { + FlightPathSegment *segment = _addFlightPathSegment( + oldSegmentTable, lastSegmentVisualItemPair); + segment->setSpecialVisual(roiActive); + if (addDirectionArrow) { + _directionArrows.append(segment); + } + lastFlyThroughVI->setSimpleFlighPathSegment(segment); + } } + firstCoordinateNotFound = false; + _waypointPath.append(QVariant::fromValue(visualItem->coordinate())); + lastFlyThroughVI = visualItem; + } } + } - _recalcAll(); + if (linkStartToHome && homePositionValid) { + _waypointPath.prepend(QVariant::fromValue(_settingsItem->coordinate())); + } - connect(_visualItems, &QmlObjectListModel::dirtyChanged, this, &MissionController::_visualItemsDirtyChanged); - connect(_visualItems, &QmlObjectListModel::countChanged, this, &MissionController::_updateContainsItems); - - emit visualItemsChanged(); - emit containsItemsChanged(containsItems()); - emit plannedHomePositionChanged(plannedHomePosition()); - - if (!_flyView) { - setCurrentPlanViewSeqNum(0, true); - } - - setDirty(false); + if (linkEndToHome && lastFlyThroughVI != _settingsItem && homePositionValid) { + lastSegmentVisualItemPair = VisualItemPair(lastFlyThroughVI, _settingsItem); + if (_flyView) { + _waypointPath.append(QVariant::fromValue(_settingsItem->coordinate())); + } + FlightPathSegment *segment = + _addFlightPathSegment(oldSegmentTable, lastSegmentVisualItemPair); + segment->setSpecialVisual(roiActive); + lastFlyThroughVI->setSimpleFlighPathSegment(segment); + } + + // Add direction arrow to last segment + if (lastSegmentVisualItemPair.first) { + FlightPathSegment *coordVector = nullptr; + + // The pair may not be in the hash, this can happen in the fly view where + // only segments with arrows on them are added to hash. check for that first + // and add if needed + + if (_flightPathSegmentHashTable.contains(lastSegmentVisualItemPair)) { + // Pair exists in the new table already just reuse it + coordVector = _flightPathSegmentHashTable[lastSegmentVisualItemPair]; + } else if (oldSegmentTable.contains(lastSegmentVisualItemPair)) { + // Pair already exists in old table, pull from old to new and reuse + _flightPathSegmentHashTable[lastSegmentVisualItemPair] = coordVector = + oldSegmentTable.take(lastSegmentVisualItemPair); + } else { + // Create a new segment. Since this is the fly view there is no need to + // wire change signals. + coordVector = new FlightPathSegment( + lastSegmentVisualItemPair.first->isSimpleItem() + ? lastSegmentVisualItemPair.first->coordinate() + : lastSegmentVisualItemPair.first->exitCoordinate(), + lastSegmentVisualItemPair.first->isSimpleItem() + ? lastSegmentVisualItemPair.first->amslEntryAlt() + : lastSegmentVisualItemPair.first->amslExitAlt(), + lastSegmentVisualItemPair.second->coordinate(), + lastSegmentVisualItemPair.second->amslEntryAlt(), + !_flyView /* queryTerrainData */, this); + _flightPathSegmentHashTable[lastSegmentVisualItemPair] = coordVector; + } + + _directionArrows.append(coordVector); + } + + _simpleFlightPathSegments.endReset(); + _directionArrows.endReset(); + _incompleteComplexItemLines.endReset(); + + // Anything left in the old table is an obsolete line object that can go + qDeleteAll(oldSegmentTable); + + emit _recalcMissionFlightStatusSignal(); + + if (_waypointPath.count() == 0) { + // MapPolyLine has a bug where if you change from a path which has elements + // to an empty path the line drawn is not cleared from the map. This hack + // works around that since it causes the previous lines to be remove as then + // doesn't draw anything on the map. + _waypointPath.append(QVariant::fromValue(QGeoCoordinate(0, 0))); + _waypointPath.append(QVariant::fromValue(QGeoCoordinate(0, 0))); + } + + emit waypointPathChanged(); + emit recalcTerrainProfile(); +} + +void MissionController::_updateBatteryInfo(int waypointIndex) { + if (_missionFlightStatus.mAhBattery != 0) { + _missionFlightStatus.hoverAmpsTotal = + (_missionFlightStatus.hoverTime / 60.0) * + _missionFlightStatus.hoverAmps; + _missionFlightStatus.cruiseAmpsTotal = + (_missionFlightStatus.cruiseTime / 60.0) * + _missionFlightStatus.cruiseAmps; + _missionFlightStatus.batteriesRequired = + ceil((_missionFlightStatus.hoverAmpsTotal + + _missionFlightStatus.cruiseAmpsTotal) / + _missionFlightStatus.ampMinutesAvailable); + // FIXME: Battery change point code pretty much doesn't work. The reason is + // that is treats complex items as a black box. It needs to be able to look + // inside complex items in order to determine a swap point that is interior + // to a complex item. Current the swap point display in PlanToolbar is + // disabled to do this problem. + if (waypointIndex != -1 && _missionFlightStatus.batteriesRequired == 2 && + _missionFlightStatus.batteryChangePoint == -1) { + _missionFlightStatus.batteryChangePoint = waypointIndex - 1; + } + } +} + +void MissionController::_addHoverTime(double hoverTime, double hoverDistance, + int waypointIndex) { + _missionFlightStatus.totalTime += hoverTime; + _missionFlightStatus.hoverTime += hoverTime; + _missionFlightStatus.hoverDistance += hoverDistance; + _missionFlightStatus.totalDistance += hoverDistance; + _updateBatteryInfo(waypointIndex); +} + +void MissionController::_addCruiseTime(double cruiseTime, double cruiseDistance, + int waypointIndex) { + _missionFlightStatus.totalTime += cruiseTime; + _missionFlightStatus.cruiseTime += cruiseTime; + _missionFlightStatus.cruiseDistance += cruiseDistance; + _missionFlightStatus.totalDistance += cruiseDistance; + _updateBatteryInfo(waypointIndex); } -void MissionController::_deinitAllVisualItems(void) -{ - disconnect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, &MissionController::_recalcAll); - disconnect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, &MissionController::plannedHomePositionChanged); - - for (int i=0; i<_visualItems->count(); i++) { - _deinitVisualItem(qobject_cast(_visualItems->get(i))); +/// Adds the specified time to the appropriate hover or cruise time values. +/// @param vtolInHover true: vtol is currrent in hover mode +/// @param hoverTime Amount of time tp add to hover +/// @param cruiseTime Amount of time to add to cruise +/// @param extraTime Amount of additional time to add to hover/cruise +/// @param seqNum Sequence number of waypoint for these values, -1 for +/// no waypoint associated +void MissionController::_addTimeDistance(bool vtolInHover, double hoverTime, + double cruiseTime, double extraTime, + double distance, int seqNum) { + if (_controllerVehicle->vtol()) { + if (vtolInHover) { + _addHoverTime(hoverTime, distance, seqNum); + _addHoverTime(extraTime, 0, -1); + } else { + _addCruiseTime(cruiseTime, distance, seqNum); + _addCruiseTime(extraTime, 0, -1); } - - disconnect(_visualItems, &QmlObjectListModel::dirtyChanged, this, &MissionController::dirtyChanged); - disconnect(_visualItems, &QmlObjectListModel::countChanged, this, &MissionController::_updateContainsItems); + } else { + if (_controllerVehicle->multiRotor()) { + _addHoverTime(hoverTime, distance, seqNum); + _addHoverTime(extraTime, 0, -1); + } else { + _addCruiseTime(cruiseTime, distance, seqNum); + _addCruiseTime(extraTime, 0, -1); + } + } } -void MissionController::_initVisualItem(VisualMissionItem* visualItem) -{ - setDirty(false); - - connect(visualItem, &VisualMissionItem::specifiesCoordinateChanged, this, &MissionController::_recalcFlightPathSegmentsSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::specifiedFlightSpeedChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::specifiedGimbalYawChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::specifiedGimbalPitchChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::specifiedVehicleYawChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::terrainAltitudeChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::additionalTimeDelayChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::currentVTOLModeChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(visualItem, &VisualMissionItem::lastSequenceNumberChanged, this, &MissionController::_recalcSequence); - - if (visualItem->isSimpleItem()) { - // We need to track commandChanged on simple item since recalc has special handling for takeoff command - SimpleMissionItem* simpleItem = qobject_cast(visualItem); +void MissionController::_recalcMissionFlightStatus() { + if (!_visualItems->count()) { + return; + } + + bool firstCoordinateItem = true; + VisualMissionItem *lastFlyThroughVI = + qobject_cast(_visualItems->get(0)); + + bool homePositionValid = _settingsItem->coordinate().isValid(); + + qCDebug(MissionControllerLog) << "_recalcMissionFlightStatus"; + + // If home position is valid we can calculate distances between all waypoints. + // If home position is not valid we can only calculate distances between + // waypoints which are both relative altitude. + + // No values for first item + lastFlyThroughVI->setAltDifference(0); + lastFlyThroughVI->setAzimuth(0); + lastFlyThroughVI->setDistance(0); + lastFlyThroughVI->setDistanceFromStart(0); + + _minAMSLAltitude = _maxAMSLAltitude = _settingsItem->coordinate().altitude(); + + _resetMissionFlightStatus(); + + bool linkStartToHome = false; + bool foundRTL = false; + double totalHorizontalDistance = 0; + + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(_visualItems->get(i)); + SimpleMissionItem *simpleItem = qobject_cast(item); + ComplexMissionItem *complexItem = qobject_cast(item); + + if (simpleItem && + simpleItem->mavCommand() == MAV_CMD_NAV_RETURN_TO_LAUNCH) { + foundRTL = true; + } + + // Assume the worst + item->setAzimuth(0); + item->setDistance(0); + item->setDistanceFromStart(0); + + // Gimbal states reflect the state AFTER executing the item + + // ROI commands cancel out previous gimbal yaw/pitch + if (simpleItem) { + switch (simpleItem->command()) { + case MAV_CMD_NAV_ROI: + case MAV_CMD_DO_SET_ROI_LOCATION: + case MAV_CMD_DO_SET_ROI_WPNEXT_OFFSET: + _missionFlightStatus.gimbalYaw = qQNaN(); + _missionFlightStatus.gimbalPitch = qQNaN(); + break; + default: + break; + } + } + + // Look for specific gimbal changes + double gimbalYaw = item->specifiedGimbalYaw(); + if (!qIsNaN(gimbalYaw) || + _planViewSettings->showGimbalOnlyWhenSet()->rawValue().toBool()) { + _missionFlightStatus.gimbalYaw = gimbalYaw; + } + double gimbalPitch = item->specifiedGimbalPitch(); + if (!qIsNaN(gimbalPitch) || + _planViewSettings->showGimbalOnlyWhenSet()->rawValue().toBool()) { + _missionFlightStatus.gimbalPitch = gimbalPitch; + } + + // We don't need to do any more processing if: + // Mission Settings Item + // We are after an RTL command + if (i != 0 && !foundRTL) { + // We must set the mission flight status prior to querying for any values + // from the item. This is because things like current speed, gimbal, vtol + // state impact the values. + item->setMissionFlightStatus(_missionFlightStatus); + + // Link back to home if first item is takeoff and we have home position + if (firstCoordinateItem && simpleItem && + (simpleItem->mavCommand() == MAV_CMD_NAV_TAKEOFF || + simpleItem->mavCommand() == MAV_CMD_NAV_VTOL_TAKEOFF)) { + if (homePositionValid) { + linkStartToHome = true; + if (_controllerVehicle->multiRotor() || _controllerVehicle->vtol()) { + // We have to special case takeoff, assuming vehicle takes off + // straight up to specified altitude + double azimuth, distance, altDifference; + _calcPrevWaypointValues(_settingsItem, simpleItem, &azimuth, + &distance, &altDifference); + double takeoffTime = + qAbs(altDifference) / _appSettings->offlineEditingAscentSpeed() + ->rawValue() + .toDouble(); + _addHoverTime(takeoffTime, 0, -1); + } + } + } + + _addTimeDistance(_missionFlightStatus.vtolMode == + QGCMAVLink::VehicleClassMultiRotor, + 0, 0, item->additionalTimeDelay(), 0, -1); + + if (item->specifiesCoordinate()) { + + // Keep track of the min/max AMSL altitude for entire mission so we can + // calculate altitude percentages in terrain status display if (simpleItem) { - connect(&simpleItem->missionItem()._commandFact, &Fact::valueChanged, this, &MissionController::_itemCommandChanged); + double amslAltitude = item->amslEntryAlt(); + _minAMSLAltitude = std::min(_minAMSLAltitude, amslAltitude); + _maxAMSLAltitude = std::max(_maxAMSLAltitude, amslAltitude); } else { - qWarning() << "isSimpleItem == true, yet not SimpleMissionItem"; - } - } else { - ComplexMissionItem* complexItem = qobject_cast(visualItem); - if (complexItem) { - connect(complexItem, &ComplexMissionItem::complexDistanceChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(complexItem, &ComplexMissionItem::greatestDistanceToChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(complexItem, &ComplexMissionItem::minAMSLAltitudeChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(complexItem, &ComplexMissionItem::maxAMSLAltitudeChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(complexItem, &ComplexMissionItem::isIncompleteChanged, this, &MissionController::_recalcFlightPathSegmentsSignal, Qt::QueuedConnection); + // Complex item + double complexMinAMSLAltitude = complexItem->minAMSLAltitude(); + double complexMaxAMSLAltitude = complexItem->maxAMSLAltitude(); + _minAMSLAltitude = std::min(_minAMSLAltitude, complexMinAMSLAltitude); + _maxAMSLAltitude = std::max(_maxAMSLAltitude, complexMaxAMSLAltitude); + } + + if (!item->isStandaloneCoordinate()) { + firstCoordinateItem = false; + + // Update vehicle yaw assuming direction to next waypoint and/or + // mission item change + if (simpleItem) { + double newVehicleYaw = simpleItem->specifiedVehicleYaw(); + if (qIsNaN(newVehicleYaw)) { + // No specific vehicle yaw set. Current vehicle yaw is determined + // from flight path segment direction. + if (simpleItem != lastFlyThroughVI) { + _missionFlightStatus.vehicleYaw = + lastFlyThroughVI->exitCoordinate().azimuthTo( + simpleItem->coordinate()); + } + } else { + _missionFlightStatus.vehicleYaw = newVehicleYaw; + } + simpleItem->setMissionVehicleYaw(_missionFlightStatus.vehicleYaw); + } + + if (lastFlyThroughVI != _settingsItem || linkStartToHome) { + // This is a subsequent waypoint or we are forcing the first + // waypoint back to home + double azimuth, distance, altDifference; + + _calcPrevWaypointValues(item, lastFlyThroughVI, &azimuth, &distance, + &altDifference); + totalHorizontalDistance += distance; + item->setAltDifference(altDifference); + item->setAzimuth(azimuth); + item->setDistance(distance); + item->setDistanceFromStart(totalHorizontalDistance); + + _missionFlightStatus.maxTelemetryDistance = + qMax(_missionFlightStatus.maxTelemetryDistance, + _calcDistanceToHome(item, _settingsItem)); + + // Calculate time/distance + double hoverTime = distance / _missionFlightStatus.hoverSpeed; + double cruiseTime = distance / _missionFlightStatus.cruiseSpeed; + _addTimeDistance(_missionFlightStatus.vtolMode == + QGCMAVLink::VehicleClassMultiRotor, + hoverTime, cruiseTime, 0, distance, + item->sequenceNumber()); + } + + if (complexItem) { + // Add in distance/time inside complex items as well + double distance = complexItem->complexDistance(); + _missionFlightStatus.maxTelemetryDistance = qMax( + _missionFlightStatus.maxTelemetryDistance, + complexItem->greatestDistanceTo(complexItem->exitCoordinate())); + + double hoverTime = distance / _missionFlightStatus.hoverSpeed; + double cruiseTime = distance / _missionFlightStatus.cruiseSpeed; + _addTimeDistance(_missionFlightStatus.vtolMode == + QGCMAVLink::VehicleClassMultiRotor, + hoverTime, cruiseTime, 0, distance, + item->sequenceNumber()); + + totalHorizontalDistance += distance; + } + + lastFlyThroughVI = item; + } + } + } + + // Speed, VTOL states changes are processed last since they take affect on + // the next item + + double newSpeed = item->specifiedFlightSpeed(); + if (!qIsNaN(newSpeed)) { + if (_controllerVehicle->multiRotor()) { + _missionFlightStatus.hoverSpeed = newSpeed; + } else if (_controllerVehicle->vtol()) { + if (_missionFlightStatus.vtolMode == + QGCMAVLink::VehicleClassMultiRotor) { + _missionFlightStatus.hoverSpeed = newSpeed; + } else { + _missionFlightStatus.cruiseSpeed = newSpeed; + } + } else { + _missionFlightStatus.cruiseSpeed = newSpeed; + } + _missionFlightStatus.vehicleSpeed = newSpeed; + } + + // Update VTOL state + if (simpleItem && _controllerVehicle->vtol()) { + switch (simpleItem->command()) { + case MAV_CMD_NAV_TAKEOFF: // This will do a fixed wing style takeoff + case MAV_CMD_NAV_VTOL_TAKEOFF: // Vehicle goes straight up and then + // transitions to FW + case MAV_CMD_NAV_LAND: + _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassFixedWing; + break; + case MAV_CMD_NAV_VTOL_LAND: + _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassMultiRotor; + break; + case MAV_CMD_DO_VTOL_TRANSITION: { + int transitionState = simpleItem->missionItem().param1(); + if (transitionState == MAV_VTOL_STATE_MC) { + _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassMultiRotor; + } else if (transitionState == MAV_VTOL_STATE_FW) { + _missionFlightStatus.vtolMode = QGCMAVLink::VehicleClassFixedWing; + } + } break; + default: + break; + } + } + } + lastFlyThroughVI->setMissionVehicleYaw(_missionFlightStatus.vehicleYaw); + + // Add the information for the final segment back to home + if (foundRTL && lastFlyThroughVI != _settingsItem && homePositionValid) { + double azimuth, distance, altDifference; + _calcPrevWaypointValues(lastFlyThroughVI, _settingsItem, &azimuth, + &distance, &altDifference); + + // Calculate time/distance + double hoverTime = distance / _missionFlightStatus.hoverSpeed; + double cruiseTime = distance / _missionFlightStatus.cruiseSpeed; + double landTime = + qAbs(altDifference) / + _appSettings->offlineEditingDescentSpeed()->rawValue().toDouble(); + _addTimeDistance(_missionFlightStatus.vtolMode == + QGCMAVLink::VehicleClassMultiRotor, + hoverTime, cruiseTime, distance, landTime, -1); + } + + if (_missionFlightStatus.mAhBattery != 0 && + _missionFlightStatus.batteryChangePoint == -1) { + _missionFlightStatus.batteryChangePoint = 0; + } + + emit missionMaxTelemetryChanged(_missionFlightStatus.maxTelemetryDistance); + emit missionDistanceChanged(_missionFlightStatus.totalDistance); + emit missionHoverDistanceChanged(_missionFlightStatus.hoverDistance); + emit missionCruiseDistanceChanged(_missionFlightStatus.cruiseDistance); + emit missionTimeChanged(); + emit missionHoverTimeChanged(); + emit missionCruiseTimeChanged(); + emit batteryChangePointChanged(_missionFlightStatus.batteryChangePoint); + emit batteriesRequiredChanged(_missionFlightStatus.batteriesRequired); + emit minAMSLAltitudeChanged(_minAMSLAltitude); + emit maxAMSLAltitudeChanged(_maxAMSLAltitude); + + // Walk the list again calculating altitude percentages + double altRange = _maxAMSLAltitude - _minAMSLAltitude; + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(_visualItems->get(i)); + + if (item->specifiesCoordinate()) { + double amslAlt = item->amslEntryAlt(); + if (altRange == 0.0) { + item->setAltPercent(0.0); + item->setTerrainPercent(qQNaN()); + item->setTerrainCollision(false); + } else { + item->setAltPercent((amslAlt - _minAMSLAltitude) / altRange); + double terrainAltitude = item->terrainAltitude(); + if (qIsNaN(terrainAltitude)) { + item->setTerrainPercent(qQNaN()); + item->setTerrainCollision(false); } else { - qWarning() << "ComplexMissionItem not found"; + item->setTerrainPercent((terrainAltitude - _minAMSLAltitude) / + altRange); + item->setTerrainCollision(amslAlt < terrainAltitude); } + } } -} + } -void MissionController::_deinitVisualItem(VisualMissionItem* visualItem) -{ - // Disconnect all signals - disconnect(visualItem, nullptr, nullptr, nullptr); -} + _updateTimer.start(UPDATE_TIMEOUT); -void MissionController::_itemCommandChanged(void) -{ - _recalcChildItems(); - emit _recalcFlightPathSegmentsSignal(); + emit recalcTerrainProfile(); } -void MissionController::_managerVehicleChanged(Vehicle* managerVehicle) -{ - if (_managerVehicle) { - _missionManager->disconnect(this); - _managerVehicle->disconnect(this); - _managerVehicle = nullptr; - _missionManager = nullptr; - } - - _managerVehicle = managerVehicle; - if (!_managerVehicle) { - qWarning() << "MissionController::managerVehicleChanged managerVehicle=NULL"; - return; - } - - _missionManager = _managerVehicle->missionManager(); - connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, &MissionController::_newMissionItemsAvailableFromVehicle); - connect(_missionManager, &MissionManager::sendComplete, this, &MissionController::_managerSendComplete); - connect(_missionManager, &MissionManager::removeAllComplete, this, &MissionController::_managerRemoveAllComplete); - connect(_missionManager, &MissionManager::inProgressChanged, this, &MissionController::_inProgressChanged); - connect(_missionManager, &MissionManager::progressPct, this, &MissionController::_progressPctChanged); - connect(_missionManager, &MissionManager::currentIndexChanged, this, &MissionController::_currentMissionIndexChanged); - connect(_missionManager, &MissionManager::lastCurrentIndexChanged, this, &MissionController::resumeMissionIndexChanged); - connect(_missionManager, &MissionManager::resumeMissionReady, this, &MissionController::resumeMissionReady); - connect(_missionManager, &MissionManager::resumeMissionUploadFail, this, &MissionController::resumeMissionUploadFail); - connect(_managerVehicle, &Vehicle::defaultCruiseSpeedChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(_managerVehicle, &Vehicle::defaultHoverSpeedChanged, this, &MissionController::_recalcMissionFlightStatusSignal, Qt::QueuedConnection); - connect(_managerVehicle, &Vehicle::vehicleTypeChanged, this, &MissionController::complexMissionItemNamesChanged); +// This will update the sequence numbers to be sequential starting from 0 +void MissionController::_recalcSequence(void) { + if (_inRecalcSequence) { + // Don't let this call recurse due to signalling + return; + } - emit complexMissionItemNamesChanged(); - emit resumeMissionIndexChanged(); -} + // Setup ascending sequence numbers for all visual items -void MissionController::_inProgressChanged(bool inProgress) -{ - emit syncInProgressChanged(inProgress); + _inRecalcSequence = true; + int sequenceNumber = 0; + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(_visualItems->get(i)); + item->setSequenceNumber(sequenceNumber); + sequenceNumber = item->lastSequenceNumber() + 1; + } + _inRecalcSequence = false; } -bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude, int* prevAltitudeMode) -{ - bool found = false; - double foundAltitude = 0; - int foundAltitudeMode = QGroundControlQmlGlobal::AltitudeModeNone; +// This will update the child item hierarchy +void MissionController::_recalcChildItems(void) { + VisualMissionItem *currentParentItem = + qobject_cast(_visualItems->get(0)); - if (newIndex > _visualItems->count()) { - return false; - } - newIndex--; + currentParentItem->childItems()->clear(); - for (int i=newIndex; i>0; i--) { - VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i)); + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem *item = _visualItems->value(i); - if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) { - if (visualItem->isSimpleItem()) { - SimpleMissionItem* simpleItem = qobject_cast(visualItem); - if (simpleItem->specifiesAltitude()) { - foundAltitude = simpleItem->altitude()->rawValue().toDouble(); - foundAltitudeMode = simpleItem->altitudeMode(); - found = true; - break; - } - } - } - } + item->setParentItem(nullptr); + item->setHasCurrentChildItem(false); - if (found) { - *prevAltitude = foundAltitude; - *prevAltitudeMode = foundAltitudeMode; + // Set up non-coordinate item child hierarchy + if (item->specifiesCoordinate()) { + item->childItems()->clear(); + currentParentItem = item; + } else if (item->isSimpleItem()) { + item->setParentItem(currentParentItem); + currentParentItem->childItems()->append(item); + if (item->isCurrentItem()) { + currentParentItem->setHasCurrentChildItem(true); + } } - - return found; + } } -double MissionController::_normalizeLat(double lat) -{ - // Normalize latitude to range: 0 to 180, S to N - return lat + 90.0; -} +void MissionController::_setPlannedHomePositionFromFirstCoordinate( + const QGeoCoordinate &clickCoordinate) { + bool foundFirstCoordinate = false; + QGeoCoordinate firstCoordinate; -double MissionController::_normalizeLon(double lon) -{ - // Normalize longitude to range: 0 to 360, W to E - return lon + 180.0; -} + if (_settingsItem->coordinate().isValid()) { + return; + } -/// Add the Mission Settings complex item to the front of the items -MissionSettingsItem* MissionController::_addMissionSettings(QmlObjectListModel* visualItems) -{ - qCDebug(MissionControllerLog) << "_addMissionSettings"; - - MissionSettingsItem* settingsItem = new MissionSettingsItem(_masterController, _flyView, visualItems); - visualItems->insert(0, settingsItem); - - if (visualItems == _visualItems) { - _settingsItem = settingsItem; - } - - return settingsItem; -} - -void MissionController::_centerHomePositionOnMissionItems(QmlObjectListModel *visualItems) -{ - qCDebug(MissionControllerLog) << "_centerHomePositionOnMissionItems"; - - if (visualItems->count() > 1) { - double north = 0.0; - double south = 0.0; - double east = 0.0; - double west = 0.0; - bool firstCoordSet = false; - - for (int i=1; icount(); i++) { - VisualMissionItem* item = qobject_cast(visualItems->get(i)); - if (item->specifiesCoordinate()) { - if (firstCoordSet) { - double lat = _normalizeLat(item->coordinate().latitude()); - double lon = _normalizeLon(item->coordinate().longitude()); - north = fmax(north, lat); - south = fmin(south, lat); - east = fmax(east, lon); - west = fmin(west, lon); - } else { - firstCoordSet = true; - north = _normalizeLat(item->coordinate().latitude()); - south = north; - east = _normalizeLon(item->coordinate().longitude()); - west = east; - } - } - } + // Set the planned home position to be a delta from first coordinate + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem *item = _visualItems->value(i); - if (firstCoordSet) { - _settingsItem->setInitialHomePositionFromUser(QGeoCoordinate((south + ((north - south) / 2)) - 90.0, (west + ((east - west) / 2)) - 180.0, 0.0)); - } + if (item->specifiesCoordinate() && item->coordinate().isValid()) { + foundFirstCoordinate = true; + firstCoordinate = item->coordinate(); + break; } -} - -int MissionController::resumeMissionIndex(void) const -{ + } - int resumeIndex = 0; + // No item specifying a coordinate was found, in this case it we have a + // clickCoordinate use that + if (!foundFirstCoordinate) { + firstCoordinate = clickCoordinate; + } - if (_flyView) { - resumeIndex = _missionManager->lastCurrentIndex() + (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() ? 0 : 1); - if (resumeIndex > 1 && resumeIndex != _visualItems->value(_visualItems->count() - 1)->sequenceNumber()) { - // Resume at the item previous to the item we were heading towards - resumeIndex--; - } else { - resumeIndex = 0; - } - } + if (firstCoordinate.isValid()) { + QGeoCoordinate plannedHomeCoord = + firstCoordinate.atDistanceAndAzimuth(30, 0); + plannedHomeCoord.setAltitude(0); + _settingsItem->setInitialHomePositionFromUser(plannedHomeCoord); + } +} - return resumeIndex; +void MissionController::_recalcAllWithCoordinate( + const QGeoCoordinate &coordinate) { + if (!_flyView) { + _setPlannedHomePositionFromFirstCoordinate(coordinate); + } + _recalcSequence(); + _recalcChildItems(); + emit _recalcFlightPathSegmentsSignal(); + _updateTimer.start(UPDATE_TIMEOUT); } -int MissionController::currentMissionIndex(void) const -{ - if (!_flyView) { - return -1; - } else { - int currentIndex = _missionManager->currentIndex(); - if (!_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle()) { - currentIndex++; - } - return currentIndex; - } +void MissionController::_recalcAll(void) { + QGeoCoordinate emptyCoord; + _recalcAllWithCoordinate(emptyCoord); } -void MissionController::_currentMissionIndexChanged(int sequenceNumber) -{ - if (_flyView) { - if (!_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle()) { - sequenceNumber++; - } +/// Initializes a new set of mission items +void MissionController::_initAllVisualItems(void) { + // Setup home position at index 0 - for (int i=0; i<_visualItems->count(); i++) { - VisualMissionItem* item = qobject_cast(_visualItems->get(i)); - item->setIsCurrentItem(item->sequenceNumber() == sequenceNumber); - } - emit currentMissionIndexChanged(currentMissionIndex()); + if (!_settingsItem) { + _settingsItem = qobject_cast(_visualItems->get(0)); + if (!_settingsItem) { + qWarning() << "First item not MissionSettingsItem"; + return; + } + } + + connect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, + &MissionController::_recalcAll); + connect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, + &MissionController::plannedHomePositionChanged); + + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(_visualItems->get(i)); + _initVisualItem(item); + + TakeoffMissionItem *takeoffItem = qobject_cast(item); + if (takeoffItem) { + _takeoffMissionItem = takeoffItem; + } + } + + _recalcAll(); + + connect(_visualItems, &QmlObjectListModel::dirtyChanged, this, + &MissionController::_visualItemsDirtyChanged); + connect(_visualItems, &QmlObjectListModel::countChanged, this, + &MissionController::_updateContainsItems); + + emit visualItemsChanged(); + emit containsItemsChanged(containsItems()); + emit plannedHomePositionChanged(plannedHomePosition()); + + if (!_flyView) { + setCurrentPlanViewSeqNum(0, true); + } + + setDirty(false); +} + +void MissionController::_deinitAllVisualItems(void) { + disconnect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, + &MissionController::_recalcAll); + disconnect(_settingsItem, &MissionSettingsItem::coordinateChanged, this, + &MissionController::plannedHomePositionChanged); + + for (int i = 0; i < _visualItems->count(); i++) { + _deinitVisualItem(qobject_cast(_visualItems->get(i))); + } + + disconnect(_visualItems, &QmlObjectListModel::dirtyChanged, this, + &MissionController::dirtyChanged); + disconnect(_visualItems, &QmlObjectListModel::countChanged, this, + &MissionController::_updateContainsItems); +} + +void MissionController::_initVisualItem(VisualMissionItem *visualItem) { + setDirty(false); + + connect(visualItem, &VisualMissionItem::specifiesCoordinateChanged, this, + &MissionController::_recalcFlightPathSegmentsSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::specifiedFlightSpeedChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::specifiedGimbalYawChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::specifiedGimbalPitchChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::specifiedVehicleYawChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::terrainAltitudeChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::additionalTimeDelayChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::currentVTOLModeChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(visualItem, &VisualMissionItem::lastSequenceNumberChanged, this, + &MissionController::_recalcSequence); + + if (visualItem->isSimpleItem()) { + // We need to track commandChanged on simple item since recalc has special + // handling for takeoff command + SimpleMissionItem *simpleItem = + qobject_cast(visualItem); + if (simpleItem) { + connect(&simpleItem->missionItem()._commandFact, &Fact::valueChanged, + this, &MissionController::_itemCommandChanged); + } else { + qWarning() << "isSimpleItem == true, yet not SimpleMissionItem"; + } + } else { + ComplexMissionItem *complexItem = + qobject_cast(visualItem); + if (complexItem) { + connect(complexItem, &ComplexMissionItem::complexDistanceChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(complexItem, &ComplexMissionItem::greatestDistanceToChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(complexItem, &ComplexMissionItem::minAMSLAltitudeChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(complexItem, &ComplexMissionItem::maxAMSLAltitudeChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(complexItem, &ComplexMissionItem::isIncompleteChanged, this, + &MissionController::_recalcFlightPathSegmentsSignal, + Qt::QueuedConnection); + } else { + qWarning() << "ComplexMissionItem not found"; } + } } -bool MissionController::syncInProgress(void) const -{ - return _missionManager->inProgress(); +void MissionController::_deinitVisualItem(VisualMissionItem *visualItem) { + // Disconnect all signals + disconnect(visualItem, nullptr, nullptr, nullptr); } -bool MissionController::dirty(void) const -{ - return _visualItems ? _visualItems->dirty() : false; +void MissionController::_itemCommandChanged(void) { + _recalcChildItems(); + emit _recalcFlightPathSegmentsSignal(); } +void MissionController::_managerVehicleChanged(Vehicle *managerVehicle) { + if (_managerVehicle) { + _missionManager->disconnect(this); + _managerVehicle->disconnect(this); + _managerVehicle = nullptr; + _missionManager = nullptr; + } -void MissionController::setDirty(bool dirty) -{ - if (_visualItems) { - _visualItems->setDirty(dirty); - } + _managerVehicle = managerVehicle; + if (!_managerVehicle) { + qWarning() + << "MissionController::managerVehicleChanged managerVehicle=NULL"; + return; + } + + _missionManager = _managerVehicle->missionManager(); + connect(_missionManager, &MissionManager::newMissionItemsAvailable, this, + &MissionController::_newMissionItemsAvailableFromVehicle); + connect(_missionManager, &MissionManager::sendComplete, this, + &MissionController::_managerSendComplete); + connect(_missionManager, &MissionManager::removeAllComplete, this, + &MissionController::_managerRemoveAllComplete); + connect(_missionManager, &MissionManager::inProgressChanged, this, + &MissionController::_inProgressChanged); + connect(_missionManager, &MissionManager::progressPct, this, + &MissionController::_progressPctChanged); + connect(_missionManager, &MissionManager::currentIndexChanged, this, + &MissionController::_currentMissionIndexChanged); + connect(_missionManager, &MissionManager::lastCurrentIndexChanged, this, + &MissionController::resumeMissionIndexChanged); + connect(_missionManager, &MissionManager::resumeMissionReady, this, + &MissionController::resumeMissionReady); + connect(_missionManager, &MissionManager::resumeMissionUploadFail, this, + &MissionController::resumeMissionUploadFail); + connect(_managerVehicle, &Vehicle::defaultCruiseSpeedChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(_managerVehicle, &Vehicle::defaultHoverSpeedChanged, this, + &MissionController::_recalcMissionFlightStatusSignal, + Qt::QueuedConnection); + connect(_managerVehicle, &Vehicle::vehicleTypeChanged, this, + &MissionController::complexMissionItemNamesChanged); + + emit complexMissionItemNamesChanged(); + emit resumeMissionIndexChanged(); +} + +void MissionController::_inProgressChanged(bool inProgress) { + emit syncInProgressChanged(inProgress); +} + +bool MissionController::_findPreviousAltitude(int newIndex, + double *prevAltitude, + int *prevAltitudeMode) { + bool found = false; + double foundAltitude = 0; + int foundAltitudeMode = QGroundControlQmlGlobal::AltitudeModeNone; + + if (newIndex > _visualItems->count()) { + return false; + } + newIndex--; + + for (int i = newIndex; i > 0; i--) { + VisualMissionItem *visualItem = + qobject_cast(_visualItems->get(i)); + + if (visualItem->specifiesCoordinate() && + !visualItem->isStandaloneCoordinate()) { + if (visualItem->isSimpleItem()) { + SimpleMissionItem *simpleItem = + qobject_cast(visualItem); + if (simpleItem->specifiesAltitude()) { + foundAltitude = simpleItem->altitude()->rawValue().toDouble(); + foundAltitudeMode = simpleItem->altitudeMode(); + found = true; + break; + } + } + } + } + + if (found) { + *prevAltitude = foundAltitude; + *prevAltitudeMode = foundAltitudeMode; + } + + return found; +} + +double MissionController::_normalizeLat(double lat) { + // Normalize latitude to range: 0 to 180, S to N + return lat + 90.0; +} + +double MissionController::_normalizeLon(double lon) { + // Normalize longitude to range: 0 to 360, W to E + return lon + 180.0; } -void MissionController::_scanForAdditionalSettings(QmlObjectListModel* visualItems, PlanMasterController* masterController) -{ - // First we look for a Landing Patterns which are at the end - if (!FixedWingLandingComplexItem::scanForItem(visualItems, _flyView, masterController)) { - VTOLLandingComplexItem::scanForItem(visualItems, _flyView, masterController); - } +/// Add the Mission Settings complex item to the front of the items +MissionSettingsItem * +MissionController::_addMissionSettings(QmlObjectListModel *visualItems) { + qCDebug(MissionControllerLog) << "_addMissionSettings"; - int scanIndex = 0; - while (scanIndex < visualItems->count()) { - VisualMissionItem* visualItem = visualItems->value(scanIndex); + MissionSettingsItem *settingsItem = + new MissionSettingsItem(_masterController, _flyView, visualItems); + visualItems->insert(0, settingsItem); - qCDebug(MissionControllerLog) << "MissionController::_scanForAdditionalSettings count:scanIndex" << visualItems->count() << scanIndex; + if (visualItems == _visualItems) { + _settingsItem = settingsItem; + } - if (!_flyView) { - MissionSettingsItem* settingsItem = qobject_cast(visualItem); - if (settingsItem) { - scanIndex++; - settingsItem->scanForMissionSettings(visualItems, scanIndex); - continue; - } - } + return settingsItem; +} - SimpleMissionItem* simpleItem = qobject_cast(visualItem); - if (simpleItem) { - scanIndex++; - simpleItem->scanForSections(visualItems, scanIndex, masterController); +void MissionController::_centerHomePositionOnMissionItems( + QmlObjectListModel *visualItems) { + qCDebug(MissionControllerLog) << "_centerHomePositionOnMissionItems"; + + if (visualItems->count() > 1) { + double north = 0.0; + double south = 0.0; + double east = 0.0; + double west = 0.0; + bool firstCoordSet = false; + + for (int i = 1; i < visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(visualItems->get(i)); + if (item->specifiesCoordinate()) { + if (firstCoordSet) { + double lat = _normalizeLat(item->coordinate().latitude()); + double lon = _normalizeLon(item->coordinate().longitude()); + north = fmax(north, lat); + south = fmin(south, lat); + east = fmax(east, lon); + west = fmin(west, lon); } else { - // Complex item, can't have sections - scanIndex++; + firstCoordSet = true; + north = _normalizeLat(item->coordinate().latitude()); + south = north; + east = _normalizeLon(item->coordinate().longitude()); + west = east; } + } } -} - -void MissionController::_updateContainsItems(void) -{ - emit containsItemsChanged(containsItems()); -} -bool MissionController::containsItems(void) const -{ - return _visualItems ? _visualItems->count() > 1 : false; -} - -void MissionController::removeAllFromVehicle(void) -{ - if (_masterController->offline()) { - qCWarning(MissionControllerLog) << "MissionControllerLog::removeAllFromVehicle called while offline"; - } else if (syncInProgress()) { - qCWarning(MissionControllerLog) << "MissionControllerLog::removeAllFromVehicle called while syncInProgress"; - } else { - _itemsRequested = true; - _missionManager->removeAll(); + if (firstCoordSet) { + _settingsItem->setInitialHomePositionFromUser( + QGeoCoordinate((south + ((north - south) / 2)) - 90.0, + (west + ((east - west) / 2)) - 180.0, 0.0)); } + } } -QStringList MissionController::complexMissionItemNames(void) const -{ - QStringList complexItems; +int MissionController::resumeMissionIndex(void) const { - complexItems.append(SurveyComplexItem::name); - complexItems.append(CorridorScanComplexItem::name); - complexItems.append(CircularSurvey::name); - if (_controllerVehicle->multiRotor() || _controllerVehicle->vtol()) { - complexItems.append(StructureScanComplexItem::name); - } + int resumeIndex = 0; - // Note: The landing pattern items are not added here since they have there own button which adds them + if (_flyView) { + resumeIndex = + _missionManager->lastCurrentIndex() + + (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() ? 0 + : 1); + if (resumeIndex > 1 && + resumeIndex != + _visualItems->value(_visualItems->count() - 1) + ->sequenceNumber()) { + // Resume at the item previous to the item we were heading towards + resumeIndex--; + } else { + resumeIndex = 0; + } + } - return qgcApp()->toolbox()->corePlugin()->complexMissionItemNames(_controllerVehicle, complexItems); + return resumeIndex; } -void MissionController::resumeMission(int resumeIndex) -{ +int MissionController::currentMissionIndex(void) const { + if (!_flyView) { + return -1; + } else { + int currentIndex = _missionManager->currentIndex(); if (!_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle()) { - resumeIndex--; + currentIndex++; } - _missionManager->generateResumeMission(resumeIndex); + return currentIndex; + } } -QGeoCoordinate MissionController::plannedHomePosition(void) const -{ - if (_settingsItem) { - return _settingsItem->coordinate(); - } else { - return QGeoCoordinate(); +void MissionController::_currentMissionIndexChanged(int sequenceNumber) { + if (_flyView) { + if (!_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle()) { + sequenceNumber++; } -} -void MissionController::applyDefaultMissionAltitude(void) -{ - double defaultAltitude = _appSettings->defaultMissionItemAltitude()->rawValue().toDouble(); - - for (int i=1; i<_visualItems->count(); i++) { - VisualMissionItem* item = _visualItems->value(i); - item->applyNewAltitude(defaultAltitude); + for (int i = 0; i < _visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(_visualItems->get(i)); + item->setIsCurrentItem(item->sequenceNumber() == sequenceNumber); } + emit currentMissionIndexChanged(currentMissionIndex()); + } } -void MissionController::_progressPctChanged(double progressPct) -{ - if (!QGC::fuzzyCompare(progressPct, _progressPct)) { - _progressPct = progressPct; - emit progressPctChanged(progressPct); - } +bool MissionController::syncInProgress(void) const { + return _missionManager->inProgress(); } -void MissionController::_visualItemsDirtyChanged(bool dirty) -{ - // We could connect signal to signal and not need this but this is handy for setting a breakpoint on - emit dirtyChanged(dirty); +bool MissionController::dirty(void) const { + return _visualItems ? _visualItems->dirty() : false; } -bool MissionController::showPlanFromManagerVehicle (void) -{ - qCDebug(MissionControllerLog) << "showPlanFromManagerVehicle _flyView" << _flyView; - if (_masterController->offline()) { - qCWarning(MissionControllerLog) << "MissionController::showPlanFromManagerVehicle called while offline"; - return true; // stops further propagation of showPlanFromManagerVehicle due to error - } else { - if (!_managerVehicle->initialPlanRequestComplete()) { - // The vehicle hasn't completed initial load, we can just wait for newMissionItemsAvailable to be signalled automatically - qCDebug(MissionControllerLog) << "showPlanFromManagerVehicle: !initialPlanRequestComplete, wait for signal"; - return true; - } else if (syncInProgress()) { - // If the sync is already in progress, newMissionItemsAvailable will be signalled automatically when it is done. So no need to do anything. - qCDebug(MissionControllerLog) << "showPlanFromManagerVehicle: syncInProgress wait for signal"; - return true; - } else { - // Fake a _newMissionItemsAvailable with the current items - qCDebug(MissionControllerLog) << "showPlanFromManagerVehicle: sync complete simulate signal"; - _itemsRequested = true; - _newMissionItemsAvailableFromVehicle(false /* removeAllRequested */); - return false; - } - } +void MissionController::setDirty(bool dirty) { + if (_visualItems) { + _visualItems->setDirty(dirty); + } } -void MissionController::_managerSendComplete(bool error) -{ - // Fly view always reloads on send complete - if (!error && _flyView) { - showPlanFromManagerVehicle(); - } -} +void MissionController::_scanForAdditionalSettings( + QmlObjectListModel *visualItems, PlanMasterController *masterController) { + // First we look for a Landing Patterns which are at the end + if (!FixedWingLandingComplexItem::scanForItem(visualItems, _flyView, + masterController)) { + VTOLLandingComplexItem::scanForItem(visualItems, _flyView, + masterController); + } + + int scanIndex = 0; + while (scanIndex < visualItems->count()) { + VisualMissionItem *visualItem = + visualItems->value(scanIndex); + + qCDebug(MissionControllerLog) + << "MissionController::_scanForAdditionalSettings count:scanIndex" + << visualItems->count() << scanIndex; -void MissionController::_managerRemoveAllComplete(bool error) -{ - if (!error) { - // Remove all from vehicle so we always update - showPlanFromManagerVehicle(); + if (!_flyView) { + MissionSettingsItem *settingsItem = + qobject_cast(visualItem); + if (settingsItem) { + scanIndex++; + settingsItem->scanForMissionSettings(visualItems, scanIndex); + continue; + } + } + + SimpleMissionItem *simpleItem = + qobject_cast(visualItem); + if (simpleItem) { + scanIndex++; + simpleItem->scanForSections(visualItems, scanIndex, masterController); + } else { + // Complex item, can't have sections + scanIndex++; } + } } -bool MissionController::_isROIBeginItem(SimpleMissionItem* simpleItem) -{ - return simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI_LOCATION || - simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI_WPNEXT_OFFSET || - (simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI && - static_cast(simpleItem->missionItem().param1()) == MAV_ROI_LOCATION); +void MissionController::_updateContainsItems(void) { + emit containsItemsChanged(containsItems()); } -bool MissionController::_isROICancelItem(SimpleMissionItem* simpleItem) -{ - return simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI_NONE || - (simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI && - static_cast(simpleItem->missionItem().param1()) == MAV_ROI_NONE); +bool MissionController::containsItems(void) const { + return _visualItems ? _visualItems->count() > 1 : false; } -void MissionController::setCurrentPlanViewSeqNum(int sequenceNumber, bool force) -{ - if (_visualItems && (force || sequenceNumber != _currentPlanViewSeqNum)) { - bool foundLand = false; - int takeoffSeqNum = -1; - int landSeqNum = -1; - int lastFlyThroughSeqNum = -1; +void MissionController::removeAllFromVehicle(void) { + if (_masterController->offline()) { + qCWarning(MissionControllerLog) + << "MissionControllerLog::removeAllFromVehicle called while offline"; + } else if (syncInProgress()) { + qCWarning(MissionControllerLog) + << "MissionControllerLog::removeAllFromVehicle called while " + "syncInProgress"; + } else { + _itemsRequested = true; + _missionManager->removeAll(); + } +} - _splitSegment = nullptr; - _currentPlanViewItem = nullptr; - _currentPlanViewSeqNum = -1; - _currentPlanViewVIIndex = -1; - _onlyInsertTakeoffValid = !_planViewSettings->takeoffItemNotRequired()->rawValue().toBool() && _visualItems->count() == 1; // First item must be takeoff - _isInsertTakeoffValid = true; - _isInsertLandValid = true; - _isROIActive = false; - _isROIBeginCurrentItem = false; - _flyThroughCommandsAllowed = true; - _previousCoordinate = QGeoCoordinate(); +QStringList MissionController::complexMissionItemNames(void) const { + QStringList complexItems; - for (int viIndex=0; viIndex<_visualItems->count(); viIndex++) { - VisualMissionItem* pVI = qobject_cast(_visualItems->get(viIndex)); - SimpleMissionItem* simpleItem = qobject_cast(pVI); - int currentSeqNumber = pVI->sequenceNumber(); + complexItems.append(SurveyComplexItem::name); + complexItems.append(CorridorScanComplexItem::name); + complexItems.append(RouteComplexItem::name); + if (_controllerVehicle->multiRotor() || _controllerVehicle->vtol()) { + complexItems.append(StructureScanComplexItem::name); + } - if (sequenceNumber != 0 && currentSeqNumber <= sequenceNumber) { - if (pVI->specifiesCoordinate() && !pVI->isStandaloneCoordinate()) { - // Coordinate based flight commands prior to where the takeoff would be inserted - _isInsertTakeoffValid = false; - } - } + // Note: The landing pattern items are not added here since they have there + // own button which adds them - if (qobject_cast(pVI)) { - takeoffSeqNum = currentSeqNumber; - _isInsertTakeoffValid = false; - } + return qgcApp()->toolbox()->corePlugin()->complexMissionItemNames( + _controllerVehicle, complexItems); +} - if (!foundLand) { - if (simpleItem) { - switch (simpleItem->mavCommand()) { - case MAV_CMD_NAV_LAND: - case MAV_CMD_NAV_VTOL_LAND: - case MAV_CMD_DO_LAND_START: - case MAV_CMD_NAV_RETURN_TO_LAUNCH: - foundLand = true; - landSeqNum = currentSeqNumber; - break; - default: - break; - } - } else { - FixedWingLandingComplexItem* fwLanding = qobject_cast(pVI); - if (fwLanding) { - foundLand = true; - landSeqNum = currentSeqNumber; - } - } - } +void MissionController::resumeMission(int resumeIndex) { + if (!_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle()) { + resumeIndex--; + } + _missionManager->generateResumeMission(resumeIndex); +} - if (simpleItem) { - // Remember previous coordinate - if (currentSeqNumber < sequenceNumber && simpleItem->specifiesCoordinate() && !simpleItem->isStandaloneCoordinate()) { - _previousCoordinate = simpleItem->coordinate(); - } +QGeoCoordinate MissionController::plannedHomePosition(void) const { + if (_settingsItem) { + return _settingsItem->coordinate(); + } else { + return QGeoCoordinate(); + } +} - // ROI state handling - if (currentSeqNumber <= sequenceNumber) { - if (_isROIActive) { - if (_isROICancelItem(simpleItem)) { - _isROIActive = false; - } - } else { - if (_isROIBeginItem(simpleItem)) { - _isROIActive = true; - } - } - } - if (currentSeqNumber == sequenceNumber && _isROIBeginItem(simpleItem)) { - _isROIBeginCurrentItem = true; - } - } +void MissionController::applyDefaultMissionAltitude(void) { + double defaultAltitude = + _appSettings->defaultMissionItemAltitude()->rawValue().toDouble(); - if (viIndex != 0) { - // Complex items are assumed to be fly through - if (!simpleItem || (simpleItem->specifiesCoordinate() && !simpleItem->isStandaloneCoordinate())) { - lastFlyThroughSeqNum = currentSeqNumber; - } - } + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem *item = _visualItems->value(i); + item->applyNewAltitude(defaultAltitude); + } +} - if (currentSeqNumber == sequenceNumber) { - pVI->setIsCurrentItem(true); - pVI->setHasCurrentChildItem(false); - - _currentPlanViewItem = pVI; - _currentPlanViewSeqNum = sequenceNumber; - _currentPlanViewVIIndex = viIndex; - - if (pVI->specifiesCoordinate()) { - if (!pVI->isStandaloneCoordinate()) { - // Determine split segment used to display line split editing ui. - for (int j=viIndex-1; j>0; j--) { - VisualMissionItem* pPrev = qobject_cast(_visualItems->get(j)); - if (pPrev->specifiesCoordinate() && !pPrev->isStandaloneCoordinate()) { - VisualItemPair splitPair(pPrev, pVI); - if (_flightPathSegmentHashTable.contains(splitPair)) { - _splitSegment = _flightPathSegmentHashTable[splitPair]; - } - } - } - } - } else if (pVI->parentItem()) { - pVI->parentItem()->setHasCurrentChildItem(true); - } - } else { - pVI->setIsCurrentItem(false); - } - } +void MissionController::_progressPctChanged(double progressPct) { + if (!QGC::fuzzyCompare(progressPct, _progressPct)) { + _progressPct = progressPct; + emit progressPctChanged(progressPct); + } +} - if (takeoffSeqNum != -1) { - // Takeoff item was found which means mission starts from ground - if (sequenceNumber < takeoffSeqNum) { - // Land is only valid after the takeoff item. - _isInsertLandValid = false; - // Fly through commands are not allowed prior to the takeoff command - _flyThroughCommandsAllowed = false; - } - } +void MissionController::_visualItemsDirtyChanged(bool dirty) { + // We could connect signal to signal and not need this but this is handy for + // setting a breakpoint on + emit dirtyChanged(dirty); +} - if (lastFlyThroughSeqNum != -1) { - // Land item must be after any fly through coordinates - if (sequenceNumber < lastFlyThroughSeqNum) { - _isInsertLandValid = false; +bool MissionController::showPlanFromManagerVehicle(void) { + qCDebug(MissionControllerLog) + << "showPlanFromManagerVehicle _flyView" << _flyView; + if (_masterController->offline()) { + qCWarning(MissionControllerLog) + << "MissionController::showPlanFromManagerVehicle called while offline"; + return true; // stops further propagation of showPlanFromManagerVehicle due + // to error + } else { + if (!_managerVehicle->initialPlanRequestComplete()) { + // The vehicle hasn't completed initial load, we can just wait for + // newMissionItemsAvailable to be signalled automatically + qCDebug(MissionControllerLog) + << "showPlanFromManagerVehicle: !initialPlanRequestComplete, wait " + "for signal"; + return true; + } else if (syncInProgress()) { + // If the sync is already in progress, newMissionItemsAvailable will be + // signalled automatically when it is done. So no need to do anything. + qCDebug(MissionControllerLog) + << "showPlanFromManagerVehicle: syncInProgress wait for signal"; + return true; + } else { + // Fake a _newMissionItemsAvailable with the current items + qCDebug(MissionControllerLog) + << "showPlanFromManagerVehicle: sync complete simulate signal"; + _itemsRequested = true; + _newMissionItemsAvailableFromVehicle(false /* removeAllRequested */); + return false; + } + } +} + +void MissionController::_managerSendComplete(bool error) { + // Fly view always reloads on send complete + if (!error && _flyView) { + showPlanFromManagerVehicle(); + } +} + +void MissionController::_managerRemoveAllComplete(bool error) { + if (!error) { + // Remove all from vehicle so we always update + showPlanFromManagerVehicle(); + } +} + +bool MissionController::_isROIBeginItem(SimpleMissionItem *simpleItem) { + return simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI_LOCATION || + simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI_WPNEXT_OFFSET || + (simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI && + static_cast(simpleItem->missionItem().param1()) == + MAV_ROI_LOCATION); +} + +bool MissionController::_isROICancelItem(SimpleMissionItem *simpleItem) { + return simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI_NONE || + (simpleItem->mavCommand() == MAV_CMD_DO_SET_ROI && + static_cast(simpleItem->missionItem().param1()) == MAV_ROI_NONE); +} + +void MissionController::setCurrentPlanViewSeqNum(int sequenceNumber, + bool force) { + if (_visualItems && (force || sequenceNumber != _currentPlanViewSeqNum)) { + bool foundLand = false; + int takeoffSeqNum = -1; + int landSeqNum = -1; + int lastFlyThroughSeqNum = -1; + + _splitSegment = nullptr; + _currentPlanViewItem = nullptr; + _currentPlanViewSeqNum = -1; + _currentPlanViewVIIndex = -1; + _onlyInsertTakeoffValid = + !_planViewSettings->takeoffItemNotRequired()->rawValue().toBool() && + _visualItems->count() == 1; // First item must be takeoff + _isInsertTakeoffValid = true; + _isInsertLandValid = true; + _isROIActive = false; + _isROIBeginCurrentItem = false; + _flyThroughCommandsAllowed = true; + _previousCoordinate = QGeoCoordinate(); + + for (int viIndex = 0; viIndex < _visualItems->count(); viIndex++) { + VisualMissionItem *pVI = + qobject_cast(_visualItems->get(viIndex)); + SimpleMissionItem *simpleItem = qobject_cast(pVI); + int currentSeqNumber = pVI->sequenceNumber(); + + if (sequenceNumber != 0 && currentSeqNumber <= sequenceNumber) { + if (pVI->specifiesCoordinate() && !pVI->isStandaloneCoordinate()) { + // Coordinate based flight commands prior to where the takeoff would + // be inserted + _isInsertTakeoffValid = false; + } + } + + if (qobject_cast(pVI)) { + takeoffSeqNum = currentSeqNumber; + _isInsertTakeoffValid = false; + } + + if (!foundLand) { + if (simpleItem) { + switch (simpleItem->mavCommand()) { + case MAV_CMD_NAV_LAND: + case MAV_CMD_NAV_VTOL_LAND: + case MAV_CMD_DO_LAND_START: + case MAV_CMD_NAV_RETURN_TO_LAUNCH: + foundLand = true; + landSeqNum = currentSeqNumber; + break; + default: + break; + } + } else { + FixedWingLandingComplexItem *fwLanding = + qobject_cast(pVI); + if (fwLanding) { + foundLand = true; + landSeqNum = currentSeqNumber; + } + } + } + + if (simpleItem) { + // Remember previous coordinate + if (currentSeqNumber < sequenceNumber && + simpleItem->specifiesCoordinate() && + !simpleItem->isStandaloneCoordinate()) { + _previousCoordinate = simpleItem->coordinate(); + } + + // ROI state handling + if (currentSeqNumber <= sequenceNumber) { + if (_isROIActive) { + if (_isROICancelItem(simpleItem)) { + _isROIActive = false; } - } - - if (foundLand) { - // Can't have more than one land sequence - _isInsertLandValid = false; - if (sequenceNumber >= landSeqNum) { - // Can't have fly through commands after a land item - _flyThroughCommandsAllowed = false; + } else { + if (_isROIBeginItem(simpleItem)) { + _isROIActive = true; } - } - - // These are not valid when only takeoff is allowed - _isInsertLandValid = _isInsertLandValid && !_onlyInsertTakeoffValid; - _flyThroughCommandsAllowed = _flyThroughCommandsAllowed && !_onlyInsertTakeoffValid; - - emit currentPlanViewSeqNumChanged(); - emit currentPlanViewVIIndexChanged(); - emit currentPlanViewItemChanged(); - emit splitSegmentChanged(); - emit onlyInsertTakeoffValidChanged(); - emit isInsertTakeoffValidChanged(); - emit isInsertLandValidChanged(); - emit isROIActiveChanged(); - emit isROIBeginCurrentItemChanged(); - emit flyThroughCommandsAllowedChanged(); - emit previousCoordinateChanged(); - } -} - -void MissionController::_updateTimeout() -{ - QGeoCoordinate firstCoordinate; - QGeoCoordinate takeoffCoordinate; - QGCGeoBoundingCube boundingCube; - double north = 0.0; - double south = 180.0; - double east = 0.0; - double west = 360.0; - double minAlt = QGCGeoBoundingCube::MaxAlt; - double maxAlt = QGCGeoBoundingCube::MinAlt; - for (int i = 1; i < _visualItems->count(); i++) { - VisualMissionItem* item = qobject_cast(_visualItems->get(i)); - if(item->isSimpleItem()) { - SimpleMissionItem* pSimpleItem = qobject_cast(item); - if(pSimpleItem) { - switch(pSimpleItem->command()) { - case MAV_CMD_NAV_TAKEOFF: - case MAV_CMD_NAV_WAYPOINT: - case MAV_CMD_NAV_LAND: - if(pSimpleItem->coordinate().isValid()) { - if((MAV_CMD)pSimpleItem->command() == MAV_CMD_NAV_TAKEOFF) { - takeoffCoordinate = pSimpleItem->coordinate(); - } else if(!firstCoordinate.isValid()) { - firstCoordinate = pSimpleItem->coordinate(); - } - double lat = pSimpleItem->coordinate().latitude() + 90.0; - double lon = pSimpleItem->coordinate().longitude() + 180.0; - double alt = pSimpleItem->coordinate().altitude(); - north = fmax(north, lat); - south = fmin(south, lat); - east = fmax(east, lon); - west = fmin(west, lon); - minAlt = fmin(minAlt, alt); - maxAlt = fmax(maxAlt, alt); - } - break; - default: - break; + } + } + if (currentSeqNumber == sequenceNumber && _isROIBeginItem(simpleItem)) { + _isROIBeginCurrentItem = true; + } + } + + if (viIndex != 0) { + // Complex items are assumed to be fly through + if (!simpleItem || (simpleItem->specifiesCoordinate() && + !simpleItem->isStandaloneCoordinate())) { + lastFlyThroughSeqNum = currentSeqNumber; + } + } + + if (currentSeqNumber == sequenceNumber) { + pVI->setIsCurrentItem(true); + pVI->setHasCurrentChildItem(false); + + _currentPlanViewItem = pVI; + _currentPlanViewSeqNum = sequenceNumber; + _currentPlanViewVIIndex = viIndex; + + if (pVI->specifiesCoordinate()) { + if (!pVI->isStandaloneCoordinate()) { + // Determine split segment used to display line split editing ui. + for (int j = viIndex - 1; j > 0; j--) { + VisualMissionItem *pPrev = + qobject_cast(_visualItems->get(j)); + if (pPrev->specifiesCoordinate() && + !pPrev->isStandaloneCoordinate()) { + VisualItemPair splitPair(pPrev, pVI); + if (_flightPathSegmentHashTable.contains(splitPair)) { + _splitSegment = _flightPathSegmentHashTable[splitPair]; } + } } - } else { - ComplexMissionItem* pComplexItem = qobject_cast(item); - if(pComplexItem) { - QGCGeoBoundingCube bc = pComplexItem->boundingCube(); - if(bc.isValid()) { - if(!firstCoordinate.isValid() && pComplexItem->coordinate().isValid()) { - firstCoordinate = pComplexItem->coordinate(); - } - north = fmax(north, bc.pointNW.latitude() + 90.0); - south = fmin(south, bc.pointSE.latitude() + 90.0); - east = fmax(east, bc.pointNW.longitude() + 180.0); - west = fmin(west, bc.pointSE.longitude() + 180.0); - minAlt = fmin(minAlt, bc.pointNW.altitude()); - maxAlt = fmax(maxAlt, bc.pointSE.altitude()); - } + } + } else if (pVI->parentItem()) { + pVI->parentItem()->setHasCurrentChildItem(true); + } + } else { + pVI->setIsCurrentItem(false); + } + } + + if (takeoffSeqNum != -1) { + // Takeoff item was found which means mission starts from ground + if (sequenceNumber < takeoffSeqNum) { + // Land is only valid after the takeoff item. + _isInsertLandValid = false; + // Fly through commands are not allowed prior to the takeoff command + _flyThroughCommandsAllowed = false; + } + } + + if (lastFlyThroughSeqNum != -1) { + // Land item must be after any fly through coordinates + if (sequenceNumber < lastFlyThroughSeqNum) { + _isInsertLandValid = false; + } + } + + if (foundLand) { + // Can't have more than one land sequence + _isInsertLandValid = false; + if (sequenceNumber >= landSeqNum) { + // Can't have fly through commands after a land item + _flyThroughCommandsAllowed = false; + } + } + + // These are not valid when only takeoff is allowed + _isInsertLandValid = _isInsertLandValid && !_onlyInsertTakeoffValid; + _flyThroughCommandsAllowed = + _flyThroughCommandsAllowed && !_onlyInsertTakeoffValid; + + emit currentPlanViewSeqNumChanged(); + emit currentPlanViewVIIndexChanged(); + emit currentPlanViewItemChanged(); + emit splitSegmentChanged(); + emit onlyInsertTakeoffValidChanged(); + emit isInsertTakeoffValidChanged(); + emit isInsertLandValidChanged(); + emit isROIActiveChanged(); + emit isROIBeginCurrentItemChanged(); + emit flyThroughCommandsAllowedChanged(); + emit previousCoordinateChanged(); + } +} + +void MissionController::_updateTimeout() { + QGeoCoordinate firstCoordinate; + QGeoCoordinate takeoffCoordinate; + QGCGeoBoundingCube boundingCube; + double north = 0.0; + double south = 180.0; + double east = 0.0; + double west = 360.0; + double minAlt = QGCGeoBoundingCube::MaxAlt; + double maxAlt = QGCGeoBoundingCube::MinAlt; + for (int i = 1; i < _visualItems->count(); i++) { + VisualMissionItem *item = + qobject_cast(_visualItems->get(i)); + if (item->isSimpleItem()) { + SimpleMissionItem *pSimpleItem = qobject_cast(item); + if (pSimpleItem) { + switch (pSimpleItem->command()) { + case MAV_CMD_NAV_TAKEOFF: + case MAV_CMD_NAV_WAYPOINT: + case MAV_CMD_NAV_LAND: + if (pSimpleItem->coordinate().isValid()) { + if ((MAV_CMD)pSimpleItem->command() == MAV_CMD_NAV_TAKEOFF) { + takeoffCoordinate = pSimpleItem->coordinate(); + } else if (!firstCoordinate.isValid()) { + firstCoordinate = pSimpleItem->coordinate(); } - } - } - //-- Figure out where this thing is taking off from - if(!takeoffCoordinate.isValid()) { - if(firstCoordinate.isValid()) { - takeoffCoordinate = firstCoordinate; - } else { - takeoffCoordinate = plannedHomePosition(); - } - } - //-- Build bounding "cube" - boundingCube = QGCGeoBoundingCube( - QGeoCoordinate(north - 90.0, west - 180.0, minAlt), - QGeoCoordinate(south - 90.0, east - 180.0, maxAlt)); - if(_travelBoundingCube != boundingCube || _takeoffCoordinate != takeoffCoordinate) { - _takeoffCoordinate = takeoffCoordinate; - _travelBoundingCube = boundingCube; - emit missionBoundingCubeChanged(); - qCDebug(MissionControllerLog) << "Bounding cube:" << _travelBoundingCube.pointNW << _travelBoundingCube.pointSE; + double lat = pSimpleItem->coordinate().latitude() + 90.0; + double lon = pSimpleItem->coordinate().longitude() + 180.0; + double alt = pSimpleItem->coordinate().altitude(); + north = fmax(north, lat); + south = fmin(south, lat); + east = fmax(east, lon); + west = fmin(west, lon); + minAlt = fmin(minAlt, alt); + maxAlt = fmax(maxAlt, alt); + } + break; + default: + break; + } + } + } else { + ComplexMissionItem *pComplexItem = + qobject_cast(item); + if (pComplexItem) { + QGCGeoBoundingCube bc = pComplexItem->boundingCube(); + if (bc.isValid()) { + if (!firstCoordinate.isValid() && + pComplexItem->coordinate().isValid()) { + firstCoordinate = pComplexItem->coordinate(); + } + north = fmax(north, bc.pointNW.latitude() + 90.0); + south = fmin(south, bc.pointSE.latitude() + 90.0); + east = fmax(east, bc.pointNW.longitude() + 180.0); + west = fmin(west, bc.pointSE.longitude() + 180.0); + minAlt = fmin(minAlt, bc.pointNW.altitude()); + maxAlt = fmax(maxAlt, bc.pointSE.altitude()); + } + } + } + } + //-- Figure out where this thing is taking off from + if (!takeoffCoordinate.isValid()) { + if (firstCoordinate.isValid()) { + takeoffCoordinate = firstCoordinate; + } else { + takeoffCoordinate = plannedHomePosition(); } + } + //-- Build bounding "cube" + boundingCube = + QGCGeoBoundingCube(QGeoCoordinate(north - 90.0, west - 180.0, minAlt), + QGeoCoordinate(south - 90.0, east - 180.0, maxAlt)); + if (_travelBoundingCube != boundingCube || + _takeoffCoordinate != takeoffCoordinate) { + _takeoffCoordinate = takeoffCoordinate; + _travelBoundingCube = boundingCube; + emit missionBoundingCubeChanged(); + qCDebug(MissionControllerLog) + << "Bounding cube:" << _travelBoundingCube.pointNW + << _travelBoundingCube.pointSE; + } } -void MissionController::_complexBoundingBoxChanged() -{ - _updateTimer.start(UPDATE_TIMEOUT); +void MissionController::_complexBoundingBoxChanged() { + _updateTimer.start(UPDATE_TIMEOUT); } -bool MissionController::isEmpty(void) const -{ - return _visualItems->count() <= 1; +bool MissionController::isEmpty(void) const { + return _visualItems->count() <= 1; } -void MissionController::_takeoffItemNotRequiredChanged(void) -{ - // Force a recalc of allowed bits - setCurrentPlanViewSeqNum(_currentPlanViewSeqNum, true /* force */); +void MissionController::_takeoffItemNotRequiredChanged(void) { + // Force a recalc of allowed bits + setCurrentPlanViewSeqNum(_currentPlanViewSeqNum, true /* force */); } -QString MissionController::surveyComplexItemName(void) const -{ - return SurveyComplexItem::name; +QString MissionController::surveyComplexItemName(void) const { + return SurveyComplexItem::name; } -QString MissionController::corridorScanComplexItemName(void) const -{ - return CorridorScanComplexItem::name; +QString MissionController::corridorScanComplexItemName(void) const { + return CorridorScanComplexItem::name; } -QString MissionController::structureScanComplexItemName(void) const -{ - return StructureScanComplexItem::name; +QString MissionController::structureScanComplexItemName(void) const { + return StructureScanComplexItem::name; } -void MissionController::_allItemsRemoved(void) -{ - // When there are no mission items we track changes to firmware/vehicle type. This allows a vehicle connection - // to adjust these items. - _controllerVehicle->trackFirmwareVehicleTypeChanges(); +void MissionController::_allItemsRemoved(void) { + // When there are no mission items we track changes to firmware/vehicle type. + // This allows a vehicle connection to adjust these items. + _controllerVehicle->trackFirmwareVehicleTypeChanges(); } -void MissionController::_firstItemAdded(void) -{ - // As soon as the first item is added we lock the firmware/vehicle type to current values. So if you then connect a vehicle - // it will not affect these values. - _controllerVehicle->stopTrackingFirmwareVehicleTypeChanges(); +void MissionController::_firstItemAdded(void) { + // As soon as the first item is added we lock the firmware/vehicle type to + // current values. So if you then connect a vehicle it will not affect these + // values. + _controllerVehicle->stopTrackingFirmwareVehicleTypeChanges(); } -MissionController::SendToVehiclePreCheckState MissionController::sendToVehiclePreCheck(void) -{ - if (_managerVehicle->isOfflineEditingVehicle()) { - return SendToVehiclePreCheckStateNoActiveVehicle; - } - if (_managerVehicle->armed() && _managerVehicle->flightMode() == _managerVehicle->missionFlightMode()) { - return SendToVehiclePreCheckStateActiveMission; - } - if (_controllerVehicle->firmwareType() != _managerVehicle->firmwareType() || QGCMAVLink::vehicleClass(_controllerVehicle->vehicleType()) != QGCMAVLink::vehicleClass(_managerVehicle->vehicleType())) { - return SendToVehiclePreCheckStateFirwmareVehicleMismatch; - } - return SendToVehiclePreCheckStateOk; +MissionController::SendToVehiclePreCheckState +MissionController::sendToVehiclePreCheck(void) { + if (_managerVehicle->isOfflineEditingVehicle()) { + return SendToVehiclePreCheckStateNoActiveVehicle; + } + if (_managerVehicle->armed() && + _managerVehicle->flightMode() == _managerVehicle->missionFlightMode()) { + return SendToVehiclePreCheckStateActiveMission; + } + if (_controllerVehicle->firmwareType() != _managerVehicle->firmwareType() || + QGCMAVLink::vehicleClass(_controllerVehicle->vehicleType()) != + QGCMAVLink::vehicleClass(_managerVehicle->vehicleType())) { + return SendToVehiclePreCheckStateFirwmareVehicleMismatch; + } + return SendToVehiclePreCheckStateOk; } -QGroundControlQmlGlobal::AltitudeMode MissionController::globalAltitudeMode(void) -{ - return _globalAltMode; +QGroundControlQmlGlobal::AltitudeMode +MissionController::globalAltitudeMode(void) { + return _globalAltMode; } -QGroundControlQmlGlobal::AltitudeMode MissionController::globalAltitudeModeDefault(void) -{ - if (_globalAltMode == QGroundControlQmlGlobal::AltitudeModeNone) { - return QGroundControlQmlGlobal::AltitudeModeRelative; - } else { - return _globalAltMode; - } +QGroundControlQmlGlobal::AltitudeMode +MissionController::globalAltitudeModeDefault(void) { + if (_globalAltMode == QGroundControlQmlGlobal::AltitudeModeNone) { + return QGroundControlQmlGlobal::AltitudeModeRelative; + } else { + return _globalAltMode; + } } -void MissionController::setGlobalAltitudeMode(QGroundControlQmlGlobal::AltitudeMode altMode) -{ - if (_globalAltMode != altMode) { - _globalAltMode = altMode; - emit globalAltitudeModeChanged(); - } +void MissionController::setGlobalAltitudeMode( + QGroundControlQmlGlobal::AltitudeMode altMode) { + if (_globalAltMode != altMode) { + _globalAltMode = altMode; + emit globalAltitudeModeChanged(); + } } diff --git a/src/MissionManager/MissionItem.h b/src/MissionManager/MissionItem.h index 41302ae0cbbfec20e1f3482ebb2d02be22ca61f6..72519e3f447f62e109bb96a9bba68f17e0d09cf2 100644 --- a/src/MissionManager/MissionItem.h +++ b/src/MissionManager/MissionItem.h @@ -26,7 +26,7 @@ #include "QmlObjectListModel.h" class SurveyComplexItem; -class CircularSurvey; +class RouteComplexItem; class SimpleMissionItem; class MissionController; #ifdef UNITTEST_BUILD @@ -153,7 +153,7 @@ private: static const char* _jsonParam4Key; friend class SurveyComplexItem; - friend class CircularSurvey; + friend class RouteComplexItem; friend class SimpleMissionItem; friend class MissionController; #ifdef UNITTEST_BUILD diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index b084c5bcdc4c16881dabf83c9b5741bedca7e2ae..3eb73244a4245f0bb21785cbd6993d19a240040f 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -7,7 +7,6 @@ * ****************************************************************************/ - /** * @file * @brief Implementation of class QGCApplication @@ -16,19 +15,19 @@ * */ +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include #include #include +#include +#include +#include +#include #ifdef QGC_ENABLE_BLUETOOTH #include @@ -40,78 +39,79 @@ #include "GStreamer.h" #endif -#include "QGC.h" -#include "QGCApplication.h" +#include "AutoPilotPlugin.h" #include "CmdLineOptParser.h" -#include "UDPLink.h" -#include "LinkManager.h" -#include "UASMessageHandler.h" -#include "QGCTemporaryFile.h" -#include "QGCPalette.h" -#include "QGCMapPalette.h" -#include "QGCLoggingCategory.h" -#include "ParameterEditorController.h" #include "ESP8266ComponentController.h" -#include "ScreenToolsController.h" -#include "QGCFileDialogController.h" -#include "RCChannelMonitorController.h" -#include "SyslinkComponentController.h" -#include "AutoPilotPlugin.h" -#include "VehicleComponent.h" #include "FirmwarePluginManager.h" -#include "MultiVehicleManager.h" -#include "Vehicle.h" +#include "FlightMapSettings.h" +#include "FlightPathSegment.h" #include "JoystickConfigController.h" #include "JoystickManager.h" -#include "QmlObjectListModel.h" -#include "QGCGeoBoundingCube.h" +#include "LinkManager.h" +#include "LogDownloadController.h" #include "MissionManager.h" -#include "QGroundControlQmlGlobal.h" -#include "FlightMapSettings.h" -#include "FlightPathSegment.h" +#include "MultiVehicleManager.h" +#include "ParameterEditorController.h" #include "PlanMasterController.h" +#include "QGC.h" +#include "QGCApplication.h" +#include "QGCFileDialogController.h" +#include "QGCGeoBoundingCube.h" +#include "QGCLoggingCategory.h" +#include "QGCMapPalette.h" +#include "QGCPalette.h" +#include "QGCTemporaryFile.h" +#include "QGroundControlQmlGlobal.h" +#include "QmlObjectListModel.h" +#include "RCChannelMonitorController.h" +#include "ScreenToolsController.h" +#include "SyslinkComponentController.h" +#include "UASMessageHandler.h" +#include "UDPLink.h" +#include "Vehicle.h" +#include "VehicleComponent.h" #include "VideoManager.h" #include "VideoReceiver.h" -#include "LogDownloadController.h" #if defined(QGC_ENABLE_MAVLINK_INSPECTOR) #include "MAVLinkInspectorController.h" #endif -#include "HorizontalFactValueGrid.h" -#include "InstrumentValueData.h" #include "AppMessages.h" -#include "SimulatedPosition.h" -#include "PositionManager.h" -#include "FollowMe.h" -#include "MissionCommandTree.h" -#include "QGCMapPolygon.h" -#include "QGCMapCircle.h" -#include "ParameterManager.h" -#include "SettingsManager.h" -#include "QGCCorePlugin.h" -#include "QGCCameraManager.h" #include "CameraCalc.h" -#include "VisualMissionItem.h" #include "EditPositionDialogController.h" #include "FactValueSliderListModel.h" -#include "ShapeFileHelper.h" -#include "QGCFileDownload.h" #include "FirmwareImage.h" -#include "MavlinkConsoleController.h" +#include "FollowMe.h" #include "GeoTagController.h" +#include "HorizontalFactValueGrid.h" +#include "InstrumentValueData.h" #include "LogReplayLink.h" -#include "VehicleObjectAvoidance.h" -#include "TrajectoryPoints.h" -#include "RCToParamDialogController.h" +#include "MavlinkConsoleController.h" +#include "MissionCommandTree.h" +#include "ParameterManager.h" +#include "PositionManager.h" +#include "QGCCameraManager.h" +#include "QGCCorePlugin.h" +#include "QGCFileDownload.h" #include "QGCImageProvider.h" +#include "QGCMAVLink.h" +#include "QGCMapCircle.h" +#include "QGCMapPolygon.h" +#include "RCToParamDialogController.h" +#include "SettingsManager.h" +#include "ShapeFileHelper.h" +#include "SimulatedPosition.h" #include "TerrainProfile.h" #include "ToolStripAction.h" #include "ToolStripActionList.h" -#include "QGCMAVLink.h" +#include "TrajectoryPoints.h" #include "VehicleLinkManager.h" +#include "VehicleObjectAvoidance.h" +#include "VisualMissionItem.h" -#include "Wima/Snake/CircularGenerator.h" -#include "Wima/Snake/LinearGenerator.h" -#include "Wima/Snake/NemoInterface.h" +#include "AreaData.h" +#include "CircularGenerator.h" +#include "LinearGenerator.h" +#include "NemoInterface.h" #if defined(QGC_ENABLE_PAIRING) #include "PairingManager.h" @@ -135,849 +135,961 @@ #ifdef Q_OS_LINUX #ifndef __mobile__ -#include #include +#include #endif #endif #include "QGCMapEngine.h" -class FinishVideoInitialization : public QRunnable -{ +class FinishVideoInitialization : public QRunnable { public: - FinishVideoInitialization(VideoManager* manager) - : _manager(manager) - {} + FinishVideoInitialization(VideoManager *manager) : _manager(manager) {} - void run () { - _manager->_initVideo(); - } + void run() { _manager->_initVideo(); } private: - VideoManager* _manager; + VideoManager *_manager; }; +QGCApplication *QGCApplication::_app = nullptr; -QGCApplication* QGCApplication::_app = nullptr; - -const char* QGCApplication::_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; -const char* QGCApplication::_settingsVersionKey = "SettingsVersion"; +const char *QGCApplication::_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; +const char *QGCApplication::_settingsVersionKey = "SettingsVersion"; // Mavlink status structures for entire app mavlink_status_t m_mavlink_status[MAVLINK_COMM_NUM_BUFFERS]; // Qml Singleton factories -static QObject* screenToolsControllerSingletonFactory(QQmlEngine*, QJSEngine*) -{ - ScreenToolsController* screenToolsController = new ScreenToolsController; - return screenToolsController; +static QObject *screenToolsControllerSingletonFactory(QQmlEngine *, + QJSEngine *) { + ScreenToolsController *screenToolsController = new ScreenToolsController; + return screenToolsController; } -static QObject* mavlinkSingletonFactory(QQmlEngine*, QJSEngine*) -{ - return new QGCMAVLink(); +static QObject *mavlinkSingletonFactory(QQmlEngine *, QJSEngine *) { + return new QGCMAVLink(); } -static QObject* qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine*, QJSEngine*) -{ - // We create this object as a QGCTool even though it isn't in the toolbox - QGroundControlQmlGlobal* qmlGlobal = new QGroundControlQmlGlobal(qgcApp(), qgcApp()->toolbox()); - qmlGlobal->setToolbox(qgcApp()->toolbox()); +static QObject *qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine *, + QJSEngine *) { + // We create this object as a QGCTool even though it isn't in the toolbox + QGroundControlQmlGlobal *qmlGlobal = + new QGroundControlQmlGlobal(qgcApp(), qgcApp()->toolbox()); + qmlGlobal->setToolbox(qgcApp()->toolbox()); - return qmlGlobal; + return qmlGlobal; } -static QObject* shapeFileHelperSingletonFactory(QQmlEngine*, QJSEngine*) -{ - return new ShapeFileHelper; +static QObject *shapeFileHelperSingletonFactory(QQmlEngine *, QJSEngine *) { + return new ShapeFileHelper; } -QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) - : QApplication (argc, argv) - , _runningUnitTests (unitTesting) -{ - _app = this; - _msecsElapsedTime.start(); +QGCApplication::QGCApplication(int &argc, char *argv[], bool unitTesting) + : QApplication(argc, argv), _runningUnitTests(unitTesting) { + _app = this; + _msecsElapsedTime.start(); #ifdef Q_OS_LINUX #ifndef __mobile__ - if (!_runningUnitTests) { - if (getuid() == 0) { - _exitWithError(QString( - tr("You are running %1 as root. " - "You should not do this since it will cause other issues with %1." - "%1 will now exit.

" - "If you are having serial port issues on Ubuntu, execute the following commands to fix most issues:
" - "
sudo usermod -a -G dialout $USER
" - "sudo apt-get remove modemmanager
").arg(qgcApp()->applicationName()))); - return; - } - // Determine if we have the correct permissions to access USB serial devices - QFile permFile("/etc/group"); - if(permFile.open(QIODevice::ReadOnly)) { - while(!permFile.atEnd()) { - QString line = permFile.readLine(); - if (line.contains("dialout") && !line.contains(getenv("USER"))) { - permFile.close(); - _exitWithError(QString( - tr("The current user does not have the correct permissions to access serial devices. " - "You should also remove modemmanager since it also interferes.

" - "If you are using Ubuntu, execute the following commands to fix these issues:
" - "
sudo usermod -a -G dialout $USER
" - "sudo apt-get remove modemmanager
"))); - return; - } - } - permFile.close(); + if (!_runningUnitTests) { + if (getuid() == 0) { + _exitWithError(QString( + tr("You are running %1 as root. " + "You should not do this since it will cause other issues with %1." + "%1 will now exit.

" + "If you are having serial port issues on Ubuntu, execute the " + "following commands to fix most issues:
" + "
sudo usermod -a -G dialout $USER
" + "sudo apt-get remove modemmanager
") + .arg(qgcApp()->applicationName()))); + return; + } + // Determine if we have the correct permissions to access USB serial devices + QFile permFile("/etc/group"); + if (permFile.open(QIODevice::ReadOnly)) { + while (!permFile.atEnd()) { + QString line = permFile.readLine(); + if (line.contains("dialout") && !line.contains(getenv("USER"))) { + permFile.close(); + _exitWithError( + QString(tr("The current user does not have the correct " + "permissions to access serial devices. " + "You should also remove modemmanager since it also " + "interferes.

" + "If you are using Ubuntu, execute the following " + "commands to fix these issues:
" + "
sudo usermod -a -G dialout $USER
" + "sudo apt-get remove modemmanager
"))); + return; } - - // Always set style to default, this way QT_QUICK_CONTROLS_STYLE environment variable doesn't cause random changes in ui - QQuickStyle::setStyle("Default"); + } + permFile.close(); } + + // Always set style to default, this way QT_QUICK_CONTROLS_STYLE environment + // variable doesn't cause random changes in ui + QQuickStyle::setStyle("Default"); + } #endif #endif - // Setup for network proxy support - QNetworkProxyFactory::setUseSystemConfiguration(true); - - // Parse command line options - - bool fClearSettingsOptions = false; // Clear stored settings - bool fClearCache = false; // Clear parameter/airframe caches - bool logging = false; // Turn on logging - QString loggingOptions; - - CmdLineOpt_t rgCmdLineOptions[] = { - { "--clear-settings", &fClearSettingsOptions, nullptr }, - { "--clear-cache", &fClearCache, nullptr }, - { "--logging", &logging, &loggingOptions }, - { "--fake-mobile", &_fakeMobile, nullptr }, - { "--log-output", &_logOutput, nullptr }, - // Add additional command line option flags here - }; - - ParseCmdLineOptions(argc, argv, rgCmdLineOptions, sizeof(rgCmdLineOptions)/sizeof(rgCmdLineOptions[0]), false); - - // Set up timer for delayed missing fact display - _missingParamsDelayedDisplayTimer.setSingleShot(true); - _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout); - connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay); - - // Set application information - QString applicationName; - if (_runningUnitTests) { - // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app - // name. Also we want to run unit tests with clean settings every time. - applicationName = QStringLiteral("%1_unittest").arg(QGC_APPLICATION_NAME); - } else { + // Setup for network proxy support + QNetworkProxyFactory::setUseSystemConfiguration(true); + + // Parse command line options + + bool fClearSettingsOptions = false; // Clear stored settings + bool fClearCache = false; // Clear parameter/airframe caches + bool logging = false; // Turn on logging + QString loggingOptions; + + CmdLineOpt_t rgCmdLineOptions[] = { + {"--clear-settings", &fClearSettingsOptions, nullptr}, + {"--clear-cache", &fClearCache, nullptr}, + {"--logging", &logging, &loggingOptions}, + {"--fake-mobile", &_fakeMobile, nullptr}, + {"--log-output", &_logOutput, nullptr}, + // Add additional command line option flags here + }; + + ParseCmdLineOptions(argc, argv, rgCmdLineOptions, + sizeof(rgCmdLineOptions) / sizeof(rgCmdLineOptions[0]), + false); + + // Set up timer for delayed missing fact display + _missingParamsDelayedDisplayTimer.setSingleShot(true); + _missingParamsDelayedDisplayTimer.setInterval( + _missingParamsDelayedDisplayTimerTimeout); + connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, + &QGCApplication::_missingParamsDisplay); + + // Set application information + QString applicationName; + if (_runningUnitTests) { + // We don't want unit tests to use the same QSettings space as the normal + // app. So we tweak the app name. Also we want to run unit tests with clean + // settings every time. + applicationName = QStringLiteral("%1_unittest").arg(QGC_APPLICATION_NAME); + } else { #ifdef DAILY_BUILD - // This gives daily builds their own separate settings space. Allowing you to use daily and stable builds - // side by side without daily screwing up your stable settings. - applicationName = QStringLiteral("%1 Daily").arg(QGC_APPLICATION_NAME); + // This gives daily builds their own separate settings space. Allowing you + // to use daily and stable builds side by side without daily screwing up + // your stable settings. + applicationName = QStringLiteral("%1 Daily").arg(QGC_APPLICATION_NAME); #else - applicationName = QGC_APPLICATION_NAME; + applicationName = QGC_APPLICATION_NAME; #endif - } - setApplicationName(applicationName); - setOrganizationName(QGC_ORG_NAME); - setOrganizationDomain(QGC_ORG_DOMAIN); + } + setApplicationName(applicationName); + setOrganizationName(QGC_ORG_NAME); + setOrganizationDomain(QGC_ORG_DOMAIN); - this->setApplicationVersion(QString(GIT_VERSION)); + this->setApplicationVersion(QString(GIT_VERSION)); - // Set settings format - QSettings::setDefaultFormat(QSettings::IniFormat); - QSettings settings; - qDebug() << "Settings location" << settings.fileName() << "Is writable?:" << settings.isWritable(); + // Set settings format + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings settings; + qDebug() << "Settings location" << settings.fileName() + << "Is writable?:" << settings.isWritable(); #ifdef UNITTEST_BUILD - if (!settings.isWritable()) { - qWarning() << "Setings location is not writable"; - } + if (!settings.isWritable()) { + qWarning() << "Setings location is not writable"; + } #endif - // The setting will delete all settings on this boot - fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey); + // The setting will delete all settings on this boot + fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey); - if (_runningUnitTests) { - // Unit tests run with clean settings - fClearSettingsOptions = true; - } + if (_runningUnitTests) { + // Unit tests run with clean settings + fClearSettingsOptions = true; + } - if (fClearSettingsOptions) { - // User requested settings to be cleared on command line + if (fClearSettingsOptions) { + // User requested settings to be cleared on command line + settings.clear(); + + // Clear parameter cache + QDir paramDir(ParameterManager::parameterCacheDir()); + paramDir.removeRecursively(); + paramDir.mkpath(paramDir.absolutePath()); + } else { + // Determine if upgrade message for settings version bump is required. Check + // and clear must happen before toolbox is started since that will write + // some settings. + if (settings.contains(_settingsVersionKey)) { + if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) { settings.clear(); - - // Clear parameter cache - QDir paramDir(ParameterManager::parameterCacheDir()); - paramDir.removeRecursively(); - paramDir.mkpath(paramDir.absolutePath()); - } else { - // Determine if upgrade message for settings version bump is required. Check and clear must happen before toolbox is started since - // that will write some settings. - if (settings.contains(_settingsVersionKey)) { - if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) { - settings.clear(); - _settingsUpgraded = true; - } - } - } - settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION); - - if (fClearCache) { - QDir dir(ParameterManager::parameterCacheDir()); - dir.removeRecursively(); - QFile airframe(cachedAirframeMetaDataFile()); - airframe.remove(); - QFile parameter(cachedParameterMetaDataFile()); - parameter.remove(); + _settingsUpgraded = true; + } } + } + settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION); + + if (fClearCache) { + QDir dir(ParameterManager::parameterCacheDir()); + dir.removeRecursively(); + QFile airframe(cachedAirframeMetaDataFile()); + airframe.remove(); + QFile parameter(cachedParameterMetaDataFile()); + parameter.remove(); + } - // Set up our logging filters - QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings(loggingOptions); + // Set up our logging filters + QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings( + loggingOptions); - // Initialize Bluetooth + // Initialize Bluetooth #ifdef QGC_ENABLE_BLUETOOTH - QBluetoothLocalDevice localDevice; - if (localDevice.isValid()) - { - _bluetoothAvailable = true; - } + QBluetoothLocalDevice localDevice; + if (localDevice.isValid()) { + _bluetoothAvailable = true; + } #endif - // Gstreamer debug settings - int gstDebugLevel = 0; - if (settings.contains(AppSettings::gstDebugLevelName)) { - gstDebugLevel = settings.value(AppSettings::gstDebugLevelName).toInt(); - } + // Gstreamer debug settings + int gstDebugLevel = 0; + if (settings.contains(AppSettings::gstDebugLevelName)) { + gstDebugLevel = settings.value(AppSettings::gstDebugLevelName).toInt(); + } #if defined(QGC_GST_STREAMING) - // Initialize Video Receiver - GStreamer::initialize(argc, argv, gstDebugLevel); + // Initialize Video Receiver + GStreamer::initialize(argc, argv, gstDebugLevel); #else - Q_UNUSED(gstDebugLevel) + Q_UNUSED(gstDebugLevel) #endif - // We need to set language as early as possible prior to loading on JSON files. - setLanguage(); + // We need to set language as early as possible prior to loading on JSON + // files. + setLanguage(); - _toolbox = new QGCToolbox(this); - _toolbox->setChildToolboxes(); + _toolbox = new QGCToolbox(this); + _toolbox->setChildToolboxes(); #ifndef __mobile__ - _gpsRtkFactGroup = new GPSRTKFactGroup(this); - GPSManager *gpsManager = _toolbox->gpsManager(); - if (gpsManager) { - connect(gpsManager, &GPSManager::onConnect, this, &QGCApplication::_onGPSConnect); - connect(gpsManager, &GPSManager::onDisconnect, this, &QGCApplication::_onGPSDisconnect); - connect(gpsManager, &GPSManager::surveyInStatus, this, &QGCApplication::_gpsSurveyInStatus); - connect(gpsManager, &GPSManager::satelliteUpdate, this, &QGCApplication::_gpsNumSatellites); - } + _gpsRtkFactGroup = new GPSRTKFactGroup(this); + GPSManager *gpsManager = _toolbox->gpsManager(); + if (gpsManager) { + connect(gpsManager, &GPSManager::onConnect, this, + &QGCApplication::_onGPSConnect); + connect(gpsManager, &GPSManager::onDisconnect, this, + &QGCApplication::_onGPSDisconnect); + connect(gpsManager, &GPSManager::surveyInStatus, this, + &QGCApplication::_gpsSurveyInStatus); + connect(gpsManager, &GPSManager::satelliteUpdate, this, + &QGCApplication::_gpsNumSatellites); + } #endif /* __mobile__ */ - _checkForNewVersion(); -} - -void QGCApplication::_exitWithError(QString errorMessage) -{ - _error = true; - QQmlApplicationEngine* pEngine = new QQmlApplicationEngine(this); - pEngine->addImportPath("qrc:/qml"); - pEngine->rootContext()->setContextProperty("errorMessage", errorMessage); - pEngine->load(QUrl(QStringLiteral("qrc:/qml/ExitWithErrorWindow.qml"))); - // Exit main application when last window is closed - connect(this, &QGCApplication::lastWindowClosed, this, QGCApplication::quit); -} - -void QGCApplication::setLanguage() -{ - _locale = QLocale::system(); - qDebug() << "System reported locale:" << _locale << "; Name" << _locale.name() << "; Preffered (used in maps): " << (QLocale::system().uiLanguages().length() > 0 ? QLocale::system().uiLanguages()[0] : "None"); - - int langID = AppSettings::_languageID(); - //-- See App.SettinsGroup.json for index - if(langID) { - switch(langID) { - case 1: - _locale = QLocale(QLocale::Bulgarian); - break; - case 2: - _locale = QLocale(QLocale::Chinese); - break; - case 3: - _locale = QLocale(QLocale::Dutch); - break; - case 4: - _locale = QLocale(QLocale::English); - break; - case 5: - _locale = QLocale(QLocale::Finnish); - break; - case 6: - _locale = QLocale(QLocale::French); - break; - case 7: - _locale = QLocale(QLocale::German); - break; - case 8: - _locale = QLocale(QLocale::Greek); - break; - case 9: - _locale = QLocale(QLocale::Hebrew); - break; - case 10: - _locale = QLocale(QLocale::Italian); - break; - case 11: - _locale = QLocale(QLocale::Japanese); - break; - case 12: - _locale = QLocale(QLocale::Korean); - break; - case 13: - _locale = QLocale(QLocale::Norwegian); - break; - case 14: - _locale = QLocale(QLocale::Polish); - break; - case 15: - _locale = QLocale(QLocale::Portuguese); - break; - case 16: - _locale = QLocale(QLocale::Russian); - break; - case 17: - _locale = QLocale(QLocale::Spanish); - break; - case 18: - _locale = QLocale(QLocale::Swedish); - break; - case 19: - _locale = QLocale(QLocale::Turkish); - break; - case 20: - _locale = QLocale(QLocale::Azerbaijani); - break; - } + _checkForNewVersion(); +} + +void QGCApplication::_exitWithError(QString errorMessage) { + _error = true; + QQmlApplicationEngine *pEngine = new QQmlApplicationEngine(this); + pEngine->addImportPath("qrc:/qml"); + pEngine->rootContext()->setContextProperty("errorMessage", errorMessage); + pEngine->load(QUrl(QStringLiteral("qrc:/qml/ExitWithErrorWindow.qml"))); + // Exit main application when last window is closed + connect(this, &QGCApplication::lastWindowClosed, this, QGCApplication::quit); +} + +void QGCApplication::setLanguage() { + _locale = QLocale::system(); + qDebug() << "System reported locale:" << _locale << "; Name" << _locale.name() + << "; Preffered (used in maps): " + << (QLocale::system().uiLanguages().length() > 0 + ? QLocale::system().uiLanguages()[0] + : "None"); + + int langID = AppSettings::_languageID(); + //-- See App.SettinsGroup.json for index + if (langID) { + switch (langID) { + case 1: + _locale = QLocale(QLocale::Bulgarian); + break; + case 2: + _locale = QLocale(QLocale::Chinese); + break; + case 3: + _locale = QLocale(QLocale::Dutch); + break; + case 4: + _locale = QLocale(QLocale::English); + break; + case 5: + _locale = QLocale(QLocale::Finnish); + break; + case 6: + _locale = QLocale(QLocale::French); + break; + case 7: + _locale = QLocale(QLocale::German); + break; + case 8: + _locale = QLocale(QLocale::Greek); + break; + case 9: + _locale = QLocale(QLocale::Hebrew); + break; + case 10: + _locale = QLocale(QLocale::Italian); + break; + case 11: + _locale = QLocale(QLocale::Japanese); + break; + case 12: + _locale = QLocale(QLocale::Korean); + break; + case 13: + _locale = QLocale(QLocale::Norwegian); + break; + case 14: + _locale = QLocale(QLocale::Polish); + break; + case 15: + _locale = QLocale(QLocale::Portuguese); + break; + case 16: + _locale = QLocale(QLocale::Russian); + break; + case 17: + _locale = QLocale(QLocale::Spanish); + break; + case 18: + _locale = QLocale(QLocale::Swedish); + break; + case 19: + _locale = QLocale(QLocale::Turkish); + break; + case 20: + _locale = QLocale(QLocale::Azerbaijani); + break; } - //-- We have specific fonts for Korean - if(_locale == QLocale::Korean) { - qCDebug(LocalizationLog) << "Loading Korean fonts" << _locale.name(); - if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Regular") < 0) { - qCWarning(LocalizationLog) << "Could not load /fonts/NanumGothic-Regular font"; - } - if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) { - qCWarning(LocalizationLog) << "Could not load /fonts/NanumGothic-Bold font"; - } + } + //-- We have specific fonts for Korean + if (_locale == QLocale::Korean) { + qCDebug(LocalizationLog) << "Loading Korean fonts" << _locale.name(); + if (QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Regular") < 0) { + qCWarning(LocalizationLog) + << "Could not load /fonts/NanumGothic-Regular font"; } - qCDebug(LocalizationLog) << "Loading localizations for" << _locale.name(); - _app->removeTranslator(&_qgcTranslatorJSON); - _app->removeTranslator(&_qgcTranslatorSourceCode); - _app->removeTranslator(&_qgcTranslatorQtLibs); - if (_locale.name() != "en_US") { - QLocale::setDefault(_locale); - if(_qgcTranslatorQtLibs.load("qt_" + _locale.name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { - _app->installTranslator(&_qgcTranslatorQtLibs); - } else { - qCWarning(LocalizationLog) << "Qt lib localization for" << _locale.name() << "is not present"; - } - if(_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) { - _app->installTranslator(&_qgcTranslatorSourceCode); - } else { - qCWarning(LocalizationLog) << "Error loading source localization for" << _locale.name(); - } - if(_qgcTranslatorJSON.load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) { - _app->installTranslator(&_qgcTranslatorJSON); - } else { - qCWarning(LocalizationLog) << "Error loading json localization for" << _locale.name(); - } + if (QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) { + qCWarning(LocalizationLog) + << "Could not load /fonts/NanumGothic-Bold font"; + } + } + qCDebug(LocalizationLog) << "Loading localizations for" << _locale.name(); + _app->removeTranslator(&_qgcTranslatorJSON); + _app->removeTranslator(&_qgcTranslatorSourceCode); + _app->removeTranslator(&_qgcTranslatorQtLibs); + if (_locale.name() != "en_US") { + QLocale::setDefault(_locale); + if (_qgcTranslatorQtLibs.load( + "qt_" + _locale.name(), + QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + _app->installTranslator(&_qgcTranslatorQtLibs); + } else { + qCWarning(LocalizationLog) + << "Qt lib localization for" << _locale.name() << "is not present"; + } + if (_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", + ":/i18n")) { + _app->installTranslator(&_qgcTranslatorSourceCode); + } else { + qCWarning(LocalizationLog) + << "Error loading source localization for" << _locale.name(); + } + if (_qgcTranslatorJSON.load(_locale, QLatin1String("qgc_json_"), "", + ":/i18n")) { + _app->installTranslator(&_qgcTranslatorJSON); + } else { + qCWarning(LocalizationLog) + << "Error loading json localization for" << _locale.name(); } - if(_qmlAppEngine) - _qmlAppEngine->retranslate(); - emit languageChanged(_locale); -} - -void QGCApplication::_shutdown() -{ - // Close out all Qml before we delete toolbox. This way we don't get all sorts of null reference complaints from Qml. - delete _qmlAppEngine; - delete _toolbox; - delete _gpsRtkFactGroup; -} - -QGCApplication::~QGCApplication() -{ - // Place shutdown code in _shutdown - _app = nullptr; -} - -void QGCApplication::_initCommon() -{ - static const char* kRefOnly = "Reference only"; - static const char* kQGroundControl = "QGroundControl"; - static const char* kQGCControllers = "QGroundControl.Controllers"; - static const char* kQGCVehicle = "QGroundControl.Vehicle"; - static const char* kQGCTemplates = "QGroundControl.Templates"; - - QSettings settings; - - // Register our Qml objects - - qmlRegisterType ("QGroundControl.Palette", 1, 0, "QGCPalette"); - qmlRegisterType ("QGroundControl.Palette", 1, 0, "QGCMapPalette"); - - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "Vehicle", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "MissionManager", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "ParameterManager", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "VehicleObjectAvoidance", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "QGCCameraManager", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "QGCCameraControl", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "QGCVideoStreamInfo", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "LinkInterface", kRefOnly); - qmlRegisterUncreatableType (kQGCVehicle, 1, 0, "VehicleLinkManager", kRefOnly); - - qmlRegisterUncreatableType (kQGCControllers, 1, 0, "MissionController", kRefOnly); - qmlRegisterUncreatableType (kQGCControllers, 1, 0, "GeoFenceController", kRefOnly); - qmlRegisterUncreatableType (kQGCControllers, 1, 0, "RallyPointController", kRefOnly); - - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "MissionItem", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "VisualMissionItem", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "FlightPathSegment", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "QmlObjectListModel", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "MissionCommandTree", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "CameraCalc", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "LogReplayLink", kRefOnly); - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "InstrumentValueData", kRefOnly); - qmlRegisterType (kQGroundControl, 1, 0, "LogReplayLinkController"); + } + if (_qmlAppEngine) + _qmlAppEngine->retranslate(); + emit languageChanged(_locale); +} + +void QGCApplication::_shutdown() { + // Close out all Qml before we delete toolbox. This way we don't get all sorts + // of null reference complaints from Qml. + delete _qmlAppEngine; + delete _toolbox; + delete _gpsRtkFactGroup; +} + +QGCApplication::~QGCApplication() { + // Place shutdown code in _shutdown + _app = nullptr; +} + +void QGCApplication::_initCommon() { + static const char *kRefOnly = "Reference only"; + static const char *kQGroundControl = "QGroundControl"; + static const char *kQGCControllers = "QGroundControl.Controllers"; + static const char *kQGCVehicle = "QGroundControl.Vehicle"; + static const char *kQGCTemplates = "QGroundControl.Templates"; + + QSettings settings; + + // Register our Qml objects + + qmlRegisterType("QGroundControl.Palette", 1, 0, "QGCPalette"); + qmlRegisterType("QGroundControl.Palette", 1, 0, + "QGCMapPalette"); + + qmlRegisterUncreatableType(kQGCVehicle, 1, 0, "Vehicle", kRefOnly); + qmlRegisterUncreatableType(kQGCVehicle, 1, 0, + "MissionManager", kRefOnly); + qmlRegisterUncreatableType(kQGCVehicle, 1, 0, + "ParameterManager", kRefOnly); + qmlRegisterUncreatableType( + kQGCVehicle, 1, 0, "VehicleObjectAvoidance", kRefOnly); + qmlRegisterUncreatableType(kQGCVehicle, 1, 0, + "QGCCameraManager", kRefOnly); + qmlRegisterUncreatableType(kQGCVehicle, 1, 0, + "QGCCameraControl", kRefOnly); + qmlRegisterUncreatableType( + kQGCVehicle, 1, 0, "QGCVideoStreamInfo", kRefOnly); + qmlRegisterUncreatableType(kQGCVehicle, 1, 0, "LinkInterface", + kRefOnly); + qmlRegisterUncreatableType( + kQGCVehicle, 1, 0, "VehicleLinkManager", kRefOnly); + + qmlRegisterUncreatableType(kQGCControllers, 1, 0, + "MissionController", kRefOnly); + qmlRegisterUncreatableType( + kQGCControllers, 1, 0, "GeoFenceController", kRefOnly); + qmlRegisterUncreatableType( + kQGCControllers, 1, 0, "RallyPointController", kRefOnly); + + qmlRegisterUncreatableType(kQGroundControl, 1, 0, "MissionItem", + kRefOnly); + qmlRegisterUncreatableType(kQGroundControl, 1, 0, + "VisualMissionItem", kRefOnly); + qmlRegisterUncreatableType(kQGroundControl, 1, 0, + "FlightPathSegment", kRefOnly); + qmlRegisterUncreatableType( + kQGroundControl, 1, 0, "QmlObjectListModel", kRefOnly); + qmlRegisterUncreatableType( + kQGroundControl, 1, 0, "MissionCommandTree", kRefOnly); + qmlRegisterUncreatableType(kQGroundControl, 1, 0, "CameraCalc", + kRefOnly); + qmlRegisterUncreatableType(kQGroundControl, 1, 0, + "LogReplayLink", kRefOnly); + qmlRegisterUncreatableType( + kQGroundControl, 1, 0, "InstrumentValueData", kRefOnly); + qmlRegisterType(kQGroundControl, 1, 0, + "LogReplayLinkController"); #if defined(QGC_ENABLE_MAVLINK_INSPECTOR) - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "MAVLinkChart", kRefOnly); + qmlRegisterUncreatableType(kQGroundControl, 1, 0, + "MAVLinkChart", kRefOnly); #endif #if defined(QGC_ENABLE_PAIRING) - qmlRegisterUncreatableType (kQGroundControl, 1, 0, "PairingManager", kRefOnly); + qmlRegisterUncreatableType(kQGroundControl, 1, 0, + "PairingManager", kRefOnly); #endif - qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", kRefOnly); - qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", kRefOnly); - qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "JoystickManager", kRefOnly); - qmlRegisterUncreatableType ("QGroundControl.JoystickManager", 1, 0, "Joystick", kRefOnly); - qmlRegisterUncreatableType ("QGroundControl.QGCPositionManager", 1, 0, "QGCPositionManager", kRefOnly); - qmlRegisterUncreatableType("QGroundControl.FactControls", 1, 0, "FactValueSliderListModel", kRefOnly); - - qmlRegisterUncreatableType ("QGroundControl.FlightMap", 1, 0, "QGCMapPolygon", kRefOnly); - qmlRegisterUncreatableType ("QGroundControl.FlightMap", 1, 0, "QGCGeoBoundingCube", kRefOnly); - qmlRegisterUncreatableType ("QGroundControl.FlightMap", 1, 0, "TrajectoryPoints", kRefOnly); - - qmlRegisterUncreatableType (kQGCTemplates, 1, 0, "FactValueGrid", kRefOnly); - qmlRegisterType (kQGCTemplates, 1, 0, "HorizontalFactValueGrid"); - - qmlRegisterType ("QGroundControl.FlightMap", 1, 0, "QGCMapCircle"); - - qmlRegisterType (kQGCControllers, 1, 0, "ParameterEditorController"); - qmlRegisterType (kQGCControllers, 1, 0, "ESP8266ComponentController"); - qmlRegisterType (kQGCControllers, 1, 0, "ScreenToolsController"); - qmlRegisterType (kQGCControllers, 1, 0, "PlanMasterController"); - qmlRegisterType (kQGCControllers, 1, 0, "QGCFileDialogController"); - qmlRegisterType (kQGCControllers, 1, 0, "RCChannelMonitorController"); - qmlRegisterType (kQGCControllers, 1, 0, "JoystickConfigController"); - qmlRegisterType (kQGCControllers, 1, 0, "LogDownloadController"); - qmlRegisterType (kQGCControllers, 1, 0, "SyslinkComponentController"); - qmlRegisterType (kQGCControllers, 1, 0, "EditPositionDialogController"); - qmlRegisterType (kQGCControllers, 1, 0, "RCToParamDialogController"); - - qmlRegisterType ("QGroundControl.Controls", 1, 0, "TerrainProfile"); - qmlRegisterType ("QGroundControl.Controls", 1, 0, "ToolStripAction"); - qmlRegisterType ("QGroundControl.Controls", 1, 0, "ToolStripActionList"); + qmlRegisterUncreatableType( + "QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", kRefOnly); + qmlRegisterUncreatableType( + "QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", kRefOnly); + qmlRegisterUncreatableType( + "QGroundControl.JoystickManager", 1, 0, "JoystickManager", kRefOnly); + qmlRegisterUncreatableType("QGroundControl.JoystickManager", 1, 0, + "Joystick", kRefOnly); + qmlRegisterUncreatableType( + "QGroundControl.QGCPositionManager", 1, 0, "QGCPositionManager", + kRefOnly); + qmlRegisterUncreatableType( + "QGroundControl.FactControls", 1, 0, "FactValueSliderListModel", + kRefOnly); + + qmlRegisterUncreatableType("QGroundControl.FlightMap", 1, 0, + "QGCMapPolygon", kRefOnly); + qmlRegisterUncreatableType( + "QGroundControl.FlightMap", 1, 0, "QGCGeoBoundingCube", kRefOnly); + qmlRegisterUncreatableType("QGroundControl.FlightMap", 1, 0, + "TrajectoryPoints", kRefOnly); + + qmlRegisterUncreatableType(kQGCTemplates, 1, 0, + "FactValueGrid", kRefOnly); + qmlRegisterType(kQGCTemplates, 1, 0, + "HorizontalFactValueGrid"); + + qmlRegisterType("QGroundControl.FlightMap", 1, 0, + "QGCMapCircle"); + + qmlRegisterType(kQGCControllers, 1, 0, + "ParameterEditorController"); + qmlRegisterType(kQGCControllers, 1, 0, + "ESP8266ComponentController"); + qmlRegisterType(kQGCControllers, 1, 0, + "ScreenToolsController"); + qmlRegisterType(kQGCControllers, 1, 0, + "PlanMasterController"); + qmlRegisterType(kQGCControllers, 1, 0, + "QGCFileDialogController"); + qmlRegisterType(kQGCControllers, 1, 0, + "RCChannelMonitorController"); + qmlRegisterType(kQGCControllers, 1, 0, + "JoystickConfigController"); + qmlRegisterType(kQGCControllers, 1, 0, + "LogDownloadController"); + qmlRegisterType(kQGCControllers, 1, 0, + "SyslinkComponentController"); + qmlRegisterType(kQGCControllers, 1, 0, + "EditPositionDialogController"); + qmlRegisterType(kQGCControllers, 1, 0, + "RCToParamDialogController"); + + qmlRegisterType("QGroundControl.Controls", 1, 0, + "TerrainProfile"); + qmlRegisterType("QGroundControl.Controls", 1, 0, + "ToolStripAction"); + qmlRegisterType("QGroundControl.Controls", 1, 0, + "ToolStripActionList"); #ifndef __mobile__ #ifndef NO_SERIAL_LINK - qmlRegisterType (kQGCControllers, 1, 0, "FirmwareUpgradeController"); + qmlRegisterType(kQGCControllers, 1, 0, + "FirmwareUpgradeController"); #endif #endif -// Wima - qmlRegisterType("Wima", 1, 0, "NemoInterface"); + qmlRegisterUncreatableType( + "RouteComplexItem", 1, 0, "LinearGenerator", kRefOnly); + qmlRegisterType("RouteComplexItem", 1, 0, "AreaData"); + qmlRegisterUncreatableType("RouteComplexItem", 1, 0, + "NemoInterface", kRefOnly); qmlRegisterInterface("GeneratorBase"); - qmlRegisterType("Wima", 1, 0, - "CircularGenerator"); - qmlRegisterType("Wima", 1, 0, "LinearGenerator"); + qmlRegisterUncreatableType( + "RouteComplexItem", 1, 0, "CircularGenerator", kRefOnly); - qmlRegisterType (kQGCControllers, 1, 0, "GeoTagController"); - qmlRegisterType (kQGCControllers, 1, 0, "MavlinkConsoleController"); + qmlRegisterType(kQGCControllers, 1, 0, "GeoTagController"); + qmlRegisterType(kQGCControllers, 1, 0, + "MavlinkConsoleController"); #if defined(QGC_ENABLE_MAVLINK_INSPECTOR) - qmlRegisterType (kQGCControllers, 1, 0, "MAVLinkInspectorController"); + qmlRegisterType(kQGCControllers, 1, 0, + "MAVLinkInspectorController"); #endif - // Register Qml Singletons - qmlRegisterSingletonType ("QGroundControl", 1, 0, "QGroundControl", qgroundcontrolQmlGlobalSingletonFactory); - qmlRegisterSingletonType ("QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController", screenToolsControllerSingletonFactory); - qmlRegisterSingletonType ("QGroundControl.ShapeFileHelper", 1, 0, "ShapeFileHelper", shapeFileHelperSingletonFactory); - qmlRegisterSingletonType ("MAVLink", 1, 0, "MAVLink", mavlinkSingletonFactory); - - // Although this should really be in _initForNormalAppBoot putting it here allowws us to create unit tests which pop up more easily - if(QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) { - qWarning() << "Could not load /fonts/opensans font"; - } - if(QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) { - qWarning() << "Could not load /fonts/opensans-demibold font"; - } + // Register Qml Singletons + qmlRegisterSingletonType( + "QGroundControl", 1, 0, "QGroundControl", + qgroundcontrolQmlGlobalSingletonFactory); + qmlRegisterSingletonType( + "QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController", + screenToolsControllerSingletonFactory); + qmlRegisterSingletonType("QGroundControl.ShapeFileHelper", 1, + 0, "ShapeFileHelper", + shapeFileHelperSingletonFactory); + qmlRegisterSingletonType("MAVLink", 1, 0, "MAVLink", + mavlinkSingletonFactory); + + // Although this should really be in _initForNormalAppBoot putting it here + // allowws us to create unit tests which pop up more easily + if (QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) { + qWarning() << "Could not load /fonts/opensans font"; + } + if (QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) { + qWarning() << "Could not load /fonts/opensans-demibold font"; + } } -bool QGCApplication::_initForNormalAppBoot() -{ - QSettings settings; +bool QGCApplication::_initForNormalAppBoot() { + QSettings settings; - _qmlAppEngine = toolbox()->corePlugin()->createQmlApplicationEngine(this); - toolbox()->corePlugin()->createRootWindow(_qmlAppEngine); + _qmlAppEngine = toolbox()->corePlugin()->createQmlApplicationEngine(this); + toolbox()->corePlugin()->createRootWindow(_qmlAppEngine); - // Image provider for PX4 Flow - QQuickImageProvider* pImgProvider = dynamic_cast(qgcApp()->toolbox()->imageProvider()); - _qmlAppEngine->addImageProvider(QStringLiteral("QGCImages"), pImgProvider); + // Image provider for PX4 Flow + QQuickImageProvider *pImgProvider = + dynamic_cast(qgcApp()->toolbox()->imageProvider()); + _qmlAppEngine->addImageProvider(QStringLiteral("QGCImages"), pImgProvider); - QQuickWindow* rootWindow = (QQuickWindow*)qgcApp()->mainRootWindow(); + QQuickWindow *rootWindow = (QQuickWindow *)qgcApp()->mainRootWindow(); - if (rootWindow) { - rootWindow->scheduleRenderJob (new FinishVideoInitialization (toolbox()->videoManager()), - QQuickWindow::BeforeSynchronizingStage); - } + if (rootWindow) { + rootWindow->scheduleRenderJob( + new FinishVideoInitialization(toolbox()->videoManager()), + QQuickWindow::BeforeSynchronizingStage); + } - // Safe to show popup error messages now that main window is created - UASMessageHandler* msgHandler = qgcApp()->toolbox()->uasMessageHandler(); - if (msgHandler) { - msgHandler->showErrorsInToolbar(); - } + // Safe to show popup error messages now that main window is created + UASMessageHandler *msgHandler = qgcApp()->toolbox()->uasMessageHandler(); + if (msgHandler) { + msgHandler->showErrorsInToolbar(); + } - // Now that main window is up check for lost log files - connect(this, &QGCApplication::checkForLostLogFiles, toolbox()->mavlinkProtocol(), &MAVLinkProtocol::checkForLostLogFiles); - emit checkForLostLogFiles(); + // Now that main window is up check for lost log files + connect(this, &QGCApplication::checkForLostLogFiles, + toolbox()->mavlinkProtocol(), &MAVLinkProtocol::checkForLostLogFiles); + emit checkForLostLogFiles(); - // Load known link configurations - toolbox()->linkManager()->loadLinkConfigurationList(); + // Load known link configurations + toolbox()->linkManager()->loadLinkConfigurationList(); - // Probe for joysticks - toolbox()->joystickManager()->init(); + // Probe for joysticks + toolbox()->joystickManager()->init(); - if (_settingsUpgraded) { - showAppMessage(QString(tr("The format for %1 saved settings has been modified. " - "Your saved settings have been reset to defaults.")).arg(applicationName())); - } + if (_settingsUpgraded) { + showAppMessage( + QString(tr("The format for %1 saved settings has been modified. " + "Your saved settings have been reset to defaults.")) + .arg(applicationName())); + } - // Connect links with flag AutoconnectLink - toolbox()->linkManager()->startAutoConnectedLinks(); + // Connect links with flag AutoconnectLink + toolbox()->linkManager()->startAutoConnectedLinks(); - if (getQGCMapEngine()->wasCacheReset()) { - showAppMessage(tr("The Offline Map Cache database has been upgraded. " - "Your old map cache sets have been reset.")); - } + if (getQGCMapEngine()->wasCacheReset()) { + showAppMessage(tr("The Offline Map Cache database has been upgraded. " + "Your old map cache sets have been reset.")); + } - settings.sync(); - return true; + settings.sync(); + return true; } -bool QGCApplication::_initForUnitTests() -{ - return true; -} +bool QGCApplication::_initForUnitTests() { return true; } -void QGCApplication::deleteAllSettingsNextBoot(void) -{ - QSettings settings; - settings.setValue(_deleteAllSettingsKey, true); +void QGCApplication::deleteAllSettingsNextBoot(void) { + QSettings settings; + settings.setValue(_deleteAllSettingsKey, true); } -void QGCApplication::clearDeleteAllSettingsNextBoot(void) -{ - QSettings settings; - settings.remove(_deleteAllSettingsKey); +void QGCApplication::clearDeleteAllSettingsNextBoot(void) { + QSettings settings; + settings.remove(_deleteAllSettingsKey); } /// @brief Returns the QGCApplication object singleton. -QGCApplication* qgcApp(void) -{ - return QGCApplication::_app; -} +QGCApplication *qgcApp(void) { return QGCApplication::_app; } -void QGCApplication::informationMessageBoxOnMainThread(const QString& /*title*/, const QString& msg) -{ - showAppMessage(msg); +void QGCApplication::informationMessageBoxOnMainThread( + const QString & /*title*/, const QString &msg) { + showAppMessage(msg); } -void QGCApplication::warningMessageBoxOnMainThread(const QString& /*title*/, const QString& msg) -{ - showAppMessage(msg); +void QGCApplication::warningMessageBoxOnMainThread(const QString & /*title*/, + const QString &msg) { + showAppMessage(msg); } -void QGCApplication::criticalMessageBoxOnMainThread(const QString& /*title*/, const QString& msg) -{ - showAppMessage(msg); +void QGCApplication::criticalMessageBoxOnMainThread(const QString & /*title*/, + const QString &msg) { + showAppMessage(msg); } -void QGCApplication::saveTelemetryLogOnMainThread(QString tempLogfile) -{ - // The vehicle is gone now and we are shutting down so we need to use a message box for errors to hold shutdown and show the error - if (_checkTelemetrySavePath(true /* useMessageBox */)) { - - QString saveDirPath = _toolbox->settingsManager()->appSettings()->telemetrySavePath(); - QDir saveDir(saveDirPath); +void QGCApplication::saveTelemetryLogOnMainThread(QString tempLogfile) { + // The vehicle is gone now and we are shutting down so we need to use a + // message box for errors to hold shutdown and show the error + if (_checkTelemetrySavePath(true /* useMessageBox */)) { - QString nameFormat("%1%2.%3"); - QString dtFormat("yyyy-MM-dd hh-mm-ss"); - - int tryIndex = 1; - QString saveFileName = nameFormat.arg( - QDateTime::currentDateTime().toString(dtFormat)).arg(QStringLiteral("")).arg(toolbox()->settingsManager()->appSettings()->telemetryFileExtension); - while (saveDir.exists(saveFileName)) { - saveFileName = nameFormat.arg( - QDateTime::currentDateTime().toString(dtFormat)).arg(QStringLiteral(".%1").arg(tryIndex++)).arg(toolbox()->settingsManager()->appSettings()->telemetryFileExtension); - } - QString saveFilePath = saveDir.absoluteFilePath(saveFileName); + QString saveDirPath = + _toolbox->settingsManager()->appSettings()->telemetrySavePath(); + QDir saveDir(saveDirPath); - QFile tempFile(tempLogfile); - if (!tempFile.copy(saveFilePath)) { - QString error = tr("Unable to save telemetry log. Error copying telemetry to '%1': '%2'.").arg(saveFilePath).arg(tempFile.errorString()); - showAppMessage(error); - } + QString nameFormat("%1%2.%3"); + QString dtFormat("yyyy-MM-dd hh-mm-ss"); + + int tryIndex = 1; + QString saveFileName = + nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat)) + .arg(QStringLiteral("")) + .arg(toolbox() + ->settingsManager() + ->appSettings() + ->telemetryFileExtension); + while (saveDir.exists(saveFileName)) { + saveFileName = + nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat)) + .arg(QStringLiteral(".%1").arg(tryIndex++)) + .arg(toolbox() + ->settingsManager() + ->appSettings() + ->telemetryFileExtension); } - QFile::remove(tempLogfile); + QString saveFilePath = saveDir.absoluteFilePath(saveFileName); + + QFile tempFile(tempLogfile); + if (!tempFile.copy(saveFilePath)) { + QString error = tr("Unable to save telemetry log. Error copying " + "telemetry to '%1': '%2'.") + .arg(saveFilePath) + .arg(tempFile.errorString()); + showAppMessage(error); + } + } + QFile::remove(tempLogfile); } -void QGCApplication::checkTelemetrySavePathOnMainThread() -{ - // This is called with an active vehicle so don't pop message boxes which holds ui thread - _checkTelemetrySavePath(false /* useMessageBox */); +void QGCApplication::checkTelemetrySavePathOnMainThread() { + // This is called with an active vehicle so don't pop message boxes which + // holds ui thread + _checkTelemetrySavePath(false /* useMessageBox */); } -bool QGCApplication::_checkTelemetrySavePath(bool /*useMessageBox*/) -{ - QString saveDirPath = _toolbox->settingsManager()->appSettings()->telemetrySavePath(); - if (saveDirPath.isEmpty()) { - QString error = tr("Unable to save telemetry log. Application save directory is not set."); - showAppMessage(error); - return false; - } +bool QGCApplication::_checkTelemetrySavePath(bool /*useMessageBox*/) { + QString saveDirPath = + _toolbox->settingsManager()->appSettings()->telemetrySavePath(); + if (saveDirPath.isEmpty()) { + QString error = tr( + "Unable to save telemetry log. Application save directory is not set."); + showAppMessage(error); + return false; + } - QDir saveDir(saveDirPath); - if (!saveDir.exists()) { - QString error = tr("Unable to save telemetry log. Telemetry save directory \"%1\" does not exist.").arg(saveDirPath); - showAppMessage(error); - return false; - } + QDir saveDir(saveDirPath); + if (!saveDir.exists()) { + QString error = tr("Unable to save telemetry log. Telemetry save directory " + "\"%1\" does not exist.") + .arg(saveDirPath); + showAppMessage(error); + return false; + } - return true; + return true; } -void QGCApplication::reportMissingParameter(int componentId, const QString& name) -{ - QPair missingParam(componentId, name); +void QGCApplication::reportMissingParameter(int componentId, + const QString &name) { + QPair missingParam(componentId, name); - if (!_missingParams.contains(missingParam)) { - _missingParams.append(missingParam); - } - _missingParamsDelayedDisplayTimer.start(); + if (!_missingParams.contains(missingParam)) { + _missingParams.append(missingParam); + } + _missingParamsDelayedDisplayTimer.start(); } /// Called when the delay timer fires to show the missing parameters warning -void QGCApplication::_missingParamsDisplay(void) -{ - if (_missingParams.count()) { - QString params; - for (QPair& missingParam: _missingParams) { - QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second); - if (params.isEmpty()) { - params += param; - } else { - params += QStringLiteral(", %1").arg(param); - } - - } - _missingParams.clear(); - - showAppMessage(tr("Parameters are missing from firmware. You may be running a version of firmware which is not fully supported or your firmware has a bug in it. Missing params: %1").arg(params)); +void QGCApplication::_missingParamsDisplay(void) { + if (_missingParams.count()) { + QString params; + for (QPair &missingParam : _missingParams) { + QString param = QStringLiteral("%1:%2") + .arg(missingParam.first) + .arg(missingParam.second); + if (params.isEmpty()) { + params += param; + } else { + params += QStringLiteral(", %1").arg(param); + } } -} + _missingParams.clear(); -QObject* QGCApplication::_rootQmlObject() -{ - if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) - return _qmlAppEngine->rootObjects()[0]; - return nullptr; + showAppMessage( + tr("Parameters are missing from firmware. You may be running a version " + "of firmware which is not fully supported or your firmware has a " + "bug in it. Missing params: %1") + .arg(params)); + } } -void QGCApplication::showCriticalVehicleMessage(const QString& message) -{ - // PreArm messages are handled by Vehicle and shown in Map - if (message.startsWith(QStringLiteral("PreArm")) || message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) { - return; - } - QObject* rootQmlObject = _rootQmlObject(); - if (rootQmlObject) { - QVariant varReturn; - QVariant varMessage = QVariant::fromValue(message); - QMetaObject::invokeMethod(_rootQmlObject(), "showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, varMessage)); - } else if (runningUnitTests()) { - // Unit tests can run without UI - qDebug() << "QGCApplication::showCriticalVehicleMessage unittest" << message; - } else { - qWarning() << "Internal error"; - } +QObject *QGCApplication::_rootQmlObject() { + if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) + return _qmlAppEngine->rootObjects()[0]; + return nullptr; } -void QGCApplication::showAppMessage(const QString& message, const QString& title) -{ - QString dialogTitle = title.isEmpty() ? applicationName() : title; +void QGCApplication::showCriticalVehicleMessage(const QString &message) { + // PreArm messages are handled by Vehicle and shown in Map + if (message.startsWith(QStringLiteral("PreArm")) || + message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) { + return; + } + QObject *rootQmlObject = _rootQmlObject(); + if (rootQmlObject) { + QVariant varReturn; + QVariant varMessage = QVariant::fromValue(message); + QMetaObject::invokeMethod(_rootQmlObject(), "showCriticalVehicleMessage", + Q_RETURN_ARG(QVariant, varReturn), + Q_ARG(QVariant, varMessage)); + } else if (runningUnitTests()) { + // Unit tests can run without UI + qDebug() << "QGCApplication::showCriticalVehicleMessage unittest" + << message; + } else { + qWarning() << "Internal error"; + } +} - QObject* rootQmlObject = _rootQmlObject(); - if (rootQmlObject) { - QVariant varReturn; - QVariant varMessage = QVariant::fromValue(message); - QMetaObject::invokeMethod(_rootQmlObject(), "showMessageDialog", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage)); - } else if (runningUnitTests()) { - // Unit tests can run without UI - qDebug() << "QGCApplication::showAppMessage unittest title:message" << dialogTitle << message; - } else { - // UI isn't ready yet - _delayedAppMessages.append(QPair(dialogTitle, message)); - QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); - } +void QGCApplication::showAppMessage(const QString &message, + const QString &title) { + QString dialogTitle = title.isEmpty() ? applicationName() : title; + + QObject *rootQmlObject = _rootQmlObject(); + if (rootQmlObject) { + QVariant varReturn; + QVariant varMessage = QVariant::fromValue(message); + QMetaObject::invokeMethod(_rootQmlObject(), "showMessageDialog", + Q_RETURN_ARG(QVariant, varReturn), + Q_ARG(QVariant, dialogTitle), + Q_ARG(QVariant, varMessage)); + } else if (runningUnitTests()) { + // Unit tests can run without UI + qDebug() << "QGCApplication::showAppMessage unittest title:message" + << dialogTitle << message; + } else { + // UI isn't ready yet + _delayedAppMessages.append(QPair(dialogTitle, message)); + QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); + } } -void QGCApplication::_showDelayedAppMessages(void) -{ - if (_rootQmlObject()) { - for (const QPair& appMsg: _delayedAppMessages) { - showAppMessage(appMsg.second, appMsg.first); - } - _delayedAppMessages.clear(); - } else { - QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); +void QGCApplication::_showDelayedAppMessages(void) { + if (_rootQmlObject()) { + for (const QPair &appMsg : _delayedAppMessages) { + showAppMessage(appMsg.second, appMsg.first); } + _delayedAppMessages.clear(); + } else { + QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); + } } -QQuickItem* QGCApplication::mainRootWindow() -{ - if(!_mainRootWindow) { - _mainRootWindow = reinterpret_cast(_rootQmlObject()); - } - return _mainRootWindow; +QQuickItem *QGCApplication::mainRootWindow() { + if (!_mainRootWindow) { + _mainRootWindow = reinterpret_cast(_rootQmlObject()); + } + return _mainRootWindow; } -void QGCApplication::showSetupView() -{ - if(_rootQmlObject()) { - QMetaObject::invokeMethod(_rootQmlObject(), "showSetupTool"); - } +void QGCApplication::showSetupView() { + if (_rootQmlObject()) { + QMetaObject::invokeMethod(_rootQmlObject(), "showSetupTool"); + } } -void QGCApplication::qmlAttemptWindowClose() -{ - if(_rootQmlObject()) { - QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose"); - } +void QGCApplication::qmlAttemptWindowClose() { + if (_rootQmlObject()) { + QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose"); + } } -bool QGCApplication::isInternetAvailable() -{ - if(_toolbox->settingsManager()->appSettings()->checkInternet()->rawValue().toBool()) - return getQGCMapEngine()->isInternetActive(); - return true; +bool QGCApplication::isInternetAvailable() { + if (_toolbox->settingsManager() + ->appSettings() + ->checkInternet() + ->rawValue() + .toBool()) + return getQGCMapEngine()->isInternetActive(); + return true; } -void QGCApplication::_checkForNewVersion() -{ +void QGCApplication::_checkForNewVersion() { #ifndef __mobile__ - if (!_runningUnitTests) { - if (_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) { - QString versionCheckFile = toolbox()->corePlugin()->stableVersionCheckFileUrl(); - if (!versionCheckFile.isEmpty()) { - QGCFileDownload* download = new QGCFileDownload(this); - connect(download, &QGCFileDownload::downloadComplete, this, &QGCApplication::_qgcCurrentStableVersionDownloadComplete); - download->download(versionCheckFile); - } - } + if (!_runningUnitTests) { + if (_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, + _buildVersion)) { + QString versionCheckFile = + toolbox()->corePlugin()->stableVersionCheckFileUrl(); + if (!versionCheckFile.isEmpty()) { + QGCFileDownload *download = new QGCFileDownload(this); + connect(download, &QGCFileDownload::downloadComplete, this, + &QGCApplication::_qgcCurrentStableVersionDownloadComplete); + download->download(versionCheckFile); + } } + } #endif } -void QGCApplication::_qgcCurrentStableVersionDownloadComplete(QString /*remoteFile*/, QString localFile, QString errorMsg) -{ +void QGCApplication::_qgcCurrentStableVersionDownloadComplete( + QString /*remoteFile*/, QString localFile, QString errorMsg) { #ifdef __mobile__ - Q_UNUSED(localFile) - Q_UNUSED(errorMsg) + Q_UNUSED(localFile) + Q_UNUSED(errorMsg) #else - if (errorMsg.isEmpty()) { - QFile versionFile(localFile); - if (versionFile.open(QIODevice::ReadOnly)) { - QTextStream textStream(&versionFile); - QString version = textStream.readLine(); - - qDebug() << version; - - int majorVersion, minorVersion, buildVersion; - if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) { - if (_majorVersion < majorVersion || - (_majorVersion == majorVersion && _minorVersion < minorVersion) || - (_majorVersion == majorVersion && _minorVersion == minorVersion && _buildVersion < buildVersion)) { - showAppMessage(tr("There is a newer version of %1 available. You can download it from %2.").arg(applicationName()).arg(toolbox()->corePlugin()->stableDownloadLocation()), tr("New Version Available")); - } - } + if (errorMsg.isEmpty()) { + QFile versionFile(localFile); + if (versionFile.open(QIODevice::ReadOnly)) { + QTextStream textStream(&versionFile); + QString version = textStream.readLine(); + + qDebug() << version; + + int majorVersion, minorVersion, buildVersion; + if (_parseVersionText(version, majorVersion, minorVersion, + buildVersion)) { + if (_majorVersion < majorVersion || + (_majorVersion == majorVersion && _minorVersion < minorVersion) || + (_majorVersion == majorVersion && _minorVersion == minorVersion && + _buildVersion < buildVersion)) { + showAppMessage( + tr("There is a newer version of %1 available. You can download " + "it from %2.") + .arg(applicationName()) + .arg(toolbox()->corePlugin()->stableDownloadLocation()), + tr("New Version Available")); } - } else { - qDebug() << "Download QGC stable version failed" << errorMsg; + } } + } else { + qDebug() << "Download QGC stable version failed" << errorMsg; + } - sender()->deleteLater(); + sender()->deleteLater(); #endif } -bool QGCApplication::_parseVersionText(const QString& versionString, int& majorVersion, int& minorVersion, int& buildVersion) -{ - QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)"); - QRegularExpressionMatch match = regExp.match(versionString); - if (match.hasMatch() && match.lastCapturedIndex() == 3) { - majorVersion = match.captured(1).toInt(); - minorVersion = match.captured(2).toInt(); - buildVersion = match.captured(3).toInt(); - return true; - } +bool QGCApplication::_parseVersionText(const QString &versionString, + int &majorVersion, int &minorVersion, + int &buildVersion) { + QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)"); + QRegularExpressionMatch match = regExp.match(versionString); + if (match.hasMatch() && match.lastCapturedIndex() == 3) { + majorVersion = match.captured(1).toInt(); + minorVersion = match.captured(2).toInt(); + buildVersion = match.captured(3).toInt(); + return true; + } - return false; + return false; } - -void QGCApplication::_onGPSConnect() -{ - _gpsRtkFactGroup->connected()->setRawValue(true); +void QGCApplication::_onGPSConnect() { + _gpsRtkFactGroup->connected()->setRawValue(true); } -void QGCApplication::_onGPSDisconnect() -{ - _gpsRtkFactGroup->connected()->setRawValue(false); +void QGCApplication::_onGPSDisconnect() { + _gpsRtkFactGroup->connected()->setRawValue(false); } -void QGCApplication::_gpsSurveyInStatus(float duration, float accuracyMM, double latitude, double longitude, float altitude, bool valid, bool active) -{ - _gpsRtkFactGroup->currentDuration()->setRawValue(duration); - _gpsRtkFactGroup->currentAccuracy()->setRawValue(static_cast(accuracyMM) / 1000.0); - _gpsRtkFactGroup->currentLatitude()->setRawValue(latitude); - _gpsRtkFactGroup->currentLongitude()->setRawValue(longitude); - _gpsRtkFactGroup->currentAltitude()->setRawValue(altitude); - _gpsRtkFactGroup->valid()->setRawValue(valid); - _gpsRtkFactGroup->active()->setRawValue(active); +void QGCApplication::_gpsSurveyInStatus(float duration, float accuracyMM, + double latitude, double longitude, + float altitude, bool valid, + bool active) { + _gpsRtkFactGroup->currentDuration()->setRawValue(duration); + _gpsRtkFactGroup->currentAccuracy()->setRawValue( + static_cast(accuracyMM) / 1000.0); + _gpsRtkFactGroup->currentLatitude()->setRawValue(latitude); + _gpsRtkFactGroup->currentLongitude()->setRawValue(longitude); + _gpsRtkFactGroup->currentAltitude()->setRawValue(altitude); + _gpsRtkFactGroup->valid()->setRawValue(valid); + _gpsRtkFactGroup->active()->setRawValue(active); } -void QGCApplication::_gpsNumSatellites(int numSatellites) -{ - _gpsRtkFactGroup->numSatellites()->setRawValue(numSatellites); +void QGCApplication::_gpsNumSatellites(int numSatellites) { + _gpsRtkFactGroup->numSatellites()->setRawValue(numSatellites); } -QString QGCApplication::cachedParameterMetaDataFile(void) -{ - QSettings settings; - QDir parameterDir = QFileInfo(settings.fileName()).dir(); - return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.xml")); +QString QGCApplication::cachedParameterMetaDataFile(void) { + QSettings settings; + QDir parameterDir = QFileInfo(settings.fileName()).dir(); + return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.xml")); } -QString QGCApplication::cachedAirframeMetaDataFile(void) -{ - QSettings settings; - QDir airframeDir = QFileInfo(settings.fileName()).dir(); - return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml")); +QString QGCApplication::cachedAirframeMetaDataFile(void) { + QSettings settings; + QDir airframeDir = QFileInfo(settings.fileName()).dir(); + return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml")); } diff --git a/src/RouteMissionItem/AreaData.cc b/src/RouteMissionItem/AreaData.cc index add9a2b0d2c607f4cb7296e74abd9c1c060deed9..1a56f2740e95958c3e2b78ec08259834e2e33acd 100644 --- a/src/RouteMissionItem/AreaData.cc +++ b/src/RouteMissionItem/AreaData.cc @@ -1,137 +1,211 @@ -#include "WimaPlanData.h" +#include "AreaData.h" -AreaData::AreaData(QObject *parent) : QObject(parent) {} +#include "geometry/MeasurementArea.h" +#include "geometry/SafeArea.h" -AreaData::AreaData(const AreaData &other, QObject *parent) - : QObject(parent) { - *this = other; -} +#include "QGCLoggingCategory.h" +#include "QGCQGeoCoordinate.h" -AreaData &AreaData::operator=(const AreaData &other) { - this->append(other.measurementArea()); - this->append(other.serviceArea()); - this->append(other.joinedArea()); - this->append(other.corridor()); +QGC_LOGGING_CATEGORY(AreaDataLog, "AreaDataLog") - return *this; -} +AreaData::AreaData(QObject *parent) : QObject(parent) {} -void AreaData::append(const WimaJoinedAreaData &areaData) { - if (_joinedArea != areaData) { - _joinedArea = areaData; - emit joinedAreaChanged(); - } -} +AreaData::~AreaData() {} -void AreaData::append(const WimaServiceAreaData &areaData) { - if (_serviceArea != areaData) { - _serviceArea = areaData; - emit serviceAreaChanged(); +AreaData::AreaData(const AreaData &other, QObject *parent) : QObject(parent) { + if (!copyAreaList(other._areaList, _areaList, this)) { + qCWarning(AreaDataLog) << "AreaData(): not able to copy other._areaList"; + } else { + _origin = other._origin; } } -void AreaData::append(const WimaCorridorData &areaData) { - if (_corridor != areaData) { - _corridor = areaData; - emit corridorChanged(); +AreaData &AreaData::operator=(const AreaData &other) { + if (!copyAreaList(other._areaList, _areaList, this)) { + qCWarning(AreaDataLog) << "operator=(): not able to copy other._areaList"; + } else { + _origin = other._origin; } + return *this; } -void AreaData::append(const WimaMeasurementAreaData &areaData) { - if (_measurementArea != areaData) { - _measurementArea = areaData; - emit measurementAreaChanged(); - - if (_measurementArea.coordinateList().size() > 0) { - setOrigin(_measurementArea.coordinateList().first()); - } else { - setOrigin(QGeoCoordinate()); +bool AreaData::insert(GeoArea *areaData) { + { + SafeArea *area = qobject_cast(areaData); + if (area != nullptr) { + if (Q_LIKELY(!this->_areaList.contains(area))) { + _areaList.append(area); + emit areaList(); + return true; + } else { + return false; + } } } -} -void AreaData::append(const WimaJoinedArea &areaData) { - if (_joinedArea != areaData) { - _joinedArea = areaData; - emit joinedAreaChanged(); + { + MeasurementArea *area = qobject_cast(areaData); + if (area != nullptr) { + if (Q_LIKELY(!this->_areaList.contains(area))) { + _areaList.append(area); + emit areaList(); + return true; + } else { + return false; + } + } } -} -void AreaData::append(const WimaArea &areaData) { - if (_serviceArea != areaData) { - _serviceArea = areaData; - emit serviceAreaChanged(); - } + return false; } -void AreaData::append(const WimaCorridor &areaData) { - if (_corridor != areaData) { - _corridor = areaData; - emit corridorChanged(); - } -} +void AreaData::remove(GeoArea *areaData) { + int index = _areaList.indexOf(areaData); + if (index >= 0) { + QObject *obj = _areaList.removeAt(index); -void AreaData::append(const WimaMeasurementArea &areaData) { - if (_measurementArea != areaData) { - _measurementArea = areaData; - emit measurementAreaChanged(); + _setOrigin(_newOrigin()); - if (_measurementArea.coordinateList().size() > 0) { - setOrigin(_measurementArea.coordinateList().first()); - } else { - setOrigin(QGeoCoordinate()); + if (obj->parent() == nullptr) { + obj->deleteLater(); } - } -} - -void AreaData::clear() { *this = AreaData(); } - -const QGeoCoordinate &AreaData::origin() const { return _origin; } -bool AreaData::isValid() { - return _measurementArea.coordinateList().size() >= 3 && - _serviceArea.coordinateList().size() >= 3 && _origin.isValid(); + emit areaListChanged(); + } } -const WimaJoinedAreaData &AreaData::joinedArea() const { - return this->_joinedArea; +void AreaData::clear() { + if (_areaList.count() > 0) { + for (int i = 0; i < _areaList.count(); ++i) { + remove(_areaList.value(i)); + } + emit areaListChanged(); + } } -const WimaServiceAreaData &AreaData::serviceArea() const { - return this->_serviceArea; -} +QmlObjectListModel *AreaData::areaList() { return &_areaList; } -const WimaCorridorData &AreaData::corridor() const { - return this->_corridor; -} +const QmlObjectListModel *AreaData::areaList() const { return &_areaList; } -const WimaMeasurementAreaData &AreaData::measurementArea() const { - return this->_measurementArea; -} +const QGeoCoordinate &AreaData::origin() const { return _origin; } -WimaJoinedAreaData &AreaData::joinedArea() { return this->_joinedArea; } +bool AreaData::isValid() const { + qWarning("AreaData::isValid(): impl. incomplete."); + auto *measurementArea = getGeoArea(_areaList); + auto *safeArea = getGeoArea(_areaList); + return measurementArea != nullptr && safeArea != nullptr && + measurementArea->count() >= 3 && safeArea->count() >= 3 && + _origin.isValid(); +} + +bool AreaData::tryMakeValid() { + qWarning("AreaData::tryMakeValid(): impl. missing."); + return true; +} + +bool AreaData::initialize(const QGeoCoordinate &bottomLeft, + const QGeoCoordinate &topRight) { + // bottomLeft and topRight define the bounding box. + if (bottomLeft.isValid() && topRight.isValid() && bottomLeft != topRight) { + auto *measurementArea = getGeoArea(_areaList); + auto *safeArea = getGeoArea(_areaList); + + if (safeArea == nullptr) { + if (!insert(new SafeArea())) { + qCCritical(AreaDataLog) + << "initialize(): safeArea == nullptr, but insert() failed."; + return false; + } + } -WimaServiceAreaData &AreaData::serviceArea() { return this->_serviceArea; } + if (measurementArea == nullptr) { + if (!insert(new MeasurementArea())) { + qCCritical(AreaDataLog) << "initialize(): measurementArea == nullptr, " + "but insert() failed."; + return false; + } + } -WimaCorridorData &AreaData::corridor() { return this->_corridor; } + // Fit safe area to bounding box. + safeArea->clear(); + safeArea->appendVertex(bottomLeft); + safeArea->appendVertex( + QGeoCoordinate(topRight.latitude(), bottomLeft.longitude())); + safeArea->appendVertex(topRight); + safeArea->appendVertex( + QGeoCoordinate(bottomLeft.latitude(), topRight.longitude())); + + // Put measurement area inside safeArea; + measurementArea->clear(); + measurementArea->appendVertex(QGeoCoordinate( + 0.8 * bottomLeft.latitude() + 0.2 * topRight.latitude(), + 0.8 * bottomLeft.longitude() + 0.2 * topRight.longitude())); + measurementArea->appendVertex(QGeoCoordinate( + 0.2 * bottomLeft.latitude() + 0.8 * topRight.latitude(), + 0.8 * bottomLeft.longitude() + 0.2 * topRight.longitude())); + measurementArea->appendVertex(QGeoCoordinate( + 0.2 * bottomLeft.latitude() + 0.8 * topRight.latitude(), + 0.2 * bottomLeft.longitude() + 0.8 * topRight.longitude())); + measurementArea->appendVertex(QGeoCoordinate( + 0.8 * bottomLeft.latitude() + 0.2 * topRight.latitude(), + 0.2 * bottomLeft.longitude() + 0.8 * topRight.longitude())); + return true; + } else { + qCWarning(AreaDataLog) + << "initialize(): bounding box invaldid (bottomLeft, topRight) " + << bottomLeft << "," << topRight; + return false; + } +} -WimaMeasurementAreaData &AreaData::measurementArea() { - return this->_measurementArea; +bool AreaData::initialized() { + auto *measurementArea = getGeoArea(_areaList); + auto *safeArea = getGeoArea(_areaList); + return measurementArea != nullptr && safeArea != nullptr && + measurementArea->count() >= 3 && safeArea->count() >= 3; } bool AreaData::operator==(const AreaData &other) const { - return this->_joinedArea == other._joinedArea && - this->_measurementArea == other._measurementArea && - this->_corridor == other._corridor && - this->_serviceArea == other._serviceArea; + if (_areaList.count() == other._areaList.count()) { + for (int i = 0; i < _areaList.count(); ++i) { + if (_areaList[i] != other._areaList[i]) { + return false; + } + } + return true; + } else { + return false; + } } bool AreaData::operator!=(const AreaData &other) const { return !(*this == other); } -void AreaData::setOrigin(const QGeoCoordinate &origin) { +void AreaData::_setOrigin(const QGeoCoordinate &origin) { if (this->_origin != origin) { this->_origin = origin; emit originChanged(); } } + +QGeoCoordinate AreaData::_newOrigin() { + auto *measurementArea = getGeoArea(_areaList); + auto *safeArea = getGeoArea(_areaList); + if (measurementArea != nullptr && measurementArea->pathModel().count() > 0) { + QGCQGeoCoordinate *ori = + measurementArea->pathModel().value(0); + if (ori != nullptr && ori->coordinate().isValid()) { + return ori->coordinate(); + } + } + + if (safeArea != nullptr && safeArea->pathModel().count() > 0) { + QGCQGeoCoordinate *ori = + measurementArea->pathModel().value(0); + if (ori != nullptr && ori->coordinate().isValid()) { + return ori->coordinate(); + } + } + + return QGeoCoordinate(); +} diff --git a/src/RouteMissionItem/AreaData.h b/src/RouteMissionItem/AreaData.h new file mode 100644 index 0000000000000000000000000000000000000000..dd056823deb6492f382eb8d500b4f227b810493e --- /dev/null +++ b/src/RouteMissionItem/AreaData.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include "QmlObjectListModel.h" + +class GeoArea; +class SafeArea; +class MeasurementArea; + +class AreaData : public QObject { + Q_OBJECT +public: + AreaData(QObject *parent = nullptr); + ~AreaData(); + AreaData(const AreaData &other, QObject *parent = nullptr); + AreaData &operator=(const AreaData &other); + + // Member Methodes + //! + //! \brief insert Inserts the area if areaList does not contain it. + //! \param areaData + bool insert(GeoArea *areaData); + //! + //! \brief remove + //! \param areaData Removes the area. + //! \note Deletes the area if it has no parent. + void remove(GeoArea *areaData); + void clear(); + //! + //! \brief areaList + //! \return Returns the list of areas. + //! \note For Qml use only, don't alter the list, or risk to break invariants. + QmlObjectListModel *areaList(); + //! + //! \brief areaList + //! \return Returns the list of areas. + const QmlObjectListModel *areaList() const; + + //! + //! \brief origin + //! \return Returns an origin near one of the areas. + //! \note Origin might change if the list of areas changes. + const QGeoCoordinate &origin() const; + + Q_INVOKABLE bool isValid() const; + Q_INVOKABLE bool tryMakeValid(); + //! + //! \brief initialize Initializes the areas in a valid way, such that they + //! area inside the bounding box. \param bottomLeft bottom left corner of the + //! bounding box. \param topRight top right corner of the bounding box. \note + //! Behavior is undefined, if \p bottomLeft and \p topRight are not the bottom + //! left and the top right corner of the bounding box. \return Returns true on + //! succes, false either. + //! + Q_INVOKABLE bool initialize(const QGeoCoordinate &bottomLeft, + const QGeoCoordinate &topRight); + //! + //! \brief initialized Checks if area data is initialized + //! \return Returns true if area list contains a SafeArea and a + //! MeasurementArea and both areas have atleast three vertices, returns false + //! either. + //! + Q_INVOKABLE bool initialized(); + + bool operator==(const AreaData &other) const; + bool operator!=(const AreaData &other) const; + +signals: + void areaListChanged(); + void originChanged(); + +private: + void _setOrigin(const QGeoCoordinate &origin); + QGeoCoordinate _newOrigin(); + + QGeoCoordinate _origin; + QmlObjectListModel _areaList; +}; diff --git a/src/RouteMissionItem/CircularGenerator.cpp b/src/RouteMissionItem/CircularGenerator.cpp index 838dae395b68dc2cc7019203fb458542ec4331fd..89636064d2d5afdc1ef8e48954a6e131b9a9be2f 100644 --- a/src/RouteMissionItem/CircularGenerator.cpp +++ b/src/RouteMissionItem/CircularGenerator.cpp @@ -1,19 +1,22 @@ #include "CircularGenerator.h" #include "QGCLoggingCategory.h" -QGC_LOGGING_CATEGORY(CircularGeneratorLog, "CircularGeneratorLog") +#include "SettingsFact.h" #define CLIPPER_SCALE 1000000 -#include "Wima/Geometry/GenericCircle.h" -#include "clipper/clipper.hpp" +#include "RoutingThread.h" +#include "geometry/GenericCircle.h" +#include "geometry/MeasurementArea.h" +#include "geometry/SafeArea.h" +#include "geometry/clipper/clipper.hpp" +#include "nemo_interface/SnakeTile.h" + +QGC_LOGGING_CATEGORY(CircularGeneratorLog, "CircularGeneratorLog") using namespace ClipperLib; template <> inline auto get<0>(const IntPoint &p) { return p.X; } template <> inline auto get<1>(const IntPoint &p) { return p.Y; } -#include "SnakeTile.h" -#include "Wima/RoutingThread.h" - namespace routing { bool circularTransects(const snake::FPoint &reference, @@ -77,7 +80,13 @@ bool CircularGenerator::get(Generator &generator) { snake::FPoint reference; snake::toENU(origin, ref, reference); - auto geoPolygon = this->_d->measurementArea().coordinateList(); + auto measurementArea = + getGeoArea(*this->_d->areaList()); + if (measurementArea == nullptr) { + qCDebug(CircularGeneratorLog) << "get(): measurement area == nullptr"; + return false; + } + auto geoPolygon = measurementArea->coordinateList(); for (auto &v : geoPolygon) { if (v.isValid()) { v.setAltitude(0); @@ -93,8 +102,8 @@ bool CircularGenerator::get(Generator &generator) { snake::areaToEnu(origin, geoPolygon, *pPolygon); // Progress and tiles. - const auto &progress = this->_d->measurementArea().progress(); - const auto *tiles = this->_d->measurementArea().tiles(); + const auto &progress = measurementArea->progress(); + const auto *tiles = measurementArea->tiles(); auto pTiles = std::make_shared>(); if (progress.size() == tiles->count()) { for (int i = 0; i < tiles->count(); ++i) { @@ -119,7 +128,12 @@ bool CircularGenerator::get(Generator &generator) { return false; } - auto geoDepot = this->_d->serviceArea().depot(); + auto serviceArea = getGeoArea(*this->_d->areaList()); + if (measurementArea == nullptr) { + qCDebug(CircularGeneratorLog) << "get(): measurement area == nullptr"; + return false; + } + auto geoDepot = serviceArea->depot(); if (!geoDepot.isValid()) { qCDebug(CircularGeneratorLog) << "get(): depot invalid." << geoDepot; return false; @@ -169,73 +183,85 @@ void CircularGenerator::resetReferenceIfInvalid() { } void CircularGenerator::resetReference() { - if (this->_d->measurementArea().center().isValid()) { - setReference(this->_d->measurementArea().center()); + auto measurementArea = + getGeoArea(*this->_d->areaList()); + + if (measurementArea != nullptr) { + if (measurementArea->center().isValid()) { + setReference(measurementArea->center()); + } else { + qCWarning(CircularGeneratorLog) + << "measurement area center" << measurementArea->center(); + } } else { - qCWarning(CircularGeneratorLog) - << "measurement area center" << this->_d->measurementArea().center(); + qCDebug(CircularGeneratorLog) + << "resetReference(): measurement area == nullptr"; } } void CircularGenerator::establishConnections() { if (this->_d && !this->_connectionsEstablished) { - connect(this->_d.get(), &AreaData::originChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::progressChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::tileDataChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::centerChanged, this, - &CircularGenerator::resetReferenceIfInvalid); - connect(&this->_d->measurementArea(), &WimaMeasurementAreaData::pathChanged, - this, &GeneratorBase::generatorChanged); - connect(&this->_d->serviceArea(), &WimaServiceAreaData::depotChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->joinedArea(), &WimaJoinedAreaData::pathChanged, this, - &GeneratorBase::generatorChanged); - connect(this->distance(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this->deltaAlpha(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this->minLength(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this, &CircularGenerator::referenceChanged, this, - &GeneratorBase::generatorChanged); - this->_connectionsEstablished = true; + auto measurementArea = + getGeoArea(*this->_d->areaList()); + auto serviceArea = getGeoArea(*this->_d->areaList()); + if (measurementArea != nullptr && serviceArea != nullptr) { + GeneratorBase::establishConnections(); + + connect(this->_d, &AreaData::originChanged, this, + &GeneratorBase::generatorChanged); + connect(measurementArea, &MeasurementArea::progressChanged, this, + &GeneratorBase::generatorChanged); + connect(measurementArea, &MeasurementArea::tilesChanged, this, + &GeneratorBase::generatorChanged); + connect(measurementArea, &MeasurementArea::centerChanged, this, + &CircularGenerator::resetReferenceIfInvalid); + connect(measurementArea, &MeasurementArea::pathChanged, this, + &GeneratorBase::generatorChanged); + connect(serviceArea, &SafeArea::depotChanged, this, + &GeneratorBase::generatorChanged); + connect(this->distance(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + connect(this->deltaAlpha(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + connect(this->minLength(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + connect(this, &CircularGenerator::referenceChanged, this, + &GeneratorBase::generatorChanged); + this->_connectionsEstablished = true; + } } } void CircularGenerator::deleteConnections() { if (this->_d && this->_connectionsEstablished) { - disconnect(this->_d.get(), &AreaData::originChanged, this, - &GeneratorBase::generatorChanged); - disconnect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::progressChanged, this, - &GeneratorBase::generatorChanged); - disconnect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::tileDataChanged, this, - &GeneratorBase::generatorChanged); - disconnect(&this->_d->measurementArea(), &WimaMeasurementAreaData::center, - this, &CircularGenerator::resetReferenceIfInvalid); - disconnect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::pathChanged, this, - &GeneratorBase::generatorChanged); - disconnect(&this->_d->serviceArea(), &WimaServiceAreaData::depotChanged, - this, &GeneratorBase::generatorChanged); - disconnect(&this->_d->joinedArea(), &WimaJoinedAreaData::pathChanged, this, - &GeneratorBase::generatorChanged); - disconnect(this->distance(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - disconnect(this->deltaAlpha(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - disconnect(this->minLength(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - disconnect(this, &CircularGenerator::referenceChanged, this, - &GeneratorBase::generatorChanged); - this->_connectionsEstablished = false; + auto measurementArea = + getGeoArea(*this->_d->areaList()); + auto serviceArea = getGeoArea(*this->_d->areaList()); + if (measurementArea != nullptr && serviceArea != nullptr) { + GeneratorBase::deleteConnections(); + + disconnect(this->_d, &AreaData::originChanged, this, + &GeneratorBase::generatorChanged); + disconnect(measurementArea, &MeasurementArea::progressChanged, this, + &GeneratorBase::generatorChanged); + disconnect(measurementArea, &MeasurementArea::tilesChanged, this, + &GeneratorBase::generatorChanged); + disconnect(measurementArea, &MeasurementArea::centerChanged, this, + &CircularGenerator::resetReferenceIfInvalid); + disconnect(measurementArea, &MeasurementArea::pathChanged, this, + &GeneratorBase::generatorChanged); + disconnect(serviceArea, &SafeArea::depotChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this->distance(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this->deltaAlpha(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this->minLength(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this, &CircularGenerator::referenceChanged, this, + &GeneratorBase::generatorChanged); + this->_connectionsEstablished = true; + } } } diff --git a/src/RouteMissionItem/CircularGenerator.h b/src/RouteMissionItem/CircularGenerator.h index d4458a2292ea9b90317d2be338f0848b6fe56503..7a9ff8b110908e83a1ec7c4255f4c75ed101a8ec 100644 --- a/src/RouteMissionItem/CircularGenerator.h +++ b/src/RouteMissionItem/CircularGenerator.h @@ -2,6 +2,8 @@ #include +#include "SettingsFact.h" + namespace routing { class CircularGenerator : public GeneratorBase { diff --git a/src/RouteMissionItem/GeneratorBase.cc b/src/RouteMissionItem/GeneratorBase.cc index bc315e54b9bd9319d433208f20a75bf29a53c5bd..beadd3511f5fcc33a006b8e988324e2da7759358 100644 --- a/src/RouteMissionItem/GeneratorBase.cc +++ b/src/RouteMissionItem/GeneratorBase.cc @@ -12,10 +12,6 @@ GeneratorBase::GeneratorBase(GeneratorBase::Data d, QObject *parent) GeneratorBase::~GeneratorBase() {} -QString GeneratorBase::editorQml() { return QStringLiteral(""); } - -QString GeneratorBase::mapVisualQml() { return QStringLiteral(""); } - GeneratorBase::Data GeneratorBase::data() const { return _d; } void GeneratorBase::setData(const Data &d) { @@ -28,4 +24,9 @@ void GeneratorBase::establishConnections() {} void GeneratorBase::deleteConnections() {} +void GeneratorBase::_areaListChangedHandler() { + deleteConnections(); + establishConnections(); +} + } // namespace routing diff --git a/src/RouteMissionItem/GeneratorBase.h b/src/RouteMissionItem/GeneratorBase.h index 96605d0543d90c260e02a7127ce86890dbd13c1d..74d3be41473d5addf18ffbb1591369d41287956a 100644 --- a/src/RouteMissionItem/GeneratorBase.h +++ b/src/RouteMissionItem/GeneratorBase.h @@ -5,16 +5,16 @@ #include #include -#include "snake.h" +#include "geometry/snake.h" -#include "Wima/WimaPlanData.h" +#include "AreaData.h" namespace routing { class GeneratorBase : public QObject { Q_OBJECT public: - using Data = std::shared_ptr; + using Data = AreaData *; using Generator = std::function; explicit GeneratorBase(QObject *parent = nullptr); @@ -24,8 +24,8 @@ public: Q_PROPERTY(QString editorQml READ editorQml CONSTANT) Q_PROPERTY(QString mapVisualQml READ mapVisualQml CONSTANT) - virtual QString editorQml(); - virtual QString mapVisualQml(); + virtual QString editorQml() = 0; + virtual QString mapVisualQml() = 0; virtual QString name() = 0; virtual QString abbreviation() = 0; @@ -42,6 +42,9 @@ protected: virtual void establishConnections(); virtual void deleteConnections(); Data _d; + +private: + void _areaListChangedHandler(); }; } // namespace routing diff --git a/src/RouteMissionItem/LinearGenerator.cpp b/src/RouteMissionItem/LinearGenerator.cpp index 7f5c4b739f038850e1a6165814f4cb157cc7aef0..acfa8047ed6b5e6386b7276776f322e8d2563fba 100644 --- a/src/RouteMissionItem/LinearGenerator.cpp +++ b/src/RouteMissionItem/LinearGenerator.cpp @@ -4,10 +4,12 @@ QGC_LOGGING_CATEGORY(LinearGeneratorLog, "LinearGeneratorLog") #define CLIPPER_SCALE 1000000 -#include "clipper/clipper.hpp" +#include "geometry/MeasurementArea.h" +#include "geometry/SafeArea.h" +#include "geometry/clipper/clipper.hpp" -#include "SnakeTile.h" -#include "Wima/RoutingThread.h" +#include "RoutingThread.h" +#include "nemo_interface/SnakeTile.h" namespace routing { @@ -38,6 +40,8 @@ QString LinearGenerator::editorQml() { return QStringLiteral("LinearGeneratorEditor.qml"); } +QString LinearGenerator::mapVisualQml() { return QStringLiteral(""); } + QString LinearGenerator::name() { return QStringLiteral("Linear Generator"); } QString LinearGenerator::abbreviation() { return QStringLiteral("L. Gen."); } @@ -52,7 +56,13 @@ bool LinearGenerator::get(Generator &generator) { qCDebug(LinearGeneratorLog) << "get(): origin invalid." << origin; } - auto geoPolygon = this->_d->measurementArea().coordinateList(); + auto measurementArea = + getGeoArea(*this->_d->areaList()); + if (measurementArea == nullptr) { + qCDebug(LinearGeneratorLog) << "get(): measurement area == nullptr"; + return false; + } + auto geoPolygon = measurementArea->coordinateList(); for (auto &v : geoPolygon) { if (v.isValid()) { v.setAltitude(0); @@ -68,8 +78,8 @@ bool LinearGenerator::get(Generator &generator) { snake::areaToEnu(origin, geoPolygon, *pPolygon); // Progress and tiles. - const auto &progress = this->_d->measurementArea().progress(); - const auto *tiles = this->_d->measurementArea().tiles(); + const auto &progress = measurementArea->progress(); + const auto *tiles = measurementArea->tiles(); auto pTiles = std::make_shared>(); if (progress.size() == tiles->count()) { for (int i = 0; i < tiles->count(); ++i) { @@ -93,7 +103,12 @@ bool LinearGenerator::get(Generator &generator) { return false; } - auto geoDepot = this->_d->serviceArea().depot(); + auto serviceArea = getGeoArea(*this->_d->areaList()); + if (serviceArea == nullptr) { + qCDebug(LinearGeneratorLog) << "get(): service area == nullptr"; + return false; + } + auto geoDepot = serviceArea->depot(); if (!geoDepot.isValid()) { qCDebug(LinearGeneratorLog) << "get(): depot invalid." << geoDepot; return false; @@ -134,53 +149,61 @@ Fact *LinearGenerator::minLength() { return &_minLength; } void LinearGenerator::establishConnections() { if (this->_d && !this->_connectionsEstablished) { - connect(this->_d.get(), &AreaData::originChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::progressChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::tileDataChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), &WimaMeasurementAreaData::pathChanged, - this, &GeneratorBase::generatorChanged); - connect(&this->_d->serviceArea(), &WimaServiceAreaData::depotChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->joinedArea(), &WimaJoinedAreaData::pathChanged, this, - &GeneratorBase::generatorChanged); - connect(this->distance(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this->alpha(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this->minLength(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - this->_connectionsEstablished = true; + auto measurementArea = + getGeoArea(*this->_d->areaList()); + auto serviceArea = getGeoArea(*this->_d->areaList()); + + if (measurementArea != nullptr && serviceArea != nullptr) { + GeneratorBase::establishConnections(); + + connect(this->_d, &AreaData::originChanged, this, + &GeneratorBase::generatorChanged); + connect(measurementArea, &MeasurementArea::progressChanged, this, + &GeneratorBase::generatorChanged); + connect(measurementArea, &MeasurementArea::tilesChanged, this, + &GeneratorBase::generatorChanged); + connect(measurementArea, &MeasurementArea::pathChanged, this, + &GeneratorBase::generatorChanged); + connect(serviceArea, &SafeArea::depotChanged, this, + &GeneratorBase::generatorChanged); + connect(this->distance(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + connect(this->alpha(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + connect(this->minLength(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + this->_connectionsEstablished = true; + } } } void LinearGenerator::deleteConnections() { if (this->_d && this->_connectionsEstablished) { - connect(this->_d.get(), &AreaData::originChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::progressChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), - &WimaMeasurementAreaData::tileDataChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->measurementArea(), &WimaMeasurementAreaData::pathChanged, - this, &GeneratorBase::generatorChanged); - connect(&this->_d->serviceArea(), &WimaServiceAreaData::depotChanged, this, - &GeneratorBase::generatorChanged); - connect(&this->_d->joinedArea(), &WimaJoinedAreaData::pathChanged, this, - &GeneratorBase::generatorChanged); - connect(this->distance(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this->alpha(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - connect(this->minLength(), &Fact::rawValueChanged, this, - &GeneratorBase::generatorChanged); - this->_connectionsEstablished = false; + auto measurementArea = + getGeoArea(*this->_d->areaList()); + auto serviceArea = getGeoArea(*this->_d->areaList()); + + if (measurementArea != nullptr && serviceArea != nullptr) { + GeneratorBase::deleteConnections(); + + disconnect(this->_d, &AreaData::originChanged, this, + &GeneratorBase::generatorChanged); + disconnect(measurementArea, &MeasurementArea::progressChanged, this, + &GeneratorBase::generatorChanged); + disconnect(measurementArea, &MeasurementArea::tilesChanged, this, + &GeneratorBase::generatorChanged); + disconnect(measurementArea, &MeasurementArea::pathChanged, this, + &GeneratorBase::generatorChanged); + disconnect(serviceArea, &SafeArea::depotChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this->distance(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this->alpha(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + disconnect(this->minLength(), &Fact::rawValueChanged, this, + &GeneratorBase::generatorChanged); + this->_connectionsEstablished = true; + } } } diff --git a/src/RouteMissionItem/LinearGenerator.h b/src/RouteMissionItem/LinearGenerator.h index ba63cf51a322f7c6e3d0b3cdc30ae3a6b5c6183e..87d35304974fd77333c8b9ad725036ce968134d0 100644 --- a/src/RouteMissionItem/LinearGenerator.h +++ b/src/RouteMissionItem/LinearGenerator.h @@ -2,6 +2,8 @@ #include +#include "SettingsFact.h" + namespace routing { class LinearGenerator : public GeneratorBase { @@ -15,6 +17,7 @@ public: Q_PROPERTY(Fact *minLength READ minLength CONSTANT) virtual QString editorQml() override; + virtual QString mapVisualQml() override; virtual QString name() override; virtual QString abbreviation() override; diff --git a/src/RouteMissionItem/NemoInterface.cpp b/src/RouteMissionItem/NemoInterface.cpp index dd07f8d3999ed8127796f5562e89f869698951d3..94a61efa883a5d5526c0362dce2fe32eef9d018c 100644 --- a/src/RouteMissionItem/NemoInterface.cpp +++ b/src/RouteMissionItem/NemoInterface.cpp @@ -1,5 +1,5 @@ #include "NemoInterface.h" -#include "SnakeTilesLocal.h" +#include "nemo_interface/SnakeTilesLocal.h" #include "QGCApplication.h" #include "QGCLoggingCategory.h" @@ -12,11 +12,11 @@ #include -#include "QNemoHeartbeat.h" -#include "QNemoProgress.h" -#include "Wima/Geometry/WimaMeasurementArea.h" -#include "Wima/Snake/SnakeTile.h" -#include "Wima/Snake/snake.h" +#include "geometry/MeasurementArea.h" +#include "geometry/snake.h" +#include "nemo_interface/QNemoHeartbeat.h" +#include "nemo_interface/QNemoProgress.h" +#include "nemo_interface/SnakeTile.h" #include "ros_bridge/include/messages/geographic_msgs/geopoint.h" #include "ros_bridge/include/messages/jsk_recognition_msgs/polygon_array.h" diff --git a/src/RouteMissionItem/OptimisationTools.cc b/src/RouteMissionItem/OptimisationTools.cc deleted file mode 100644 index 0d03739df441d601eb955d9c7ff7bd85e3f762c0..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/OptimisationTools.cc +++ /dev/null @@ -1,105 +0,0 @@ -#include "OptimisationTools.h" - -namespace OptimisationTools { - namespace { - - } // end anonymous namespace - - bool dijkstraAlgorithm(const int numElements, int startIndex, int endIndex, QVector &elementPath, std::function distanceDij) - { - if ( numElements < 0 - || startIndex < 0 - || endIndex < 0 - || startIndex >= numElements - || endIndex >= numElements - || endIndex == startIndex) { - return false; - } - // Node struct - // predecessorIndex is the index of the predecessor node (nodeList[predecessorIndex]) - // distance is the distance between the node and the start node - // node number is stored by the position in nodeList - struct Node{ - int predecessorIndex = -1; - double distance = std::numeric_limits::infinity(); - }; - - // The list with all Nodes (elements) - QVector nodeList(numElements); - // This list will be initalized with indices referring to the elements of nodeList. - // Elements will be successively remove during the execution of the Dijkstra Algorithm. - QVector workingSet(numElements); - - //append elements to node list - for (int i = 0; i < numElements; ++i) workingSet[i] = i; - - - nodeList[startIndex].distance = 0; - -// qDebug() << "nodeList" ; -// for (auto node : nodeList) { -// qDebug() << "predecessor: " << node.predecessorIndex; -// qDebug() << "distance: " << node.distance; -// } -// qDebug() << "workingSet"; -// for (auto node : workingSet) { -// qDebug() << "index: " << node; -// } - - // Dijkstra Algorithm - // https://de.wikipedia.org/wiki/Dijkstra-Algorithmus - while (workingSet.size() > 0) { - // serach Node with minimal distance - double minDist = std::numeric_limits::infinity(); - int minDistIndex_WS = -1; // WS = workinSet - for (int i = 0; i < workingSet.size(); ++i) { - const int nodeIndex = workingSet.at(i); - const double dist = nodeList.at(nodeIndex).distance; - if (dist < minDist) { - minDist = dist; - minDistIndex_WS = i; - } - } - if (minDistIndex_WS == -1) - return false; - - int indexU_NL = workingSet.takeAt(minDistIndex_WS); // NL = nodeList - if (indexU_NL == endIndex) // shortest path found - break; - - const double distanceU = nodeList.at(indexU_NL).distance; - //update distance - for (int i = 0; i < workingSet.size(); ++i) { - int indexV_NL = workingSet[i]; // NL = nodeList - Node* v = &nodeList[indexV_NL]; - double dist = distanceDij(indexU_NL, indexV_NL); - // is ther an alternative path which is shorter? - double alternative = distanceU + dist; - if (alternative < v->distance) { - v->distance = alternative; - v->predecessorIndex = indexU_NL; - } - } - - } - // end Djikstra Algorithm - - - // reverse assemble path - int e = endIndex; - while (1) { - if (e == -1) { - if (elementPath[0] == startIndex)// check if starting point was reached - break; - return false; - } - elementPath.prepend(e); - - //Update Node - e = nodeList[e].predecessorIndex; - - } - return true; - } - -} // end OptimisationTools namespace diff --git a/src/RouteMissionItem/OptimisationTools.h b/src/RouteMissionItem/OptimisationTools.h deleted file mode 100644 index 11c37431dc27d7ae63663b741889d775d5dd14d1..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/OptimisationTools.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include -#include - -#include -#include - -namespace OptimisationTools { - bool dijkstraAlgorithm(const int numElements, int startIndex, int endIndex, QVector &elementPath, std::function distanceDij); -} - - diff --git a/src/RouteMissionItem/RouteComplexItem.cc b/src/RouteMissionItem/RouteComplexItem.cc index c5c2ffc2cf17d91e182e7d6ba935af1699590684..a0ee639745eb63beb1dd6ce6f2ab290b46ca0854 100644 --- a/src/RouteMissionItem/RouteComplexItem.cc +++ b/src/RouteMissionItem/RouteComplexItem.cc @@ -1,91 +1,87 @@ -#include "CircularSurvey.h" +#include "RouteComplexItem.h" + +#include "CircularGenerator.h" +#include "LinearGenerator.h" #include "RoutingThread.h" +#include "geometry/GenericCircle.h" +#include "geometry/MeasurementArea.h" +#include "geometry/SafeArea.h" +#include "geometry/clipper/clipper.hpp" +#include "geometry/snake.h" +#include "nemo_interface/SnakeTile.h" + // QGC #include "JsonHelper.h" #include "QGCApplication.h" #include "QGCLoggingCategory.h" -// Wima -#include "snake.h" -#define CLIPPER_SCALE 1000000 -#include "clipper/clipper.hpp" - -#include "Geometry/GenericCircle.h" -#include "Snake/SnakeTile.h" // boost #include #include -#include "CircularGenerator.h" -#include "LinearGenerator.h" - -// ToDo: Check what happened to _transectsDirty +#define CLIPPER_SCALE 1000000 -QGC_LOGGING_CATEGORY(CircularSurveyLog, "CircularSurveyLog") +QGC_LOGGING_CATEGORY(RouteComplexItemLog, "RouteComplexItemLog") template constexpr typename std::underlying_type::type integral(T value) { return static_cast::type>(value); } -const char *CircularSurvey::settingsGroup = "CircularSurvey"; -const char *CircularSurvey::jsonComplexItemTypeValue = "CircularSurvey"; -const char *CircularSurvey::variantName = "Variant"; -const QString CircularSurvey::name(tr("Circular Survey")); +const char *RouteComplexItem::settingsGroup = "Route"; +const char *RouteComplexItem::jsonComplexItemTypeValue = "Route"; +const char *RouteComplexItem::variantName = "Variant"; +const QString RouteComplexItem::name(tr("Route")); -CircularSurvey::CircularSurvey(PlanMasterController *masterController, - bool flyView, const QString &kmlOrShpFile, - QObject *parent) +RouteComplexItem::RouteComplexItem(PlanMasterController *masterController, + bool flyView, const QString &kmlOrShpFile, + QObject *parent) : TransectStyleComplexItem(masterController, flyView, settingsGroup, parent), _state(STATE::IDLE), _metaDataMap(FactMetaData::createMapFromJsonFile( - QStringLiteral(":/json/CircularSurvey.SettingsGroup.json"), this)), + QStringLiteral(":/json/RouteComplexItem.SettingsGroup.json"), this)), _variant(settingsGroup, _metaDataMap[variantName]), - _areaData(std::make_shared()), - _pWorker(std::make_unique()) { + _areaData(new AreaData(this)), _editorData(new AreaData(this)), + _currentData(_areaData), _pWorker(new RoutingThread(this)) { Q_UNUSED(kmlOrShpFile) _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml"; // Connect facts. connect(&this->_variant, &Fact::rawValueChanged, this, - &CircularSurvey::_changeVariant); + &RouteComplexItem::_changeVariant); // Connect worker. - connect(this->_pWorker.get(), &RoutingThread::result, this, - &CircularSurvey::_setTransects); - connect(this->_pWorker.get(), &RoutingThread::calculatingChanged, this, - &CircularSurvey::calculatingChanged); + connect(this->_pWorker, &RoutingThread::result, this, + &RouteComplexItem::_setTransects); + connect(this->_pWorker, &RoutingThread::calculatingChanged, this, + &RouteComplexItem::calculatingChanged); // Register Generators. - auto lg = std::make_shared(this->_areaData); + auto lg = new routing::LinearGenerator(this->_areaData, this); registerGenerator(lg->name(), lg); - auto cg = std::make_shared(this->_areaData); + auto cg = new routing::CircularGenerator(this->_areaData, this); registerGenerator(cg->name(), cg); } -CircularSurvey::~CircularSurvey() {} +RouteComplexItem::~RouteComplexItem() {} -void CircularSurvey::reverse() { +void RouteComplexItem::revertPath() { this->_setState(STATE::REVERT_PATH); this->_rebuildTransects(); } -void CircularSurvey::setPlanData(const AreaData &d) { - *this->_areaData = d; -} - -const AreaData &CircularSurvey::planData() const { - return *this->_areaData; +const AreaData *RouteComplexItem::areaData() const { + return this->_currentData; } -AreaData &CircularSurvey::planData() { return *this->_areaData; } +AreaData *RouteComplexItem::areaData() { return this->_currentData; } -QStringList CircularSurvey::variantNames() const { return _variantNames; } +QStringList RouteComplexItem::variantNames() const { return _variantNames; } -bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber, - QString &errorString) { +bool RouteComplexItem::load(const QJsonObject &complexObject, + int sequenceNumber, QString &errorString) { // We need to pull version first to determine what validation/conversion // needs to be performed QList versionKeyInfoList = { @@ -152,11 +148,11 @@ bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber, return true; } -QString CircularSurvey::mapVisualQML() const { +QString RouteComplexItem::mapVisualQML() const { return QStringLiteral("CircularSurveyMapVisual.qml"); } -void CircularSurvey::save(QJsonArray &planItems) { +void RouteComplexItem::save(QJsonArray &planItems) { QJsonObject saveObject; _save(saveObject); @@ -175,22 +171,20 @@ void CircularSurvey::save(QJsonArray &planItems) { planItems.append(saveObject); } -bool CircularSurvey::specifiesCoordinate() const { +bool RouteComplexItem::specifiesCoordinate() const { return _transects.count() > 0 ? _transects.first().count() > 0 : false; } -bool CircularSurvey::_switchToGenerator( - const CircularSurvey::PtrGenerator &newG) { +bool RouteComplexItem::_setGenerator(PtrGenerator newG) { if (this->_pGenerator != newG) { if (this->_pGenerator != nullptr) { - disconnect(this->_pGenerator.get(), - &routing::GeneratorBase::generatorChanged, this, - &CircularSurvey::_rebuildTransects); + disconnect(this->_pGenerator, &routing::GeneratorBase::generatorChanged, + this, &RouteComplexItem::_rebuildTransects); } this->_pGenerator = newG; - connect(this->_pGenerator.get(), &routing::GeneratorBase::generatorChanged, - this, &CircularSurvey::_rebuildTransects); + connect(this->_pGenerator, &routing::GeneratorBase::generatorChanged, this, + &RouteComplexItem::_rebuildTransects); emit generatorChanged(); this->_setState(STATE::IDLE); @@ -202,7 +196,7 @@ bool CircularSurvey::_switchToGenerator( } } -void CircularSurvey::_setState(CircularSurvey::STATE state) { +void RouteComplexItem::_setState(RouteComplexItem::STATE state) { if (this->_state != state) { auto oldState = this->_state; this->_state = state; @@ -212,16 +206,30 @@ void CircularSurvey::_setState(CircularSurvey::STATE state) { } } -bool CircularSurvey::_calculating(CircularSurvey::STATE state) const { +bool RouteComplexItem::_calculating(RouteComplexItem::STATE state) const { return state == STATE::ROUTING; } -void CircularSurvey::_changeVariant() { +void RouteComplexItem::_setEditing(bool editing) { + if (editing != _editing) { + _editing = editing; + emit editingChanged(); + } +} + +void RouteComplexItem::_setAreaData(RouteComplexItem::PtrAreaData data) { + if (_currentData != data) { + _currentData = data; + emit areaDataChanged(); + } +} + +void RouteComplexItem::_changeVariant() { this->_setState(STATE::CHANGE_VARIANT); this->_rebuildTransects(); } -bool CircularSurvey::_updateWorker() { +bool RouteComplexItem::_updateWorker() { // Reset data. this->_transects.clear(); this->_variantVector.clear(); @@ -234,15 +242,17 @@ bool CircularSurvey::_updateWorker() { auto origin = this->_areaData->origin(); origin.setAltitude(0); if (!origin.isValid()) { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_updateWorker(): origin invalid." << origin; return false; } // Convert safe area. - auto geoSafeArea = this->_areaData->joinedArea().coordinateList(); + auto serviceArea = + getGeoArea(*this->_areaData->areaList()); + auto geoSafeArea = serviceArea->coordinateList(); if (!(geoSafeArea.size() >= 3)) { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_updateWorker(): safe area invalid." << geoSafeArea; return false; } @@ -250,7 +260,7 @@ bool CircularSurvey::_updateWorker() { if (v.isValid()) { v.setAltitude(0); } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_updateWorker(): safe area contains invalid coordinate." << geoSafeArea; return false; @@ -271,24 +281,24 @@ bool CircularSurvey::_updateWorker() { this->_pWorker->route(par, g); return true; } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_updateWorker(): generator creation failed."; return false; } } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_updateWorker(): pGenerator == nullptr, number of registered " "generators: " << this->_generatorList.size(); return false; } } else { - qCDebug(CircularSurveyLog) << "_updateWorker(): plan data invalid."; + qCDebug(RouteComplexItemLog) << "_updateWorker(): plan data invalid."; return false; } } -void CircularSurvey::_changeVariantWorker() { +void RouteComplexItem::_changeVariantWorker() { auto variant = this->_variant.rawValue().toUInt(); // Find old variant and run. Old run corresponts with empty list. @@ -315,15 +325,15 @@ void CircularSurvey::_changeVariantWorker() { this->_transects.swap(newVariantCoordinates); } else { // error - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "Variant out of bounds (variant =" << variant << ")."; - qCDebug(CircularSurveyLog) << "Resetting variant to zero."; + qCDebug(RouteComplexItemLog) << "Resetting variant to zero."; disconnect(&this->_variant, &Fact::rawValueChanged, this, - &CircularSurvey::_changeVariant); + &RouteComplexItem::_changeVariant); this->_variant.setCookedValue(QVariant(0)); connect(&this->_variant, &Fact::rawValueChanged, this, - &CircularSurvey::_changeVariant); + &RouteComplexItem::_changeVariant); if (this->_variantVector.size() > 0) { this->_changeVariantWorker(); @@ -332,25 +342,23 @@ void CircularSurvey::_changeVariantWorker() { } } -void CircularSurvey::_reverseWorker() { +void RouteComplexItem::_reverseWorker() { if (this->_transects.size() > 0) { auto &t = this->_transects.front(); std::reverse(t.begin(), t.end()); } } -double CircularSurvey::timeBetweenShots() { return 0; } +double RouteComplexItem::timeBetweenShots() { return 0; } -QString CircularSurvey::commandDescription() const { - return tr("Circular Survey"); -} +QString RouteComplexItem::commandDescription() const { return tr("Route"); } -QString CircularSurvey::commandName() const { return tr("Circular Survey"); } +QString RouteComplexItem::commandName() const { return tr("Route"); } -QString CircularSurvey::abbreviation() const { return tr("C.S."); } +QString RouteComplexItem::abbreviation() const { return tr("R"); } TransectStyleComplexItem::ReadyForSaveState -CircularSurvey::readyForSaveState() const { +RouteComplexItem::readyForSaveState() const { if (TransectStyleComplexItem::readyForSaveState() == TransectStyleComplexItem::ReadyForSaveState::ReadyForSave) { if (this->_state == STATE::IDLE) { @@ -363,31 +371,31 @@ CircularSurvey::readyForSaveState() const { } } -double CircularSurvey::additionalTimeDelay() const { return 0; } +double RouteComplexItem::additionalTimeDelay() const { return 0; } -QString CircularSurvey::patternName() const { return name; } +QString RouteComplexItem::patternName() const { return name; } -bool CircularSurvey::registerGenerator( - const QString &name, std::shared_ptr g) { +bool RouteComplexItem::registerGenerator(const QString &name, + routing::GeneratorBase *g) { if (name.isEmpty()) { - qCDebug(CircularSurveyLog) << "registerGenerator(): empty name string."; + qCDebug(RouteComplexItemLog) << "registerGenerator(): empty name string."; return false; } if (!g) { - qCDebug(CircularSurveyLog) << "registerGenerator(): empty generator."; + qCDebug(RouteComplexItemLog) << "registerGenerator(): empty generator."; return false; } if (this->_generatorNameList.contains(name)) { - qCDebug(CircularSurveyLog) << "registerGenerator(): generator " - "already registered."; + qCDebug(RouteComplexItemLog) << "registerGenerator(): generator " + "already registered."; return false; } else { this->_generatorNameList.push_back(name); this->_generatorList.push_back(g); if (this->_generatorList.size() == 1) { - _switchToGenerator(g); + _setGenerator(g); } emit generatorNameListChanged(); @@ -395,111 +403,128 @@ bool CircularSurvey::registerGenerator( } } -bool CircularSurvey::unregisterGenerator(const QString &name) { +bool RouteComplexItem::unregisterGenerator(const QString &name) { auto index = this->_generatorNameList.indexOf(name); if (index >= 0) { // Is this the current generator? const auto &g = this->_generatorList.at(index); if (g == this->_pGenerator) { if (index > 0) { - _switchToGenerator(this->_generatorList.at(index - 1)); + _setGenerator(this->_generatorList.at(index - 1)); } else { - _switchToGenerator(nullptr); - qCDebug(CircularSurveyLog) + _setGenerator(nullptr); + qCDebug(RouteComplexItemLog) << "unregisterGenerator(): last generator unregistered."; } } this->_generatorNameList.removeAt(index); - this->_generatorList.removeAt(index); + auto gen = this->_generatorList.takeAt(index); + gen->deleteLater(); emit generatorNameListChanged(); return true; } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "unregisterGenerator(): generator " << name << " not registered."; return false; } } -bool CircularSurvey::unregisterGenerator(int index) { +bool RouteComplexItem::unregisterGenerator(int index) { if (index > 0 && index < this->_generatorNameList.size()) { return unregisterGenerator(this->_generatorNameList.at(index)); } else { - qCDebug(CircularSurveyLog) << "unregisterGenerator(): index (" << index - << ") out" - "of bounds ( " - << this->_generatorList.size() << " )."; + qCDebug(RouteComplexItemLog) << "unregisterGenerator(): index (" << index + << ") out" + "of bounds ( " + << this->_generatorList.size() << " )."; return false; } } -bool CircularSurvey::switchToGenerator(const QString &name) { +bool RouteComplexItem::switchToGenerator(const QString &name) { auto index = this->_generatorNameList.indexOf(name); if (index >= 0) { - _switchToGenerator(this->_generatorList.at(index)); + _setGenerator(this->_generatorList.at(index)); return true; } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "switchToGenerator(): generator " << name << " not registered."; return false; } } -bool CircularSurvey::switchToGenerator(int index) { +bool RouteComplexItem::switchToGenerator(int index) { if (index >= 0) { - _switchToGenerator(this->_generatorList.at(index)); + _setGenerator(this->_generatorList.at(index)); return true; } else { - qCDebug(CircularSurveyLog) << "unregisterGenerator(): index (" << index - << ") out" - "of bounds ( " - << this->_generatorNameList.size() << " )."; + qCDebug(RouteComplexItemLog) << "unregisterGenerator(): index (" << index + << ") out" + "of bounds ( " + << this->_generatorNameList.size() << " )."; return false; } } -QStringList CircularSurvey::generatorNameList() { +QStringList RouteComplexItem::generatorNameList() { return this->_generatorNameList; } -routing::GeneratorBase *CircularSurvey::generator() { - return _pGenerator.get(); -} +routing::GeneratorBase *RouteComplexItem::generator() { return _pGenerator; } -int CircularSurvey::generatorIndex() { +int RouteComplexItem::generatorIndex() { return this->_generatorList.indexOf(this->_pGenerator); } -void CircularSurvey::_rebuildTransectsPhase1(void) { +void RouteComplexItem::editingStart() { + if (!_editing) { + *_editorData = *_areaData; + _setAreaData(_editorData); + _setEditing(true); + } +} + +void RouteComplexItem::editingStop() { + if (_editing) { + if (_editorData->isValid()) { + *_areaData = *_editorData; + } + _setAreaData(_areaData); + _setEditing(false); + } +} + +void RouteComplexItem::_rebuildTransectsPhase1(void) { auto start = std::chrono::high_resolution_clock::now(); switch (this->_state) { case STATE::SKIPP: - qCDebug(CircularSurveyLog) << "rebuildTransectsPhase1: skipp."; + qCDebug(RouteComplexItemLog) << "rebuildTransectsPhase1: skipp."; this->_setState(STATE::IDLE); break; case STATE::CHANGE_VARIANT: - qCDebug(CircularSurveyLog) << "rebuildTransectsPhase1: variant change."; + qCDebug(RouteComplexItemLog) << "rebuildTransectsPhase1: variant change."; this->_changeVariantWorker(); this->_setState(STATE::IDLE); break; case STATE::REVERT_PATH: - qCDebug(CircularSurveyLog) << "rebuildTransectsPhase1: reverse."; + qCDebug(RouteComplexItemLog) << "rebuildTransectsPhase1: reverse."; this->_reverseWorker(); this->_setState(STATE::IDLE); break; case STATE::IDLE: case STATE::ROUTING: this->_setState(STATE::ROUTING); - qCDebug(CircularSurveyLog) << "rebuildTransectsPhase1: update."; + qCDebug(RouteComplexItemLog) << "rebuildTransectsPhase1: update."; if (!this->_updateWorker()) { this->_setState(STATE::IDLE); } break; } - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "rebuildTransectsPhase1(): " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) @@ -507,10 +532,9 @@ void CircularSurvey::_rebuildTransectsPhase1(void) { << " ms"; } -// no cameraShots in Circular Survey, add if desired -void CircularSurvey::_recalcCameraShots() { _cameraShots = 0; } +void RouteComplexItem::_recalcCameraShots() { _cameraShots = 0; } -void CircularSurvey::_setTransects(CircularSurvey::PtrRoutingData pRoute) { +void RouteComplexItem::_setTransects(RouteComplexItem::PtrRoutingData pRoute) { // Store solutions. auto ori = this->_areaData->origin(); ori.setAltitude(0); @@ -567,19 +591,19 @@ void CircularSurvey::_setTransects(CircularSurvey::PtrRoutingData pRoute) { } } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_setTransects(): lastTransect.size() == 0"; } } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_setTransects(): firstTransect.size() == 0"; } } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_setTransects(): transectsInfo.size() <= 1"; } } else { - qCDebug(CircularSurveyLog) << "_setTransects(): solution.size() == 0"; + qCDebug(RouteComplexItemLog) << "_setTransects(): solution.size() == 0"; } if (var.size() > 0 && var.front().size() > 0) { @@ -609,38 +633,25 @@ void CircularSurvey::_setTransects(CircularSurvey::PtrRoutingData pRoute) { emit variantNamesChanged(); disconnect(&this->_variant, &Fact::rawValueChanged, this, - &CircularSurvey::_changeVariant); + &RouteComplexItem::_changeVariant); this->_variant.setCookedValue(QVariant(0)); connect(&this->_variant, &Fact::rawValueChanged, this, - &CircularSurvey::_changeVariant); + &RouteComplexItem::_changeVariant); this->_changeVariantWorker(); this->_setState(STATE::SKIPP); this->_rebuildTransects(); } else { - qCDebug(CircularSurveyLog) + qCDebug(RouteComplexItemLog) << "_setTransects(): failed, variantVector empty."; this->_setState(STATE::IDLE); } } -Fact *CircularSurvey::variant() { return &_variant; } +Fact *RouteComplexItem::variant() { return &_variant; } -bool CircularSurvey::calculating() const { +bool RouteComplexItem::calculating() const { return this->_calculating(this->_state); } -/*! - \class CircularSurveyComplexItem - \inmodule Wima - - \brief The \c CircularSurveyComplexItem class provides a survey mission - item with circular transects around a point of interest. - - CircularSurveyComplexItem class provides a survey mission item with - circular transects around a point of interest. Within the \c Wima module - it's used to scan a defined area with constant angle (circular transects) - to the base station (point of interest). - - \sa WimaArea -*/ +bool RouteComplexItem::editing() const { return this->_editing; } diff --git a/src/RouteMissionItem/RouteComplexItem.h b/src/RouteMissionItem/RouteComplexItem.h index 83981c9c457d859fec9338a2fdb4492282210d07..57c12c1d7c0466e98b3c01715165e8895984909c 100644 --- a/src/RouteMissionItem/RouteComplexItem.h +++ b/src/RouteMissionItem/RouteComplexItem.h @@ -7,9 +7,7 @@ #include "SettingsFact.h" #include "TransectStyleComplexItem.h" -#include "Geometry/WimaJoinedAreaData.h" -#include "Geometry/WimaMeasurementAreaData.h" -#include "WimaPlanData.h" +#include "AreaData.h" class RoutingThread; class RoutingData; @@ -18,21 +16,22 @@ namespace routing { class GeneratorBase; } -class CircularSurvey : public TransectStyleComplexItem { +class RouteComplexItem : public TransectStyleComplexItem { Q_OBJECT - using PtrGenerator = std::shared_ptr; + using PtrGenerator = routing::GeneratorBase *; + using PtrAreaData = AreaData *; using PtrRoutingData = std::shared_ptr; - using PtrWorker = std::unique_ptr; + using PtrWorker = RoutingThread *; using Transects = QList>; using Variant = Transects; enum class STATE { IDLE, ROUTING, SKIPP, REVERT_PATH, CHANGE_VARIANT }; public: - CircularSurvey(PlanMasterController *masterController, bool flyView, - const QString &kmlOrShpFile, QObject *parent); - ~CircularSurvey(); + RouteComplexItem(PlanMasterController *masterController, bool flyView, + const QString &kmlOrShpFile, QObject *parent); + ~RouteComplexItem(); Q_PROPERTY(Fact *variant READ variant CONSTANT) Q_PROPERTY( @@ -43,14 +42,18 @@ public: Q_PROPERTY( routing::GeneratorBase *generator READ generator NOTIFY generatorChanged) Q_PROPERTY(int generatorIndex READ generatorIndex NOTIFY generatorChanged) + Q_PROPERTY(bool editing READ editing NOTIFY editingChanged) + Q_PROPERTY(AreaData *areaData READ areaData NOTIFY areaDataChanged) - Q_INVOKABLE void revert_path(void); + Q_INVOKABLE void revertPath(void); // Property getters - const AreaData &planData() const; + const AreaData *areaData() const; + AreaData *areaData(); Fact *variant(); QStringList variantNames() const; bool calculating() const; + bool editing() const; // Overrides virtual bool load(const QJsonObject &complexObject, int sequenceNumber, @@ -67,8 +70,7 @@ public: virtual QString patternName(void) const override; // Generator - bool registerGenerator(const QString &name, - std::shared_ptr g); + bool registerGenerator(const QString &name, routing::GeneratorBase *g); bool unregisterGenerator(const QString &name); bool unregisterGenerator(int index); Q_INVOKABLE bool switchToGenerator(const QString &name); @@ -77,6 +79,22 @@ public: routing::GeneratorBase *generator(); int generatorIndex(); + // Editing. + //! + //! \brief editingStart Starts area data editing. + //! + //! Starts area data editing. Transects will not be updated bewteen a call + //! sequence of editingStart() and editingStop(). + //! + void editingStart(); + //! + //! \brief editingStop Stops area editing. + //! + //! Stops area editing. Will reset area data to the state before + //! editingStart() if it is invalid. Triggers a transect update. + //! + void editingStop(); + static const char *settingsGroup; static const char *variantName; static const char *jsonComplexItemTypeValue; @@ -87,6 +105,8 @@ signals: void variantNamesChanged(); void generatorNameListChanged(); void generatorChanged(); + void editingChanged(); + void areaDataChanged(); private slots: // Overrides from TransectStyleComplexItem @@ -101,20 +121,25 @@ private slots: void _reverseWorker(); private: - bool _switchToGenerator(const PtrGenerator &newG); + bool _setGenerator(PtrGenerator newG); void _setState(STATE state); bool _calculating(STATE state) const; + void _setEditing(bool editing); + void _setAreaData(PtrAreaData data); // State. STATE _state; - // center of the circular lanes, e.g. base station + // Facts QMap _metaDataMap; SettingsFact _variant; QStringList _variantNames; // Area data - AreaData _areaData; + PtrAreaData _areaData; + PtrAreaData _editorData; + PtrAreaData _currentData; + bool _editing; // Generators QList _generatorList; diff --git a/src/RouteMissionItem/RoutingThread.h b/src/RouteMissionItem/RoutingThread.h index bc5a3f11389c15e847db2438e26abe9c316adc93..79b97cab862ef424506ee52b6bfe2130991fb597 100644 --- a/src/RouteMissionItem/RoutingThread.h +++ b/src/RouteMissionItem/RoutingThread.h @@ -4,7 +4,7 @@ #include #include -#include "snake.h" +#include "geometry/snake.h" #include #include #include diff --git a/src/RouteMissionItem/geometry/GeoArea.cc b/src/RouteMissionItem/geometry/GeoArea.cc new file mode 100644 index 0000000000000000000000000000000000000000..7ff5b13244d75794f2d85fac34eba9669cdc678b --- /dev/null +++ b/src/RouteMissionItem/geometry/GeoArea.cc @@ -0,0 +1,63 @@ +#include "GeoArea.h" + +#include + +const char *GeoArea::wimaAreaName = "GeoArea"; +const char *GeoArea::areaTypeName = "AreaType"; +const char *GeoArea::settingsGroup = "GeoArea"; + +// Constructors +GeoArea::GeoArea(QObject *parent) : QGCMapPolygon(parent) { init(); } + +GeoArea::GeoArea(const GeoArea &other, QObject *parent) + : QGCMapPolygon(other, parent) { + init(); +} + +GeoArea &GeoArea::operator=(const GeoArea &other) { + QGCMapPolygon::operator=(other); + + return *this; +} + +void GeoArea::saveToJson(QJsonObject &json) { + this->QGCMapPolygon::saveToJson(json); +} + +bool GeoArea::loadFromJson(const QJsonObject &json, QString &errorString) { + if (!this->QGCMapPolygon::loadFromJson(json, false /*no poly required*/, + errorString)) { + qWarning() << errorString; + return false; + } + + return true; +} + +bool GeoArea::isSimplePolygon() { + qWarning() << "WimaArea::isSimplePolygon: impl. missing."; + return false; +} + +void GeoArea::init() { this->setObjectName(wimaAreaName); } + +bool copyAreaList(const QmlObjectListModel &from, QmlObjectListModel &to, + QObject *parent) { + // Check if elements are valid. + for (int i = 0; i < from.count(); ++i) { + auto obj = from[i]; + auto area = qobject_cast(obj); + if (area == nullptr) { + return false; + } + } + + // Clone elements. + for (int i = 0; i < from.count(); ++i) { + auto obj = from[i]; + auto area = qobject_cast(obj); + to.append(area->clone(parent)); + } + + return true; +} diff --git a/src/RouteMissionItem/geometry/GeoArea.h b/src/RouteMissionItem/geometry/GeoArea.h new file mode 100644 index 0000000000000000000000000000000000000000..aeb9d54e6b7c2e46a2a6c5fcc2d30a50b381ae9e --- /dev/null +++ b/src/RouteMissionItem/geometry/GeoArea.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "QGCMapPolygon.h" + +class GeoArea : public QGCMapPolygon { + Q_OBJECT +public: + GeoArea(QObject *parent = nullptr); + GeoArea(const GeoArea &other, QObject *parent = nullptr); + GeoArea &operator=(const GeoArea &other); + + Q_PROPERTY(QString mapVisualQML READ mapVisualQML CONSTANT) + Q_PROPERTY(QString editorQML READ editorQML CONSTANT) + + virtual QString mapVisualQML(void) const = 0; + virtual QString editorQML(void) const = 0; + + virtual void saveToJson(QJsonObject &jsonObject); + virtual bool loadFromJson(const QJsonObject &jsonObject, + QString &errorString); + + virtual GeoArea *clone(QObject *parent = nullptr) const = 0; + + bool isSimplePolygon(); + + // static Members + static const char *wimaAreaName; + static const char *areaTypeName; + static const char *settingsGroup; + +private: + void init(); +}; + +// Example usage: +// QmlObjecListModel list; +// .... add areas .... +// auto area = getArea(list); // returns the first +// WimaMeasurementArea or nullptr +template +inline AreaPtr getGeoArea(QObjectList &list) { + static_assert(std::is_pointer::value, + "AreaPtr must be a pointer type."); + for (int i = 0; i < list.count(); ++i) { + auto obj = list[i]; + auto area = qobject_cast(obj); + if (area != nullptr) { + return area; + } + } + return nullptr; +} + +bool copyAreaList(const QmlObjectListModel &from, QmlObjectListModel &to, + QObject *parent); diff --git a/src/RouteMissionItem/geometry/WimaMeasurementArea.cc b/src/RouteMissionItem/geometry/MeasurementArea.cc similarity index 70% rename from src/RouteMissionItem/geometry/WimaMeasurementArea.cc rename to src/RouteMissionItem/geometry/MeasurementArea.cc index ccc2813964cf465358457deab238fddfb39de5d8..3bea51cc6fc911192eebe4844bc1b2da6019ed2e 100644 --- a/src/RouteMissionItem/geometry/WimaMeasurementArea.cc +++ b/src/RouteMissionItem/geometry/MeasurementArea.cc @@ -1,6 +1,6 @@ -#include "WimaMeasurementArea.h" +#include "MeasurementArea.h" #include "QtConcurrentRun" -#include "SnakeTile.h" +#include "nemo_interface/SnakeTile.h" #include "snake.h" #include @@ -11,7 +11,7 @@ #define SNAKE_MAX_TILES 1000 #endif -QGC_LOGGING_CATEGORY(WimaMeasurementAreaLog, "WimaMeasurementAreaLog") +QGC_LOGGING_CATEGORY(MeasurementAreaLog, "MeasurementAreaLog") TileData::TileData() : tiles(this) {} @@ -25,7 +25,7 @@ TileData &TileData::operator=(const TileData &other) { if (tile != nullptr) { this->tiles.append(new SnakeTile(*tile, this)); } else { - qCWarning(WimaMeasurementAreaLog) << "TileData::operator=: nullptr"; + qCWarning(MeasurementAreaLog) << "TileData::operator=: nullptr"; } } this->tileCenterPoints = other.tileCenterPoints; @@ -63,15 +63,15 @@ size_t TileData::size() const { } } -const char *WimaMeasurementArea::settingsGroup = "MeasurementArea"; -const char *WimaMeasurementArea::tileHeightName = "TileHeight"; -const char *WimaMeasurementArea::tileWidthName = "TileWidth"; -const char *WimaMeasurementArea::minTileAreaName = "MinTileAreaPercent"; -const char *WimaMeasurementArea::showTilesName = "ShowTiles"; -const char *WimaMeasurementArea::WimaMeasurementAreaName = "Measurement Area"; +const char *MeasurementArea::settingsGroup = "MeasurementArea"; +const char *MeasurementArea::tileHeightName = "TileHeight"; +const char *MeasurementArea::tileWidthName = "TileWidth"; +const char *MeasurementArea::minTileAreaName = "MinTileAreaPercent"; +const char *MeasurementArea::showTilesName = "ShowTiles"; +const char *MeasurementArea::MeasurementAreaName = "Measurement Area"; -WimaMeasurementArea::WimaMeasurementArea(QObject *parent) - : WimaArea(parent), +MeasurementArea::MeasurementArea(QObject *parent) + : GeoArea(parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/WimaMeasurementArea.SettingsGroup.json"), this /* QObject parent */)), @@ -88,9 +88,8 @@ WimaMeasurementArea::WimaMeasurementArea(QObject *parent) init(); } -WimaMeasurementArea::WimaMeasurementArea(const WimaMeasurementArea &other, - QObject *parent) - : WimaArea(other, parent), +MeasurementArea::MeasurementArea(const MeasurementArea &other, QObject *parent) + : GeoArea(other, parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/WimaMeasurementArea.SettingsGroup.json"), this /* QObject parent */)), @@ -107,79 +106,71 @@ WimaMeasurementArea::WimaMeasurementArea(const WimaMeasurementArea &other, init(); } -/*! - * \overload operator=() - * - * Calls the inherited operator WimaArea::operator=(). - */ -WimaMeasurementArea &WimaMeasurementArea:: -operator=(const WimaMeasurementArea &other) { - WimaArea::operator=(other); +MeasurementArea &MeasurementArea::operator=(const MeasurementArea &other) { + GeoArea::operator=(other); return *this; } -WimaMeasurementArea::~WimaMeasurementArea() {} +MeasurementArea::~MeasurementArea() {} -QString WimaMeasurementArea::mapVisualQML() const { - return QStringLiteral("WimaMeasurementAreaMapVisual.qml"); +QString MeasurementArea::mapVisualQML() const { + return QStringLiteral("MeasurementAreaMapVisual.qml"); } -QString WimaMeasurementArea::editorQML() const { - return QStringLiteral("WimaMeasurementAreaEditor.qml"); +QString MeasurementArea::editorQML() const { + return QStringLiteral("MeasurementAreaEditor.qml"); } -Fact *WimaMeasurementArea::tileHeight() { return &_tileHeight; } +MeasurementArea *MeasurementArea::clone(QObject *parent) const { + return new MeasurementArea(*this, parent); +} -Fact *WimaMeasurementArea::tileWidth() { return &_tileWidth; } +Fact *MeasurementArea::tileHeight() { return &_tileHeight; } -Fact *WimaMeasurementArea::minTileArea() { return &_minTileAreaPercent; } +Fact *MeasurementArea::tileWidth() { return &_tileWidth; } -Fact *WimaMeasurementArea::showTiles() { return &_showTiles; } +Fact *MeasurementArea::minTileArea() { return &_minTileAreaPercent; } -QmlObjectListModel *WimaMeasurementArea::tiles() { - return &this->_tileData.tiles; -} +Fact *MeasurementArea::showTiles() { return &_showTiles; } -const QVector &WimaMeasurementArea::progress() const { - return this->_progress; -} +QmlObjectListModel *MeasurementArea::tiles() { return &this->_tileData.tiles; } -QVector WimaMeasurementArea::progressQml() const { +const QVector &MeasurementArea::progress() const { return this->_progress; } -const QmlObjectListModel *WimaMeasurementArea::tiles() const { +QVector MeasurementArea::progressQml() const { return this->_progress; } + +const QmlObjectListModel *MeasurementArea::tiles() const { return &this->_tileData.tiles; } -const QVariantList &WimaMeasurementArea::tileCenterPoints() const { +const QVariantList &MeasurementArea::tileCenterPoints() const { return this->_tileData.tileCenterPoints; } -const TileData &WimaMeasurementArea::tileData() const { - return this->_tileData; -} +const TileData &MeasurementArea::tileData() const { return this->_tileData; } -int WimaMeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; } +int MeasurementArea::maxTiles() const { return SNAKE_MAX_TILES; } -bool WimaMeasurementArea::ready() const { return this->_state == STATE::IDLE; } +bool MeasurementArea::ready() const { return this->_state == STATE::IDLE; } -void WimaMeasurementArea::saveToJson(QJsonObject &json) { +void MeasurementArea::saveToJson(QJsonObject &json) { if (ready()) { - this->WimaArea::saveToJson(json); + this->GeoArea::saveToJson(json); json[tileHeightName] = _tileHeight.rawValue().toDouble(); json[tileWidthName] = _tileWidth.rawValue().toDouble(); json[minTileAreaName] = _minTileAreaPercent.rawValue().toDouble(); json[showTilesName] = _showTiles.rawValue().toBool(); - json[areaTypeName] = WimaMeasurementAreaName; + json[areaTypeName] = MeasurementAreaName; } else { - qCDebug(WimaMeasurementAreaLog) << "saveToJson(): not ready for saveing."; + qCDebug(MeasurementAreaLog) << "saveToJson(): not ready for saveing."; } } -bool WimaMeasurementArea::loadFromJson(const QJsonObject &json, - QString &errorString) { - if (this->WimaArea::loadFromJson(json, errorString)) { +bool MeasurementArea::loadFromJson(const QJsonObject &json, + QString &errorString) { + if (this->GeoArea::loadFromJson(json, errorString)) { disableUpdate(); bool retVal = true; @@ -220,7 +211,7 @@ bool WimaMeasurementArea::loadFromJson(const QJsonObject &json, } } -bool WimaMeasurementArea::setProgress(const QVector &p) { +bool MeasurementArea::setProgress(const QVector &p) { if (ready()) { if (p.size() == this->tiles()->count() && this->_progress != p) { this->_progress = p; @@ -232,10 +223,10 @@ bool WimaMeasurementArea::setProgress(const QVector &p) { return false; } //! -//! \brief WimaMeasurementArea::doUpdate -//! \pre WimaMeasurementArea::deferUpdate must be called first, don't call +//! \brief MeasurementArea::doUpdate +//! \pre MeasurementArea::deferUpdate must be called first, don't call //! this function directly! -void WimaMeasurementArea::doUpdate() { +void MeasurementArea::doUpdate() { using namespace snake; using namespace boost::units; @@ -292,7 +283,7 @@ void WimaMeasurementArea::doUpdate() { } pData->moveToThread(th); - qCDebug(WimaMeasurementAreaLog) + qCDebug(MeasurementAreaLog) << "doUpdate(): update time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) @@ -305,7 +296,7 @@ void WimaMeasurementArea::doUpdate() { this->_watcher.setFuture(future); } } - qCDebug(WimaMeasurementAreaLog) + qCDebug(MeasurementAreaLog) << "doUpdate(): execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) @@ -313,9 +304,9 @@ void WimaMeasurementArea::doUpdate() { << " ms"; } -void WimaMeasurementArea::deferUpdate() { +void MeasurementArea::deferUpdate() { if (this->_state == STATE::IDLE || this->_state == STATE::DEFERED) { - qCDebug(WimaMeasurementAreaLog) << "defereUpdate(): defer update."; + qCDebug(MeasurementAreaLog) << "defereUpdate(): defer update."; if (this->_state == STATE::IDLE) { this->_progress.clear(); this->_tileData.clear(); @@ -325,16 +316,16 @@ void WimaMeasurementArea::deferUpdate() { this->setState(STATE::DEFERED); this->_timer.start(100); } else if (this->_state == STATE::UPDATEING) { - qCDebug(WimaMeasurementAreaLog) << "defereUpdate(): restart."; + qCDebug(MeasurementAreaLog) << "defereUpdate(): restart."; setState(STATE::RESTARTING); } } -void WimaMeasurementArea::storeTiles() { +void MeasurementArea::storeTiles() { auto start = std::chrono::high_resolution_clock::now(); if (this->_state == STATE::UPDATEING) { - qCDebug(WimaMeasurementAreaLog) << "storeTiles(): update."; + qCDebug(MeasurementAreaLog) << "storeTiles(): update."; this->_tileData = *this->_watcher.result(); // This is expensive. Drawing tiles is expensive too. @@ -343,12 +334,12 @@ void WimaMeasurementArea::storeTiles() { emit this->tilesChanged(); setState(STATE::IDLE); } else if (this->_state == STATE::RESTARTING) { - qCDebug(WimaMeasurementAreaLog) << "storeTiles(): restart."; + qCDebug(MeasurementAreaLog) << "storeTiles(): restart."; doUpdate(); } else if (this->_state == STATE::STOP) { - qCDebug(WimaMeasurementAreaLog) << "storeTiles(): stop."; + qCDebug(MeasurementAreaLog) << "storeTiles(): stop."; } - qCDebug(WimaMeasurementAreaLog) + qCDebug(MeasurementAreaLog) << "storeTiles() execution time: " << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start) @@ -356,36 +347,34 @@ void WimaMeasurementArea::storeTiles() { << " ms"; } -void WimaMeasurementArea::disableUpdate() { +void MeasurementArea::disableUpdate() { setState(STATE::IDLE); this->_timer.stop(); } -void WimaMeasurementArea::enableUpdate() { +void MeasurementArea::enableUpdate() { if (this->_state == STATE::STOP) { setState(STATE::IDLE); } } -void WimaMeasurementArea::init() { - this->setObjectName(WimaMeasurementAreaName); +void MeasurementArea::init() { + this->setObjectName(MeasurementAreaName); connect(&this->_tileHeight, &Fact::rawValueChanged, this, - &WimaMeasurementArea::deferUpdate); + &MeasurementArea::deferUpdate); connect(&this->_tileWidth, &Fact::rawValueChanged, this, - &WimaMeasurementArea::deferUpdate); + &MeasurementArea::deferUpdate); connect(&this->_minTileAreaPercent, &Fact::rawValueChanged, this, - &WimaMeasurementArea::deferUpdate); - connect(this, &WimaArea::pathChanged, this, - &WimaMeasurementArea::deferUpdate); + &MeasurementArea::deferUpdate); + connect(this, &GeoArea::pathChanged, this, &MeasurementArea::deferUpdate); this->_timer.setSingleShot(true); - connect(&this->_timer, &QTimer::timeout, this, - &WimaMeasurementArea::doUpdate); + connect(&this->_timer, &QTimer::timeout, this, &MeasurementArea::doUpdate); connect(&this->_watcher, &QFutureWatcher>::finished, this, - &WimaMeasurementArea::storeTiles); + &MeasurementArea::storeTiles); } -void WimaMeasurementArea::setState(WimaMeasurementArea::STATE s) { +void MeasurementArea::setState(MeasurementArea::STATE s) { if (this->_state != s) { auto oldState = this->_state; this->_state = s; diff --git a/src/RouteMissionItem/geometry/WimaMeasurementArea.h b/src/RouteMissionItem/geometry/MeasurementArea.h similarity index 79% rename from src/RouteMissionItem/geometry/WimaMeasurementArea.h rename to src/RouteMissionItem/geometry/MeasurementArea.h index 07266032584d19d5dfcdf81560e8bc77e4152fe2..1dc40a0be81341cba3595fd397217f761d23e7ca 100644 --- a/src/RouteMissionItem/geometry/WimaMeasurementArea.h +++ b/src/RouteMissionItem/geometry/MeasurementArea.h @@ -2,9 +2,10 @@ #include #include +#include #include -#include "WimaArea.h" +#include "GeoArea.h" #include "SettingsFact.h" @@ -24,17 +25,16 @@ public: std::size_t size() const; }; -class WimaMeasurementArea : public WimaArea { +class MeasurementArea : public GeoArea { Q_OBJECT - enum class STATE { IDLE, DEFERED, UPDATEING, RESTARTING, STOP }; + using DataPtr = QSharedPointer; public: - WimaMeasurementArea(QObject *parent = nullptr); - WimaMeasurementArea(const WimaMeasurementArea &other, - QObject *parent = nullptr); - WimaMeasurementArea &operator=(const WimaMeasurementArea &other); - ~WimaMeasurementArea(); + MeasurementArea(QObject *parent = nullptr); + MeasurementArea(const MeasurementArea &other, QObject *parent = nullptr); + MeasurementArea &operator=(const MeasurementArea &other); + ~MeasurementArea(); Q_PROPERTY(Fact *tileHeight READ tileHeight CONSTANT) Q_PROPERTY(Fact *tileWidth READ tileWidth CONSTANT) @@ -44,9 +44,12 @@ public: Q_PROPERTY(int maxTiles READ maxTiles NOTIFY maxTilesChanged) Q_PROPERTY(QVector progress READ progressQml NOTIFY progressChanged) - // Overrides from WimaPolygon - QString mapVisualQML(void) const; - QString editorQML(void) const; + // Overrides from GeoArea + QString mapVisualQML(void) const override; + QString editorQML(void) const override; + MeasurementArea *clone(QObject *parent = nullptr) const; + void saveToJson(QJsonObject &json) override; + bool loadFromJson(const QJsonObject &json, QString &errorString) override; // Property getters. Fact *tileHeight(); @@ -62,17 +65,13 @@ public: int maxTiles() const; bool ready() const; - // Member Methodes - void saveToJson(QJsonObject &json); - bool loadFromJson(const QJsonObject &json, QString &errorString); - // Static Variables static const char *settingsGroup; static const char *tileHeightName; static const char *tileWidthName; static const char *minTileAreaName; static const char *showTilesName; - static const char *WimaMeasurementAreaName; + static const char *MeasurementAreaName; signals: void tilesChanged(); @@ -109,7 +108,6 @@ private: // Tile stuff. // Tile stuff. mutable QTimer _timer; - using DataPtr = std::shared_ptr; mutable STATE _state; mutable TileData _tileData; mutable QFutureWatcher _watcher; diff --git a/src/RouteMissionItem/geometry/PlanimetryCalculus.cc b/src/RouteMissionItem/geometry/PlanimetryCalculus.cc deleted file mode 100644 index d8053d351c3d131cdfb41c5c7ca6de26cd0a5d8e..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/PlanimetryCalculus.cc +++ /dev/null @@ -1,774 +0,0 @@ -#include "PlanimetryCalculus.h" - -template qreal get(QPointF &p); -template <> qreal get<0>(QPointF &p) { return p.x(); } -template <> qreal get<1>(QPointF &p) { return p.y(); } - -namespace PlanimetryCalculus { -namespace { -/*! - \fn IntersectType intersects(const Circle &circle, const QLineF &line, - PointList &intersectionPoints, bool calcInstersect) Returns the Intersection - type of \a circle and \a line. Stores the intersection points in \a - intersectionPoints if \a calcIntersect is \c true. Returns \c Error if either - line or circe \c {isNull() == true}. - - \sa QPointF, Circle -*/ -bool intersects(const Circle &circle, const QLineF &line, - QPointFVector &intersectionPoints, IntersectType &type, - bool calcInstersect) { - if (!line.isNull()) { - QPointF translationVector = line.p1(); - double alpha = angle(line); // angle between wold and line coordinate system - - QPointF originCircleL = circle.origin() - translationVector; - rotateReference(originCircleL, - -alpha); // circle origin in line corrdinate system - - double y = originCircleL.y(); - double r = circle.radius(); - if (qAbs(y) > r) { - type = NoIntersection; - return false; - } else if (qFuzzyCompare(qFabs(y), r)) { // tangent - double x_ori = originCircleL.x(); - - if (x_ori >= 0 && x_ori <= line.length()) { - if (calcInstersect) { - QPointF intersectionPt = QPointF(x_ori, 0); - rotateReference(intersectionPt, alpha); - intersectionPoints.append(intersectionPt + translationVector); - } - - type = Tangent; - return true; - } - - type = NoIntersection; - return false; - } else { // secant - double x_ori = originCircleL.x(); - double y_ori = originCircleL.y(); - double delta = qSqrt(qPow(r, 2) - qPow(y_ori, 2)); - double x1 = - x_ori + - delta; // x coordinate (line system) of fist intersection point - double x2 = - x_ori - - delta; // x coordinate (line system) of second intersection point - bool doesIntersect = - false; // remember if actual intersection was on the line - - if (x1 >= 0 && - x1 <= line.length()) { // check if intersection point is on the line - if (calcInstersect) { - QPointF intersectionPt = - QPointF(x1, 0); // first intersection point (line system) - rotateReference(intersectionPt, alpha); - intersectionPoints.append( - intersectionPt + - translationVector); // transform (to world system) and append - // first intersection point - } - doesIntersect = true; - } - if (x2 >= 0 && - x2 <= line.length()) { // check if intersection point is on the line - if (calcInstersect) { - QPointF intersectionPt = - QPointF(x2, 0); // second intersection point (line system) - rotateReference(intersectionPt, alpha); - intersectionPoints.append( - intersectionPt + - translationVector); // transform (to world system) and append - // second intersection point - } - doesIntersect = true; - } - type = doesIntersect ? Secant : NoIntersection; - return doesIntersect ? true : false; - } - } - - type = Error; - return false; -} - -/*! - \fn bool intersects(const Circle &circle1, const Circle &circle2, PointList - &intersectionPoints, IntersectType type) Calculates the intersection points - of two circles if present and stores the result in \a intersectionPoints. - Returns the intersection type of the two cirles \a circle1 and \a circle2. - - The function assumes that the list \a intersectionPoints is empty. - - \note Returns Error if circle.isNull() returns true; - - \sa Circle -*/ -bool intersects(const Circle &circle1, const Circle &circle2, - QPointFVector &intersectionPoints, IntersectType type, - bool calcIntersection) { - double r1 = circle1.radius(); - double r2 = circle2.radius(); - double d = distance(circle1.origin(), circle2.origin()); - double r = 0; - double R = 0; - if (r1 > r2) { - R = r1; // large - r = r2; // smallline1 - } else { - // this branch is also choosen if r1 == r2 - R = r2; - r = r1; - } - - // determine intersection type - if (r + d < R) { - // this branch is also reached if d < rLarge && rSmall == 0 - type = InsideNoIntersection; - return false; - } else if (qFuzzyCompare(r + d, R)) { - if (qFuzzyIsNull(d)) { - type = CirclesEqual; - return true; - } else { - type = InsideTouching; - } - } else if (d < R) { - type = InsideIntersection; - } else if (d - r < R) { - type = OutsideIntersection; - } else if (qFuzzyCompare(d - r, R)) { - type = OutsideTouching; - } else { - type = OutsideNoIntersection; - return false; - } - - if (calcIntersection) { - // calculate intersection - - // Coordinate system circle1: origin = circle1.origin(), x-axis towars - // circle2.origin() y-axis such that the coordinate system is dextrorse - // with z-axis outward faceing with respect to the drawing plane. - double alpha = - angle(circle1.origin(), - circle2.origin()); // angle between world and circle1 system - QPointF translationVector = - circle1.origin(); // translation vector between world and circle1 system - - if (type == InsideTouching || type == OutsideTouching) { - // Intersection point in coordinate system of circle 1. - // Coordinate system circle1: origin = circle1.origin(), x-axis towars - // circle2.origin() y-axis such that the coordinate system is dextrorse - // with z-axis outward faceing with respect to the drawing plane. - intersectionPoints.append(rotateReturn(QPointF(0, r1), alpha) + - translationVector); - } else { // triggered if ( type == InsideIntersection - // || type == OutsideIntersection) - // See fist branch for explanation - - // this equations are obtained by solving x^2+y^2=R^2 and (x - - // d)^2+y^2=r^2 - double x = (qPow(d, 2) - qPow(r, 2) + qPow(R, 2)) / 2 / d; - double y = 1 / 2 / d * - qSqrt(4 * qPow(d * R, 2) - - qPow(qPow(d, 2) - qPow(r, 2) + qPow(R, 2), 2)); - - intersectionPoints.append(rotateReturn(QPointF(x, y), alpha) + - translationVector); - intersectionPoints.append(rotateReturn(QPointF(x, -y), alpha) + - translationVector); - } - // Transform the coordinate to the world coordinate system. Alpha is the - // angle between world and circle1 coordinate system. - - return true; - } - type = Error; - return false; -} -} // end anonymous namespace - -/*! - \fn void rotatePoint(QPointF &point, double alpha) - Rotates the \a point counter clockwisely by the angle \a alpha (in - radiants). -*/ -void rotateReference(QPointF &point, double alpha) { - if (!point.isNull()) { - double x = point.x(); - double y = point.y(); - - point.setX(x * qCos(alpha) - y * qSin(alpha)); - point.setY(x * qSin(alpha) + y * qCos(alpha)); - } -} - -void rotateReference(QPointFVector &points, double alpha) { - for (int i = 0; i < points.size(); i++) { - rotateReference(points[i], alpha); - } -} - -/*! - \fn void rotatePointDegree(QPointF &point, double alpha) - Rotates the \a point counter clockwisely by the angle \a alpha (in degrees). -*/ -void rotateReferenceDegree(QPointF &point, double alpha) { - rotateReference(point, alpha / 180 * M_PI); -} - -void rotateReferenceDegree(QPointFVector &points, double alpha) { - for (int i = 0; i < points.size(); i++) { - rotateReferenceDegree(points[i], alpha); - } -} - -/*! - \fn IntersectType intersects(const Circle &circle, const QLineF &line) - Returns the Intersection type of \a circle and \a line. - Returns \c Error if either line or circe \c {isNull() == true}. - - \sa QPointF, Circle -*/ -bool intersects(const Circle &circle, const QLineF &line) { - QPointFVector dummyList; - IntersectType type = NoIntersection; - return intersects(circle, line, dummyList, type, - false /* calculate intersection points*/); -} - -bool intersects(const Circle &circle, const QLineF &line, - QPointFVector &intersectionPoints) { - IntersectType type = NoIntersection; - return intersects(circle, line, intersectionPoints, type, - true /* calculate intersection points*/); -} - -/*! - \fn double distance(const QPointF &p1, const QPointF p2) - Calculates the distance (2-norm) between \a p1 and \a p2. - \sa QPointF -*/ -double distance(const QPointF &p1, const QPointF p2) { - double dx = p2.x() - p1.x(); - double dy = p2.y() - p1.y(); - - return norm(dx, dy); -} - -/*! - \fn double distance(const QPointF &p1, const QPointF p2) - Calculates the angle (in radiants) between the line defined by \a p1 and \a - p2 and the x-axis according to the following rule. Angle = qAtan2(dy, dx), - where dx = p2.x()-p1.x() and dy = p2.y()-p1.y(). - - \note The order of \a p1 and \a p2 matters. Swapping \a p1 and \a p2 will - result in a angle of oposite sign. \sa QPointF -*/ -double angle(const QPointF &p1, const QPointF p2) { - double dx = p2.x() - p1.x(); - double dy = p2.y() - p1.y(); - - return qAtan2(dy, dx); -} - -/*! - \fn double distance(const QPointF &p1, const QPointF p2) - Calculates the angle (in degrees) between the line defined by \a p1 and \a -p2 and the x-axis according to the following rule. Angle = qAtan2(dy, -dx)*180/pi, where dx = p2.x()-p1.x() and dy = p2.y()-p1.y(). angle \note The -order of \a p1 and \a p2 matters. Swapping \a p1 and \a p2 will result in a -angle of oposite sign. \sa QPointF -*/ -double angleDegree(const QPointF &p1, const QPointF p2) { - return angle(p1, p2) * 180 / M_PI; -} - -double truncateAngle(double angle) { - while (angle < 0) { - angle += 2 * M_PI; - } - while (angle > 2 * M_PI) { - angle -= 2 * M_PI; - } - - return angle; -} - -double truncateAngleDegree(double angle) { - return truncateAngle(angle / 180 * M_PI); -} - -/*! - * \fn IntersectType intersects(const QLineF &line1, const QLineF &line2, - * QPointF &intersectionPt) Determines wheter \a line1 and \a line2 intersect - * and of which type the intersection is. Stores the intersection point in \a - * intersectionPt Returns the intersection type (\c IntersectType). - * - * Intersection Types: - * \c NoIntersection - * \c CornerCornerIntersection; A intersection is present such that two of the - * lines cornes touch. \c EdgeCornerIntersection; A intersection is present such - * that a corner and a edge touch. \c EdgeEdgeIntersection; A intersection is - * present such two edges intersect. \c LinesParallel \c LinesEqual \c Error; - * Returned if at least on line delivers isNULL() == true. - * - * - * \sa QPointF - */ -bool intersects(const QLineF &line1, const QLineF &line2, - QPointF &intersectionPt, IntersectType &type) { - if (line1.isNull() || line2.isNull()) { - type = Error; - return false; - } - - // line 1 coordinate system: origin line1.p1(), x-axis towards line1.p2() - QPointF translationVector = - line1.p1(); // translation vector between world and line1 system - double alpha = angle(line1); - double l1 = line1.length(); - - QLineF line2L1 = line2; - line2L1.translate(-translationVector); - rotateReference(line2L1, -alpha); - - double x1 = line2L1.x1(); - double x2 = line2L1.x2(); - double y1 = line2L1.y1(); - double y2 = line2L1.y2(); - if (x1 > x2) { - x1 = x2; - x2 = line2L1.x1(); - y1 = y2; - y2 = line2L1.y1(); - } - - double dx = (x2 - x1); - double dy = (y2 - y1); - double xNull = 0; // (xNull, 0) intersection point in line1 system - - if (!qFuzzyIsNull(dx)) { - double k = dy / dx; - - if (qFuzzyIsNull(k)) { - if (qFuzzyIsNull(x1) && qFuzzyIsNull(y1) && qFuzzyCompare(x2, l1)) { - type = LinesEqual; - return true; - } else { - type = LinesParallel; - return false; - } - } - - double d = (y1 * x2 - y2 * x1) / dx; - xNull = -d / k; - } else { - // lines orthogonal - if (signum(y1) != signum(y2)) { - xNull = x1; - } else { - type = NoIntersection; - return false; - } - } - - if (xNull >= x1 && xNull <= x2) { - // determine intersection type#include "QVector3D" - - if (qFuzzyIsNull(xNull) || qFuzzyCompare(xNull, l1)) { - if (qFuzzyIsNull(y1) || qFuzzyIsNull(y2)) - type = CornerCornerIntersection; - else - type = EdgeCornerIntersection; - } else if (xNull > 0 && xNull < l1) { - if (qFuzzyIsNull(y1) || qFuzzyIsNull(y2)) - type = EdgeCornerIntersection; - else - type = EdgeEdgeIntersection; - } else { - type = NoIntersection; - return false; - } - } else { - type = NoIntersection; - return false; - } - - intersectionPt = QPointF(xNull, 0); // intersection point in line1 system - rotateReference(intersectionPt, alpha); - intersectionPt += translationVector; - - return true; -} - -/*!IntersectType type = NoIntersection; - * \overload bool intersects(const QPolygonF &polygon, const QLineF &line, - * PointList &intersectionList, NeighbourList &neighbourList, IntersectList - * &typeList) Checks if \a polygon intersect with \a line. Stores the - * intersection points in \a intersectionList. - * - * Stores the indices of the closest two \a area vetices for each of - * coorespoinding intersection points in \a neighbourList. * For example if an - * intersection point is found between the first and the second vertex of the \a - * area the intersection point will be stored in \a intersectionList and the - * indices 1 and 2 will be stored in \a neighbourList. \a neighbourList has - * entries of type \c {QPair}, where \c{pair.first} would contain 1 - * and \c{pair.second} would contain 2, when relating to the above example. - * - * Returns the \c IntersectionType of each intersection point within a QVector. - * - * \sa QPair, QVector - */ -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList, NeighbourVector &neighbourList, - IntersectVector &typeList) { - - if (polygon.size() >= 2) { - IntersectVector intersectionTypeList; - // Assemble a line form each tow consecutive polygon vertices and check - // whether it intersects with line - for (int i = 0; i < polygon.size(); i++) { - - QLineF interatorLine; - QPointF currentVertex = polygon[i]; - QPointF nextVertex = - polygon[PolygonCalculus::nextVertexIndex(polygon.size(), i)]; - interatorLine.setP1(currentVertex); - interatorLine.setP2(nextVertex); - - QPointF intersectionPoint; - IntersectType type; - if (intersects(line, interatorLine, intersectionPoint, type)) { - intersectionList.append(intersectionPoint); - - QPair neighbours; - neighbours.first = i; - neighbours.second = PolygonCalculus::nextVertexIndex(polygon.size(), i); - neighbourList.append(neighbours); - - typeList.append(type); - } - } - - if (typeList.size() > 0) { - return true; - } else { - return false; - } - } else { - return false; - } -} - -/*! - * \overload IntersectType intersects(const QPolygonF &polygon, const QLineF - * &line) Returns \c true if any intersection between \a polygon and \a line - * exists, \c false else. - * - * \sa QPair, QVector - */ -bool intersects(const QPolygonF &polygon, const QLineF &line) { - QPointFVector dummyGeo; - QVector> dummyNeighbour; - intersects(polygon, line, dummyGeo, dummyNeighbour); - - if (dummyGeo.size() > 0) - return true; - - return false; -} - -void rotateReference(QLineF &line, double alpha) { - line.setP1(rotateReturn(line.p1(), alpha)); - line.setP2(rotateReturn(line.p2(), alpha)); -} - -QPointF rotateReturn(QPointF point, double alpha) { - rotateReference(point, alpha); - return point; -} - -QPointFVector rotateReturn(QPointFVector points, double alpha) { - rotateReference(points, alpha); - return points; -} - -QLineF rotateReturn(QLineF line, double alpha) { - rotateReference(line, alpha); - return line; -} - -bool intersects(const QLineF &line1, const QLineF &line2, - QPointF &intersectionPt) { - IntersectType dummyType; - return intersects(line1, line2, intersectionPt, dummyType); -} - -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList, - NeighbourVector &neighbourList) { - IntersectVector typeList; - return intersects(polygon, line, intersectionList, neighbourList, typeList); -} - -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList, IntersectVector &typeList) { - NeighbourVector neighbourList; - return intersects(polygon, line, intersectionList, neighbourList, typeList); -} - -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList) { - NeighbourVector neighbourList; - IntersectVector typeList; - return intersects(polygon, line, intersectionList, neighbourList, typeList); -} - -/*! - \fn IntersectType intersects(const Circle &circle1, const Circle &circle2) - Returns the intersection type of the two cirles \a circle1 and \a circle2. - - \note Returns Error if circle.isNull() returns true; - - \sa Circle -*/ -bool intersects(const Circle &circle1, const Circle &circle2) { - IntersectType type = NoIntersection; - QPointFVector intersectionPoints; - return intersects(circle1, circle2, intersectionPoints, type, - false /*calculate intersection points*/); -} - -bool intersects(const Circle &circle1, const Circle &circle2, - IntersectType &type) { - QPointFVector intersectionPoints; - return intersects(circle1, circle2, intersectionPoints, type, - false /*calculate intersection points*/); -} - -bool intersects(const Circle &circle1, const Circle &circle2, - QPointFVector &intersectionPoints) { - IntersectType type; - return intersects(circle1, circle2, intersectionPoints, type); -} - -bool intersects(const Circle &circle1, const Circle &circle2, - QPointFVector &intersectionPoints, IntersectType &type) { - return intersects(circle1, circle2, intersectionPoints, type, - true /*calculate intersection points*/); -} - -; - -double angle(const QLineF &line) { return angle(line.p1(), line.p2()); } - -bool contains(const QLineF &line, const QPointF &point, IntersectType &type) { - QPointF translationVector = line.p1(); - double alpha = angle(line); - double l = line.length(); - - QPointF pointL1 = rotateReturn(point - translationVector, -alpha); - - double x = pointL1.x(); - double y = pointL1.y(); - - if (x >= 0 && x <= l) { - if (qFuzzyIsNull(x) || qFuzzyCompare(x, l)) { - if (qFuzzyIsNull(y)) { - type = CornerCornerIntersection; - return true; - } - } else { - if (qFuzzyIsNull(y)) { - type = EdgeCornerIntersection; - return true; - } - } - } - type = NoIntersection; - return false; -} - -bool contains(const QLineF &line, const QPointF &point) { - IntersectType dummyType; - return contains(line, point, dummyType); -} - -bool contains(const QPolygonF &polygon, const QPointF &point, - IntersectType &type) { - using namespace PolygonCalculus; - if (polygon.containsPoint(point, Qt::FillRule::OddEvenFill)) { - type = Interior; - return true; - } - int size = polygon.size(); - for (int i = 0; i < size; i++) { - QLineF line(polygon[i], polygon[nextVertexIndex(size, i)]); - if (contains(line, point, type)) { - return true; - } - } - - return false; -} - -bool contains(const QPolygonF &polygon, const QPointF &point) { - IntersectType dummyType; - return contains(polygon, point, dummyType); -} - -double norm(double x, double y) { return qSqrt(x * x + y * y); } - -double norm(const QPointF &p) { return norm(p.x(), p.y()); } - -double angle(const QPointF &p1) { - return truncateAngle(qAtan2(p1.y(), p1.x())); -} - -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints, - NeighbourVector &neighbourList, IntersectVector &typeList) { - using namespace PolygonCalculus; - for (int i = 0; i < polygon.size(); i++) { - QPointF p1 = polygon[i]; - int j = nextVertexIndex(polygon.size(), i); - QPointF p2 = polygon[j]; - QLineF line(p1, p2); - - QPointFVector lineIntersecPts; - IntersectType type; - if (intersects(circle, line, lineIntersecPts, type)) { - QPair neigbours; - neigbours.first = i; - neigbours.second = j; - neighbourList.append(neigbours); - typeList.append(type); - intersectionPoints.append(lineIntersecPts); - } - } - - if (intersectionPoints.size() > 0) - return true; - else { - return false; - } -} - -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints, - NeighbourVector &neighbourList) { - QVector types; - return intersects(circle, polygon, intersectionPoints, neighbourList, types); -} - -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints, - IntersectVector &typeList) { - NeighbourVector neighbourList; - return intersects(circle, polygon, intersectionPoints, neighbourList, - typeList); -} - -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints) { - NeighbourVector neighbourList; - return intersects(circle, polygon, intersectionPoints, neighbourList); -} - -bool intersects(const Circle &circle, const QPolygonF &polygon) { - QVector intersectionPoints; - return intersects(circle, polygon, intersectionPoints); -} - -bool intersects(const QLineF &line1, const QLineF &line2) { - QPointF intersectionPoint; - return intersects(line1, line2, intersectionPoint); -} - -bool intersects(const Circle &circle, const QLineF &line, - QPointFVector &intersectionPoints, IntersectType &type) { - return intersects(circle, line, intersectionPoints, type, - true /* calculate intersection points*/); -} - -bool intersectsFast(const QPolygonF &polygon, const QLineF &line) { - for (int i = 0; i < polygon.size() - 1; ++i) { - QLineF polygonSegment(polygon[i], polygon[i + 1]); - - if (QLineF::BoundedIntersection == line.intersect(polygonSegment, nullptr)) - return true; - } - - return false; -} - -// bool intersectsFast(const QLineF &line1, const QLineF &line2, QPointF -// &intersectionPt) -// { -// if (line1.isNull() || line2.isNull()){ -// return false; -// } - -// // determine line equations y = kx+d -// double dx1 = line1.dx(); -// double dy1 = line1.dy(); -// double k1; -// double d1 = 0; -// if (qFuzzyIsNull(dx1)) { -// k1 = std::numeric_limits::infinity(); -// } else { -// k1 = dy1/dx1; -// d1 = line1.y1()-k1*line1.x1(); -// } - -// double dx2 = line2.dx(); -// double dy2 = line2.dy(); -// double k2; -// double d2 = 0; -// if (qFuzzyIsNull(dx2)) { -// k2 = std::numeric_limits::infinity(); -// } else { -// k2 = dy2/dx2; -// d2 = line2.y1()-k2*line2.x1(); -// } - -// if ( !qFuzzyCompare(k1, std::numeric_limits::infinity()) -// && !qFuzzyCompare(k1, std::numeric_limits::infinity())) -// { -// double dk = k2-k1; -// double dd = d2-d1; -// if (qFuzzyIsNull(dk)) { // lines parallel -// if (qFuzzyIsNull(dd)) { // lines colinear -// if ( ((line1.x1() >= line2.x1()) && ((line1.x1() <= -// line2.x2()))) -// || ((line1.x2() >= line2.x1()) && ((line1.x2() <= -// line2.x2()))) ) // intersect? return true; - -// return false; -// } -// return false; -// } - -// double x_intersect = -dd/dk; - -// if (((x_intersect >= line2.x1()) && ((line1.x1() <= line2.x2()))) - -// } - -// return true; -// } - -} // end namespace PlanimetryCalculus - -/*! - \class PlanimetryCalculus - \inmodule Wima - - \brief The \c PlanimetryCalculus class provides routines handy for - planimetrical (2D) calculations. -*/ diff --git a/src/RouteMissionItem/geometry/PlanimetryCalculus.h b/src/RouteMissionItem/geometry/PlanimetryCalculus.h deleted file mode 100644 index c54946dc5cc2fad09f3d6f205c534da80caa075c..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/PlanimetryCalculus.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "GenericCircle.h" -#include "PolygonCalculus.h" - -using Circle = GenericCircle; - -namespace PlanimetryCalculus { - -enum IntersectType { - InsideNoIntersection, - InsideTouching, - InsideIntersection, - OutsideIntersection, - OutsideTouching, - OutsideNoIntersection, - CirclesEqual, // Circle Circle intersection - - Tangent, - Secant, // Circle Line Intersetion - - EdgeCornerIntersection, - EdgeEdgeIntersection, - CornerCornerIntersection, - LinesParallel, - LinesEqual, // Line Line intersection - - Interior, // Polygon contains - - NoIntersection, - Error // general -}; - -typedef QVector> NeighbourVector; -typedef QVector QPointFVector; -typedef QVector IntersectVector; - -void rotateReference(QPointF &point, double alpha); -void rotateReference(QPointFVector &points, double alpha); -void rotateReference(QLineF &line, double alpha); -// void rotateReference(QPolygonF &polygon, double alpha); - -QPointF rotateReturn(QPointF point, double alpha); -QPointFVector rotateReturn(QPointFVector points, double alpha); -QLineF rotateReturn(QLineF line, double alpha); -// QPolygonF rotateReturn(QPolygonF &polygon, double alpha); - -bool intersects(const Circle &circle1, const Circle &circle2); -bool intersects(const Circle &circle1, const Circle &circle2, - IntersectType &type); -bool intersects(const Circle &circle1, const Circle &circle2, - QPointFVector &intersectionPoints); -bool intersects(const Circle &circle1, const Circle &circle2, - QPointFVector &intersectionPoints, IntersectType &type); - -bool intersects(const Circle &circle, const QLineF &line); -bool intersects(const Circle &circle, const QLineF &line, IntersectType &type); -bool intersects(const Circle &circle, const QLineF &line, - QPointFVector &intersectionPoints); -bool intersects(const Circle &circle, const QLineF &line, - QPointFVector &intersectionPoints, IntersectType &type); - -bool intersects(const Circle &circle, const QPolygonF &polygon); -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints); -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints, - IntersectVector &typeList); -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints, - NeighbourVector &neighbourList); -bool intersects(const Circle &circle, const QPolygonF &polygon, - QVector &intersectionPoints, - NeighbourVector &neighbourList, IntersectVector &typeList); - -bool intersects(const QLineF &line1, const QLineF &line2); -bool intersects(const QLineF &line1, const QLineF &line2, - QPointF &intersectionPt); -bool intersects(const QLineF &line1, const QLineF &line2, - QPointF &intersectionPt, IntersectType &type); -// bool intersectsFast(const QLineF &line1, const QLineF &line2, QPointF -// &intersectionPt, IntersectType &type); -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList); -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList, IntersectVector &typeList); -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList, - NeighbourVector &neighbourList); -bool intersects(const QPolygonF &polygon, const QLineF &line, - QPointFVector &intersectionList, NeighbourVector &neighbourList, - IntersectVector &typeList); - -bool intersectsFast(const QPolygonF &polygon, const QLineF &line); - -bool contains(const QLineF &line, const QPointF &point); -bool contains(const QLineF &line, const QPointF &point, IntersectType &type); -bool contains(const QPolygonF &polygon, const QPointF &point); -bool contains(const QPolygonF &polygon, const QPointF &point, - IntersectType &type); - -double distance(const QPointF &p1, const QPointF p2); -double norm(double x, double y); -double norm(const QPointF &p); -double angle(const QPointF &p1, const QPointF p2); -double angle(const QPointF &p1); -double angle(const QLineF &line); -double angleDegree(const QPointF &p1, const QPointF p2); -double truncateAngle(double angle); -double truncateAngleDegree(double angle); - -/*! - * \fntemplate int signum(T val) - * Returns the signum of a value \a val. - * - * \sa QPair, QVector - */ -template int signum(T val) { return (T(0) < val) - (val < T(0)); } -} // namespace PlanimetryCalculus diff --git a/src/RouteMissionItem/geometry/PolygonCalculus.cc b/src/RouteMissionItem/geometry/PolygonCalculus.cc deleted file mode 100644 index a296ed1e98b74e23bef029f011e279f5e05d30ed..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/PolygonCalculus.cc +++ /dev/null @@ -1,578 +0,0 @@ -#include "PolygonCalculus.h" -#include "PlanimetryCalculus.h" -#include "Wima/OptimisationTools.h" - -#include - -#include - -namespace PolygonCalculus { - namespace { - bool isReflexVertex(const QPolygonF& polygon, const QPointF *vertex) { - // Original Code from SurveyComplexItem::_VertexIsReflex() - auto vertexBefore = vertex == polygon.begin() ? polygon.end() - 1 : vertex - 1; - auto vertexAfter = vertex == polygon.end() - 1 ? polygon.begin() : vertex + 1; - auto area = ( ((vertex->x() - vertexBefore->x())*(vertexAfter->y() - vertexBefore->y())) - -((vertexAfter->x() - vertexBefore->x())*(vertex->y() - vertexBefore->y()))); - return area > 0; - } - - } // end anonymous namespace - - /*! - * \fn bool containsPath(QPolygonF polygon, const QPointF &c1, const QPointF &c2) - * Returns true if the shortest path between the two coordinates is not fully inside the \a area. - * - * \sa QPointF, QPolygonF - */ - bool containsPath(QPolygonF polygon, const QPointF &c1, const QPointF &c2) - { - - if ( !polygon.isEmpty()) { - if ( !polygon.containsPoint(c1, Qt::FillRule::OddEvenFill) - || !polygon.containsPoint(c2, Qt::FillRule::OddEvenFill)) - return false; - - QLineF line(c1, c2); - if (PlanimetryCalculus::intersectsFast(polygon, line)) - return false; - return true; - } else { - return false; - } - } - - /*! - * \fn int closestVertexIndex(const QPolygonF &polygon, const QPointF &coordinate) - * Returns the vertex index of \a polygon which has the least distance to \a coordinate. - * - * \sa QPointF, QPolygonF - */ - int closestVertexIndex(const QPolygonF &polygon, const QPointF &coordinate) - { - if (polygon.size() == 0) { - qWarning("Path is empty!"); - return -1; - }else if (polygon.size() == 1) { - return 0; - }else { - int index = 0; // the index of the closest vertex - double min_dist = PlanimetryCalculus::distance(coordinate, polygon[index]); - for(int i = 1; i < polygon.size(); i++){ - double dist = PlanimetryCalculus::distance(coordinate, polygon[i]); - if (dist < min_dist){ - min_dist = dist; - index = i; - } - } - return index; - } - } - - /*!auto distance - * \fn QPointF closestVertex(const QPolygonF &polygon, const QPointF &coordinate); - * Returns the vertex of \a polygon with the least distance to \a coordinate. - * - * \sa QPointF, QPolygonF - */ - QPointF closestVertex(const QPolygonF &polygon, const QPointF &coordinate) - { - int index = closestVertexIndex(polygon, coordinate); - if (index >=0 ) { - return polygon[index]; - } else { - return QPointF(); - } - } - - /*! - * \fn int nextPolygonIndex(int pathsize, int index) - * Returns the index of the next vertex (of a polygon), which is \a index + 1 if \a index is smaller than \c {\a pathsize - 1}, - * or 0 if \a index equals \c {\a pathsize - 1}, or -1 if the \a index is out of bounds. - * \note \a pathsize is usually obtained by invoking polygon.size() - */ - int nextVertexIndex(int pathsize, int index) - { - if (index >= 0 && index < pathsize-1) { - return index + 1; - } else if (index == pathsize-1) { - return 0; - } else { - qWarning("nextPolygonIndex(): Index out of bounds! index:count = %i:%i", index, pathsize); - return -1; - } - } - - /*! - * \fn int previousPolygonIndex(int pathsize, int index) - * Returns the index of the previous vertex (of a polygon), which is \a index - 1 if \a index is larger 0, - * or \c {\a pathsize - 1} if \a index equals 0, or -1 if the \a index is out of bounds. - * \note pathsize is usually obtained by invoking polygon.size() - */ - int previousVertexIndex(int pathsize, int index) - { - if (index > 0 && index = 3 && polygon2.size() >= 3) { - - if ( !isSimplePolygon(polygon1) || !isSimplePolygon(polygon2)) { - return JoinPolygonError::NotSimplePolygon; - } - - if (polygon1 == polygon2) { - joinedPolygon = polygon1; - return JoinPolygonError::PolygonJoined; - } - if ( !hasClockwiseWinding(polygon1)) { - reversePath(polygon1); - } - if ( !hasClockwiseWinding(polygon2)) { - reversePath(polygon2); - } - - const QPolygonF *walkerPoly = &polygon1; // "walk" on this polygon towards higher indices - const QPolygonF *crossPoly = &polygon2; // check for crossings with this polygon while "walking" - // and swicht to this polygon on a intersection, - // continue to walk towards higher indices - - // begin with the first index which is not inside crosspoly, if all Vertices are inside crosspoly return crosspoly - int startIndex = 0; - bool crossContainsWalker = true; - for (int i = 0; i < walkerPoly->size(); i++) { - if ( !contains(*crossPoly, walkerPoly->at(i)) ) { - crossContainsWalker = false; - startIndex = i; - break; - } - } - - if ( crossContainsWalker == true) { - joinedPolygon.append(*crossPoly); - return JoinPolygonError::PolygonJoined; - } - QPointF currentVertex = walkerPoly->at(startIndex); - QPointF startVertex = currentVertex; - // possible nextVertex (if no intersection between currentVertex and protoVertex with crossPoly) - int nextVertexNumber = nextVertexIndex(walkerPoly->size(), startIndex); - QPointF protoNextVertex = walkerPoly->value(nextVertexNumber); - long counter = 0; - while (1) { - //qDebug("nextVertexNumber: %i", nextVertexNumber); - joinedPolygon.append(currentVertex); - - QLineF walkerPolySegment; - walkerPolySegment.setP1(currentVertex); - walkerPolySegment.setP2(protoNextVertex); - - QVector> neighbourList; - QVector intersectionList; - //qDebug("IntersectionList.size() on init: %i", intersectionList.size()); - PlanimetryCalculus::intersects(*crossPoly, walkerPolySegment, intersectionList, neighbourList); - - //qDebug("IntersectionList.size(): %i", intersectionList.size()); - - if (intersectionList.size() > 0) { - int minDistIndex = -1; - - double minDist = std::numeric_limits::infinity(); - for (int i = 0; i < intersectionList.size(); i++) { - double currentDist = PlanimetryCalculus::distance(currentVertex, intersectionList[i]); - - if ( minDist > currentDist && !qFuzzyIsNull(distance(currentVertex, intersectionList[i])) ) { - minDist = currentDist; - minDistIndex = i; - } - } - - if (minDistIndex != -1){ - currentVertex = intersectionList.value(minDistIndex); - QPair neighbours = neighbourList.value(minDistIndex); - protoNextVertex = crossPoly->value(neighbours.second); - nextVertexNumber = neighbours.second; - - // switch walker and cross poly - const QPolygonF *temp = walkerPoly; - walkerPoly = crossPoly; - crossPoly = temp; - } else { - currentVertex = walkerPoly->value(nextVertexNumber); - nextVertexNumber = nextVertexIndex(walkerPoly->size(), nextVertexNumber); - protoNextVertex = walkerPoly->value(nextVertexNumber); - } - - } else { - currentVertex = walkerPoly->value(nextVertexNumber); - nextVertexNumber = nextVertexIndex(walkerPoly->size(), nextVertexNumber); - protoNextVertex = walkerPoly->value(nextVertexNumber); - } - - if (currentVertex == startVertex) { - if (polygon1.size() == joinedPolygon.size()) { - for (int i = 0; i < polygon1.size(); i++) { - if (polygon1[i] != joinedPolygon[i]) - return PolygonJoined; - } - return Disjoint; - } else { - return PolygonJoined; - } - } - - counter++; - if (counter > 1e5) { - qWarning("PolygonCalculus::join(): no successfull termination!"); - return Error; - } - } - - } else { - return PathSizeLow; - } - } - - /*! - * \fn bool isSimplePolygon(const QPolygonF &polygon); - * Returns \c true if \a polygon is a \l {Simple Polygon}, \c false else. - * \note A polygon is a Simple Polygon iff it is not self intersecting. - */ - bool isSimplePolygon(const QPolygonF &polygon) - { - int i = 0; - if (polygon.size() > 3) { - // check if any edge of the area (formed by two adjacent vertices) intersects with any other edge of the area - while(i < polygon.size()-1) { - double cCIntersectCounter = 0; // corner corner intersection counter - QPointF refBeginCoordinate = polygon[i]; - QPointF refEndCoordinate = polygon[nextVertexIndex(polygon.size(), i)]; - QLineF refLine; - refLine.setP1(refBeginCoordinate); - refLine.setP2(refEndCoordinate); - int j = nextVertexIndex(polygon.size(), i); - while(j < polygon.size()) { - - QPointF intersectionPt; - QLineF iteratorLine; - iteratorLine.setP1(polygon[j]); - iteratorLine.setP2(polygon[nextVertexIndex(polygon.size(), j)]); - PlanimetryCalculus::IntersectType intersectType; - PlanimetryCalculus::intersects(refLine, iteratorLine, intersectionPt, intersectType); - if ( intersectType == PlanimetryCalculus::CornerCornerIntersection) { - cCIntersectCounter++; - // max two corner corner intersections allowed, a specific coordinate can appear only once in a simple polygon - } - if ( cCIntersectCounter > 2 - || intersectType == PlanimetryCalculus::EdgeEdgeIntersection - || intersectType == PlanimetryCalculus::EdgeCornerIntersection - || intersectType == PlanimetryCalculus::LinesEqual - || intersectType == PlanimetryCalculus::Error){ - return false; - } - - j++; - } - i++; - } - } - return true; - } - - /*! - * \fn bool hasClockwiseWinding(const QPolygonF &polygon) - * Returns \c true if \a path has clockwiauto distancese winding, \c false else. - */ - bool hasClockwiseWinding(const QPolygonF &polygon) - { - if (polygon.size() <= 2) { - return false; - } - - double sum = 0; - for (int i=0; i 2) { - - // Walk the edges, offsetting by theauto distance specified distance - QList rgOffsetEdges; - for (int i = 0; i < polygon.size(); i++) { - int nextIndex = nextVertexIndex(polygon.size(), i); - QLineF offsetEdge; - QLineF originalEdge(polygon[i], polygon[nextIndex]); - - QLineF workerLine = originalEdge; - workerLine.setLength(offset); - workerLine.setAngle(workerLine.angle() - 90.0); - offsetEdge.setP1(workerLine.p2()); - - workerLine.setPoints(originalEdge.p2(), originalEdge.p1()); bool containsPath (const QPointF &c1, const QPointF &c2, QPolygonF polygon); - workerLine.setLength(offset); - workerLine.setAngle(workerLine.angle() + 90.0); - offsetEdge.setP2(workerLine.p2()); - - rgOffsetEdges << offsetEdge; - } - - // Intersect the offset edges to generate new vertices - polygon.clear(); - QPointF newVertex; - for (int i=0; i &convexPolygons) - { - // Original Code SurveyComplexItem::_PolygonDecomposeConvex() - // this follows "Mark Keil's Algorithm" https://mpen.ca/406/keil - int decompSize = std::numeric_limits::max(); - if (polygon.size() < 3) return; - if (polygon.size() == 3) { - convexPolygons << polygon; - return; - } - - QList decomposedPolygonsMin{}; - - for (const QPointF *vertex = polygon.begin(); vertex != polygon.end(); ++vertex) - { - // is vertex reflex? - bool vertexIsReflex = isReflexVertex(polygon, vertex); - - if (!vertexIsReflex) continue; - - for (const QPointF *vertexOther = polygon.begin(); vertexOther != polygon.end(); ++vertexOther) - { - const QPointF *vertexBefore = vertex == polygon.begin() ? polygon.end() - 1 : vertex - 1; - const QPointF *vertexAfter = vertex == polygon.end() - 1 ? polygon.begin() : vertex + 1; - if (vertexOther == vertex) continue; - if (vertexAfter == vertexOther) continue; - if (vertexBefore == vertexOther) continue; - bool canSee = containsPath(polygon, *vertex, *vertexOther); - if (!canSee) continue; - - QPolygonF polyLeft; - const QPointF *v = vertex; - bool polyLeftContainsReflex = false; - while ( v != vertexOther) { - if (v != vertex && isReflexVertex(polygon, v)) { - polyLeftContainsReflex = true; - } - polyLeft << *v; - ++v; - if (v == polygon.end()) v = polygon.begin(); - } - polyLeft << *vertexOther; - bool polyLeftValid = !(polyLeftContainsReflex && polyLeft.size() == 3); - - QPolygonF polyRight; - v = vertexOther; - bool polyRightContainsReflex = false; - while ( v != vertex) { - if (v != vertex && isReflexVertex(polygon, v)) { - polyRightContainsReflex = true; - } - polyRight << *v; - ++v; - if (v == polygon.end()) v = polygon.begin(); - } - polyRight << *vertex; - auto polyRightValid = !(polyRightContainsReflex && polyRight.size() == 3); - - if (!polyLeftValid || ! polyRightValid) { - // decompSize = std::numeric_limits::max(); - continue; - } - - // recursion - QList polyLeftDecomposed{}; - decomposeToConvex(polyLeft, polyLeftDecomposed); - - QList polyRightDecomposed{}; - decomposeToConvex(polyRight, polyRightDecomposed); - - // compositon - int subSize = polyLeftDecomposed.size() + polyRightDecomposed.size(); - if ( (polyLeftContainsReflex && polyLeftDecomposed.size() == 1) - || (polyRightContainsReflex && polyRightDecomposed.size() == 1)) - { - // don't accept polygons that contian reflex vertices and were not split - subSize = std::numeric_limits::max(); - } - if (subSize < decompSize) { - decompSize = subSize; - decomposedPolygonsMin = polyLeftDecomposed + polyRightDecomposed; - } - } - } - - // assemble output - if (decomposedPolygonsMin.size() > 0) { - convexPolygons << decomposedPolygonsMin; - } else { - convexPolygons << polygon; - } - - return; - } - - bool shortestPath(const QPolygonF &polygon,const QPointF &startVertex, const QPointF &endVertex, QVector &shortestPath) - { - using namespace PlanimetryCalculus; - QPolygonF bigPolygon(polygon); - offsetPolygon(bigPolygon, 0.5); // solves numerical errors - if ( bigPolygon.containsPoint(startVertex, Qt::FillRule::OddEvenFill) - && bigPolygon.containsPoint(endVertex, Qt::FillRule::OddEvenFill)) { - - QVector elementList; - elementList.append(startVertex); - elementList.append(endVertex); - for (auto vertex : polygon) elementList.append(vertex); - const int listSize = elementList.size(); - - - std::function distanceDij = [bigPolygon, elementList](const int ind1, const int ind2) -> double { - QPointF p1 = elementList[ind1]; - QPointF p2 = elementList[ind2]; - double dist = std::numeric_limits::infinity(); - if (containsPathFast(bigPolygon, p1, p2)){ // containsPathFast can be used since all points are inside bigPolygon - double dx = p1.x()-p2.x(); - double dy = p1.y()-p2.y(); - dist = sqrt((dx*dx)+(dy*dy)); - } - - return dist; - }; - - QVector shortestPathIndex; - bool retVal = OptimisationTools::dijkstraAlgorithm(listSize, 0, 1, shortestPathIndex, distanceDij); - for (auto index : shortestPathIndex) shortestPath.append(elementList[index]); - return retVal; - } else { - return false; - } - } - - QVector3DList toQVector3DList(const QPolygonF &polygon) - { - QVector3DList list; - for ( auto vertex : polygon ) - list.append(QVector3D(vertex)); - - return list; - } - - QPolygonF toQPolygonF(const QPointFList &listF) - { - QPolygonF polygon; - for ( auto vertex : listF ) - polygon.append(vertex); - - return polygon; - } - - QPointFList toQPointFList(const QPolygonF &polygon) - { - QPointFList listF; - for ( auto vertex : polygon ) - listF.append(vertex); - - return listF; - } - - void reversePath(QPointFList &path) - { - QPointFList pathReversed; - for ( auto element : path) { - pathReversed.prepend(element); - } - path = pathReversed; - } - - void reversePath(QPointFVector &path) - { - QPointFVector pathReversed; - for ( auto element : path) { - pathReversed.prepend(element); - } - path = pathReversed; - } - - - - bool containsPathFast(QPolygonF polygon, const QPointF &c1, const QPointF &c2) - { - - if ( !polygon.isEmpty()) { - QLineF line(c1, c2); - if (PlanimetryCalculus::intersectsFast(polygon, line)) - return false; - return true; - } else { - return false; - } - } - - - -} // end PolygonCalculus namespace - diff --git a/src/RouteMissionItem/geometry/PolygonCalculus.h b/src/RouteMissionItem/geometry/PolygonCalculus.h deleted file mode 100644 index 6914a1555c855f5e5aebe120d89bda1f68e784a6..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/PolygonCalculus.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - - -#include -#include -#include - - - -namespace PolygonCalculus { - - enum JoinPolygonError { NotSimplePolygon, PolygonJoined, Disjoint, PathSizeLow, Error}; - - typedef QList QVector3DList; - typedef QList QPointFList; - typedef QVector QPointFVector; - - int closestVertexIndex (const QPolygonF &polygon, const QPointF &coordinate); - QPointF closestVertex (const QPolygonF &polygon, const QPointF &coordinate); - int nextVertexIndex (int pathsize, int index); - int previousVertexIndex (int pathsize, int index); - JoinPolygonError join (QPolygonF polygon1, QPolygonF polygon2, QPolygonF &joinedPolygon); - bool isSimplePolygon (const QPolygonF &polygon); - bool hasClockwiseWinding (const QPolygonF &path); - void reversePath (QPolygonF &path); - void reversePath (QPointFList &path); - void reversePath (QPointFVector &path); - void offsetPolygon (QPolygonF &polygon, double offset); - // returns true if the line c1-c2 is fully inside the polygon - bool containsPath (QPolygonF polygon, const QPointF &c1, const QPointF &c2); - // same as containsPath(), but works only if c1 and c2 are inside the polygon! - bool containsPathFast (QPolygonF polygon, const QPointF &c1, const QPointF &c2); - void decomposeToConvex (const QPolygonF &polygon, QList &convexPolygons); - bool shortestPath (const QPolygonF &polygon, const QPointF &startVertex, const QPointF &endVertex, QVector &shortestPath); - - QPolygonF toQPolygonF(const QVector3DList &polygon); - QPolygonF toQPolygonF(const QPointFList &polygon); - QLineF toQLineF(const QVector3DList &line); - QPointFList toQPointFList(const QVector3DList &list); - QPointFList toQPointFList(const QPolygonF &list); - QVector3DList toQVector3DList(const QPointFList &listF); - QVector3DList toQVector3DList(const QPolygonF &listF); -} - - diff --git a/src/RouteMissionItem/geometry/WimaServiceArea.cc b/src/RouteMissionItem/geometry/SafeArea.cc similarity index 58% rename from src/RouteMissionItem/geometry/WimaServiceArea.cc rename to src/RouteMissionItem/geometry/SafeArea.cc index 84b6c6afd10848675321f2039f506eb6d1b5deb3..47f9e2d540033f30dd6ea9d9bf25efdfb2a0af9e 100644 --- a/src/RouteMissionItem/geometry/WimaServiceArea.cc +++ b/src/RouteMissionItem/geometry/SafeArea.cc @@ -1,38 +1,43 @@ -#include "WimaServiceArea.h" +#include "SafeArea.h" #include "QGCLoggingCategory.h" #include "QGCQGeoCoordinate.h" -QGC_LOGGING_CATEGORY(WimaServiceAreaLog, "WimaServiceAreaLog") +#include -const char *WimaServiceArea::wimaServiceAreaName = "Service Area"; -const char *WimaServiceArea::depotLatitudeName = "DepotLatitude"; -const char *WimaServiceArea::depotLongitudeName = "DepotLongitude"; -const char *WimaServiceArea::depotAltitudeName = "DepotAltitude"; +QGC_LOGGING_CATEGORY(SafeAreaLog, "SafeAreaLog") -WimaServiceArea::WimaServiceArea(QObject *parent) : WimaArea(parent) { init(); } +const char *SafeArea::safeAreaName = "Safe Area"; +const char *SafeArea::depotLatitudeName = "DepotLatitude"; +const char *SafeArea::depotLongitudeName = "DepotLongitude"; +const char *SafeArea::depotAltitudeName = "DepotAltitude"; -WimaServiceArea::WimaServiceArea(const WimaServiceArea &other, QObject *parent) - : WimaArea(other, parent), _depot(other.depot()) { +SafeArea::SafeArea(QObject *parent) : GeoArea(parent) { init(); } + +SafeArea::SafeArea(const SafeArea &other, QObject *parent) + : GeoArea(other, parent), _depot(other.depot()) { init(); } -/*! - * \overload operator=() - * - * Calls the inherited operator WimaArea::operator=(). - */ -WimaServiceArea &WimaServiceArea::operator=(const WimaServiceArea &other) { - WimaArea::operator=(other); +SafeArea &SafeArea::operator=(const SafeArea &other) { + GeoArea::operator=(other); this->setDepot(other.depot()); return *this; } -const QGeoCoordinate &WimaServiceArea::depot() const { return _depot; } +QString SafeArea::mapVisualQML() const { return "SafeAreaMapVisual.qml"; } + +QString SafeArea::editorQML() const { return "SafeAreaEditor.qml"; } + +SafeArea *SafeArea::clone(QObject *parent) const { + return new SafeArea(*this, parent); +} + +const QGeoCoordinate &SafeArea::depot() const { return _depot; } -QGeoCoordinate WimaServiceArea::depotQml() const { return _depot; } +QGeoCoordinate SafeArea::depotQml() const { return _depot; } -bool WimaServiceArea::setDepot(const QGeoCoordinate &coordinate) { +bool SafeArea::setDepot(const QGeoCoordinate &coordinate) { if (_depot.latitude() != coordinate.latitude() || _depot.longitude() != coordinate.longitude()) { if (this->containsCoordinate(coordinate)) { @@ -45,18 +50,17 @@ bool WimaServiceArea::setDepot(const QGeoCoordinate &coordinate) { return false; } -void WimaServiceArea::saveToJson(QJsonObject &json) { - this->WimaArea::saveToJson(json); - json[areaTypeName] = wimaServiceAreaName; +void SafeArea::saveToJson(QJsonObject &json) { + this->GeoArea::saveToJson(json); + json[areaTypeName] = safeAreaName; json[depotLatitudeName] = _depot.latitude(); json[depotLongitudeName] = _depot.longitude(); json[depotAltitudeName] = _depot.altitude(); } -bool WimaServiceArea::loadFromJson(const QJsonObject &json, - QString &errorString) { +bool SafeArea::loadFromJson(const QJsonObject &json, QString &errorString) { bool retVal = false; - if (this->WimaArea::loadFromJson(json, errorString)) { + if (this->GeoArea::loadFromJson(json, errorString)) { double lat = 0; if (json.contains(depotLatitudeName) && json[depotLatitudeName].isDouble()) { @@ -85,9 +89,9 @@ bool WimaServiceArea::loadFromJson(const QJsonObject &json, return retVal; } -void WimaServiceArea::init() { - this->setObjectName(wimaServiceAreaName); - connect(this, &WimaArea::pathChanged, [this] { +void SafeArea::init() { + this->setObjectName(safeAreaName); + connect(this, &GeoArea::pathChanged, [this] { if (!this->_depot.isValid() || !this->containsCoordinate(this->_depot)) { if (this->containsCoordinate(this->center())) { // Use center. @@ -106,13 +110,16 @@ void WimaServiceArea::init() { minIndex = idx; } } else { - qCCritical(WimaServiceAreaLog) << "init(): nullptr catched!"; + qCCritical(SafeAreaLog) << "init(): nullptr catched!"; } } - this->setDepot(this->pathModel().value(minIndex)->coordinate()); + this->setDepot(this->pathModel() + .value(minIndex) + ->coordinate()); } else if (this->pathModel().count() > 0) { // Use first coordinate. - this->setDepot(this->pathModel().value(0)->coordinate()); + this->setDepot( + this->pathModel().value(0)->coordinate()); } } }); diff --git a/src/RouteMissionItem/geometry/WimaServiceArea.h b/src/RouteMissionItem/geometry/SafeArea.h similarity index 56% rename from src/RouteMissionItem/geometry/WimaServiceArea.h rename to src/RouteMissionItem/geometry/SafeArea.h index 08ff94c34ccec0a73ebc2f8b09228892b5e9b6d9..fce0f6daf7c2aec39c17de737dbc0ef4a92575f4 100644 --- a/src/RouteMissionItem/geometry/WimaServiceArea.h +++ b/src/RouteMissionItem/geometry/SafeArea.h @@ -1,33 +1,31 @@ #pragma once -#include "WimaArea.h" -#include "WimaTrackerPolyline.h" +#include "GeoArea.h" #include -class WimaServiceArea : public WimaArea { +class SafeArea : public GeoArea { Q_OBJECT public: - WimaServiceArea(QObject *parent = nullptr); - WimaServiceArea(const WimaServiceArea &other, QObject *parent); - WimaServiceArea &operator=(const WimaServiceArea &other); + SafeArea(QObject *parent = nullptr); + SafeArea(const SafeArea &other, QObject *parent); + SafeArea &operator=(const SafeArea &other); Q_PROPERTY( QGeoCoordinate depot READ depotQml WRITE setDepot NOTIFY depotChanged) // Overrides from WimaPolygon - QString mapVisualQML(void) const { return "WimaServiceAreaMapVisual.qml"; } - QString editorQML(void) const { return "WimaServiceAreaEditor.qml"; } + QString mapVisualQML(void) const override; + QString editorQML(void) const override; + SafeArea *clone(QObject *parent = nullptr) const; + void saveToJson(QJsonObject &json) override; + bool loadFromJson(const QJsonObject &json, QString &errorString) override; // Property acessors const QGeoCoordinate &depot(void) const; QGeoCoordinate depotQml(void) const; - // Member Methodes - void saveToJson(QJsonObject &json); - bool loadFromJson(const QJsonObject &json, QString &errorString); - // static Members - static const char *wimaServiceAreaName; + static const char *safeAreaName; static const char *depotLatitudeName; static const char *depotLongitudeName; static const char *depotAltitudeName; diff --git a/src/RouteMissionItem/geometry/WimaArea.cc b/src/RouteMissionItem/geometry/WimaArea.cc deleted file mode 100644 index f83a0d79f8e30847d8336548f45b5a3093682a78..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaArea.cc +++ /dev/null @@ -1,525 +0,0 @@ -#include "WimaArea.h" - -const char *WimaArea::wimaAreaName = "WimaArea"; -const char *WimaArea::areaTypeName = "AreaType"; -const char *WimaArea::settingsGroup = "MeasurementArea"; - -// Constructors -WimaArea::WimaArea(QObject *parent) - : QGCMapPolygon(parent), - _metaDataMap(FactMetaData::createMapFromJsonFile( - QStringLiteral(":/json/WimaArea.SettingsGroup.json"), - this /* QObject parent */)), - _borderPolygonOffset(SettingsFact(settingsGroup, - _metaDataMap[borderPolygonOffsetName], - this /* QObject parent */)), - _showBorderPolygon(SettingsFact(settingsGroup, - _metaDataMap[showBorderPolygonName], - this /* QObject parent */)), - _borderPolygon(QGCMapPolygon(this)), _wimaAreaInteractive(false) { - init(); - _maxAltitude = 30; -} - -WimaArea::WimaArea(const WimaArea &other, QObject *parent) - : QGCMapPolygon(parent), - _metaDataMap(FactMetaData::createMapFromJsonFile( - QStringLiteral(":/json/WimaArea.SettingsGroup.json"), - this /* QObject parent */)), - _borderPolygonOffset(SettingsFact(settingsGroup, - _metaDataMap[borderPolygonOffsetName], - this /* QObject parent */)), - _showBorderPolygon(SettingsFact(settingsGroup, - _metaDataMap[showBorderPolygonName], - this /* QObject parent */)), - _borderPolygon(QGCMapPolygon(this)), _wimaAreaInteractive(false) { - init(); - *this = other; -} - -/*! - *\fn WimaArea &WimaArea::operator=(const WimaArea &other) - * - * Assigns \a other to this \c WimaArea and returns a reference to this \c - *WimaArea. - * - * Copies only path and maximum altitude. - */ -WimaArea &WimaArea::operator=(const WimaArea &other) { - QGCMapPolygon::operator=(other); - this->setMaxAltitude(other._maxAltitude); - this->setPath(other.path()); - - return *this; -} - -void WimaArea::setWimaAreaInteractive(bool interactive) { - if (WimaArea::_wimaAreaInteractive != interactive) { - WimaArea::_wimaAreaInteractive = interactive; - - emit WimaArea::wimaAreaInteractiveChanged(); - } -} - -/*! - \fn void WimaArea::setMaxAltitude(double altitude) - - Sets the \c _maxAltitude member to \a altitude and emits the signal \c - maxAltitudeChanged() if \c _maxAltitude is not equal to altitude. - */ -void WimaArea::setMaxAltitude(double altitude) { - if (altitude > 0 && qFuzzyCompare(altitude, _maxAltitude)) { - _maxAltitude = altitude; - emit maxAltitudeChanged(); - } -} - -void WimaArea::setShowBorderPolygon(bool showBorderPolygon) { - _showBorderPolygon.setRawValue(showBorderPolygon); -} - -void WimaArea::setBorderPolygonOffset(double offset) { - if (!qFuzzyCompare(_borderPolygonOffset.rawValue().toDouble(), offset)) { - _borderPolygonOffset.setRawValue(offset); - - emit borderPolygonOffsetChanged(); - } -} - -void WimaArea::recalcPolygons() { - if (_showBorderPolygon.rawValue().toBool() == true) { - - if (_borderPolygon.count() >= 3) { - //_borderPolygon.verifyClockwiseWinding(); // causes seg. fault - this->setPath(_borderPolygon.coordinateList()); - this->offset(-_borderPolygonOffset.rawValue().toDouble()); - } - } else { - - if (this->count() >= 3) { - // this->verifyClockwiseWinding(); // causes seg. fault - _borderPolygon.setPath(this->coordinateList()); - _borderPolygon.offset(_borderPolygonOffset.rawValue().toDouble()); - } - - emit borderPolygonChanged(); - } -} - -void WimaArea::updatePolygonConnections(QVariant showBorderPolygon) { - if (showBorderPolygon.toBool() == true) { - connect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, - &WimaArea::recalcPolygons); - disconnect(this, &QGCMapPolygon::pathChanged, this, - &WimaArea::recalcPolygons); - } else { - disconnect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, - &WimaArea::recalcPolygons); - connect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); - } -} - -void WimaArea::recalcInteractivity() { - if (_wimaAreaInteractive == false) { - QGCMapPolygon::setInteractive(false); - _borderPolygon.setInteractive(false); - } else { - if (_showBorderPolygon.rawValue().toBool() == true) { - _borderPolygon.setInteractive(true); - QGCMapPolygon::setInteractive(false); - } else { - _borderPolygon.setInteractive(false); - QGCMapPolygon::setInteractive(true); - } - } -} - -/*! - * \fn int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) - * const Returns the index of the vertex (element of the polygon path) which has - * the least distance to \a coordinate. - * - * \sa QGeoCoordinate - */ -int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) const { - if (this->count() == 0) { - qWarning("Polygon count == 0!"); - return -1; - } else if (this->count() == 1) { - return 0; - } else { - int index = 0; - double min_dist = coordinate.distanceTo(this->vertexCoordinate(index)); - for (int i = 1; i < this->count(); i++) { - double dist = coordinate.distanceTo(this->vertexCoordinate(i)); - if (dist < min_dist) { - min_dist = dist; - index = i; - } - } - - return index; - } -} - -/*! - * \fn QGeoCoordinate WimaArea::getClosestVertex(const QGeoCoordinate& - * coordinate) const Returns the vertex of the polygon path with the least - * distance to \a coordinate. - * - * \sa QGeoCoordinate - */ -QGeoCoordinate -WimaArea::getClosestVertex(const QGeoCoordinate &coordinate) const { - return this->vertexCoordinate(getClosestVertexIndex(coordinate)); -} - -/*! - * \fn QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &area) - * Converts the \c WimaArea \a area to \c QGCMapPolygon by copying the path - * only. - */ -QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &area) { - QGCMapPolygon qgcPoly; - qgcPoly.setPath(area.path()); - - return QGCMapPolygon(qgcPoly); -} - -/*! - * \fn QGCMapPolygon WimaArea::toQGCPolygon() const - * Converts the calling \c WimaArea to \c QGCMapPolygon by copying the path - * only. - */ -QGCMapPolygon WimaArea::toQGCPolygon() const { return toQGCPolygon(*this); } - -/*! - * \fn bool WimaArea::join(WimaArea &area1, WimaArea &area2, WimaArea - * &joinedArea, QString &errorString) Joins the areas \a area1 and \a area2 such - * that a \l {Simple Polygon} is created. Stores the result inside \a - * joinedArea. Stores error messages in \a errorString. Returns \c true if the - * algorithm was able to join the areas; false else. The algorithm will be able - * to join the areas, if either their edges intersect with each other, or one - * area contains the other. - */ -bool WimaArea::join(const WimaArea &area1, const WimaArea &area2, - WimaArea &joinedArea, QString &errorString) { - using namespace GeoUtilities; - using namespace PolygonCalculus; - - Q_UNUSED(errorString); - - QList GeoPolygon1 = area1.coordinateList(); - QList GeoPolygon2 = area2.coordinateList(); - - // qWarning("befor joining"); - // qWarning() << GeoPolygon1; - // qWarning() << GeoPolygon2; - - QGeoCoordinate origin = GeoPolygon1[0]; - - // QGeoCoordinate tset = GeoPolygon1[2]; - - // qWarning() << tset;qWarning() << toGeo(toCartesian2D(tset, origin), - // origin); - - QPolygonF polygon1; - toCartesianList(GeoPolygon1, origin, polygon1); - QPolygonF polygon2; - toCartesianList(GeoPolygon2, origin, polygon2); - - // qWarning("after 1 transform"); - // qWarning() << polygon1; - // qWarning() << polygon2; - - QPolygonF joinedPolygon; - JoinPolygonError retValue = - PolygonCalculus::join(polygon1, polygon2, joinedPolygon); - - // qWarning("after joining"); - // qWarning() << joinedPolygon; - - if (retValue == JoinPolygonError::Disjoint) { - qWarning("Polygons are disjoint."); - } else if (retValue == JoinPolygonError::NotSimplePolygon) { - qWarning("Not a simple polygon."); - } else if (retValue == JoinPolygonError::PathSizeLow) { - qWarning("Polygon vertex count is low."); - } else { - QList path; - toGeoList(joinedPolygon, origin, path); - // qWarning("after transform"); - // qWarning() << path; - joinedArea.setPath(path); - return true; - } - - return false; -} - -/*! - * \fn bool WimaArea::join(WimaArea &area1, WimaArea &area2, WimaArea - * &joinedArea) Joins the areas \a area1 and \a area2 such that a \l {Simple - * Polygon} is created. Stores the result inside \a joinedArea. Returns \c true - * if the algorithm was able to join the areas; false else. The algorithm will - * be able to join the areas, if either their edges intersect with each other, - * or one area contains the other. - */ -bool WimaArea::join(const WimaArea &area1, const WimaArea &area2, - WimaArea &joinedArea) { - QString dummy; - return join(area1, area2, joinedArea, dummy); -} - -/*! - * \fn bool WimaArea::join(WimaArea &area) - * Joins the calling \c WimaArea and the \a area such that a \l {Simple Polygon} - * is created. Overwrites the calling \c WimaArea with the result, if the - * algorithm was successful. Returns \c true if the algorithm was able to join - * the areas; false else. The algorithm will be able to join the areas, if - * either their edges intersect with each other, or one area contains the other. - */ -bool WimaArea::join(WimaArea &area) { - WimaArea joinedArea; - if (join(*this, area, joinedArea)) { - // qWarning("WimaArea::join(WimaArea &area)"); - // qWarning() << joinedArea.coordinateList(); - this->setPath(joinedArea.path()); - return true; - } else { - return false; - } -} - -/*! - * \fn bool WimaArea::join(WimaArea &area, QString &errorString) - * Joins the calling \c WimaArea and the \a area such that a \l {Simple Polygon} - * is created. Overwrites the calling \c WimaArea with the result, if the - * algorithm was successful. - * - * Returns \c true if the algorithm was able to join the areas; false else. - * Stores error messages in \a errorString. - * - * The algorithm will be able to join the areas, if either their edges intersect - * with each other, or one area contains the other. - */ -bool WimaArea::join(WimaArea &area, QString &errorString) { - WimaArea joinedArea; - if (join(*this, area, joinedArea, errorString)) { - this->setPath(joinedArea.path()); - return true; - } else { - return false; - } -} - -/*! - * \fn int WimaArea::nextVertexIndex(int index) const - * Returns the index of the next vertex (of the areas path), which is \a index + - * 1 if \a index is smaller than \c {area.count() - 1}, or 0 if \a index equals - * \c {area.count() - 1}, or -1 if the \a index is out of bounds. \note The - * function \c {area.count()} (derived from \c QGCMapPolygon) returns the number - * of vertices defining the area. - */ -int WimaArea::nextVertexIndex(int index) const { - if (index >= 0 && index < count() - 1) { - return index + 1; - } else if (index == count() - 1) { - return 0; - } else { - qWarning( - "WimaArea::nextVertexIndex(): Index out of bounds! index:count = %i:%i", - index, count()); - return -1; - } -} - -/*! - * \fn int WimaArea::previousVertexIndex(int index) const - * Returns the index of the previous vertex (of the areas path), which is \a - * index - 1 if \a index is larger 0, or \c {area.count() - 1} if \a index - * equals 0, or -1 if the \a index is out of bounds. \note The function \c - * {area.count()} (derived from \c QGCMapPolygon) returns the number of vertices - * defining the area. - */ -int WimaArea::previousVertexIndex(int index) const { - if (index > 0 && index < count()) { - return index - 1; - } else if (index == 0) { - return count() - 1; - } else { - qWarning("WimaArea::previousVertexIndex(): Index out of bounds! " - "index:count = %i:%i", - index, count()); - return -1; - } -} - -/*! - * \fn bool WimaArea::isSelfIntersecting() - * Returns \c true if the calling area is self intersecting, \c false else. - * \note If the calling area is self intersecting, it's not a \l {Simple - * Polygon}. - */ -bool WimaArea::isSimplePolygon() const { - using namespace PolygonCalculus; - using namespace GeoUtilities; - - if (this->count() > 2) { - QPolygonF polygon; - toCartesianList(this->coordinateList(), this->vertexCoordinate(0), polygon); - return PolygonCalculus::isSimplePolygon(polygon); - } else - return false; -} - -bool WimaArea::containsCoordinate(const QGeoCoordinate &coordinate) const { - using namespace PlanimetryCalculus; - using namespace PolygonCalculus; - using namespace GeoUtilities; - - if (this->count() > 2) { - QPolygonF polygon; - toCartesianList(this->coordinateList(), coordinate, polygon); - return PlanimetryCalculus::contains(polygon, QPointF(0, 0)); - } else - return false; -} - -/*! - * \fn void WimaArea::saveToJson(QJsonObject &json) - * Saves the calling area to \c QJsonObject object and stores it inside \a json. - * - * \sa QJsonObject - */ -void WimaArea::saveToJson(QJsonObject &json) { - this->QGCMapPolygon::saveToJson(json); - - json[maxAltitudeName] = _maxAltitude; - json[borderPolygonOffsetName] = _borderPolygonOffset.rawValue().toDouble(); - json[showBorderPolygonName] = _showBorderPolygon.rawValue().toDouble(); - json[areaTypeName] = wimaAreaName; -} - -/*! - * \fn bool WimaArea::loadFromJson(const QJsonObject &json, QString& - * errorString) Loads data from \a json and stores it inside the calling area. - * Returns \c true if loading was successful, \c false else. - * Stores error messages inside \a errorString. - * - * \sa QJsonObject - */ -bool WimaArea::loadFromJson(const QJsonObject &json, QString &errorString) { - if (this->QGCMapPolygon::loadFromJson(json, false /*no poly required*/, - errorString)) { - if (json.contains(maxAltitudeName) && json[maxAltitudeName].isDouble()) { - _maxAltitude = json[maxAltitudeName].toDouble(); - } else { - errorString.append(tr("Could not load Maximum Altitude value!\n")); - return false; - } - - if (json.contains(borderPolygonOffsetName) && - json[borderPolygonOffsetName].isDouble()) { - _borderPolygonOffset.setRawValue( - json[borderPolygonOffsetName].toDouble()); - } else { - errorString.append(tr("Could not load border polygon offset value!\n")); - return false; - } - - if (json.contains(showBorderPolygonName) && - json[showBorderPolygonName].isDouble()) { - _showBorderPolygon.setRawValue(json[showBorderPolygonName].toBool()); - } else { - errorString.append(tr("Could not load border polygon offset value!\n")); - return false; - } - } else { - qWarning() << errorString; - return false; - } - - return true; -} - -/*! - * \fn void WimaArea::init() - * Funtion to be called during construction. - */ -void WimaArea::init() { - this->setObjectName(wimaAreaName); - - if (_showBorderPolygon.rawValue().toBool() == true) { - connect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, - &WimaArea::recalcPolygons); - - } else { - connect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); - } - - connect(&_borderPolygonOffset, &SettingsFact::rawValueChanged, this, - &WimaArea::recalcPolygons); - connect(&_showBorderPolygon, &SettingsFact::rawValueChanged, this, - &WimaArea::updatePolygonConnections); - connect(&_showBorderPolygon, &SettingsFact::rawValueChanged, this, - &WimaArea::recalcInteractivity); - connect(this, &WimaArea::wimaAreaInteractiveChanged, this, - &WimaArea::recalcInteractivity); -} - -// QDoc Documentation - -/*! - \group WimaAreaGroup - \title Group of WimaAreas - - Every \c WimaArea of the equally named group uses a \l {Simple Polygon} - derived from \c {QGCMapPolygon} to define areas inside which certain taskts - are performed. -*/ - -/*! - \class WimaArea - \inmodule Wima - \ingroup WimaArea - - \brief The \c WimaArea class provides the a base class for - all areas used within the Wima extension. - - \c WimaArea uses a \l {Simple Polygon} derived from \c {QGCMapPolygon} - to define areas inside which certain taskts are performed. The polygon - (often refered to as the path) can be displayed visually on a map. -*/ - -/*! - \variable WimaArea::_maxAltitude - \brief The maximum altitude vehicles are allowed to fly inside this area. -*/ - -/*! - \property WimaArea::maxAltitude - \brief The maximum altitude at which vehicles are allowed to fly. -*/ - -/*! - \property WimaArea::mapVisualQML - \brief A string containing the name of the QML file used to displays this area - on a map. -*/ - -/*! - \property WimaArea::editorQML - \brief A string containing the name of the QML file allowing to edit the - area's properties. -*/ - -/*! - \externalpage https://en.wikipedia.org/wiki/Simple_polygon - \title Simple Polygon -*/ - -/*! - \externalpage https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm - \title Dijkstra Algorithm -*/ diff --git a/src/RouteMissionItem/geometry/WimaArea.h b/src/RouteMissionItem/geometry/WimaArea.h deleted file mode 100644 index 34b12c51456dbf87932dba7a5fc6f857b4c8a789..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaArea.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "QGCGeo.h" -#include "QGCMapPolygon.h" -#include "QGCMapPolyline.h" -#include "Vehicle.h" -#include "qobject.h" -#include -#include -#include - -#include "GeoUtilities.h" -#include "PlanimetryCalculus.h" -#include "PolygonCalculus.h" - -class WimaArea : public QGCMapPolygon // abstract base class for all WimaAreas -{ - Q_OBJECT -public: - WimaArea(QObject *parent = nullptr); - WimaArea(const WimaArea &other, QObject *parent = nullptr); - WimaArea &operator=(const WimaArea &other); - - Q_PROPERTY(QString mapVisualQML READ mapVisualQML CONSTANT) - Q_PROPERTY(QString editorQML READ editorQML CONSTANT) - - virtual QString mapVisualQML(void) const = 0; - virtual QString editorQML(void) const = 0; - - void saveToJson(QJsonObject &jsonObject); - bool loadFromJson(const QJsonObject &jsonObject, QString &errorString); - - // static Members - static const char *wimaAreaName; - static const char *areaTypeName; - static const char *settingsGroup; - -private: - void init(); -}; diff --git a/src/RouteMissionItem/geometry/WimaAreaData.cc b/src/RouteMissionItem/geometry/WimaAreaData.cc deleted file mode 100644 index 69756dac197e9d7fe8926fe564dd580352a28ab2..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaAreaData.cc +++ /dev/null @@ -1,127 +0,0 @@ -#include "WimaAreaData.h" - -WimaAreaData::WimaAreaData(QObject *parent) : QObject(parent) {} - -WimaAreaData::WimaAreaData(const WimaAreaData &other, QObject *parent) - : QObject(parent), _path(other._path), _list(other._list), - _center(other._center) {} - -WimaAreaData::WimaAreaData(const WimaArea &otherData, QObject *parent) - : QObject(parent), _center(otherData.center()) { - _setPathImpl(otherData.path()); -} - -bool WimaAreaData::operator==(const WimaAreaData &data) const { - return this->_path == data._path && this->_center == data._center; -} - -bool WimaAreaData::operator!=(const WimaAreaData &data) const { - return !this->operator==(data); -} - -QVariantList WimaAreaData::path() const { return _path; } - -QGeoCoordinate WimaAreaData::center() const { return _center; } - -const QList &WimaAreaData::coordinateList() const { - return _list; -} - -bool WimaAreaData::containsCoordinate(const QGeoCoordinate &coordinate) const { - using namespace PlanimetryCalculus; - using namespace PolygonCalculus; - using namespace GeoUtilities; - - if (this->coordinateList().size() > 2) { - QPolygonF polygon; - toCartesianList(this->coordinateList(), coordinate, polygon); - return PlanimetryCalculus::contains(polygon, QPointF(0, 0)); - } else - return false; -} - -void WimaAreaData::append(const QGeoCoordinate &c) { - _list.append(c); - _path.push_back(QVariant::fromValue(c)); - emit pathChanged(); -} - -void WimaAreaData::push_back(const QGeoCoordinate &c) { append(c); } - -void WimaAreaData::clear() { - if (_list.size() > 0 || _path.size() > 0) { - _list.clear(); - _path.clear(); - emit pathChanged(); - } -} - -void WimaAreaData::setPath(const QVariantList &coordinateList) { - if (_path != coordinateList) { - _setPathImpl(coordinateList); - emit pathChanged(); - } -} - -void WimaAreaData::setCenter(const QGeoCoordinate ¢er) { - if (_center != center) { - _center = center; - emit centerChanged(); - } -} - -WimaAreaData &WimaAreaData::operator=(const WimaAreaData &otherData) { - setPath(otherData._list); - setCenter(otherData._center); - return *this; -} - -WimaAreaData &WimaAreaData::operator=(const WimaArea &otherData) { - setPath(otherData.path()); - setCenter(otherData.center()); - return *this; -} - -void WimaAreaData::_setPathImpl(const QList &coordinateList) { - _list = coordinateList; - _path.clear(); - // copy all coordinates to _path - for (const auto &vertex : coordinateList) { - _path.append(QVariant::fromValue(vertex)); - } -} - -void WimaAreaData::_setPathImpl(const QVariantList &coordinateList) { - _path = coordinateList; - _list.clear(); - for (const auto &variant : coordinateList) { - _list.push_back(variant.value()); - } -} - -/*! - * \fn void WimaAreaData::setPath(const QList &coordinateList) - * - * Sets the path member to \a coordinateList by copying all entries of \a - * coordinateList. Emits the \c pathChanged() signal. - */ -void WimaAreaData::setPath(const QList &coordinateList) { - if (_list != coordinateList) { - _setPathImpl(coordinateList); - emit pathChanged(); - } -} - -bool operator==(const WimaAreaData &m1, const WimaArea &m2) { - return m1.path() == m2.path() && m1.center() == m2.center(); -} - -bool operator!=(const WimaAreaData &m1, const WimaArea &m2) { - return !operator==(m1, m2); -} - -bool operator==(const WimaArea &m1, const WimaAreaData &m2) { return m2 == m1; } - -bool operator!=(const WimaArea &m1, const WimaAreaData &m2) { - return !operator==(m2, m1); -} diff --git a/src/RouteMissionItem/geometry/WimaAreaData.h b/src/RouteMissionItem/geometry/WimaAreaData.h deleted file mode 100644 index b9348aa5fcfe9ad63271cc7aea27a023665f7101..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaAreaData.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include - -#include "QGeoCoordinate" - -#include "WimaArea.h" - -class WimaAreaData - : public QObject // Abstract class for all WimaAreaData derived objects -{ - Q_OBJECT -public: - WimaAreaData(QObject *parent = nullptr); - - Q_PROPERTY(const QVariantList path READ path NOTIFY pathChanged) - Q_PROPERTY(QString type READ type CONSTANT) - Q_PROPERTY(QString mapVisualQML READ mapVisualQML CONSTANT) - - bool operator==(const WimaAreaData &data) const; - bool operator!=(const WimaAreaData &data) const; - - virtual QString mapVisualQML(void) const = 0; - - QVariantList path() const; - QGeoCoordinate center() const; - const QList &coordinateList() const; - bool containsCoordinate(const QGeoCoordinate &coordinate) const; - virtual QString type() const = 0; - - void append(const QGeoCoordinate &c); - void push_back(const QGeoCoordinate &c); - void clear(); - -signals: - void pathChanged(); - void centerChanged(); - -public slots: - void setPath(const QList &coordinateList); - void setPath(const QVariantList &coordinateList); - void setCenter(const QGeoCoordinate ¢er); - -protected: - WimaAreaData(const WimaAreaData &otherData, QObject *parent); - WimaAreaData(const WimaArea &otherData, QObject *parent); - WimaAreaData &operator=(const WimaAreaData &otherData); - WimaAreaData &operator=(const WimaArea &otherData); - -private: - void _setPathImpl(const QList &coordinateList); - void _setPathImpl(const QVariantList &coordinateList); - // Member Variables - mutable QVariantList _path; - QList _list; - QGeoCoordinate _center; -}; - -bool operator==(const WimaAreaData &m1, const WimaArea &m2); -bool operator!=(const WimaAreaData &m1, const WimaArea &m2); -bool operator==(const WimaArea &m1, const WimaAreaData &m2); -bool operator!=(const WimaArea &m1, const WimaAreaData &m2); diff --git a/src/RouteMissionItem/geometry/WimaMeasurementAreaData.cc b/src/RouteMissionItem/geometry/WimaMeasurementAreaData.cc deleted file mode 100644 index 5d0b68fcc3b16aaf18b248600ab6404002e08378..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaMeasurementAreaData.cc +++ /dev/null @@ -1,137 +0,0 @@ -#include "WimaMeasurementAreaData.h" -#include "SnakeTile.h" - -const char *WimaMeasurementAreaData::typeString = "WimaMeasurementAreaData"; - -WimaMeasurementAreaData::WimaMeasurementAreaData(QObject *parent) - : WimaAreaData(parent) {} - -WimaMeasurementAreaData::WimaMeasurementAreaData( - const WimaMeasurementAreaData &other, QObject *parent) - : WimaAreaData(parent) { - *this = other; -} - -WimaMeasurementAreaData::WimaMeasurementAreaData( - const WimaMeasurementArea &other, QObject *parent) - : WimaAreaData(parent) { - *this = other; -} - -bool WimaMeasurementAreaData:: -operator==(const WimaMeasurementAreaData &other) const { - return this->WimaAreaData::operator==(other) && - this->_tileData == other.tileData() && - this->_progress == other.progress() && - this->center() == other.center(); -} - -bool WimaMeasurementAreaData:: -operator!=(const WimaMeasurementAreaData &other) const { - return !(*this == other); -} - -void WimaMeasurementAreaData::setTileData(const TileData &d) { - if (this->_tileData != d) { - this->_tileData = d; - this->_progress.fill(0, d.size()); - emit progressChanged(); - emit tileDataChanged(); - } -} - -void WimaMeasurementAreaData::setProgress(const QVector &d) { - if (this->_progress != d && d.size() == this->_tileData.tiles.count()) { - this->_progress = d; - emit progressChanged(); - } -} - -/*! - * \overload operator=(); - * - * Assigns \a other to the invoking object. - */ -WimaMeasurementAreaData &WimaMeasurementAreaData:: -operator=(const WimaMeasurementAreaData &other) { - WimaAreaData::operator=(other); - setTileData(other._tileData); - setProgress(other._progress); - - return *this; -} - -/*! - * \overload operator=(); - * - * Assigns \a other to the invoking object. - */ -WimaMeasurementAreaData &WimaMeasurementAreaData:: -operator=(const WimaMeasurementArea &other) { - WimaAreaData::operator=(other); - if (other.ready()) { - setTileData(other.tileData()); - setProgress(other.progress()); - } else { - qWarning() << "WimaMeasurementAreaData::operator=(): WimaMeasurementArea " - "not ready."; - } - - return *this; -} - -QString WimaMeasurementAreaData::mapVisualQML() const { - return QStringLiteral("WimaMeasurementAreaDataVisual.qml"); -} - -QString WimaMeasurementAreaData::type() const { return this->typeString; } - -QmlObjectListModel *WimaMeasurementAreaData::tiles() { - return &this->_tileData.tiles; -} - -const QmlObjectListModel *WimaMeasurementAreaData::tiles() const { - return &this->_tileData.tiles; -} - -const QVariantList &WimaMeasurementAreaData::tileCenterPoints() const { - return this->_tileData.tileCenterPoints; -} - -QVariantList &WimaMeasurementAreaData::tileCenterPoints() { - return this->_tileData.tileCenterPoints; -} - -const TileData &WimaMeasurementAreaData::tileData() const { - return this->_tileData; -} - -TileData &WimaMeasurementAreaData::tileData() { return this->_tileData; } - -const QVector &WimaMeasurementAreaData::progress() const { - return this->_progress; -} - -QVector &WimaMeasurementAreaData::progress() { return this->_progress; } - -bool operator==(const WimaMeasurementAreaData &m1, - const WimaMeasurementArea &m2) { - return operator==(*static_cast(&m1), - *static_cast(&m2)) && - m1.tileData() == m2.tileData() && m1.progress() == m2.progress(); -} - -bool operator!=(const WimaMeasurementAreaData &m1, - const WimaMeasurementArea &m2) { - return !(m1 == m2); -} - -bool operator==(const WimaMeasurementArea &m1, - const WimaMeasurementAreaData &m2) { - return m2 == m1; -} - -bool operator!=(const WimaMeasurementArea &m1, - const WimaMeasurementAreaData &m2) { - return m2 != m1; -} diff --git a/src/RouteMissionItem/geometry/WimaMeasurementAreaData.h b/src/RouteMissionItem/geometry/WimaMeasurementAreaData.h deleted file mode 100644 index dde208291fdd5c69080d651e9f828dcdd3852bf7..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaMeasurementAreaData.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include - -#include "WimaAreaData.h" -#include "WimaMeasurementArea.h" - -class WimaMeasurementAreaData : public WimaAreaData { - Q_OBJECT - -public: - WimaMeasurementAreaData(QObject *parent = nullptr); - WimaMeasurementAreaData(const WimaMeasurementAreaData &other, - QObject *parent = nullptr); - WimaMeasurementAreaData(const WimaMeasurementArea &other, - QObject *parent = nullptr); - WimaMeasurementAreaData &operator=(const WimaMeasurementAreaData &other); - WimaMeasurementAreaData &operator=(const WimaMeasurementArea &other); - - Q_PROPERTY(QmlObjectListModel *tiles READ tiles NOTIFY tileDataChanged) - Q_PROPERTY(QVector progress READ progress NOTIFY progressChanged) - - virtual QString mapVisualQML() const override; - - bool operator==(const WimaMeasurementAreaData &other) const; - bool operator!=(const WimaMeasurementAreaData &other) const; - - // Property setters. - void setTileData(const TileData &d); - void setProgress(const QVector &d); - - // Property getters. - QString type() const; - WimaMeasurementAreaData *Clone() const { - return new WimaMeasurementAreaData(*this); - } - QmlObjectListModel *tiles(); - const QmlObjectListModel *tiles() const; - const QVariantList &tileCenterPoints() const; - QVariantList &tileCenterPoints(); - const TileData &tileData() const; - TileData &tileData(); - const QVector &progress() const; - QVector &progress(); - - static const char *typeString; - -signals: - void tileDataChanged(); - void progressChanged(); - -private: - TileData _tileData; - QVector _progress; -}; - -bool operator==(const WimaMeasurementAreaData &m1, - const WimaMeasurementArea &m2); -bool operator!=(const WimaMeasurementAreaData &m1, - const WimaMeasurementArea &m2); -bool operator==(const WimaMeasurementArea &m1, - const WimaMeasurementAreaData &m2); -bool operator!=(const WimaMeasurementArea &m1, - const WimaMeasurementAreaData &m2); diff --git a/src/RouteMissionItem/geometry/WimaServiceAreaData.cc b/src/RouteMissionItem/geometry/WimaServiceAreaData.cc deleted file mode 100644 index b90acabfaf55cb705ab665f7368fbc99090a9a1b..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaServiceAreaData.cc +++ /dev/null @@ -1,54 +0,0 @@ -#include "WimaServiceAreaData.h" - -const char *WimaServiceAreaData::typeString = "WimaServiceAreaData"; - -WimaServiceAreaData::WimaServiceAreaData(QObject *parent) - : WimaAreaData(parent) {} - -WimaServiceAreaData::WimaServiceAreaData(const WimaServiceAreaData &other, - QObject *parent) - : WimaAreaData(other, parent), _depot(other._depot) {} - -WimaServiceAreaData::WimaServiceAreaData(const WimaServiceArea &other, - QObject *parent) - : WimaAreaData(other, parent), _depot(other.depot()) {} - -WimaServiceAreaData &WimaServiceAreaData:: -operator=(const WimaServiceAreaData &otherData) { - WimaAreaData::operator=(otherData); - this->setDepot(otherData.depot()); - return *this; -} - -WimaServiceAreaData &WimaServiceAreaData:: -operator=(const WimaServiceArea &otherArea) { - WimaAreaData::operator=(otherArea); - this->setDepot(otherArea.depot()); - return *this; -} - -QString WimaServiceAreaData::mapVisualQML() const { - return QStringLiteral("WimaServiceAreaDataVisual.qml"); -} - -/*! - * \fn const QGeoCoordinate &WimaServiceAreaData::takeOffPosition() const - * Returns a constant reference to the takeOffPosition. - * - */ -const QGeoCoordinate &WimaServiceAreaData::depot() const { return _depot; } - -QString WimaServiceAreaData::type() const { return this->typeString; } -/*! - * \fn void WimaServiceAreaData::setTakeOffPosition(const QGeoCoordinate - * &newCoordinate) Sets the takeoff position to the \a newCoordinate and emits - * the takeOffPositionChanged() signal, if newCoordinate differs from the member - * value. - * - */ -void WimaServiceAreaData::setDepot(const QGeoCoordinate &newCoordinate) { - if (_depot != newCoordinate) { - _depot = newCoordinate; - emit depotChanged(_depot); - } -} diff --git a/src/RouteMissionItem/geometry/WimaServiceAreaData.h b/src/RouteMissionItem/geometry/WimaServiceAreaData.h deleted file mode 100644 index 2b93a20964008977061f992314174feee299d408..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/geometry/WimaServiceAreaData.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "QGeoCoordinate" -#include - -#include "WimaAreaData.h" -#include "WimaServiceArea.h" - -class WimaServiceAreaData : public WimaAreaData { - Q_OBJECT - -public: - WimaServiceAreaData(QObject *parent = nullptr); - WimaServiceAreaData(const WimaServiceAreaData &other, - QObject *parent = nullptr); - WimaServiceAreaData(const WimaServiceArea &other, QObject *parent = nullptr); - WimaServiceAreaData &operator=(const WimaServiceAreaData &otherData); - WimaServiceAreaData &operator=(const WimaServiceArea &otherArea); - - virtual QString mapVisualQML() const override; - - const QGeoCoordinate &depot() const; - - QString type() const; - WimaServiceAreaData *Clone() const { return new WimaServiceAreaData(); } - static const char *typeString; - -signals: - void depotChanged(const QGeoCoordinate &other); - -public slots: - void setDepot(const QGeoCoordinate &newCoordinate); - -private: - // see WimaServieArea.h for explanation - QGeoCoordinate _depot; -}; diff --git a/src/RouteMissionItem/json/CircularSurvey.SettingsGroup.json b/src/RouteMissionItem/json/RouteComplexItem.SettingsGroup.json similarity index 100% rename from src/RouteMissionItem/json/CircularSurvey.SettingsGroup.json rename to src/RouteMissionItem/json/RouteComplexItem.SettingsGroup.json diff --git a/src/RouteMissionItem/json/WimaController.SettingsGroup.json b/src/RouteMissionItem/json/WimaController.SettingsGroup.json deleted file mode 100644 index a6abbe7628ab4d8ccf522851405fa496356b106c..0000000000000000000000000000000000000000 --- a/src/RouteMissionItem/json/WimaController.SettingsGroup.json +++ /dev/null @@ -1,104 +0,0 @@ -[ -{ - "name": "EnableWimaController", - "shortDescription": "Enables or disables the WimaController, which performes different tasks inside the flight view window.", - "type": "bool", - "defaultValue": 1 -}, -{ - "name": "OverlapWaypoints", - "shortDescription": "Determines the number of overlapping waypoints between two consecutive mission phases.", - "type": "uint32", - "defaultValue": 2 -}, -{ - "name": "MaxWaypointsPerPhase", - "shortDescription": "Determines the maximum number of waypoints per phase. This number does not include the arrival and return path.", - "type": "uint32", - "min" : 1, - "defaultValue": 30 -}, -{ - "name": "StartWaypointIndex", - "shortDescription": "The index of the start waypoint for the next mission phase.", - "type": "uint32", - "min" : 1, - "defaultValue": 1 -}, -{ - "name": "ShowAllMissionItems", - "shortDescription": "Determines whether the mission items of the overall mission are displayed or not.", - "type": "bool", - "defaultValue": 1 -}, -{ - "name": "ShowCurrentMissionItems", - "shortDescription": "Determines whether the mission items of the current mission phase are displayed or not.", - "type": "bool", - "defaultValue": 1 -}, -{ - "name": "FlightSpeed", - "shortDescription": "The phase flight speed.", - "type": "double", - "min" : 0.3, - "max" : 5, - "defaultValue": 2 -}, -{ - "name": "ArrivalReturnSpeed", - "shortDescription": "The flight speed for arrival and return path.", - "type": "double", - "min" : 0.3, - "max" : 5, - "defaultValue": 5 -}, -{ - "name": "Altitude", - "shortDescription": "The mission altitude.", - "type": "double", - "min" : 1, - "defaultValue": 5 -}, -{ - "name": "Reverse", - "shortDescription": "Reverses the phase direction. Phases go from high to low waypoint numbers, if checked.", - "type": "bool", - "defaultValue": false -}, -{ - "name": "SnakeTileWidth", - "shortDescription": "Snake: Tile Width.", - "type": "double", - "min" : 0.3, - "defaultValue": 5 -}, -{ - "name": "SnakeTileHeight", - "shortDescription": "Snake: Tile Height.", - "type": "double", - "min" : 0.3, - "defaultValue": 5 -}, -{ - "name": "SnakeMinTileArea", - "shortDescription": "Snake: Minimal Tile Area.", - "type": "double", - "min" : 0.1, - "defaultValue": 5 -}, -{ - "name": "SnakeLineDistance", - "shortDescription": "Snake: Line Distance.", - "type": "double", - "min" : 0.3, - "defaultValue": 2 -}, -{ - "name": "SnakeMinTransectLength", - "shortDescription": "Snake: Minimal Transect Length.", - "type": "double", - "min" : 0.3, - "defaultValue": 2 -} -] diff --git a/src/RouteMissionItem/nemo_interface/SnakeTile.cpp b/src/RouteMissionItem/nemo_interface/SnakeTile.cpp index 397a2e917bcbd6281144aabb62ae42f4964c20f8..aae74f6a6a9d3fc5857ebadaef4a17cea20de846 100644 --- a/src/RouteMissionItem/nemo_interface/SnakeTile.cpp +++ b/src/RouteMissionItem/nemo_interface/SnakeTile.cpp @@ -1,23 +1,20 @@ #include "SnakeTile.h" -SnakeTile::SnakeTile(QObject *parent) : WimaAreaData(parent) {} +SnakeTile::SnakeTile(QObject *parent) : GeoArea(parent) {} SnakeTile::SnakeTile(const SnakeTile &other, QObject *parent) - : WimaAreaData(parent) { + : GeoArea(parent) { *this = other; } SnakeTile::~SnakeTile() {} -QString SnakeTile::mapVisualQML() const { - return QStringLiteral("WimaAreaNoVisual.qml"); -} - -QString SnakeTile::type() const { return "Tile"; } +QString SnakeTile::mapVisualQML() const { return QStringLiteral(""); } -SnakeTile *SnakeTile::Clone() const { return new SnakeTile(*this); } +QString SnakeTile::editorQML() const { return QStringLiteral(""); } -SnakeTile &SnakeTile::operator=(const SnakeTile &other) { - this->WimaAreaData::operator=(other); - return *this; +SnakeTile *SnakeTile::clone(QObject *parent) const { + return new SnakeTile(*this, parent); } + +void SnakeTile::push_back(const QGeoCoordinate &c) { this->appendVertex(c); } diff --git a/src/RouteMissionItem/nemo_interface/SnakeTile.h b/src/RouteMissionItem/nemo_interface/SnakeTile.h index 8117048fa64bd05fa3b5e2d4b81a89e7cc642319..8db2e7066ea4a5b68404bb70979388a504160f60 100644 --- a/src/RouteMissionItem/nemo_interface/SnakeTile.h +++ b/src/RouteMissionItem/nemo_interface/SnakeTile.h @@ -1,8 +1,10 @@ #pragma once -#include "Wima/Geometry/WimaAreaData.h" +#include "geometry/GeoArea.h" -class SnakeTile : public WimaAreaData { +#include + +class SnakeTile : public GeoArea { Q_OBJECT public: SnakeTile(QObject *parent = nullptr); @@ -10,12 +12,8 @@ public: ~SnakeTile(); virtual QString mapVisualQML() const override; + virtual QString editorQML() const override; + virtual SnakeTile *clone(QObject *parent) const; - QString type() const override; - SnakeTile *Clone() const; - - SnakeTile &operator=(const SnakeTile &other); - -protected: - void assign(const SnakeTile &other); + void push_back(const QGeoCoordinate &c); }; diff --git a/src/RouteMissionItem/nemo_interface/SnakeTileLocal.h b/src/RouteMissionItem/nemo_interface/SnakeTileLocal.h index fa323fa1434ec3fcc5746c8c93a179dfaed08f39..cd2a9c8c4a76cb6eb53a8c68f401f396d466ed1f 100644 --- a/src/RouteMissionItem/nemo_interface/SnakeTileLocal.h +++ b/src/RouteMissionItem/nemo_interface/SnakeTileLocal.h @@ -1,4 +1,4 @@ #pragma once -#include "Wima/Geometry/GenericPolygon.h" +#include "geometry/GenericPolygon.h" using SnakeTileLocal = GenericPolygon; diff --git a/src/RouteMissionItem/nemo_interface/SnakeTilesLocal.h b/src/RouteMissionItem/nemo_interface/SnakeTilesLocal.h index f7ccdc7d2365f9c5b1cbcf50ea4c9f0b259eb59f..6a7406e1f08fe6c00bf807af3f3956d18dbc251e 100644 --- a/src/RouteMissionItem/nemo_interface/SnakeTilesLocal.h +++ b/src/RouteMissionItem/nemo_interface/SnakeTilesLocal.h @@ -1,6 +1,6 @@ #pragma once -#include "Wima/Geometry/GenericPolygonArray.h" -#include "Wima/Snake/SnakeTileLocal.h" +#include "RouteMissionItem/geometry/GenericPolygonArray.h" +#include "RouteMissionItem/nemo_interface/SnakeTileLocal.h" #include typedef GenericPolygonArray SnakeTilesLocal; diff --git a/src/WimaView/CircularSurveyMapVisual.qml b/src/WimaView/CircularSurveyMapVisual.qml index 1ba73b8668f4efca9f7383cab5df9902be30e2eb..3f4e86eab845e65492d2eaf27bf461a9381dde22 100644 --- a/src/WimaView/CircularSurveyMapVisual.qml +++ b/src/WimaView/CircularSurveyMapVisual.qml @@ -26,17 +26,26 @@ Item { property var qgcView ///< QGCView to use for popping dialogs property var _missionItem: object - property var _generator: _missionItem.generator + property var _areaData: _missionItem.areaData + property bool _editing: _missionItem.editing + property var _generator: _missionItem.generator - property var _transectsComponent: undefined - property var _entryCoordinate: undefined - property var _exitCoordinate: undefined - property var _generatorVisuals: undefined + property var _transectsComponent: undefined + property var _entryCoordinate: undefined + property var _exitCoordinate: undefined + property var _generatorVisuals: undefined property bool _isCurrentItem: _missionItem.isCurrentItem signal clicked(int sequenceNumber) + on_EditingChanged: { + _destroyEntryCoordinate() + _destroyExitCoordinate() + _destroyTransectsComponent() + _destroyGeneratorVisuals() + } + Component.onCompleted: { _addEntryCoordinate() _addExitCoordinate() @@ -108,6 +117,19 @@ Item { } } + + Repeater { + model: _areaData.areaList + delegate: WimaMapVisual { + map: _root.map + qgcView: _root.qgcView + } + + onItemAdded: { + //console.log("Item added") + } + } + // Generator visuals function _addGeneratorVisuals(){ if (_generator.mapVisualQml && !_generatorVisuals) { @@ -183,6 +205,4 @@ Item { _transectsComponent = undefined } } - - } diff --git a/src/WimaView/WimaMeasurementAreaEditor.qml b/src/WimaView/MeasurementAreaEditor.qml similarity index 100% rename from src/WimaView/WimaMeasurementAreaEditor.qml rename to src/WimaView/MeasurementAreaEditor.qml diff --git a/src/WimaView/WimaMeasurementAreaMapVisual.qml b/src/WimaView/MeasurementAreaMapVisual.qml similarity index 100% rename from src/WimaView/WimaMeasurementAreaMapVisual.qml rename to src/WimaView/MeasurementAreaMapVisual.qml diff --git a/src/WimaView/WimaServiceAreaEditor.qml b/src/WimaView/SafeAreaEditor.qml similarity index 100% rename from src/WimaView/WimaServiceAreaEditor.qml rename to src/WimaView/SafeAreaEditor.qml diff --git a/src/WimaView/WimaServiceAreaMapVisual.qml b/src/WimaView/SafeAreaMapVisual.qml similarity index 100% rename from src/WimaView/WimaServiceAreaMapVisual.qml rename to src/WimaView/SafeAreaMapVisual.qml diff --git a/src/WimaView/WimaCorridorDataVisual.qml b/src/WimaView/WimaCorridorDataVisual.qml deleted file mode 100644 index 86b323bbe4cc26d442d69e30f9883326e61ab66c..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaCorridorDataVisual.qml +++ /dev/null @@ -1,58 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtLocation 5.3 -import QtPositioning 5.3 - -import QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 - -/// Wima Measurement Area Data visuals -Item { - id: _root - - property var map ///< Map control to place item in - property var qgcView ///< QGCView to use for popping dialogs - - property var areaItem: object - signal clicked(int sequenceNumber) - - property var _polygonComponent - - - function _addPolygon(){ - if(!_polygonComponent){ - _polygonComponent = polygon.createObject(_root) - map.addMapItem(_polygonComponent) - } - } - - function _destroyPolygon(){ - if(_polygonComponent){ - map.removeMapItem(_polygonComponent) - _polygonComponent.destroy() - } - } - - Component.onCompleted: { - _addPolygon() - } - - Component.onDestruction: { - _destroyPolygon() - } - - // Polygon component. - Component{ - id:polygon - - MapPolygon { - path: object.path; - border.color: "black" - color: "blue" - opacity: 0.25 - } - } -} diff --git a/src/WimaView/WimaCorridorMapVisual.qml b/src/WimaView/WimaCorridorMapVisual.qml deleted file mode 100644 index a34b88372d600befbdfba12c7d253c7219f6e370..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaCorridorMapVisual.qml +++ /dev/null @@ -1,111 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2016 QGROUNDCONTROL PROJECT - * - * 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 QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 - -/// Wima Global Measurement Area visuals -Item { - id: _root - - property var map ///< Map control to place item in - property var qgcView ///< QGCView to use for popping dialogs - - property var areaItem: object - property var _polygon: areaItem - //property var _polyline: areaItem.polyline - - signal clicked(int sequenceNumber) - - /// Add an initial 4 sided polygon if there is none - function _addInitialPolygon() { - - // Initial polygon is inset to take 2/3rds space - var rect = Qt.rect(map.centerViewport.x, map.centerViewport.y, map.centerViewport.width, map.centerViewport.height) - rect.x += (rect.width * 0.25) / 2 - rect.y += (rect.height * 0.25) / 2 - rect.width *= 0.75 - rect.height *= 0.75 - - var centerCoord = map.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */) - var topLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */) - var topRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */) - var bottomLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */) - var bottomRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */) - - // Adjust polygon to max size - var maxSize = 100 - var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), maxSize) / 2 - var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), maxSize) / 2 - topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0) - topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0) - bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180) - bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180) - - if (areaItem.showBorderPolygon.rawValue === true) { - - if (areaItem.borderPolygon.count < 3) { - - areaItem.borderPolygon.appendVertex(topLeftCoord) - areaItem.borderPolygon.appendVertex(topRightCoord) - areaItem.borderPolygon.appendVertex(bottomRightCoord) - areaItem.borderPolygon.appendVertex(bottomLeftCoord) - } - } else { - if (_polygon.count < 3) { - - _polygon.appendVertex(topLeftCoord) - _polygon.appendVertex(topRightCoord) - _polygon.appendVertex(bottomRightCoord) - _polygon.appendVertex(bottomLeftCoord) - } - } - - - } - - - - Component.onCompleted: { - _addInitialPolygon() - //_addInitialPolyline() - } - - Component.onDestruction: { - } - - WimaMapPolygonVisuals { - qgcView: _root.qgcView - mapControl: map - mapPolygon: _polygon - borderWidth: 1 - borderColor: "black" - interiorColor: "blue" - interiorOpacity: 0.2 - } - - WimaMapPolygonVisuals { - qgcView: _root.qgcView - mapControl: map - mapPolygon: areaItem.borderPolygon - borderWidth: 1 - borderColor: areaItem.borderPolygon.interactive ? "white" : "transparent" - interiorColor: "transparent" - interiorOpacity: 1 - } - -} diff --git a/src/WimaView/WimaJoinedAreaDataVisual.qml b/src/WimaView/WimaJoinedAreaDataVisual.qml deleted file mode 100644 index 8564d85f1fa0dcafca7de17f03edfa64c7d93f95..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaJoinedAreaDataVisual.qml +++ /dev/null @@ -1,58 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtLocation 5.3 -import QtPositioning 5.3 - -import QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 - -/// Wima Measurement Area Data visuals -Item { - id: _root - - property var map ///< Map control to place item in - property var qgcView ///< QGCView to use for popping dialogs - - property var areaItem: object - signal clicked(int sequenceNumber) - - property var _polygonComponent - - - function _addPolygon(){ - if(!_polygonComponent){ - _polygonComponent = polygon.createObject(_root) - map.addMapItem(_polygonComponent) - } - } - - function _destroyPolygon(){ - if(_polygonComponent){ - map.removeMapItem(_polygonComponent) - _polygonComponent.destroy() - } - } - - Component.onCompleted: { - _addPolygon() - } - - Component.onDestruction: { - _destroyPolygon() - } - - // Polygon component. - Component{ - id:polygon - - MapPolygon { - path: object.path; - border.color: "black" - color: "gray" - opacity: 0.25 - } - } -} diff --git a/src/WimaView/WimaJoinedAreaMapVisual.qml b/src/WimaView/WimaJoinedAreaMapVisual.qml deleted file mode 100644 index fac7abe93221d3ae1e65ce8c0f4f2d4043b18535..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaJoinedAreaMapVisual.qml +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2016 QGROUNDCONTROL PROJECT - * - * 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 QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 - -/// Wima Global Measurement Area visuals -Item { - id: _root - - property var map ///< Map control to place item in - property var qgcView ///< QGCView to use for popping dialogs - - property var areaItem: object - property var _polygon: areaItem - //property var _polyline: areaItem.polyline - - signal clicked(int sequenceNumber) - - /// Add an initial 4 sided polygon if there is none - function _addInitialPolygon() { - if (_polygon.count < 3) { - // Initial polygon is inset to take 2/3rds space - var rect = Qt.rect(map.centerViewport.x, map.centerViewport.y, map.centerViewport.width, map.centerViewport.height) - rect.x += (rect.width * 0.25) / 2 - rect.y += (rect.height * 0.25) / 2 - rect.width *= 0.25 - rect.height *= 0.25 - - var centerCoord = map.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */) - var topLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */) - var topRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */) - var bottomLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */) - var bottomRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */) - - // Adjust polygon to max size - var maxSize = 100 - var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), maxSize) / 2 - var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), maxSize) / 2 - topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0) - topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0) - bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180) - bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180) - - _polygon.appendVertex(topLeftCoord) - _polygon.appendVertex(topRightCoord) - _polygon.appendVertex(bottomRightCoord) - _polygon.appendVertex(bottomLeftCoord) - } - } - - /*function _addInitialPolyline(){ - _polyline.setStartVertexIndex(0); - _polyline.setEndVertexIndex(1); - }*/ - - - - Component.onCompleted: { - //_addInitialPolygon() - //_addInitialPolyline() - } - - Component.onDestruction: { - } - - WimaMapPolygonVisuals { - qgcView: _root.qgcView - mapControl: map - mapPolygon: _polygon - borderWidth: 1 - borderColor: "black" - interiorColor: "blue" - interiorOpacity: 0.25 - } - - /*WimaMapPolylineVisuals { - qgcView: _root.qgcView - mapControl: map - mapPolyline: _polyline - lineWidth: 4 - lineColor: interactive ? "white" : "yellow" - enableSplitHandels: false - enableDragHandels: true - edgeHandelsOnly: true - }*/ -} diff --git a/src/WimaView/WimaMapPolylineVisuals.qml b/src/WimaView/WimaMapPolylineVisuals.qml deleted file mode 100644 index 72c72ddbaf18e01866eb12a8c902137a4ddc99b8..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaMapPolylineVisuals.qml +++ /dev/null @@ -1,353 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2016 QGROUNDCONTROL PROJECT - * - * 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.Dialogs 1.2 - -import QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 -import QGroundControl.ShapeFileHelper 1.0 - -/// QGCmapPolyline map visuals -Item { - id: _root - - property var qgcView ///< QGCView for popping dialogs - property var mapControl ///< Map control to place item in - property var mapPolyline ///< QGCMapPolyline object - property bool interactive: mapPolyline.interactive - property int lineWidth: 3 - property color lineColor: "#be781c" - property bool enableSplitHandels: true - property bool enableDragHandels: true - property bool edgeHandelsOnly: false - - - property var _polylineComponent - property var _dragHandlesComponent - property var _splitHandlesComponent - - property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap - property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2 - - function addVisuals() { - _polylineComponent = polylineComponent.createObject(mapControl) - mapControl.addMapItem(_polylineComponent) - } - - function removeVisuals() { - _polylineComponent.destroy() - } - - function addHandles() { - if (!_dragHandlesComponent) { - if (enableDragHandels){ - _dragHandlesComponent = dragHandlesComponent.createObject(mapControl) - } - - if (enableSplitHandels){ - _splitHandlesComponent = splitHandlesComponent.createObject(mapControl) - } - } - } - - function removeHandles() { - if (_dragHandlesComponent) { - _dragHandlesComponent.destroy() - _dragHandlesComponent = undefined - } - if (_splitHandlesComponent) { - _splitHandlesComponent.destroy() - _splitHandlesComponent = undefined - } - } - - /// Calculate the default/initial polyline - function defaultPolylineVertices() { - var x = map.centerViewport.x + (map.centerViewport.width / 2) - var yInset = map.centerViewport.height / 4 - var topPointCoord = map.toCoordinate(Qt.point(x, map.centerViewport.y + yInset), false /* clipToViewPort */) - var bottomPointCoord = map.toCoordinate(Qt.point(x, map.centerViewport.y + map.centerViewport.height - yInset), false /* clipToViewPort */) - return [ topPointCoord, bottomPointCoord ] - } - - /// Add an initial 2 point polyline - function addInitialPolyline() { - if (mapPolyline.count < 2) { - mapPolyline.clear() - var initialVertices = defaultPolylineVertices() - mapPolyline.appendVertex(initialVertices[0]) - mapPolyline.appendVertex(initialVertices[1]) - } - } - - /// Reset polyline back to initial default - function resetPolyline() { - mapPolyline.clear() - addInitialPolyline() - } - - onInteractiveChanged: { - if (interactive) { - addHandles() - } else { - removeHandles() - } - } - - Component.onCompleted: { - addInitialPolyline() - addVisuals() - if (interactive) { - addHandles() - } - } - - Component.onDestruction: { - removeVisuals() - removeHandles() - } - - QGCPalette { id: qgcPal } - - QGCFileDialog { - id: kmlLoadDialog - qgcView: _root.qgcView - folder: QGroundControl.settingsManager.appSettings.missionSavePath - title: qsTr("Select KML File") - selectExisting: true - nameFilters: ShapeFileHelper.fileDialogKMLFilters - fileExtension: QGroundControl.settingsManager.appSettings.kmlFileExtension - - onAcceptedForLoad: { - mapPolyline.loadKMLFile(file) - close() - } - } - - Menu { - id: menu - property int _removeVertexIndex - - function popUpWithIndex(curIndex) { - _removeVertexIndex = curIndex - removeVertexItem.visible = mapPolyline.count > 2 - menu.popup() - } - - MenuItem { - id: removeVertexItem - text: qsTr("Remove vertex" ) - onTriggered: mapPolyline.removeVertex(menu._removeVertexIndex) - } - MenuItem { - id: swapEndPoints - text: qsTr("Swap End-Points" ) - onTriggered: mapPolyline.swapEndPoints() - } - - MenuSeparator { - visible: removeVertexItem.visible - } - - MenuItem { - text: qsTr("Edit position..." ) - onTriggered: qgcView.showDialog(editPositionDialog, qsTr("Edit Position"), qgcView.showDialogDefaultWidth, StandardButton.Cancel) - } - - /*MenuItem { - text: qsTr("Load KML...") - onTriggered: kmlLoadDialog.openForLoad() - }*/ - } - - Component { - id: editPositionDialog - - EditPositionDialog { - Component.onCompleted: coordinate = mapPolyline.path[menu._removeVertexIndex] - onCoordinateChanged: mapPolyline.adjustVertex(menu._removeVertexIndex,coordinate) - } - } - - Component { - id: polylineComponent - - MapPolyline { - line.width: lineWidth - line.color: lineColor - path: mapPolyline.path - } - } - - Component { - id: splitHandleComponent - - MapQuickItem { - id: mapQuickItem - anchorPoint.x: splitHandle.width / 2 - anchorPoint.y: splitHandle.height / 2 - - property int vertexIndex - - sourceItem: Rectangle { - id: splitHandle - width: ScreenTools.defaultFontPixelHeight * 1.5 - height: width - radius: width / 2 - border.color: "white" - color: "transparent" - opacity: .50 - z: _zorderSplitHandle - - QGCLabel { - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - text: "+" - } - - QGCMouseArea { - fillItem: parent - onClicked: mapPolyline.splitSegment(mapQuickItem.vertexIndex) - } - } - } - } - - Component { - id: splitHandlesComponent - - Repeater { - model: mapPolyline.path - - delegate: Item { - property var _splitHandle - property var _vertices: mapPolyline.path - - function _setHandlePosition() { - var nextIndex = index + 1 - var distance = _vertices[index].distanceTo(_vertices[nextIndex]) - var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex]) - _splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth) - } - - Component.onCompleted: { - if (index + 1 <= _vertices.length - 1) { - _splitHandle = splitHandleComponent.createObject(mapControl) - _splitHandle.vertexIndex = index - _setHandlePosition() - mapControl.addMapItem(_splitHandle) - } - } - - Component.onDestruction: { - if (_splitHandle) { - _splitHandle.destroy() - } - } - } - } - } - - // Control which is used to drag polygon vertices - Component { - id: dragAreaComponent - - MissionItemIndicatorDrag { - mapControl: _root.mapControl - id: dragArea - z: _zorderDragHandle - - property int polylineVertex - - property bool _creationComplete: false - - Component.onCompleted: _creationComplete = true - - onItemCoordinateChanged: { - if (_creationComplete) { - // During component creation some bad coordinate values got through which screws up draw - mapPolyline.adjustVertex(polylineVertex, itemCoordinate) - } - } - - onClicked: { - menu.popUpWithIndex(polylineVertex) - } - onDragStop: { - if (_creationComplete) { - // During component creation some bad coordinate values got through which screws up draw - mapPolyline.snapVertex(polylineVertex) - } - } - - } - } - - Component { - id: dragHandleComponent - - MapQuickItem { - id: mapQuickItem - anchorPoint.x: dragHandle.width / 2 - anchorPoint.y: dragHandle.height / 2 - z: _zorderDragHandle - - property int polylineVertex - - sourceItem: Rectangle { - id: dragHandle - width: ScreenTools.defaultFontPixelHeight * 1.5 - height: width - radius: width * 0.5 - color: Qt.rgba(1,1,1,0.8) - border.color: Qt.rgba(0,0,0,0.25) - border.width: 1 - } - } - } - - // Add all polygon vertex drag handles to the map - Component { - id: dragHandlesComponent - - Repeater { - model: mapPolyline.pathModel - - delegate: Item { - property var _visuals: [ ] - - Component.onCompleted: { - var dragHandle = dragHandleComponent.createObject(mapControl) - dragHandle.coordinate = Qt.binding(function() { return object.coordinate }) - dragHandle.polylineVertex = Qt.binding(function() { return index }) - mapControl.addMapItem(dragHandle) - var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate }) - dragArea.polylineVertex = Qt.binding(function() { return index }) - _visuals.push(dragHandle) - _visuals.push(dragArea) - } - - Component.onDestruction: { - for (var i=0; i<_visuals.length; i++) { - _visuals[i].destroy() - } - _visuals = [ ] - } - } - } - } -} - diff --git a/src/WimaView/WimaMeasurementAreaDataVisual.qml b/src/WimaView/WimaMeasurementAreaDataVisual.qml deleted file mode 100644 index f9bbb829fbd586e5e34f6efdd7e65d6805a2d92e..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaMeasurementAreaDataVisual.qml +++ /dev/null @@ -1,124 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtLocation 5.3 -import QtPositioning 5.3 - -import QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 - -/// Wima Measurement Area Data visuals -Item { - id: _root - - property var map ///< Map control to place item in - property var qgcView ///< QGCView to use for popping dialogs - - property var areaItem: object - signal clicked(int sequenceNumber) - - property var _polygonComponent - - - function _addPolygon(){ - if(!_polygonComponent){ - _polygonComponent = polygon.createObject(_root) - map.addMapItem(_polygonComponent) - } - } - - function _destroyPolygon(){ - if(_polygonComponent){ - map.removeMapItem(_polygonComponent) - _polygonComponent.destroy() - } - } - - Component.onCompleted: { - _addPolygon() - } - - Component.onDestruction: { - _destroyPolygon() - } - - // Add tiles. - Repeater { - id: progressRepeater - model: areaItem.tiles - - Item{ - property var _tileComponent - property int _progress: _root.areaItem.progress[index] ? - _root.areaItem.progress[index] : 0 - - Component.onCompleted: { - _tileComponent = tileComponent.createObject(map) - - _tileComponent.polygon.path = - Qt.binding(function(){return object.path}) - _tileComponent.polygon.opacity = 0.6 - _tileComponent.polygon.border.color = "black" - _tileComponent.polygon.border.width = 1 - _tileComponent.polygon.color = - Qt.binding(function(){return getColor(_progress)}) - } - - Component.onDestruction: { - _tileComponent.destroy() - } - - } - } - - // Polygon component. - Component{ - id:polygon - - MapPolygon { - path: object.path; - border.color: "black" - color: "green" - opacity: 0.25 - } - } - - // Tile component. - Component { - id: tileComponent - Item{ - id: root - - property MapPolygon polygon - - MapPolygon{ - id:mapPolygon - path: [] - } - - Component.onCompleted: { - polygon = mapPolygon - map.addMapItem(mapPolygon) - } - - Component.onDestruction: { - map.removeMapItem(mapPolygon) - } - } - } - - function getColor(progress) { - if (progress === 0) - return "transparent" - if (progress < 33) - return "orange" - if (progress < 66) - return "yellow" - if (progress < 100) - return "greenyellow" - return "limegreen" - } - -} diff --git a/src/WimaView/WimaServiceAreaDataVisual.qml b/src/WimaView/WimaServiceAreaDataVisual.qml deleted file mode 100644 index 705bebb29e29176241b77ff50475b2ef4611225f..0000000000000000000000000000000000000000 --- a/src/WimaView/WimaServiceAreaDataVisual.qml +++ /dev/null @@ -1,58 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtLocation 5.3 -import QtPositioning 5.3 - -import QGroundControl 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.FlightMap 1.0 - -/// Wima Measurement Area Data visuals -Item { - id: _root - - property var map ///< Map control to place item in - property var qgcView ///< QGCView to use for popping dialogs - - property var areaItem: object - signal clicked(int sequenceNumber) - - property var _polygonComponent - - - function _addPolygon(){ - if(!_polygonComponent){ - _polygonComponent = polygon.createObject(_root) - map.addMapItem(_polygonComponent) - } - } - - function _destroyPolygon(){ - if(_polygonComponent){ - map.removeMapItem(_polygonComponent) - _polygonComponent.destroy() - } - } - - Component.onCompleted: { - _addPolygon() - } - - Component.onDestruction: { - _destroyPolygon() - } - - // Polygon component. - Component{ - id:polygon - - MapPolygon { - path: object.path; - border.color: "black" - color: "yellow" - opacity: 0.25 - } - } -}