WimaPlaner.cc 32.9 KB
Newer Older
1
#include "WimaPlaner.h"
2

3 4 5 6
#include "MissionController.h"
#include "MissionSettingsItem.h"
#include "PlanMasterController.h"
#include "QGCApplication.h"
7
#include "QGCLoggingCategory.h"
8 9
#include "QGCMapPolygon.h"
#include "SimpleMissionItem.h"
10

11 12 13
#include "Geometry/GeoUtilities.h"
#include "Geometry/PlanimetryCalculus.h"
#include "OptimisationTools.h"
14

15 16 17 18
#include "CircularSurvey.h"
#include "Geometry/WimaArea.h"
#include "Geometry/WimaAreaData.h"
#include "WimaBridge.h"
19

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#include "StateMachine.h"
using namespace wima_planer_detail;

#include <functional>

QGC_LOGGING_CATEGORY(WimaPlanerLog, "WimaPlanerLog")

class CommandRAII {
  std::function<void(void)> f;

public:
  CommandRAII(const std::function<void(void)> &fun) : f(fun) {}
  ~CommandRAII() { f(); }
};

35 36 37
const char *WimaPlaner::wimaFileExtension = "wima";
const char *WimaPlaner::areaItemsName = "AreaItems";
const char *WimaPlaner::missionItemsName = "MissionItems";
38 39

WimaPlaner::WimaPlaner(QObject *parent)
40
    : QObject(parent), _masterController(nullptr), _missionController(nullptr),
41 42 43 44
      _currentAreaIndex(-1), _wimaBridge(nullptr), _copyMAreaToSurvey(true),
      _copySAreaToSurvey(true), _corridorChanged(true), _joinedArea(this),
      _arrivalPathLength(0), _returnPathLength(0), _survey(nullptr),
      _surveyChanged(true), _synchronized(false), _nemoInterface(this),
45 46
      _stateMachine(new StateMachine), _areasMonitored(false),
      _missionControllerMonitored(false) {
47 48

  connect(this, &WimaPlaner::currentPolygonIndexChanged, this,
49
          &WimaPlaner::updatePolygonInteractivity);
50

51 52 53
  // Monitoring.
  enableAreaMonitoring();
  // Mission controller not set at this point. Not enabling monitoring.
54

Valentin Platzgummer's avatar
Valentin Platzgummer committed
55
#ifndef NDEBUG
56 57 58 59 60
  // 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
61
#endif
62 63 64 65

  // NemoInterface
  connect(&this->_nemoInterface, &NemoInterface::progressChanged, [this] {
    this->_measurementArea.setProgress(this->_nemoInterface.progress());
Valentin Platzgummer's avatar
Valentin Platzgummer committed
66
    this->_update();
67
  });
68 69 70 71

  // StateMachine
  connect(this->_stateMachine.get(), &StateMachine::upToDateChanged, this,
          &WimaPlaner::needsUpdateChanged);
72 73
  connect(this->_stateMachine.get(), &StateMachine::surveyReadyChanged, this,
          &WimaPlaner::readyForSynchronizationChanged);
74
}
75

76 77
WimaPlaner::~WimaPlaner() {}

78 79 80 81 82 83
PlanMasterController *WimaPlaner::masterController() {
  return _masterController;
}

MissionController *WimaPlaner::missionController() {
  return _missionController;
84 85
}

86
QmlObjectListModel *WimaPlaner::visualItems() { return &_visualItems; }
87

88 89 90 91
int WimaPlaner::currentPolygonIndex() const { return _currentAreaIndex; }

QString WimaPlaner::currentFile() const { return _currentFile; }

92 93
QStringList WimaPlaner::loadNameFilters() const {
  QStringList filters;
94

95 96 97 98 99
  filters << tr("Supported types (*.%1 *.%2)")
                 .arg(wimaFileExtension)
                 .arg(AppSettings::planFileExtension)
          << tr("All Files (*.*)");
  return filters;
100 101
}

102 103
QStringList WimaPlaner::saveNameFilters() const {
  QStringList filters;
104

105 106 107 108
  filters << tr("Supported types (*.%1 *.%2)")
                 .arg(wimaFileExtension)
                 .arg(AppSettings::planFileExtension);
  return filters;
109 110
}

111 112
QString WimaPlaner::fileExtension() const { return wimaFileExtension; }

113 114
QGeoCoordinate WimaPlaner::joinedAreaCenter() const {
  return _joinedArea.center();
115 116
}

