Skip to content
WimaPlaner.cc 28.2 KiB
Newer Older
#include "WimaPlaner.h"
#include "MissionController.h"
#include "MissionSettingsItem.h"
#include "PlanMasterController.h"
#include "QGCApplication.h"
#include "QGCMapPolygon.h"
#include "SimpleMissionItem.h"
#include "Geometry/GeoUtilities.h"
#include "Geometry/PlanimetryCalculus.h"
#include "OptimisationTools.h"
#include "CircularSurvey.h"
#include "Geometry/WimaArea.h"
#include "Geometry/WimaAreaData.h"
#include "WimaBridge.h"
const char *WimaPlaner::wimaFileExtension = "wima";
const char *WimaPlaner::areaItemsName = "AreaItems";
const char *WimaPlaner::missionItemsName = "MissionItems";

WimaPlaner::WimaPlaner(QObject *parent)
    : QObject(parent), _currentAreaIndex(-1), _wimaBridge(nullptr),
      _joinedArea(this), _joinedAreaValid(false), _measurementArea(this),
      _serviceArea(this), _corridor(this), _TSComplexItem(nullptr),
      _surveyRefChanging(false), _measurementAreaChanging(false),
      _corridorChanging(false), _serviceAreaChanging(false),
      _syncronizedWithController(false), _readyForSync(false) {
  _lastMeasurementAreaPath = _measurementArea.path();
  _lastServiceAreaPath = _serviceArea.path();
  _lastCorridorPath = _corridor.path();

  connect(this, &WimaPlaner::currentPolygonIndexChanged, this,
          &WimaPlaner::recalcPolygonInteractivity);
  connect(&_updateTimer, &QTimer::timeout, this, &WimaPlaner::updateTimerSlot);
  connect(this, &WimaPlaner::joinedAreaValidChanged, this,
          &WimaPlaner::updateMission);

  _updateTimer.setInterval(300); // 300 ms means: max update time 2*300 ms
  _updateTimer.start();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#ifndef NDEBUG
  // for debugging and testing purpose, remove if not needed anymore
  connect(&_autoLoadTimer, &QTimer::timeout, this,
          &WimaPlaner::autoLoadMission);
  _autoLoadTimer.setSingleShot(true);
  _autoLoadTimer.start(300);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
#endif
  _calcArrivalAndReturnPathTimer.setInterval(100);
  _calcArrivalAndReturnPathTimer.setSingleShot(true);
  connect(&_calcArrivalAndReturnPathTimer, &QTimer::timeout, this,
          &WimaPlaner::calcArrivalAndReturnPath);
QmlObjectListModel *WimaPlaner::visualItems() { return &_visualItems; }
QStringList WimaPlaner::loadNameFilters() const {
  QStringList filters;
  filters << tr("Supported types (*.%1 *.%2)")
                 .arg(wimaFileExtension)
                 .arg(AppSettings::planFileExtension)
          << tr("All Files (*.*)");
  return filters;
QStringList WimaPlaner::saveNameFilters() const {
  QStringList filters;
  filters << tr("Supported types (*.%1 *.%2)")
                 .arg(wimaFileExtension)
                 .arg(AppSettings::planFileExtension);
  return filters;
QGeoCoordinate WimaPlaner::joinedAreaCenter() const {
  return _joinedArea.center();
void WimaPlaner::setMasterController(PlanMasterController *masterC) {
  _masterController = masterC;
  emit masterControllerChanged();
void WimaPlaner::setMissionController(MissionController *missionC) {
  _missionController = missionC;
  emit missionControllerChanged();
void WimaPlaner::setCurrentPolygonIndex(int index) {
  if (index >= 0 && index < _visualItems.count() &&
      index != _currentAreaIndex) {
    _currentAreaIndex = index;
    emit currentPolygonIndexChanged(index);
  }
void WimaPlaner::setWimaBridge(WimaBridge *bridge) {
  if (bridge != nullptr) {
    _wimaBridge = bridge;
    emit wimaBridgeChanged();
  }
bool WimaPlaner::syncronizedWithController() {
  return _syncronizedWithController;
bool WimaPlaner::readyForSync() { return _readyForSync; }
WimaPlaner *WimaPlaner::thisPointer() { return this; }
void WimaPlaner::removeArea(int index) {
  if (index >= 0 && index < _visualItems.count()) {
    WimaArea *area = qobject_cast<WimaArea *>(_visualItems.removeAt(index));
    if (area == nullptr) {
      qWarning("WimaPlaner::removeArea(): nullptr catched, internal error.");
      return;
    area->clear();
    area->borderPolygon()->clear();
    emit visualItemsChanged();
    if (_visualItems.count() == 0) {
      // this branch is reached if all items are removed
      // to guarentee proper behavior, _currentAreaIndex must be set to a
      // invalid value, as on constructor init.
      resetAllInteractive();
      _currentAreaIndex = -1;
      return;
    if (_currentAreaIndex >= _visualItems.count()) {
      setCurrentPolygonIndex(_visualItems.count() - 1);
      recalcPolygonInteractivity(_currentAreaIndex);
  } else {
    qWarning("Index out of bounds!");
  }
bool WimaPlaner::addMeasurementArea() {
  if (!_visualItems.contains(&_measurementArea)) {
    _visualItems.append(&_measurementArea);
    int newIndex = _visualItems.count() - 1;
    setCurrentPolygonIndex(newIndex);
    emit visualItemsChanged();
    return true;
  } else {
    return false;
  }
bool WimaPlaner::addServiceArea() {
  if (!_visualItems.contains(&_serviceArea)) {
    _visualItems.append(&_serviceArea);
    int newIndex = _visualItems.count() - 1;
    setCurrentPolygonIndex(newIndex);
    emit visualItemsChanged();
    return true;
  } else {
    return false;
  }
}
bool WimaPlaner::addCorridor() {
  if (!_visualItems.contains(&_corridor)) {
    _visualItems.append(&_corridor);
    int newIndex = _visualItems.count() - 1;
    setCurrentPolygonIndex(newIndex);
    emit visualItemsChanged();
    return true;
  } else {
    return false;
  }
void WimaPlaner::removeAll() {
  bool changesApplied = false;
  while (_visualItems.count() > 0) {
    removeArea(0);
    changesApplied = true;
  }
  _missionController->removeAll();
  _TSComplexItem = nullptr;
  _surveyRefChanging = false;
  emit currentFileChanged();
  if (changesApplied)
    emit visualItemsChanged();
}
bool WimaPlaner::updateMission() {
  setReadyForSync(false);
  if (!_joinedAreaValid)
    return false;
  // extract old survey data
  QmlObjectListModel *missionItems = _missionController->visualItems();
  int surveyIndex = missionItems->indexOf(_TSComplexItem);
  // create survey item if not yet present
  if (surveyIndex == -1) {
    _missionController->insertComplexMissionItem(
        _missionController->circularSurveyComplexItemName(),
        _measurementArea.center(), missionItems->count());
    _TSComplexItem = qobject_cast<CircularSurvey *>(
        missionItems->get(missionItems->count() - 1));
    if (_TSComplexItem == nullptr) {
      qWarning("WimaPlaner::updateMission(): survey == nullptr");
      return false;
    // establish connections
    _TSComplexItem->setRefPoint(_measurementArea.center());
    _lastSurveyRefPoint = _measurementArea.center();
    _surveyRefChanging = false;
    _TSComplexItem->setIsInitialized(
        true); // prevents reinitialisation from gui
    connect(_TSComplexItem, &TransectStyleComplexItem::missionItemReady, this,
            &WimaPlaner::calcArrivalAndReturnPath);
  }

  // update survey area
  _TSComplexItem->surveyAreaPolygon()->clear();
  _TSComplexItem->surveyAreaPolygon()->appendVertices(
      _measurementArea.coordinateList());

  setReadyForSync(true);
  return true;
}

void WimaPlaner::saveToCurrent() { saveToFile(_currentFile); }

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

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

  QFile file(planFilename);
  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    qgcApp()->showMessage(
        tr("Plan save error %1 : %2").arg(filename).arg(file.errorString()));
    _currentFile.clear();
    emit currentFileChanged();
  } else {
    FileType fileType = FileType::WimaFile;
    if (planFilename.contains(QString(".%1").arg(wimaFileExtension))) {
      fileType = FileType::WimaFile;
    } else if (planFilename.contains(
                   QString(".%1").arg(AppSettings::planFileExtension))) {
      fileType = FileType::PlanFile;
      if (planFilename.contains(".")) {
        qgcApp()->showMessage(tr("File format not supported"));
      } else {
        qgcApp()->showMessage(tr("File without file extension not accepted."));
        return;
      }
    }
    QJsonDocument saveDoc = saveToJson(fileType);
    file.write(saveDoc.toJson());
    if (_currentFile != planFilename) {
      _currentFile = planFilename;
      emit currentFileChanged();
bool WimaPlaner::loadFromCurrent() { return loadFromFile(_currentFile); }
bool WimaPlaner::loadFromFile(const QString &filename) {
#define debug 0
  QString errorString;
  QString errorMessage =
      tr("Error loading Plan file (%1). %2").arg(filename).arg("%1");
  if (filename.isEmpty()) {
    return false;
  }
  QFileInfo fileInfo(filename);
  QFile file(filename);
  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    errorString = file.errorString() + QStringLiteral(" ") + filename;
    qgcApp()->showMessage(errorMessage.arg(errorString));
    return false;
  }
  if (fileInfo.suffix() == wimaFileExtension) {
    QJsonDocument jsonDoc;
    QByteArray bytes = file.readAll();
    if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorString)) {
      qgcApp()->showMessage(errorMessage.arg(errorString));
      return false;
    }
    QJsonObject json = jsonDoc.object();
    // AreaItems
    QJsonArray areaArray = json[areaItemsName].toArray();
    _visualItems.clear();
    int validAreaCounter = 0;
    for (int i = 0; i < areaArray.size() && validAreaCounter < 3; i++) {
      QJsonObject jsonArea = areaArray[i].toObject();
      if (jsonArea.contains(WimaArea::areaTypeName) &&
          jsonArea[WimaArea::areaTypeName].isString()) {
        if (jsonArea[WimaArea::areaTypeName] ==
            WimaMeasurementArea::WimaMeasurementAreaName) {
          bool success = _measurementArea.loadFromJson(jsonArea, errorString);
          if (!success) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
          }

          _lastMeasurementAreaPath =
              _measurementArea.path(); // prevents error messages at this point
          validAreaCounter++;
          _visualItems.append(&_measurementArea);
          emit visualItemsChanged();
        } else if (jsonArea[WimaArea::areaTypeName] ==
                   WimaServiceArea::wimaServiceAreaName) {
          bool success = _serviceArea.loadFromJson(jsonArea, errorString);

          if (!success) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
          }

          _lastServiceAreaPath =
              _serviceArea.path(); // prevents error messages at this point
          validAreaCounter++;
          _visualItems.append(&_serviceArea);
          emit visualItemsChanged();
        } else if (jsonArea[WimaArea::areaTypeName] ==
                   WimaCorridor::WimaCorridorName) {
          bool success = _corridor.loadFromJson(jsonArea, errorString);

          if (!success) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
            return false;
          }
          _lastCorridorPath =
              _corridor.path(); // prevents error messages at this point
          validAreaCounter++;
          _visualItems.append(&_corridor);
          emit visualItemsChanged();
        } else {
          errorString +=
              QString(tr("%s not supported.\n").arg(WimaArea::areaTypeName));
          qgcApp()->showMessage(errorMessage.arg(errorString));
          return false;
      } else {
        errorString += QString(tr("Invalid or non existing entry for %s.\n")
                                   .arg(WimaArea::areaTypeName));
    _currentFile.sprintf("%s/%s.%s", fileInfo.path().toLocal8Bit().data(),
                         fileInfo.completeBaseName().toLocal8Bit().data(),
                         wimaFileExtension);
    emit currentFileChanged();
    QJsonObject missionObject = json[missionItemsName].toObject();
    // qWarning() << json[missionItemsName].type();
    QJsonDocument missionJsonDoc = QJsonDocument(missionObject);
    // create temporary file with missionItems
    QFile temporaryFile;
    QString cropedFileName = filename.section("/", 0, -2);
#if debug
    qWarning() << cropedFileName;
#endif
    QString temporaryFileName;
    for (int i = 0;; i++) {
      temporaryFileName =
          cropedFileName +
          QString("/temp%1.%2").arg(i).arg(AppSettings::planFileExtension);
      // qWarning() << temporaryFileName;

      if (!QFile::exists(temporaryFileName)) {
        temporaryFile.setFileName(temporaryFileName);
        if (temporaryFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
          break;
        }
      }
      if (i > 1000) {
        qWarning(
            "WimaPlaner::loadFromFile(): not able to create temporary file.");
    // qWarning() << missionJsonDoc.toVariant().toString();
    temporaryFile.write(missionJsonDoc.toJson());
    temporaryFile.close();
    // load from temporary file
    _masterController->loadFromFile(temporaryFileName);
    QmlObjectListModel *missionItems = _missionController->visualItems();
    _TSComplexItem = nullptr;
    for (int i = 0; i < missionItems->count(); i++) {
      _TSComplexItem = missionItems->value<CircularSurvey *>(i);
      if (_TSComplexItem != nullptr) {
        _lastSurveyRefPoint = _TSComplexItem->refPoint();
        _surveyRefChanging = false;
        _TSComplexItem->setIsInitialized(
            true); // prevents reinitialisation from gui
        connect(_TSComplexItem, &TransectStyleComplexItem::missionItemReady,
                this, &WimaPlaner::calcArrivalAndReturnPath);
        break;
      }
    // if (_circularSurvey == nullptr)
    if (!recalcJoinedArea())
      return false;
    if (!updateMission())
      return false;
    // remove temporary file
    if (!temporaryFile.remove()) {
      qWarning(
          "WimaPlaner::loadFromFile(): not able to remove temporary file.");
    setSyncronizedWithController(false);
  } else if (fileInfo.suffix() == AppSettings::planFileExtension) {
    _masterController->loadFromFile(filename);

    return true; // might be wrong return value
  } else {
    errorString += QString(tr("File extension not supported.\n"));
    qgcApp()->showMessage(errorMessage.arg(errorString));
    return false;
  }
}

void WimaPlaner::recalcPolygonInteractivity(int index) {
  if (index >= 0 && index < _visualItems.count()) {
    resetAllInteractive();
    WimaArea *interactivePoly =
        qobject_cast<WimaArea *>(_visualItems.get(index));
    if (interactivePoly != nullptr)
      interactivePoly->setWimaAreaInteractive(true);
  }
}

bool WimaPlaner::calcArrivalAndReturnPath() {
  setReadyForSync(false);

  // extract old survey data
  QmlObjectListModel *missionItems = _missionController->visualItems();

  int surveyIndex = missionItems->indexOf(_TSComplexItem);

  if (surveyIndex == -1) {
    qWarning("WimaPlaner::calcArrivalAndReturnPath(): no survey item");
    return false;
  }

  bool restorePlanViewIndex = false;
  if (surveyIndex == _missionController->currentPlanViewIndex())
    restorePlanViewIndex = true;

  // remove old arrival and return path
  int size = missionItems->count();
  for (int i = surveyIndex + 1; i < size; i++)
    _missionController->removeMissionItem(surveyIndex + 1);
  for (int i = surveyIndex - 1; i > 1; i--)
    _missionController->removeMissionItem(i);

  // set home position to serArea center
  MissionSettingsItem *settingsItem =
      qobject_cast<MissionSettingsItem *>(missionItems->get(0));
  if (settingsItem == nullptr) {
    qWarning("WimaPlaner::calcArrivalAndReturnPath(): settingsItem == nullptr");
    return false;
  }

  // set altitudes, temporary measure to solve bugs
  QGeoCoordinate center = _serviceArea.center();
  center.setAltitude(0);
  _serviceArea.setCenter(center);
  center = _measurementArea.center();
  center.setAltitude(0);
  _measurementArea.setCenter(center);
  center = _corridor.center();
  center.setAltitude(0);
  _corridor.setCenter(center);
  // set HomePos. to serArea center
  settingsItem->setCoordinate(_serviceArea.center());

  // set takeoff position
  bool setCommandNeeded = false;
  if (missionItems->count() < 3) {
    setCommandNeeded = true;
    _missionController->insertSimpleMissionItem(_serviceArea.center(), 1);
  }
  SimpleMissionItem *takeOffItem =
      qobject_cast<SimpleMissionItem *>(missionItems->get(1));
  if (takeOffItem == nullptr) {
    qWarning("WimaPlaner::calcArrivalAndReturnPath(): takeOffItem == nullptr");
    return false;
  }
  if (setCommandNeeded)
    _missionController->setTakeoffCommand(*takeOffItem);
  takeOffItem->setCoordinate(_serviceArea.center());

  if (_TSComplexItem->visualTransectPoints().size() == 0) {
    qWarning("WimaPlaner::calcArrivalAndReturnPath(): survey no points.");
    return false;
  }

  // calculate path from take off to survey
  QGeoCoordinate start = _serviceArea.center();
  QGeoCoordinate end = _TSComplexItem->coordinate();

#ifdef QT_DEBUG
// if (!_visualItems.contains(&_joinedArea))
//_visualItems.append(&_joinedArea);
#endif
  QVector<QGeoCoordinate> path;
  if (!calcShortestPath(start, end, path)) {
    qgcApp()->showMessage(QString(tr("Not able to calculate the path from "
                                     "takeoff position to measurement area."))
                              .toLocal8Bit()
                              .data());
    return false;
  }
  _arrivalPathLength = path.size() - 1;
  int sequenceNumber = 0;
  for (int i = 1; i < path.count() - 1; i++) {
    sequenceNumber = _missionController->insertSimpleMissionItem(
        path[i], missionItems->count() - 1);
    _missionController->setCurrentPlanViewIndex(sequenceNumber, true);
  }

  // calculate return path
  start = _TSComplexItem->exitCoordinate();
  end = _serviceArea.center();
  path.clear();
  if (!calcShortestPath(start, end, path)) {
    qgcApp()->showMessage(QString(tr("Not able to calculate the path from "
                                     "measurement area to landing position."))
                              .toLocal8Bit()
                              .data());
    return false;
  }
  _returnPathLength =
      path.size() - 1; // -1: fist item is last measurement point
  for (int i = 1; i < path.count() - 1; i++) {
    sequenceNumber = _missionController->insertSimpleMissionItem(
        path[i], missionItems->count());
    _missionController->setCurrentPlanViewIndex(sequenceNumber, true);
  }

  // create land position item
  sequenceNumber = _missionController->insertSimpleMissionItem(
      _serviceArea.center(), missionItems->count());
  _missionController->setCurrentPlanViewIndex(sequenceNumber, true);
  SimpleMissionItem *landItem = qobject_cast<SimpleMissionItem *>(
      missionItems->get(missionItems->count() - 1));
  if (landItem == nullptr) {
    qWarning("WimaPlaner::calcArrivalAndReturnPath(): landItem == nullptr");
    return false;
  } else {
    if (!_missionController->setLandCommand(*landItem))
      return false;
  }

  if (restorePlanViewIndex)
    _missionController->setCurrentPlanViewIndex(
        missionItems->indexOf(_TSComplexItem), false);
  setSyncronizedWithControllerFalse();
  setReadyForSync(true);

  return true;
}

bool WimaPlaner::recalcJoinedArea() {
  setJoinedAreaValid(false);
  // check if at least service area and measurement area are available
  if (_visualItems.indexOf(&_serviceArea) == -1 ||
      _visualItems.indexOf(&_measurementArea) == -1)
    return false;

  // check if area paths form simple polygons
  if (!_serviceArea.isSimplePolygon()) {
    qgcApp()->showMessage(
        tr("Service area is self intersecting and thus not a simple polygon. "
           "Only simple polygons allowed.\n"));
    return false;
  }

  if (!_corridor.isSimplePolygon() && _corridor.count() > 0) {
    qgcApp()->showMessage(
        tr("Corridor is self intersecting and thus not a simple polygon. Only "
           "simple polygons allowed.\n"));
    return false;
  }

  if (!_measurementArea.isSimplePolygon()) {
    qgcApp()->showMessage(
        tr("Measurement area is self intersecting and thus not a simple "
           "polygon. Only simple polygons allowed.\n"));
    return false;
  }

  _joinedArea.setPath(_serviceArea.path());
  _joinedArea.join(_corridor);
  if (!_joinedArea.join(_measurementArea)) {
    /*qgcApp()->showMessage(tr("Not able to join areas. Service area and
       measurement"
                             " must have a overlapping section, or be connected
       through a corridor."));*/
    return false; // this happens if all areas are pairwise disjoint
  }

  // join service area, op area and corridor
  setJoinedAreaValid(true);
  return true;
}

void WimaPlaner::pushToWimaController() {
  if (_wimaBridge != nullptr) {
    if (!_readyForSync)
      return;
    WimaPlanData planData = toPlanData();
    (void)_wimaBridge->setWimaPlanData(planData);
    setSyncronizedWithController(true);
  } else {
    qWarning("WimaPlaner::uploadToContainer(): no container assigned.");
  }
Valentin Platzgummer's avatar
Valentin Platzgummer committed
bool WimaPlaner::calcShortestPath(const QGeoCoordinate &start,
                                  const QGeoCoordinate &destination,
                                  QVector<QGeoCoordinate> &path) {
  using namespace GeoUtilities;
  using namespace PolygonCalculus;
  QPolygonF polygon2D;
  toCartesianList(_joinedArea.coordinateList(), /*origin*/ start, polygon2D);
  QPointF start2D(0, 0);
  QPointF end2D;
  QVector<QPointF> path2D;
  toCartesian(destination, start, end2D);
  bool retVal =
      PolygonCalculus::shortestPath(polygon2D, start2D, end2D, path2D);
  toGeoList(path2D, /*origin*/ start, path);

  return retVal;
}

void WimaPlaner::resetAllInteractive() {
  // Marks all areas as inactive (area.interactive == false)
  int itemCount = _visualItems.count();
  if (itemCount > 0) {
    for (int i = 0; i < itemCount; i++) {
      WimaArea *iteratorPoly = qobject_cast<WimaArea *>(_visualItems.get(i));
      iteratorPoly->setWimaAreaInteractive(false);
void WimaPlaner::setInteractive() {
  recalcPolygonInteractivity(_currentAreaIndex);
 * Returns a \c WimaPlanData object containing information about the current
 * mission. The \c WimaPlanData object holds only the data which is relevant for
 * the \c WimaController class. Should only be called if updateMission() was
 * successful.
WimaPlanData WimaPlaner::toPlanData() {
  WimaPlanData planData;

  // store areas
  planData.append(WimaMeasurementAreaData(_measurementArea));
  planData.append(WimaServiceAreaData(_serviceArea));
  planData.append(WimaCorridorData(_corridor));
  planData.append(WimaJoinedAreaData(_joinedArea));

  // convert mission items to mavlink commands
  QObject deleteObject; // used to automatically delete content of
                        // rgMissionItems after this function call
  QList<MissionItem *> rgMissionItems;
  QmlObjectListModel *visualItems = _missionController->visualItems();
  QmlObjectListModel visualItemsToCopy;
  for (unsigned long i = _arrivalPathLength + 1;
       i < visualItems->count() - _returnPathLength; i++)
    visualItemsToCopy.append(visualItems->get(i));
  MissionController::convertToMissionItems(&visualItemsToCopy, rgMissionItems,
                                           &deleteObject);

  // store mavlink commands
  planData.append(rgMissionItems);

  return planData;
}

void WimaPlaner::setSyncronizedWithController(bool sync) {
  if (_syncronizedWithController != sync) {
    _syncronizedWithController = sync;
    emit syncronizedWithControllerChanged();
  }
}

void WimaPlaner::setReadyForSync(bool ready) {
  if (_readyForSync != ready) {
    _readyForSync = ready;

    emit readyForSyncChanged();
  }
}

void WimaPlaner::setJoinedAreaValid(bool valid) {
  if (_joinedAreaValid != valid) {
    _joinedAreaValid = valid;

    emit joinedAreaValidChanged();
  }
}

void WimaPlaner::updateTimerSlot() {
  // General operation of this function:
  // Check if parameter has changed, wait until it stops changing, update
  // mission

  // circular survey reference point
  //    if (_missionController != nullptr
  //            && _missionController->visualItems()->indexOf(_circularSurvey)
  //            != -1
  //            && _circularSurvey != nullptr)
  //    {
  //        if (_surveyRefChanging) {
  //            if (_circularSurvey->refPoint() == _lastSurveyRefPoint) { // is
  //            it still changing?
  //                calcArrivalAndReturnPath();
  //                _surveyRefChanging = false;
  //            }
  //        } else {
  //            if (_circularSurvey->refPoint() != _lastSurveyRefPoint) // does
  //            it started changing?
  //                _surveyRefChanging = true;
  //        }
  //    }

  // measurementArea
  if (_measurementAreaChanging) {
    if (_measurementArea.path() ==
        _lastMeasurementAreaPath) { // is it still changing?
      setReadyForSync(false);
      if (recalcJoinedArea() && calcArrivalAndReturnPath())
        setReadyForSync(true);
      _measurementAreaChanging = false;
    }
  } else {
    if (_measurementArea.path() !=
        _lastMeasurementAreaPath) // does it started changing?
      _measurementAreaChanging = true;
  }

  // corridor
  if (_corridorChanging) {
    if (_corridor.path() == _lastCorridorPath) { // is it still changing?
      setReadyForSync(false);
      if (recalcJoinedArea() && calcArrivalAndReturnPath())
        setReadyForSync(true);
      _corridorChanging = false;
    }
  } else {
    if (_corridor.path() != _lastCorridorPath) // does it started changing?
      _corridorChanging = true;
  }

  // service area
  if (_serviceAreaChanging) {
    if (_serviceArea.path() == _lastServiceAreaPath) { // is it still changing?
      setReadyForSync(false);
      if (recalcJoinedArea() && calcArrivalAndReturnPath())
        setReadyForSync(true);
      _serviceAreaChanging = false;
    }
  } else {
    if (_serviceArea.path() !=
        _lastServiceAreaPath) // does it started changing?
      _serviceAreaChanging = true;
  }

  // update old values
  //    if (_missionController != nullptr
  //            && _missionController->visualItems()->indexOf(_circularSurvey)
  //            != -1
  //            && _circularSurvey != nullptr)
  //        _lastSurveyRefPoint = _circularSurvey->refPoint();

  _lastMeasurementAreaPath = _measurementArea.path();
  _lastCorridorPath = _corridor.path();
  _lastServiceAreaPath = _serviceArea.path();
}

void WimaPlaner::setSyncronizedWithControllerFalse() {
  setSyncronizedWithController(false);
#ifndef NDEBUG
void WimaPlaner::autoLoadMission() {
  loadFromFile("/home/valentin/Desktop/drones/qgroundcontrol/Paths/"
               "KlingenbachTest.wima");
  pushToWimaController();
void WimaPlaner::startCalcArrivalAndReturnTimer() {
  _calcArrivalAndReturnPathTimer.start();
QJsonDocument WimaPlaner::saveToJson(FileType fileType) {
  /// This function save all areas (of WimaPlaner) and all mission items (of
  /// MissionController) to a QJsonDocument
  /// @param fileType is either WimaFile or PlanFile (enum), if fileType ==
  /// PlanFile only mission items are stored
  QJsonObject json;
  if (fileType == FileType::WimaFile) {
    QJsonArray jsonArray;
    for (int i = 0; i < _visualItems.count(); i++) {
      QJsonObject json;
      WimaArea *area = qobject_cast<WimaArea *>(_visualItems.get(i));
      if (area == nullptr) {
        qWarning("WimaPlaner::saveToJson(): Internal error, area == nullptr!");
        return QJsonDocument();
      }
      // check the type of area, create and append the JsonObject to the
      // JsonArray once determined
      WimaMeasurementArea *opArea = qobject_cast<WimaMeasurementArea *>(area);
      if (opArea != nullptr) {
        opArea->saveToJson(json);
        jsonArray.append(json);
        continue;
      }
      WimaServiceArea *serArea = qobject_cast<WimaServiceArea *>(area);
      if (serArea != nullptr) {
        serArea->saveToJson(json);
        jsonArray.append(json);
        continue;
      }
      WimaCorridor *corridor = qobject_cast<WimaCorridor *>(area);
      if (corridor != nullptr) {
        corridor->saveToJson(json);
        jsonArray.append(json);
        continue;
      }
      // if non of the obove branches was trigger, type must be WimaArea
      area->saveToJson(json);
      jsonArray.append(json);
    json[areaItemsName] = jsonArray;
    json[missionItemsName] = _masterController->saveToJson().object();
    return QJsonDocument(json);
  } else if (fileType == FileType::PlanFile) {
    return _masterController->saveToJson();
  }
  return QJsonDocument(json);
}