#include "WimaController.h" const char* WimaController::wimaFileExtension = "wima"; const char* WimaController::areaItemsName = "AreaItems"; const char* WimaController::missionItemsName = "MissionItems"; WimaController::WimaController(QObject *parent) : QObject (parent) , _flyView (true) , _readyForSaveSend (false) , _currentPolygonIndex (-1) , _container (nullptr) , _joinedArea (this) , _opArea (this) , _serArea (this) , _corridor (this) { connect(this, &WimaController::currentPolygonIndexChanged, this, &WimaController::recalcPolygonInteractivity); } const QmlObjectListModel* WimaController::visualItems() const { return &_visualItems; } QStringList WimaController::loadNameFilters() const { QStringList filters; filters << tr("Supported types (*.%1 *.%2)").arg(wimaFileExtension).arg(AppSettings::planFileExtension) << tr("All Files (*.*)"); return filters; } QStringList WimaController::saveNameFilters() const { QStringList filters; filters << tr("Supported types (*.%1 *.%2)").arg(wimaFileExtension).arg(AppSettings::planFileExtension); return filters; } QGeoCoordinate WimaController::joinedAreaCenter() const { return _joinedArea.center(); } WimaArea WimaController::joinedArea() const { return _joinedArea; } void WimaController::setMasterController(PlanMasterController *masterC) { _masterController = masterC; emit masterControllerChanged(); } void WimaController::setMissionController(MissionController *missionC) { _missionController = missionC; emit missionControllerChanged(); } void WimaController::setCurrentPolygonIndex(int index) { if(index >= 0 && index < _visualItems.count() && index != _currentPolygonIndex){ _currentPolygonIndex = index; emit currentPolygonIndexChanged(index); } } void WimaController::setDataContainer(WimaDataContainer *container) { if (_container == nullptr && container != nullptr) { _container = container; if (_flyView) { downloadFromContainer(); _visualItems.append(&_opArea); _visualItems.append(&_serArea); _visualItems.append(&_joinedArea); connect(_container, &WimaDataContainer::opAreaChanged, this, &WimaController::setOpArea); connect(_container, &WimaDataContainer::serAreaChanged, this, &WimaController::setSerArea); connect(_container, &WimaDataContainer::corridorChanged, this, &WimaController::setCorridor); connect(_container, &WimaDataContainer::joinedAreaChanged, this, &WimaController::setJoinedArea); } emit dataContainerChanged(); } } void WimaController::startWimaController(bool flyView) { _flyView = flyView; } void WimaController::removeArea(int index) { if(index >= 0 && index < _visualItems.count()){ if (_flyView) { //not editing allowed in flyView return; } WimaArea* area = qobject_cast(_visualItems.removeAt(index)); if ( area == nullptr) { qWarning("WimaController::removeArea(): nullptr catched, internal error."); return; } area->clear(); emit visualItemsChanged(); if (_visualItems.count() == 0) { // this branch is reached if all items are removed // to guarentee proper behavior, _currentPolygonIndex must be set to a invalid value, as on constructor init. _currentPolygonIndex = -1; return; } if(_currentPolygonIndex >= _visualItems.count()){ setCurrentPolygonIndex(_visualItems.count() - 1); }else{ recalcPolygonInteractivity(_currentPolygonIndex); } }else{ qWarning("Index out of bounds!"); } } bool WimaController::addGOperationArea() { if (!_flyView && !_visualItems.contains(&_opArea)) { _visualItems.append(&_opArea); int newIndex = _visualItems.count()-1; setCurrentPolygonIndex(newIndex); emit visualItemsChanged(); return true; } else { return false; } } bool WimaController::addServiceArea() { if (!_flyView && !_visualItems.contains(&_serArea)) { _visualItems.append(&_serArea); int newIndex = _visualItems.count()-1; setCurrentPolygonIndex(newIndex); emit visualItemsChanged(); return true; } else { return false; } } bool WimaController::addVehicleCorridor() { if (!_flyView && !_visualItems.contains(&_corridor)) { _visualItems.append(&_corridor); int newIndex = _visualItems.count()-1; setCurrentPolygonIndex(newIndex); emit visualItemsChanged(); return true; } else { return false; } } void WimaController::removeAll() { if (_flyView) { return; } bool changesApplied = false; while (_visualItems.count() > 0) { removeArea(0); changesApplied = true; } _missionController->removeAll(); _currentFile = ""; emit currentFileChanged(); if ( changesApplied ) emit visualItemsChanged(); } void WimaController::startMission() { } void WimaController::abortMission() { } void WimaController::pauseMission() { } void WimaController::resumeMission() { } bool WimaController::updateMission() { if (!_flyView) { setReadyForSaveSend(false); #define debug 0 if ( !updateJoinedArea()) { qgcApp()->showMessage(tr("Not able to join areas. Areas must be overlapping")); return false; } #if debug _visualItems.append(&_joinedArea); #endif // reset visual items _missionController->removeAll(); QmlObjectListModel* missionItems = _missionController->visualItems(); // set home position to serArea center MissionSettingsItem* settingsItem= qobject_cast(missionItems->get(0)); if (settingsItem == nullptr){ qWarning("WimaController::updateMission(): settingsItem == nullptr"); return false; } // set altitudes, temporary measure to solve bugs QGeoCoordinate center = _serArea.center(); center.setAltitude(0); _serArea.setCenter(center); center = _opArea.center(); center.setAltitude(0); _opArea.setCenter(center); center = _corridor.center(); center.setAltitude(0); _corridor.setCenter(center); // set HomePos. to serArea center settingsItem->setCoordinate(_serArea.center()); // create take off position item int sequenceNumber = _missionController->insertSimpleMissionItem(_serArea.center(), missionItems->count()); _missionController->setCurrentPlanViewIndex(sequenceNumber, true); // create survey item, will be extened with more()-> mission types in the future _missionController->insertComplexMissionItem(_missionController->surveyComplexItemName(), _opArea.center(), missionItems->count()); SurveyComplexItem* survey = qobject_cast(missionItems->get(missionItems->count()-1)); if (survey == nullptr){ qWarning("WimaController::updateMission(): survey == nullptr"); return false; } else { survey->surveyAreaPolygon()->clear(); survey->surveyAreaPolygon()->appendVertices(_opArea.coordinateList()); //survey-> } // calculate path from take off to opArea QGeoCoordinate start = _serArea.center(); QGeoCoordinate end = survey->visualTransectPoints().first().value(); QList path; if ( !WimaArea::dijkstraPath(start, end, _joinedArea, path)) { qgcApp()->showMessage(tr("Not able to calculate the path from takeoff position to measurement area.")); return false; } for (int i = 1; i < path.count()-1; i++) { sequenceNumber = _missionController->insertSimpleMissionItem(path.value(i), missionItems->count()-1); _missionController->setCurrentPlanViewIndex(sequenceNumber, true); } // calculate return path start = survey->visualTransectPoints().last().value(); end = _serArea.center(); path.clear(); if ( ! WimaArea::dijkstraPath(start, end, _joinedArea, path)) { qgcApp()->showMessage(tr("Not able to calculate the path from measurement area to landing position.")); return false; } for (int i = 1; i < path.count()-1; i++) { sequenceNumber = _missionController->insertSimpleMissionItem(path.value(i), missionItems->count()); _missionController->setCurrentPlanViewIndex(sequenceNumber, true); } // create land position item sequenceNumber = _missionController->insertSimpleMissionItem(_serArea.center(), missionItems->count()); _missionController->setCurrentPlanViewIndex(sequenceNumber, true); SimpleMissionItem* landItem = qobject_cast(missionItems->get(missionItems->count()-1)); if (landItem == nullptr){ qWarning("WimaController::updateMission(): landItem == nullptr"); return false; } else { Vehicle* controllerVehicle = _masterController->controllerVehicle(); MAV_CMD landCmd = controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_LAND : MAV_CMD_NAV_LAND; if (controllerVehicle->firmwarePlugin()->supportedMissionCommands().contains(landCmd)) { landItem->setCommand(landCmd); } } updateContainer(); setReadyForSaveSend(true); return true; } else { return true; } } void WimaController::saveToCurrent() { saveToFile(_currentFile); } void WimaController::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 WimaController::loadFromCurrent() { return loadFromFile(_currentFile); } bool WimaController::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(); for( int i = 0; i < areaArray.size(); i++) { QJsonObject jsonArea = areaArray[i].toObject(); if (jsonArea.contains(WimaArea::areaTypeName) && jsonArea[WimaArea::areaTypeName].isString()) { if ( jsonArea[WimaArea::areaTypeName] == WimaGOperationArea::wimaGOperationAreaName) { bool success = _opArea.loadFromJson(jsonArea, errorString); if ( !success ) { qgcApp()->showMessage(errorMessage.arg(errorString)); return false; } _visualItems.append(&_opArea); emit visualItemsChanged(); } else if ( jsonArea[WimaArea::areaTypeName] == WimaServiceArea::wimaServiceAreaName) { bool success = _serArea.loadFromJson(jsonArea, errorString); if ( !success ) { qgcApp()->showMessage(errorMessage.arg(errorString)); return false; } _visualItems.append(&_serArea); emit visualItemsChanged(); } else if ( jsonArea[WimaArea::areaTypeName] == WimaVCorridor::wimaVCorridorName) { bool success = _corridor.loadFromJson(jsonArea, errorString); if ( !success ) { qgcApp()->showMessage(errorMessage.arg(errorString)); return false; } _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(); updateJoinedArea(); // MissionItems // extrac MissionItems part QJsonDocument missionJsonDoc = QJsonDocument(json[missionItemsName].toObject()); // 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.append("/temp%1.%2").arg(i).arg(AppSettings::planFileExtension); if ( !QFile::exists(temporaryFileName) ) { temporaryFile.setFileName(temporaryFileName); if ( temporaryFile.open(QIODevice::WriteOnly | QIODevice::Text) ) { break; } } if ( i > 1000) { qWarning("WimaController::loadFromFile(): not able to create temporary file."); return false; } } temporaryFile.write(missionJsonDoc.toJson()); // load from temporary file _masterController->loadFromFile(temporaryFileName); // remove temporary file if ( !temporaryFile.remove() ){ qWarning("WimaController::loadFromFile(): not able to remove temporary file."); } 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 WimaController::recalcPolygonInteractivity(int index) { if (index >= 0 && index < _visualItems.count()) { resetAllInteractive(); WimaArea* interactivePoly = qobject_cast(_visualItems.get(index)); interactivePoly->setInteractive(true); } } bool WimaController::updateJoinedArea() { // join service area, op area and corridor _joinedArea = _serArea; _joinedArea.join(_corridor); if ( !_joinedArea.join(_opArea) ) return false; else { emit joinedAreaChanged() ; return true; } } void WimaController::updateContainer() { if (_container != nullptr) { _container->setOpArea(_opArea); _container->setSerArea(_serArea); _container->setCorridor(_corridor); _container->setJoinedArea(_joinedArea); } else { qWarning("WimaController::uploadToContainer(): no container assigned."); } } void WimaController::downloadFromContainer() { if (_flyView) { _opArea = _container->opArea(); _serArea = _container->serArea(); _corridor = _container->corridor(); _joinedArea = _container->joinedArea(); } } void WimaController::setOpArea(const WimaGOperationArea &area) { if (_flyView) { _opArea = area; } } void WimaController::setSerArea(const WimaServiceArea &area) { if (_flyView) { _serArea = area; } } void WimaController::setCorridor(const WimaVCorridor &area) { if (_flyView) { _corridor = area; } } void WimaController::setJoinedArea(const WimaArea &area) { if (_flyView) { _joinedArea = area; } } void WimaController::resetAllInteractive() { int itemCount = _visualItems.count(); if (itemCount > 0){ for (int i = 0; i < itemCount; i++) { WimaArea* iteratorPoly = qobject_cast(_visualItems.get(i)); iteratorPoly->setInteractive(false); } } } void WimaController::setInteractive() { recalcPolygonInteractivity(_currentPolygonIndex); } QJsonDocument WimaController::saveToJson(FileType fileType) { 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("WimaController::saveToJson(): Internal error, area == nullptr!"); return QJsonDocument(); } WimaGOperationArea* 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; } WimaVCorridor* 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); } void WimaController::setReadyForSaveSend(bool ready) { if (ready != _readyForSaveSend) { _readyForSaveSend = ready; emit readyForSaveSendChanged(ready); } }