117 118
WimaBridge *WimaPlaner::wimaBridge() { return _wimaBridge; }

119 120
NemoInterface *WimaPlaner::nemoInterface() { return &_nemoInterface; }

121
void WimaPlaner::setMasterController(PlanMasterController *masterC) {
122 123 124 125
  if (_masterController != masterC) {
    _masterController = masterC;
    emit masterControllerChanged();
  }
126 127
}

128
void WimaPlaner::setMissionController(MissionController *missionC) {
129 130 131 132 133 134
  if (_missionController != missionC) {
    disableMissionControllerMonitoring();
    _missionController = missionC;
    enableMissionControllerMonitoring();
    emit missionControllerChanged();
  }
135 136
}

137 138 139 140
void WimaPlaner::setCurrentPolygonIndex(int index) {
  if (index >= 0 && index < _visualItems.count() &&
      index != _currentAreaIndex) {
    _currentAreaIndex = index;
141

142 143
    emit currentPolygonIndexChanged(index);
  }
144 145
}

146 147 148 149 150
void WimaPlaner::setWimaBridge(WimaBridge *bridge) {
  if (bridge != nullptr) {
    _wimaBridge = bridge;
    emit wimaBridgeChanged();
  }
151 152
}

153
bool WimaPlaner::synchronized() { return _synchronized; }
154

155
bool WimaPlaner::needsUpdate() { return !this->_stateMachine->upToDate(); }
156

157 158 159 160
bool WimaPlaner::readyForSynchronization() {
  return this->_stateMachine->surveyReady();
}

161
WimaPlaner *WimaPlaner::thisPointer() { return this; }
162

163 164 165
void WimaPlaner::removeArea(int index) {
  if (index >= 0 && index < _visualItems.count()) {
    WimaArea *area = qobject_cast<WimaArea *>(_visualItems.removeAt(index));
166

167
    if (area == nullptr) {
168 169
      qCWarning(WimaPlanerLog)
          << "removeArea(): nullptr catched, internal error.";
170
      return;
171
    }
172 173
    area->clear();
    area->borderPolygon()->clear();
174

175
    emit visualItemsChanged();
176

177 178 179 180 181 182 183
    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;
184 185
    }

186 187
    if (_currentAreaIndex >= _visualItems.count()) {
      setCurrentPolygonIndex(_visualItems.count() - 1);
188
    } else {
189
      updatePolygonInteractivity(_currentAreaIndex);
190
    }
191
  } else {
192
    qCWarning(WimaPlanerLog) << "removeArea(): Index out of bounds!";
193
  }
194 195
}

196 197 198
bool WimaPlaner::addMeasurementArea() {
  if (!_visualItems.contains(&_measurementArea)) {
    _visualItems.append(&_measurementArea);
199

200 201
    int newIndex = _visualItems.count() - 1;
    setCurrentPolygonIndex(newIndex);
202

203 204 205 206 207
    emit visualItemsChanged();
    return true;
  } else {
    return false;
  }
208 209
}

210 211 212
bool WimaPlaner::addServiceArea() {
  if (!_visualItems.contains(&_serviceArea)) {
    _visualItems.append(&_serviceArea);
213

214 215
    int newIndex = _visualItems.count() - 1;
    setCurrentPolygonIndex(newIndex);
216

217 218 219 220 221 222
    emit visualItemsChanged();
    return true;
  } else {
    return false;
  }
}
223

224 225 226
bool WimaPlaner::addCorridor() {
  if (!_visualItems.contains(&_corridor)) {
    _visualItems.append(&_corridor);
227

228 229
    int newIndex = _visualItems.count() - 1;
    setCurrentPolygonIndex(newIndex);
230

231 232 233 234 235
    emit visualItemsChanged();
    return true;
  } else {
    return false;
  }
236 237
}

238 239 240 241 242 243
void WimaPlaner::removeAll() {
  bool changesApplied = false;
  while (_visualItems.count() > 0) {
    removeArea(0);
    changesApplied = true;
  }
244

245
  _missionController->removeAll();
246

247
  _currentFile = "";
248

249
  _survey = nullptr;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
250

251 252 253 254
  emit currentFileChanged();
  if (changesApplied)
    emit visualItemsChanged();
}
Valentin Platzgummer's avatar
Valentin Platzgummer committed
255

256
void WimaPlaner::update() { this->_update(); }
257

