Skip to content
PlanMasterController.cc 25.2 KiB
Newer Older
/****************************************************************************
 *
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#include "PlanMasterController.h"
#include "AppSettings.h"
#include "BlankPlanCreator.h"
#include "CorridorScanPlanCreator.h"
#include "JsonHelper.h"
#include "KMLPlanDomDocument.h"
#include "MeasurementPlanCreator.h"
#include "MissionManager.h"
#include "MultiVehicleManager.h"
#include "QGCApplication.h"
#include "QGCCorePlugin.h"
#include "SettingsManager.h"
#include "StructureScanPlanCreator.h"
#include "SurveyPlanCreator.h"
#if defined(QGC_AIRMAP_ENABLED)
#include "AirspaceFlightPlanProvider.h"
#endif
#include <QDomDocument>
Don Gagne's avatar
Don Gagne committed
#include <QFileInfo>
#include <QJsonDocument>
DonLakeFlyer's avatar
DonLakeFlyer committed
QGC_LOGGING_CATEGORY(PlanMasterControllerLog, "PlanMasterControllerLog")

const int PlanMasterController::kPlanFileVersion = 1;
const char *PlanMasterController::kPlanFileType = "Plan";
const char *PlanMasterController::kJsonMissionObjectKey = "mission";
const char *PlanMasterController::kJsonGeoFenceObjectKey = "geoFence";
const char *PlanMasterController::kJsonRallyPointsObjectKey = "rallyPoints";
PlanMasterController::PlanMasterController(QObject *parent)
    : QObject(parent),
      _multiVehicleMgr(qgcApp()->toolbox()->multiVehicleManager()),
      _controllerVehicle(
          new Vehicle(Vehicle::MAV_AUTOPILOT_TRACK, Vehicle::MAV_TYPE_TRACK,
                      qgcApp()->toolbox()->firmwarePluginManager(), this)),
      _managerVehicle(_controllerVehicle), _missionController(this),
      _geoFenceController(this), _rallyPointController(this) {
  _commonInit();
PlanMasterController::PlanMasterController(MAV_AUTOPILOT firmwareType,
                                           MAV_TYPE vehicleType,
                                           QObject *parent)
    : QObject(parent),
      _multiVehicleMgr(qgcApp()->toolbox()->multiVehicleManager()),
      _controllerVehicle(
          new Vehicle(firmwareType, vehicleType,
                      qgcApp()->toolbox()->firmwarePluginManager())),
      _managerVehicle(_controllerVehicle), _missionController(this),
      _geoFenceController(this), _rallyPointController(this) {
  _commonInit();
void PlanMasterController::_commonInit(void) {
  connect(&_missionController, &MissionController::dirtyChanged, this,
          &PlanMasterController::dirtyChanged);
  connect(&_geoFenceController, &GeoFenceController::dirtyChanged, this,
          &PlanMasterController::dirtyChanged);
  connect(&_rallyPointController, &RallyPointController::dirtyChanged, this,
          &PlanMasterController::dirtyChanged);
  connect(&_missionController, &MissionController::containsItemsChanged, this,
          &PlanMasterController::containsItemsChanged);
  connect(&_geoFenceController, &GeoFenceController::containsItemsChanged, this,
          &PlanMasterController::containsItemsChanged);
  connect(&_rallyPointController, &RallyPointController::containsItemsChanged,
          this, &PlanMasterController::containsItemsChanged);
  connect(&_missionController, &MissionController::syncInProgressChanged, this,
          &PlanMasterController::syncInProgressChanged);
  connect(&_geoFenceController, &GeoFenceController::syncInProgressChanged,
          this, &PlanMasterController::syncInProgressChanged);
  connect(&_rallyPointController, &RallyPointController::syncInProgressChanged,
          this, &PlanMasterController::syncInProgressChanged);
  // Offline vehicle can change firmware/vehicle type
  connect(_controllerVehicle, &Vehicle::vehicleTypeChanged, this,
          &PlanMasterController::_updatePlanCreatorsList);
PlanMasterController::~PlanMasterController() {}
void PlanMasterController::start(void) {
  _missionController.start(_flyView);
  _geoFenceController.start(_flyView);
  _rallyPointController.start(_flyView);
  _activeVehicleChanged(_multiVehicleMgr->activeVehicle());
  connect(_multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this,
          &PlanMasterController::_activeVehicleChanged);
  _updatePlanCreatorsList();
  //-- This assumes there is one single instance of PlanMasterController in edit
  //mode.
  if (!flyView) {
    // Wait for signal confirming AirMap client connection before starting
    // flight planning
    connect(qgcApp()->toolbox()->airspaceManager(),
            &AirspaceManager::connectStatusChanged, this,
            &PlanMasterController::_startFlightPlanning);
  }
void PlanMasterController::startStaticActiveVehicle(
    Vehicle *vehicle, bool deleteWhenSendCompleted) {
  _flyView = true;
  _deleteWhenSendCompleted = deleteWhenSendCompleted;
  _missionController.start(_flyView);
  _geoFenceController.start(_flyView);
  _rallyPointController.start(_flyView);
  _activeVehicleChanged(vehicle);
}

void PlanMasterController::_activeVehicleChanged(Vehicle *activeVehicle) {
  if (_managerVehicle == activeVehicle) {
    // We are already setup for this vehicle
    return;
  }

  qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged" << activeVehicle;

  if (_managerVehicle) {
    // Disconnect old vehicle. Be careful of wildcarding disconnect too much
    // since _managerVehicle may equal _controllerVehicle
    disconnect(_managerVehicle->missionManager(), nullptr, nullptr, nullptr);
    disconnect(_managerVehicle->geoFenceManager(), nullptr, nullptr, nullptr);
    disconnect(_managerVehicle->rallyPointManager(), nullptr, nullptr, nullptr);
  }

  bool newOffline = false;
  if (activeVehicle == nullptr) {
    // Since there is no longer an active vehicle we use the offline controller
    // vehicle as the manager vehicle
    _managerVehicle = _controllerVehicle;
    newOffline = true;
  } else {
    newOffline = false;
    _managerVehicle = activeVehicle;

    // Update controllerVehicle to the currently connected vehicle
    AppSettings *appSettings =
        qgcApp()->toolbox()->settingsManager()->appSettings();
    appSettings->offlineEditingFirmwareClass()->setRawValue(
        QGCMAVLink::firmwareClass(_managerVehicle->firmwareType()));
    appSettings->offlineEditingVehicleClass()->setRawValue(
        QGCMAVLink::vehicleClass(_managerVehicle->vehicleType()));

    // We use these signals to sequence upload and download to the multiple
    // controller/managers
    connect(_managerVehicle->missionManager(),
            &MissionManager::newMissionItemsAvailable, this,
            &PlanMasterController::_loadMissionComplete);
    connect(_managerVehicle->geoFenceManager(), &GeoFenceManager::loadComplete,
            this, &PlanMasterController::_loadGeoFenceComplete);
    connect(_managerVehicle->rallyPointManager(),
            &RallyPointManager::loadComplete, this,
            &PlanMasterController::_loadRallyPointsComplete);
    connect(_managerVehicle->missionManager(), &MissionManager::sendComplete,
            this, &PlanMasterController::_sendMissionComplete);
    connect(_managerVehicle->geoFenceManager(), &GeoFenceManager::sendComplete,
            this, &PlanMasterController::_sendGeoFenceComplete);
    connect(_managerVehicle->rallyPointManager(),
            &RallyPointManager::sendComplete, this,
            &PlanMasterController::_sendRallyPointsComplete);
  }

  _offline = newOffline;
  emit offlineChanged(offline());
  emit managerVehicleChanged(_managerVehicle);

  if (_flyView) {
    // We are in the Fly View
    if (newOffline) {
      // No active vehicle, clear mission
      qCDebug(PlanMasterControllerLog)
          << "_activeVehicleChanged: Fly View - No active vehicle, clearing "
             "stale plan";
      removeAll();
      // Fly view has changed to a new active vehicle, update to show correct
      // mission
      qCDebug(PlanMasterControllerLog)
          << "_activeVehicleChanged: Fly View - New active vehicle, loading "
             "new plan from manager vehicle";
      _showPlanFromManagerVehicle();
    }
  } else {
    // We are in the Plan view.
    if (containsItems()) {
      // The plan view has a stale plan in it
      if (dirty()) {
        // Plan is dirty, the user must decide what to do in all cases
        qCDebug(PlanMasterControllerLog)
            << "_activeVehicleChanged: Plan View - Previous dirty plan exists, "
               "no new active vehicle, sending "
               "promptForPlanUsageOnVehicleChange signal";
        emit promptForPlanUsageOnVehicleChange();
      } else {
        // Plan is not dirty
        if (newOffline) {
          // The active vehicle went away with no new active vehicle
          qCDebug(PlanMasterControllerLog)
              << "_activeVehicleChanged: Plan View - Previous clean plan "
                 "exists, no new active vehicle, clear stale plan";
          removeAll();
DonLakeFlyer's avatar
DonLakeFlyer committed
        } else {
          // We are transitioning from one active vehicle to another. Show the
          // plan from the new vehicle.
          qCDebug(PlanMasterControllerLog)
              << "_activeVehicleChanged: Plan View - Previous clean plan "
                 "exists, new active vehicle, loading from new manager vehicle";
          _showPlanFromManagerVehicle();
      // There is no previous Plan in the view
      if (newOffline) {
        // Nothing special to do in this case
        qCDebug(PlanMasterControllerLog)
            << "_activeVehicleChanged: Plan View - No previous plan, no longer "
               "connected to vehicle, nothing to do";
      } else {
        // Just show the plan from the new vehicle
        qCDebug(PlanMasterControllerLog)
            << "_activeVehicleChanged: Plan View - No previous plan, new "
               "active vehicle, loading from new manager vehicle";
        _showPlanFromManagerVehicle();
      }
    }
  }

  // Vehicle changed so we need to signal everything
  emit containsItemsChanged(containsItems());
  emit syncInProgressChanged();
  emit dirtyChanged(dirty());

  _updatePlanCreatorsList();
}

void PlanMasterController::loadFromVehicle(void) {
  if (_managerVehicle->vehicleLinkManager()
          ->primaryLink()
          ->linkConfiguration()
          ->isHighLatency()) {
    qgcApp()->showAppMessage(
        tr("Download not supported on high latency links."));
    return;
  }

  if (offline()) {
    qCWarning(PlanMasterControllerLog)
        << "PlanMasterController::loadFromVehicle called while offline";
  } else if (_flyView) {
    qCWarning(PlanMasterControllerLog)
        << "PlanMasterController::loadFromVehicle called from Fly view";
  } else if (syncInProgress()) {
    qCWarning(PlanMasterControllerLog)
        << "PlanMasterController::loadFromVehicle called while syncInProgress";
  } else {
    _loadGeoFence = true;
    qCDebug(PlanMasterControllerLog)
        << "PlanMasterController::loadFromVehicle calling "
           "_missionController.loadFromVehicle";
    _missionController.loadFromVehicle();
    setDirty(false);
  }
}

void PlanMasterController::_loadMissionComplete(void) {
  if (!_flyView && _loadGeoFence) {
    _loadGeoFence = false;
    _loadRallyPoints = true;
    if (_geoFenceController.supported()) {
      qCDebug(PlanMasterControllerLog)
          << "PlanMasterController::_loadMissionComplete calling "
             "_geoFenceController.loadFromVehicle";
      _geoFenceController.loadFromVehicle();
DonLakeFlyer's avatar
DonLakeFlyer committed
    } else {
      qCDebug(PlanMasterControllerLog)
          << "PlanMasterController::_loadMissionComplete GeoFence not "
             "supported skipping";
      _geoFenceController.removeAll();
      _loadGeoFenceComplete();
    }
    setDirty(false);
  }
}

void PlanMasterController::_loadGeoFenceComplete(void) {
  if (!_flyView && _loadRallyPoints) {
    _loadRallyPoints = false;
    if (_rallyPointController.supported()) {
      qCDebug(PlanMasterControllerLog)
          << "PlanMasterController::_loadGeoFenceComplete calling "
             "_rallyPointController.loadFromVehicle";
      _rallyPointController.loadFromVehicle();
    } else {
      qCDebug(PlanMasterControllerLog)
          << "PlanMasterController::_loadMissionComplete Rally Points not "
             "supported skipping";
      _rallyPointController.removeAll();
      _loadRallyPointsComplete();
void PlanMasterController::_loadRallyPointsComplete(void) {
  qCDebug(PlanMasterControllerLog)
      << "PlanMasterController::_loadRallyPointsComplete";
void PlanMasterController::_sendMissionComplete(void) {
  if (_sendGeoFence) {
    _sendGeoFence = false;
    _sendRallyPoints = true;
    if (_geoFenceController.supported()) {
      qCDebug(PlanMasterControllerLog)
          << "PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
      _geoFenceController.sendToVehicle();
    } else {
      qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle "
                                          "GeoFence not supported skipping";
      _sendGeoFenceComplete();
void PlanMasterController::_sendGeoFenceComplete(void) {
  if (_sendRallyPoints) {
    _sendRallyPoints = false;
    if (_rallyPointController.supported()) {
      qCDebug(PlanMasterControllerLog)
          << "PlanMasterController::sendToVehicle start rally sendToVehicle";
      _rallyPointController.sendToVehicle();
    } else {
      qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle "
                                          "Rally Points not support skipping";
      _sendRallyPointsComplete();
void PlanMasterController::_sendRallyPointsComplete(void) {
  qCDebug(PlanMasterControllerLog)
      << "PlanMasterController::sendToVehicle Rally Point send complete";
  if (_deleteWhenSendCompleted) {
    this->deleteLater();
  }
#if defined(QGC_AIRMAP_ENABLED)
void PlanMasterController::_startFlightPlanning(void) {
  if (qgcApp()->toolbox()->airspaceManager()->connected()) {
    qCDebug(PlanMasterControllerLog)
        << "PlanMasterController::_startFlightPlanning client connected, start "
           "flight planning";
    qgcApp()->toolbox()->airspaceManager()->flightPlan()->startFlightPlanning(
        this);
  }
void PlanMasterController::sendToVehicle(void) {
  if (_managerVehicle->vehicleLinkManager()
          ->primaryLink()
          ->linkConfiguration()
          ->isHighLatency()) {
    qgcApp()->showAppMessage(tr("Upload not supported on high latency links."));
    return;
  }

  if (offline()) {
    qCWarning(PlanMasterControllerLog)
        << "PlanMasterController::sendToVehicle called while offline";
  } else if (syncInProgress()) {
    qCWarning(PlanMasterControllerLog)
        << "PlanMasterController::sendToVehicle called while syncInProgress";
  } else {
    qCDebug(PlanMasterControllerLog)
        << "PlanMasterController::sendToVehicle start mission sendToVehicle";
    _sendGeoFence = true;
    _missionController.sendToVehicle();
    setDirty(false);
  }
}

void PlanMasterController::loadFromFile(const QString &filename) {
  QString errorString;
  QString errorMessage =
      tr("Error loading Plan file (%1). %2").arg(filename).arg("%1");

  if (filename.isEmpty()) {
    return;
  }

  QFileInfo fileInfo(filename);
  QFile file(filename);

  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    errorString = file.errorString() + QStringLiteral(" ") + filename;
    qgcApp()->showAppMessage(errorMessage.arg(errorString));
    return;
  }

  bool success = false;
  if (fileInfo.suffix() == AppSettings::planFileExtension) {
    QJsonDocument jsonDoc;
    QByteArray bytes = file.readAll();

    if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorString)) {
      qgcApp()->showAppMessage(errorMessage.arg(errorString));
      return;
    }

    QJsonObject json = jsonDoc.object();
    //-- Allow plugins to pre process the load
    qgcApp()->toolbox()->corePlugin()->preLoadFromJson(this, json);

    int version;
    if (!JsonHelper::validateExternalQGCJsonFile(
            json, kPlanFileType, kPlanFileVersion, kPlanFileVersion, version,
            errorString)) {
      qgcApp()->showAppMessage(errorMessage.arg(errorString));
      return;
    }

    QList<JsonHelper::KeyValidateInfo> rgKeyInfo = {
        {kJsonMissionObjectKey, QJsonValue::Object, true},
        {kJsonGeoFenceObjectKey, QJsonValue::Object, true},
        {kJsonRallyPointsObjectKey, QJsonValue::Object, true},
    };
    if (!JsonHelper::validateKeys(json, rgKeyInfo, errorString)) {
      qgcApp()->showAppMessage(errorMessage.arg(errorString));
      return;
    }

    if (!_missionController.load(json[kJsonMissionObjectKey].toObject(),
                                 errorString) ||
        !_geoFenceController.load(json[kJsonGeoFenceObjectKey].toObject(),
                                  errorString) ||
        !_rallyPointController.load(json[kJsonRallyPointsObjectKey].toObject(),
                                    errorString)) {
      qgcApp()->showAppMessage(errorMessage.arg(errorString));
DonLakeFlyer's avatar
DonLakeFlyer committed
    } else {
      //-- Allow plugins to post process the load
      qgcApp()->toolbox()->corePlugin()->postLoadFromJson(this, json);
      success = true;
  } else if (fileInfo.suffix() == AppSettings::missionFileExtension) {
    if (!_missionController.loadJsonFile(file, errorString)) {
      qgcApp()->showAppMessage(errorMessage.arg(errorString));
  } else if (fileInfo.suffix() == AppSettings::waypointsFileExtension ||
             fileInfo.suffix() == QStringLiteral("txt")) {
    if (!_missionController.loadTextFile(file, errorString)) {
      qgcApp()->showAppMessage(errorMessage.arg(errorString));
      success = true;
    }
  } else {
    //-- TODO: What then?
  }

  if (success) {
    _currentPlanFile =
        QString::asprintf("%s/%s.%s", fileInfo.path().toLocal8Bit().data(),
                          fileInfo.completeBaseName().toLocal8Bit().data(),
                          AppSettings::planFileExtension);
  } else {
    _currentPlanFile.clear();
  }
  emit currentPlanFileChanged();

  if (!offline()) {
    setDirty(true);
  }
}

QJsonDocument PlanMasterController::saveToJson() {
  QJsonObject planJson;
  qgcApp()->toolbox()->corePlugin()->preSaveToJson(this, planJson);
  QJsonObject missionJson;
  QJsonObject fenceJson;
  QJsonObject rallyJson;
  JsonHelper::saveQGCJsonFileHeader(planJson, kPlanFileType, kPlanFileVersion);
  //-- Allow plugin to preemptly add its own keys to mission
  qgcApp()->toolbox()->corePlugin()->preSaveToMissionJson(this, missionJson);
  _missionController.save(missionJson);
  //-- Allow plugin to add its own keys to mission
  qgcApp()->toolbox()->corePlugin()->postSaveToMissionJson(this, missionJson);
  _geoFenceController.save(fenceJson);
  _rallyPointController.save(rallyJson);
  planJson[kJsonMissionObjectKey] = missionJson;
  planJson[kJsonGeoFenceObjectKey] = fenceJson;
  planJson[kJsonRallyPointsObjectKey] = rallyJson;
  qgcApp()->toolbox()->corePlugin()->postSaveToJson(this, planJson);
  return QJsonDocument(planJson);
}

void PlanMasterController::saveToCurrent() {
  if (!_currentPlanFile.isEmpty()) {
    saveToFile(_currentPlanFile);
  }
}

void PlanMasterController::saveToFile(const QString &filename) {
  if (filename.isEmpty()) {
    return;
  }

  QString planFilename = filename;
  if (!QFileInfo(filename).fileName().contains(".")) {
    planFilename += QString(".%1").arg(fileExtension());
  }

  QFile file(planFilename);

  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    qgcApp()->showAppMessage(
        tr("Plan save error %1 : %2").arg(filename).arg(file.errorString()));
    _currentPlanFile.clear();
    emit currentPlanFileChanged();
  } else {
    QJsonDocument saveDoc = saveToJson();
    file.write(saveDoc.toJson());
    if (_currentPlanFile != planFilename) {
      _currentPlanFile = planFilename;
      emit currentPlanFileChanged();
    }
  }

  // Only clear dirty bit if we are offline
  if (offline()) {
    setDirty(false);
  }
}

void PlanMasterController::saveToKml(const QString &filename) {
  if (filename.isEmpty()) {
    return;
  }

  QString kmlFilename = filename;
  if (!QFileInfo(filename).fileName().contains(".")) {
    kmlFilename += QString(".%1").arg(kmlFileExtension());
  }

  QFile file(kmlFilename);

  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    qgcApp()->showAppMessage(
        tr("KML save error %1 : %2").arg(filename).arg(file.errorString()));
  } else {
    KMLPlanDomDocument planKML;
    _missionController.addMissionToKML(planKML);
    QTextStream stream(&file);
    stream << planKML.toString();
    file.close();
  }
}

void PlanMasterController::removeAll(void) {
  _missionController.removeAll();
  _geoFenceController.removeAll();
  _rallyPointController.removeAll();
  if (_offline) {
    _missionController.setDirty(false);
    _geoFenceController.setDirty(false);
    _rallyPointController.setDirty(false);
    _currentPlanFile.clear();
    emit currentPlanFileChanged();
  }
void PlanMasterController::removeAllFromVehicle(void) {
  if (!offline()) {
    _missionController.removeAllFromVehicle();
    if (_geoFenceController.supported()) {
      _geoFenceController.removeAllFromVehicle();
    if (_rallyPointController.supported()) {
      _rallyPointController.removeAllFromVehicle();
    setDirty(false);
  } else {
    qWarning()
        << "PlanMasterController::removeAllFromVehicle called while offline";
  }
bool PlanMasterController::containsItems(void) const {
  return _missionController.containsItems() ||
         _geoFenceController.containsItems() ||
         _rallyPointController.containsItems();
bool PlanMasterController::dirty(void) const {
  return _missionController.dirty() || _geoFenceController.dirty() ||
         _rallyPointController.dirty();
void PlanMasterController::setDirty(bool dirty) {
  _missionController.setDirty(dirty);
  _geoFenceController.setDirty(dirty);
  _rallyPointController.setDirty(dirty);
QString PlanMasterController::fileExtension(void) const {
  return AppSettings::planFileExtension;
QString PlanMasterController::kmlFileExtension(void) const {
  return AppSettings::kmlFileExtension;
QStringList PlanMasterController::loadNameFilters(void) const {
  QStringList filters;
  filters << tr("Supported types (*.%1 *.%2 *.%3 *.%4)")
                 .arg(AppSettings::planFileExtension)
                 .arg(AppSettings::missionFileExtension)
                 .arg(AppSettings::waypointsFileExtension)
                 .arg("txt")
          << tr("All Files (*.*)");
  return filters;
QStringList PlanMasterController::saveNameFilters(void) const {
  QStringList filters;
  filters << tr("Plan Files (*.%1)").arg(fileExtension())
          << tr("All Files (*.*)");
  return filters;
void PlanMasterController::sendPlanToVehicle(Vehicle *vehicle,
                                             const QString &filename) {
  // Use a transient PlanMasterController to accomplish this
  PlanMasterController *controller = new PlanMasterController();
  controller->startStaticActiveVehicle(vehicle,
                                       true /* deleteWhenSendCompleted */);
  controller->loadFromFile(filename);
  controller->sendToVehicle();
