Skip to content
WimaController.cc 19.9 KiB
Newer Older
#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;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
QStringList WimaController::loadNameFilters() const
{
    QStringList filters;

    filters << tr("Supported types (*.%1 *.%2)").arg(wimaFileExtension).arg(AppSettings::planFileExtension) <<
Valentin Platzgummer's avatar
Valentin Platzgummer committed
               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<WimaArea*>(_visualItems.removeAt(index));

        if ( area == nullptr) {
            qWarning("WimaController::removeArea(): nullptr catched, internal error.");
            return;
        }
        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()
Valentin Platzgummer's avatar
Valentin Platzgummer committed
{
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    bool changesApplied = false;
    while (_visualItems.count() > 0) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
        changesApplied = true;
    }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
    _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<MissionSettingsItem*>(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<SurveyComplexItem*>(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<QGeoCoordinate>();
        QList<QGeoCoordinate> 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<QGeoCoordinate>();
        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<SimpleMissionItem*>(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;
void WimaController::saveToCurrent()
Valentin Platzgummer's avatar
Valentin Platzgummer committed
    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)
{
    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();
        // 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<WimaArea*>(_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<WimaArea*>(_visualItems.get(i));
            iteratorPoly->setInteractive(false);
        }
void WimaController::setInteractive()
{
    recalcPolygonInteractivity(_currentPolygonIndex);
}

QJsonDocument WimaController::saveToJson(FileType fileType)
    if ( fileType == FileType::WimaFile ) {
        QJsonArray jsonArray;
        for (int i = 0; i < _visualItems.count(); i++) {
            WimaArea* area = qobject_cast<WimaArea*>(_visualItems.get(i));
            if (area == nullptr) {
                qWarning("WimaController::saveToJson(): Internal error, area == nullptr!");
                return QJsonDocument();
            }
            WimaGOperationArea* opArea =  qobject_cast<WimaGOperationArea*>(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;
            }
            WimaVCorridor* corridor =  qobject_cast<WimaVCorridor*>(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);
    }
}