258 259
void WimaPlaner::_update() {
  setSynchronized(false);
260

261 262
  switch (this->_stateMachine->state()) {
  case STATE::NEEDS_INIT: {
263 264 265 266 267 268 269 270 271
    if (this->_measurementArea.ready()) {
      this->_stateMachine->updateState(EVENT::INIT_DONE);
      this->_update();
    } else {
      this->_stateMachine->updateState(EVENT::M_AREA_NOT_READY);
    }
  } break;

  case STATE::WAITING_FOR_TILE_UPDATE: {
272
  } break;
273

274 275 276 277 278
  case STATE::NEEDS_J_AREA_UPDATE: {
    // check if at least service area and measurement area are available
    if (_visualItems.indexOf(&_serviceArea) == -1 ||
        _visualItems.indexOf(&_measurementArea) == -1)
      return;
279

280 281 282 283 284
    // Check if polygons have at least three vertices
    if (_serviceArea.count() < 3) {
      qgcApp()->showMessage(tr("Service area has less than three vertices."));
      return;
    }
285

286 287 288 289 290
    if (_measurementArea.count() < 3) {
      qgcApp()->showMessage(
          tr("Measurement area has less than three vertices."));
      return;
    }
291

292 293 294 295 296 297
    // Check for simple polygons
    if (!_serviceArea.isSimplePolygon()) {
      qgcApp()->showMessage(tr("Service area is not a simple polygon. "
                               "Only simple polygons allowed.\n"));
      return;
    }
298

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
    if (!_corridor.isSimplePolygon() && _corridor.count() > 0) {
      qgcApp()->showMessage(tr("Corridor is not a simple polygon. Only "
                               "simple polygons allowed.\n"));
      return;
    }

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

    if (!_serviceArea.containsCoordinate(_serviceArea.depot())) {
      qgcApp()->showMessage(tr("Depot not inside service area."));
      return;
    }
315

316
    // Join areas.
317 318 319 320 321 322 323 324 325
    _joinedArea.setPath(_serviceArea.path());
    if (_corridor.count() >= 3) {
      _joinedArea.join(_corridor);
    }
    if (!_joinedArea.join(_measurementArea)) {
      qgcApp()->showMessage(
          tr("Not able to join areas. Service and measurement area"
             " must be overlapping, or connected through a "
             "corridor."));
326
      return;
327
    }
328 329 330
    this->_stateMachine->updateState(EVENT::J_AREA_UPDATED);
    this->_update();
  } break; // STATE::NEEDS_J_AREA_UPDATE
331

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
  case STATE::NEEDS_SURVEY_UPDATE: {
    // Need to insert Survey?
    QmlObjectListModel *missionItems = _missionController->visualItems();
    int surveyIndex = missionItems->indexOf(_survey);
    // Create survey item if not yet present.
    if (surveyIndex < 0) {
      _missionController->insertComplexMissionItem(
          _missionController->circularSurveyComplexItemName(),
          _measurementArea.center(), missionItems->count());
      _survey = qobject_cast<CircularSurvey *>(
          missionItems->get(missionItems->count() - 1));

      if (_survey == nullptr) {
        qCWarning(WimaPlanerLog) << "_survey == nullptr";
        return;
      }
348

349 350 351 352 353 354 355 356 357 358
      // establish connections
      _survey->setRefPoint(_measurementArea.center());
      _survey->setHidePolygon(true);
      connect(_survey, &CircularSurvey::calculatingChanged, this,
              &WimaPlaner::CSCalculatingChangedHandler);
      connect(_survey, &CircularSurvey::missionItemReady, this,
              &WimaPlaner::CSMissionItemReadyHandler);
      connect(_survey, &CircularSurvey::destroyed, this,
              &WimaPlaner::CSDestroyedHandler);
    }
359 360

    // update survey area
361 362 363 364
    disconnect(_survey, &CircularSurvey::calculatingChanged, this,
               &WimaPlaner::CSCalculatingChangedHandler);
    _survey->setMeasurementArea(this->_measurementArea);
    _survey->setJoinedArea(this->_joinedArea);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
365
    _survey->setDepot(this->_serviceArea.depot());
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
    connect(_survey, &CircularSurvey::calculatingChanged, this,
            &WimaPlaner::CSCalculatingChangedHandler);

    // Folloing statement just for completeness.
    this->_stateMachine->updateState(EVENT::SURVEY_UPDATE_TRIGGERED);
  } break; // STATE::NEEDS_SURVEY_UPDATE

  case STATE::WAITING_FOR_SURVEY_UPDATE: {
  } break;

  case STATE::NEEDS_PATH_UPDATE: {
    // Check if survey is present.
    QmlObjectListModel *missionItems = _missionController->visualItems();
    int surveyIndex = missionItems->indexOf(_survey);
    if (surveyIndex < 0) {
      this->_stateMachine->updateState(EVENT::SURVEY_DESTROYED);
      this->_update();
    } else {
384

385 386 387 388 389 390 391 392 393 394 395 396 397 398
      // 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) {
        qCWarning(WimaPlanerLog) << "update(): settingsItem == nullptr";
        return;
      }
399

400 401 402 403
      // set altitudes
      auto depot = _serviceArea.depot();
      depot.setAltitude(0);
      settingsItem->setCoordinate(depot);
404

405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
      // set takeoff position
      bool setCommandNeeded = false;
      if (missionItems->count() < 3) {
        setCommandNeeded = true;
        _missionController->insertSimpleMissionItem(depot, 1);
      }
      SimpleMissionItem *takeOffItem =
          qobject_cast<SimpleMissionItem *>(missionItems->get(1));
      if (takeOffItem == nullptr) {
        qCWarning(WimaPlanerLog) << "update(): takeOffItem == nullptr";
        return;
      }
      if (setCommandNeeded)
        _missionController->setTakeoffCommand(*takeOffItem);
      takeOffItem->setCoordinate(depot);
420

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
      if (_survey->visualTransectPoints().size() == 0) {
        qCWarning(WimaPlanerLog) << "update(): survey no points";
        return;
      }

      // calculate path from take off to survey
      QGeoCoordinate start = depot;
      QGeoCoordinate end = _survey->coordinate();
      QVector<QGeoCoordinate> path;
      if (!shortestPath(start, end, path)) {
        qgcApp()->showMessage(
            QString(tr("Not able to calculate path from "
                       "takeoff position to measurement area."))
                .toLocal8Bit()
                .data());
        return;
      }
      _arrivalPathLength = path.size() - 1;
      for (int i = 1; i < path.count() - 1; i++) {
440
        (void)_missionController->insertSimpleMissionItem(
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
            path[i], missionItems->count() - 1);
      }

      // calculate return path
      start = _survey->exitCoordinate();
      end = depot;
      path.clear();
      if (!shortestPath(start, end, path)) {
        qgcApp()->showMessage(
            QString(tr("Not able to calculate the path from "
                       "measurement area to landing position."))
                .toLocal8Bit()
                .data());
        return;
      }
      _returnPathLength =
          path.size() - 1; // -1: fist item is last measurement point
      for (int i = 1; i < path.count() - 1; i++) {
459
        (void)_missionController->insertSimpleMissionItem(
460 461 462
            path[i], missionItems->count());
      }

463 464 465
      // Add waypoint (rover ignores land command).
      (void)_missionController->insertSimpleMissionItem(depot,
                                                        missionItems->count());
466
      // create land position item
467 468
      (void)_missionController->insertSimpleMissionItem(depot,
                                                        missionItems->count());
469 470 471 472 473 474
      SimpleMissionItem *landItem = qobject_cast<SimpleMissionItem *>(
          missionItems->get(missionItems->count() - 1));
      if (landItem == nullptr) {
        qCWarning(WimaPlanerLog) << "update(): landItem == nullptr";
        return;
      }
475
      if (!_missionController->setLandCommand(*landItem))
476 477 478
        return;

      this->_stateMachine->updateState(EVENT::PATH_UPDATED);
479
    }
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
  } break; // STATE::NEEDS_PATH_UPDATE

  case STATE::UP_TO_DATE: {
  } break; // STATE::UP_TO_DATE

  } // switch
}