void PlanMasterController::_showPlanFromManagerVehicle(void) {
  if (!_managerVehicle->initialPlanRequestComplete() && !syncInProgress()) {
    // Something went wrong with initial load. All controllers are idle, so just
    // force it off
    _managerVehicle->forceInitialPlanRequestComplete();
  }
  // The crazy if structure is to handle the load propagating by itself through
  // the system
  if (!_missionController.showPlanFromManagerVehicle()) {
    if (!_geoFenceController.showPlanFromManagerVehicle()) {
      _rallyPointController.showPlanFromManagerVehicle();
bool PlanMasterController::syncInProgress(void) const {
  return _missionController.syncInProgress() ||
         _geoFenceController.syncInProgress() ||
         _rallyPointController.syncInProgress();
bool PlanMasterController::isEmpty(void) const {
  return _missionController.isEmpty() && _geoFenceController.isEmpty() &&
         _rallyPointController.isEmpty();
void PlanMasterController::_updatePlanCreatorsList(void) {
  if (!_flyView) {
    if (!_planCreators) {
      _planCreators = new QmlObjectListModel(this);
      _planCreators->append(new BlankPlanCreator(this, this));
      _planCreators->append(new SurveyPlanCreator(this, this));
      _planCreators->append(new CorridorScanPlanCreator(this, this));
      _planCreators->append(new MeasurementPlanCreator(this, this));
      emit planCreatorsChanged(_planCreators);
    if (_managerVehicle->fixedWing()) {
      if (_planCreators->count() == 5) {
        _planCreators->removeAt(_planCreators->count() - 1);
      }
      if (_planCreators->count() != 5) {
        _planCreators->append(new StructureScanPlanCreator(this, this));
      }
    }
  }
}

void PlanMasterController::showPlanFromManagerVehicle(void) {
  if (offline()) {
    // There is no new vehicle so clear any previous plan
    qCDebug(PlanMasterControllerLog)
        << "showPlanFromManagerVehicle: Plan View - No new vehicle, clear any "
           "previous plan";
    removeAll();
  } else {
    // We have a new active vehicle, show the plan from that
    qCDebug(PlanMasterControllerLog)
        << "showPlanFromManagerVehicle: Plan View - New vehicle available, "
           "show plan from new manager vehicle";
    _showPlanFromManagerVehicle();
  }