#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(); #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); #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(_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); } else { 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(); _currentFile = ""; _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( 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; } else { 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)); return false; } _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)); return false; } _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)); return false; } } _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."); return false; } } // 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(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); return true; } 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(_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(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(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 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( 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."); } } bool WimaPlaner::calcShortestPath(const QGeoCoordinate &start, const QGeoCoordinate &destination, QVector &path) { using namespace GeoUtilities; using namespace PolygonCalculus; QPolygonF polygon2D; toCartesianList(_joinedArea.coordinateList(), /*origin*/ start, polygon2D); QPointF start2D(0, 0); QPointF end2D; QVector 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(_visualItems.get(i)); iteratorPoly->setWimaAreaInteractive(false); } } } void WimaPlaner::setInteractive() { recalcPolygonInteractivity(_currentAreaIndex); } /*! * \fn WimaPlanData WimaPlaner::toPlanData() * * 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. * * \sa WimaController, WimaPlanData */ 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 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(); } #endif 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(_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(area); if (opArea != nullptr) { opArea->saveToJson(json); jsonArray.append(json); continue; } WimaServiceArea *serArea = qobject_cast(area); if (serArea != nullptr) { serArea->saveToJson(json); jsonArray.append(json); continue; } WimaCorridor *corridor = qobject_cast(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); }