void WimaPlaner::CSDestroyedHandler() {
  this->_stateMachine->updateState(EVENT::SURVEY_DESTROYED);
}

void WimaPlaner::CSMissionItemReadyHandler() {
  this->_stateMachine->updateState(EVENT::SURVEY_UPDATED);
  this->_update();
}

void WimaPlaner::CSCalculatingChangedHandler() {
  if (this->_survey->calculating()) {
    this->_stateMachine->updateState(EVENT::SURVEY_UPDATE_TRIGGERED);
500
  }
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
}

void WimaPlaner::mAreaPathChangedHandler() {
  this->_stateMachine->updateState(EVENT::M_AREA_PATH_CHANGED);
}

void WimaPlaner::mAreaTilesChangedHandler() {
  this->_nemoInterface.setTileData(this->_measurementArea.tileData());
  this->_stateMachine->updateState(EVENT::M_AREA_TILES_CHANGED);
}

void WimaPlaner::mAreaProgressChangedHandler() {
  this->_stateMachine->updateState(EVENT::M_AREA_PROGRESS_CHANGED);
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
516 517
void WimaPlaner::mAreaProgressAcceptedHandler() { this->_update(); }

518 519 520 521 522 523 524 525
void WimaPlaner::mAreaReadyChangedHandler() {
  if (this->_measurementArea.ready()) {
    this->_stateMachine->updateState(EVENT::M_AREA_READY);
  } else {
    this->_stateMachine->updateState(EVENT::M_AREA_NOT_READY);
  }
}

526 527 528 529 530 531 532
void WimaPlaner::sAreaPathChangedHandler() {
  this->_stateMachine->updateState(EVENT::S_AREA_PATH_CHANGED);
}

void WimaPlaner::corridorPathChangedHandler() {
  this->_stateMachine->updateState(EVENT::CORRIDOR_PATH_CHANGED);
}
533

534 535
void WimaPlaner::depotChangedHandler() {
  this->_stateMachine->updateState(EVENT::DEPOT_CHANGED);
536
}
537

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
void WimaPlaner::missionControllerVisualItemsChangedHandler() {
  // Search for survey.
  auto surveyIndex = _missionController->visualItems()->indexOf(_survey);
  if (surveyIndex < 0) {
    // survey not found.
    this->_stateMachine->updateState(EVENT::SURVEY_DESTROYED);
  } else {
    this->_stateMachine->updateState(EVENT::PATH_CHANGED);
  }
}

void WimaPlaner::missionControllerWaypointPathChangedHandler() {
  missionControllerVisualItemsChangedHandler();
}

void WimaPlaner::missionControllerNewItemsFromVehicleHandler() {
  this->_stateMachine->updateState(EVENT::MISSION_ITEMS_DESTROYED);
}

void WimaPlaner::missionControllerMissionItemCountChangedHandler() {
  missionControllerVisualItemsChangedHandler();
}

561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
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;
586
    } else {
587 588 589 590 591 592 593
      if (planFilename.contains(".")) {
        qgcApp()->showMessage(tr("File format not supported"));
      } else {
        qgcApp()->showMessage(tr("File without file extension not accepted."));
        return;
      }
    }
594

595 596 597 598 599
    QJsonDocument saveDoc = saveToJson(fileType);
    file.write(saveDoc.toJson());
    if (_currentFile != planFilename) {
      _currentFile = planFilename;
      emit currentFileChanged();
600
    }
601
  }
602 603
}

604
bool WimaPlaner::loadFromCurrent() { return loadFromFile(_currentFile); }
605

606
bool WimaPlaner::loadFromFile(const QString &filename) {
607
  // Remove obsolete connections.
608 609 610 611 612 613 614
  disableAreaMonitoring();
  disableMissionControllerMonitoring();
  CommandRAII onExit([this] {
    this->enableAreaMonitoring();
    this->enableMissionControllerMonitoring();
  });

615 616 617 618 619 620 621 622 623 624 625 626 627
  // disconnect old survey
  if (_survey != nullptr) {
    disconnect(_survey, &CircularSurvey::calculatingChanged, this,
               &WimaPlaner::CSCalculatingChangedHandler);
    disconnect(_survey, &CircularSurvey::missionItemReady, this,
               &WimaPlaner::CSMissionItemReadyHandler);
    disconnect(_survey, &CircularSurvey::destroyed, this,
               &WimaPlaner::CSDestroyedHandler);
  }

  setSynchronized(false);

  // Precondition.
628 629 630
  QString errorString;
  QString errorMessage =
      tr("Error loading Plan file (%1). %2").arg(filename).arg("%1");
631

632 633 634
  if (filename.isEmpty()) {
    return false;
  }
635

636 637
  QFileInfo fileInfo(filename);
  QFile file(filename);
638

639 640 641 642 643
  if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    errorString = file.errorString() + QStringLiteral(" ") + filename;
    qgcApp()->showMessage(errorMessage.arg(errorString));
    return false;
  }
644

645 646 647
  if (fileInfo.suffix() == wimaFileExtension) {
    QJsonDocument jsonDoc;
    QByteArray bytes = file.readAll();
648

649 650 651 652
    if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorString)) {
      qgcApp()->showMessage(errorMessage.arg(errorString));
      return false;
    }
653

654 655 656 657
    QJsonObject json = jsonDoc.object();
    // AreaItems
    QJsonArray areaArray = json[areaItemsName].toArray();
    _visualItems.clear();
658

659 660 661
    int validAreaCounter = 0;
    for (int i = 0; i < areaArray.size() && validAreaCounter < 3; i++) {
      QJsonObject jsonArea = areaArray[i].toObject();
662

663 664 665 666 667
      if (jsonArea.contains(WimaArea::areaTypeName) &&
          jsonArea[WimaArea::areaTypeName].isString()) {
        if (jsonArea[WimaArea::areaTypeName] ==
            WimaMeasurementArea::WimaMeasurementAreaName) {
          bool success = _measurementArea.loadFromJson(jsonArea, errorString);
668

669 670
          if (!success) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
671
            return false;
672 673 674 675 676 677 678 679 680 681 682
          }

          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));
683
            return false;
684 685 686 687 688 689 690 691 692 693 694 695 696
          }

          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;
          }
697

698 699 700 701 702 703 704 705
          validAreaCounter++;
          _visualItems.append(&_corridor);
          emit visualItemsChanged();
        } else {
          errorString +=
              QString(tr("%s not supported.\n").arg(WimaArea::areaTypeName));
          qgcApp()->showMessage(errorMessage.arg(errorString));
          return false;
706
        }
707 708 709
      } else {
        errorString += QString(tr("Invalid or non existing entry for %s.\n")
                                   .arg(WimaArea::areaTypeName));
710
        return false;
711
      }
712 713
    }

714 715 716
    _currentFile.sprintf("%s/%s.%s", fileInfo.path().toLocal8Bit().data(),
                         fileInfo.completeBaseName().toLocal8Bit().data(),
                         wimaFileExtension);
717

718
    emit currentFileChanged();
719

720
    QJsonObject missionObject = json[missionItemsName].toObject();
721

722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
    QJsonDocument missionJsonDoc = QJsonDocument(missionObject);
    // create temporary file with missionItems
    QFile temporaryFile;
    QString cropedFileName = filename.section("/", 0, -2);
    QString temporaryFileName;
    for (int i = 0;; i++) {
      temporaryFileName =
          cropedFileName +
          QString("/temp%1.%2").arg(i).arg(AppSettings::planFileExtension);

      if (!QFile::exists(temporaryFileName)) {
        temporaryFile.setFileName(temporaryFileName);
        if (temporaryFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
          break;
        }
      }
738

739
      if (i > 1000) {
740 741
        qCWarning(WimaPlanerLog)
            << "loadFromFile(): not able to create temporary file.";
742
        return false;
743
      }
744 745
    }

746 747
    temporaryFile.write(missionJsonDoc.toJson());
    temporaryFile.close();
748

749 750 751
    // load from temporary file
    _masterController->loadFromFile(temporaryFileName);
    QmlObjectListModel *missionItems = _missionController->visualItems();
752
    _survey = nullptr;
753
    for (int i = 0; i < missionItems->count(); i++) {
754 755 756 757 758 759 760 761 762
      _survey = missionItems->value<CircularSurvey *>(i);
      if (_survey != nullptr) {
        _survey->setHidePolygon(true);
        connect(_survey, &CircularSurvey::calculatingChanged, this,
                &WimaPlaner::CSCalculatingChangedHandler);
        connect(_survey, &CircularSurvey::missionItemReady, this,
                &WimaPlaner::CSMissionItemReadyHandler);
        connect(_survey, &CircularSurvey::destroyed, this,
                &WimaPlaner::CSDestroyedHandler);
763 764
        break;
      }
765 766
    }

767 768
    // remove temporary file
    if (!temporaryFile.remove()) {
769 770 771
      qCWarning(WimaPlanerLog)
          << "WimaPlaner::loadFromFile(): not able to remove "
             "temporary file.";
772 773
    }
    return true;
774 775 776 777 778 779 780
  } else {
    errorString += QString(tr("File extension not supported.\n"));
    qgcApp()->showMessage(errorMessage.arg(errorString));
    return false;
  }
}

781
void WimaPlaner::updatePolygonInteractivity(int index) {
782 783 784 785 786 787 788 789 790
  if (index >= 0 && index < _visualItems.count()) {
    resetAllInteractive();
    WimaArea *interactivePoly =
        qobject_cast<WimaArea *>(_visualItems.get(index));
    if (interactivePoly != nullptr)
      interactivePoly->setWimaAreaInteractive(true);
  }
}

791
void WimaPlaner::synchronize() {
792
  if (_wimaBridge != nullptr) {
793
    if (readyForSynchronization()) {
794
      auto planData = toPlanData();
795 796
      if (planData) {
        (void)_wimaBridge->setWimaPlanData(planData);
797
        setSynchronized(true);
798
      } else {
799
        qCWarning(WimaPlanerLog) << "error creating plan data.";
800
      }
801
    }
802
  } else {
803
    qCWarning(WimaPlanerLog) << "no container assigned.";
804
  }
805 806
}

807 808 809
bool WimaPlaner::shortestPath(const QGeoCoordinate &start,
                              const QGeoCoordinate &destination,
                              QVector<QGeoCoordinate> &path) {
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
  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;
}

825 826 827 828
void WimaPlaner::setSynchronized(bool s) {
  if (this->_synchronized != s) {
    this->_synchronized = s;
    emit this->synchronizedChanged();
829 830 831
  }
}

832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
void WimaPlaner::enableAreaMonitoring() {
  if (!areasMonitored()) {
    connect(&this->_measurementArea, &WimaArea::pathChanged, this,
            &WimaPlaner::mAreaPathChangedHandler);
    connect(&this->_measurementArea, &WimaMeasurementArea::tilesChanged, this,
            &WimaPlaner::mAreaTilesChangedHandler);
    connect(&this->_measurementArea, &WimaMeasurementArea::progressChanged,
            this, &WimaPlaner::mAreaProgressChangedHandler);
    connect(&this->_measurementArea, &WimaMeasurementArea::progressAccepted,
            this, &WimaPlaner::mAreaProgressAcceptedHandler);
    connect(&this->_measurementArea, &WimaMeasurementArea::readyChanged, this,
            &WimaPlaner::mAreaReadyChangedHandler);
    connect(&this->_serviceArea, &WimaArea::pathChanged, this,
            &WimaPlaner::sAreaPathChangedHandler);
    connect(&this->_serviceArea, &WimaServiceArea::depotChanged, this,
            &WimaPlaner::depotChangedHandler);
    connect(&this->_corridor, &WimaArea::pathChanged, this,
            &WimaPlaner::corridorPathChangedHandler);
    this->_areasMonitored = true;
  }
}

void WimaPlaner::disableAreaMonitoring() {
  if (areasMonitored()) {
    disconnect(&this->_measurementArea, &WimaArea::pathChanged, this,
               &WimaPlaner::mAreaPathChangedHandler);
    disconnect(&this->_measurementArea, &WimaMeasurementArea::tilesChanged,
               this, &WimaPlaner::mAreaTilesChangedHandler);
    disconnect(&this->_measurementArea, &WimaMeasurementArea::progressChanged,
               this, &WimaPlaner::mAreaProgressChangedHandler);
    disconnect(&this->_measurementArea, &WimaMeasurementArea::progressAccepted,
               this, &WimaPlaner::mAreaProgressAcceptedHandler);
    disconnect(&this->_measurementArea, &WimaMeasurementArea::readyChanged,
               this, &WimaPlaner::mAreaReadyChangedHandler);
    disconnect(&this->_serviceArea, &WimaArea::pathChanged, this,
               &WimaPlaner::sAreaPathChangedHandler);
    disconnect(&this->_serviceArea, &WimaServiceArea::depotChanged, this,
               &WimaPlaner::depotChangedHandler);
    disconnect(&this->_corridor, &WimaArea::pathChanged, this,
               &WimaPlaner::corridorPathChangedHandler);
    this->_areasMonitored = false;
  }
}

void WimaPlaner::enableMissionControllerMonitoring() {
  if (!missionControllerMonitored() && this->missionController() != nullptr) {
    connect(this->missionController(), &MissionController::visualItemsChanged,
            this, &WimaPlaner::missionControllerVisualItemsChangedHandler);
    connect(this->missionController(), &MissionController::waypointPathChanged,
            this, &WimaPlaner::missionControllerWaypointPathChangedHandler);
    connect(this->missionController(), &MissionController::newItemsFromVehicle,
            this, &WimaPlaner::missionControllerNewItemsFromVehicleHandler);
    connect(this->missionController(),
            &MissionController::missionItemCountChanged, this,
            &WimaPlaner::missionControllerMissionItemCountChangedHandler);
    this->_missionControllerMonitored = true;
  }
}

void WimaPlaner::disableMissionControllerMonitoring() {
  if (missionControllerMonitored() && this->missionController() != nullptr) {
    disconnect(this->missionController(),
               &MissionController::visualItemsChanged, this,
               &WimaPlaner::missionControllerVisualItemsChangedHandler);
    disconnect(this->missionController(),
               &MissionController::waypointPathChanged, this,
               &WimaPlaner::missionControllerWaypointPathChangedHandler);
    disconnect(this->missionController(),
               &MissionController::newItemsFromVehicle, this,
               &WimaPlaner::missionControllerNewItemsFromVehicleHandler);
    disconnect(this->missionController(),
               &MissionController::missionItemCountChanged, this,
               &WimaPlaner::missionControllerMissionItemCountChangedHandler);
    this->_missionControllerMonitored = false;
  }
}

bool WimaPlaner::areasMonitored() { return this->_areasMonitored; }

bool WimaPlaner::missionControllerMonitored() {
  return this->_missionControllerMonitored;
913 914
}

915 916 917 918 919 920 921
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);
922
    }
923
  }
924 925
}

926
void WimaPlaner::setInteractive() {
927
  updatePolygonInteractivity(_currentAreaIndex);
928 929
}

930 931 932
/*!
 * \fn WimaPlanData WimaPlaner::toPlanData()
 *
933
 * Returns a \c WimaPlanData object containing information about the current
934 935
 * mission. The \c WimaPlanData object holds only the data which is relevant
 * for the \c WimaController class. Should only be called if update() was
936
 * successful.
937 938 939
 *
 * \sa WimaController, WimaPlanData
 */
940 941
QSharedPointer<WimaPlanData> WimaPlaner::toPlanData() {
  QSharedPointer<WimaPlanData> planData(new WimaPlanData());
942 943

  // store areas
944 945 946 947
  planData->append(WimaMeasurementAreaData(_measurementArea));
  planData->append(WimaServiceAreaData(_serviceArea));
  planData->append(WimaCorridorData(_corridor));
  planData->append(WimaJoinedAreaData(_joinedArea));
948 949

  // convert mission items to mavlink commands
950
  if (_missionController && _missionController->visualItems()) {
951
    int surveyIndex = _missionController->visualItems()->indexOf(_survey);
952
    if (surveyIndex > 0) {
953
      QList<MissionItem *> missionItems;
954
      _survey->appendMissionItems(missionItems, nullptr);
955
      planData->append(missionItems);
956
      planData->setTransects(this->_survey->rawTransects());
957
      return planData;
958
    }
959
  }
960
  return QSharedPointer<WimaPlanData>();
961 962 963 964 965 966
}

#ifndef NDEBUG
void WimaPlaner::autoLoadMission() {
  loadFromFile("/home/valentin/Desktop/drones/qgroundcontrol/Paths/"
               "KlingenbachTest.wima");
967
  synchronize();
968
}
969
#endif
970

971 972 973 974 975 976
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;
977

978 979
  if (fileType == FileType::WimaFile) {
    QJsonArray jsonArray;
980

981 982
    for (int i = 0; i < _visualItems.count(); i++) {
      QJsonObject json;
983

984
      WimaArea *area = qobject_cast<WimaArea *>(_visualItems.get(i));
985

986
      if (area == nullptr) {
987
        qCWarning(WimaPlanerLog) << "saveing, area == nullptr!";
988 989
        return QJsonDocument();
      }
990

991 992 993 994 995 996 997 998
      // 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;
      }
999

1000 1001 1002 1003 1004 1005
      WimaServiceArea *serArea = qobject_cast<WimaServiceArea *>(area);
      if (serArea != nullptr) {
        serArea->saveToJson(json);
        jsonArray.append(json);
        continue;
      }
1006

1007 1008 1009 1010 1011 1012
      WimaCorridor *corridor = qobject_cast<WimaCorridor *>(area);
      if (corridor != nullptr) {
        corridor->saveToJson(json);
        jsonArray.append(json);
        continue;
      }
1013

1014 1015 1016
      // if non of the obove branches was trigger, type must be WimaArea
      area->saveToJson(json);
      jsonArray.append(json);
1017 1018
    }

1019 1020
    json[areaItemsName] = jsonArray;
    json[missionItemsName] = _masterController->saveToJson().object();
1021

1022 1023 1024 1025
    return QJsonDocument(json);
  } else if (fileType == FileType::PlanFile) {
    return _masterController->saveToJson();
  }
1026

1027 1028
  return QJsonDocument(json);
}