Skip to content
Snippets Groups Projects
CircularSurvey.cc 40.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • #include "CircularSurvey.h"
    
    #include "RoutingThread.h"
    
    #include "JsonHelper.h"
    #include "QGCApplication.h"
    
    #include "QGCLoggingCategory.h"
    
    #define CLIPPER_SCALE 1000000
    
    #include "clipper/clipper.hpp"
    
    #include "Geometry/GenericCircle.h"
    
    #include "Snake/SnakeTile.h"
    
    
    #include <boost/units/io.hpp>
    #include <boost/units/systems/si.hpp>
    
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    #include "CircularGenerator.h"
    #include "LinearGenerator.h"
    
    
    QGC_LOGGING_CATEGORY(CircularSurveyLog, "CircularSurveyLog")
    
    
    template <typename T>
    constexpr typename std::underlying_type<T>::type integral(T value) {
      return static_cast<typename std::underlying_type<T>::type>(value);
    }
    
    
    const char *CircularSurvey::settingsGroup = "CircularSurvey";
    
    const char *CircularSurvey::typeName = "Type";
    
    const char *CircularSurvey::CircularSurveyName = "CircularSurvey";
    
    const char *CircularSurvey::variantName = "Variant";
    
    
    CircularSurvey::CircularSurvey(Vehicle *vehicle, bool flyView,
                                   const QString &kmlOrShpFile, QObject *parent)
        : TransectStyleComplexItem(vehicle, flyView, settingsGroup, parent),
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          _state(STATE::IDLE),
    
          _metaDataMap(FactMetaData::createMapFromJsonFile(
              QStringLiteral(":/json/CircularSurvey.SettingsGroup.json"), this)),
    
          _type(settingsGroup, _metaDataMap[typeName]),
    
          _variant(settingsGroup, _metaDataMap[variantName]),
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          _areaData(std::make_shared<WimaPlanData>()),
          _pWorker(std::make_unique<RoutingThread>()) {
    
    
      Q_UNUSED(kmlOrShpFile)
      _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml";
    
    
      connect(&this->_type, &Fact::rawValueChanged, this,
              &CircularSurvey::_rebuildTransects);
    
      connect(&this->_variant, &Fact::rawValueChanged, this,
              &CircularSurvey::_changeVariant);
    
      // Connect worker.
    
      connect(this->_pWorker.get(), &RoutingThread::result, this,
    
              &CircularSurvey::_setTransects);
    
      connect(this->_pWorker.get(), &RoutingThread::calculatingChanged, this,
    
              &CircularSurvey::calculatingChanged);
    
      this->_transectsDirty = true;
    
    
      // Altitude
      connect(&_cameraCalc, &CameraCalc::distanceToSurfaceRelativeChanged, this,
              &CircularSurvey::coordinateHasRelativeAltitudeChanged);
      connect(&_cameraCalc, &CameraCalc::distanceToSurfaceRelativeChanged, this,
              &CircularSurvey::exitCoordinateHasRelativeAltitudeChanged);
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    
      // Register Generators.
      auto cg = std::make_shared<routing::CircularGenerator>(this->_areaData);
      registerGenerator(cg->name(), cg);
      auto lg = std::make_shared<routing::LinearGenerator>(this->_areaData);
      registerGenerator(lg->name(), lg);
    
    CircularSurvey::~CircularSurvey() {}
    
    void CircularSurvey::reverse() {
    
      this->_state = STATE::REVERSE;
    
      this->_rebuildTransects();
    }
    
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    void CircularSurvey::setPlanData(const WimaPlanData &d) {
      *this->_areaData = d;
    
    QList<QString> CircularSurvey::variantNames() const { return _variantNames; }
    
    
    bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber,
                              QString &errorString) {
    
      // We need to pull version first to determine what validation/conversion
      // needs to be performed
    
      QList<JsonHelper::KeyValidateInfo> versionKeyInfoList = {
          {JsonHelper::jsonVersionKey, QJsonValue::Double, true},
      };
      if (!JsonHelper::validateKeys(complexObject, versionKeyInfoList,
                                    errorString)) {
        return false;
      }
    
      int version = complexObject[JsonHelper::jsonVersionKey].toInt();
      if (version != 1) {
        errorString = tr("Survey items do not support version %1").arg(version);
        return false;
      }
    
      QList<JsonHelper::KeyValidateInfo> keyInfoList = {
          {VisualMissionItem::jsonTypeKey, QJsonValue::String, true},
          {ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true},
    
          {transectDistanceName, QJsonValue::Double, true},
          {alphaName, QJsonValue::Double, true},
          {minLengthName, QJsonValue::Double, true},
          {typeName, QJsonValue::Double, true},
    
          {variantName, QJsonValue::Double, false},
    
          {numRunsName, QJsonValue::Double, false},
          {runName, QJsonValue::Double, false},
    
          {refPointLatitudeName, QJsonValue::Double, true},
          {refPointLongitudeName, QJsonValue::Double, true},
          {refPointAltitudeName, QJsonValue::Double, true},
      };
    
      if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) {
        return false;
      }
    
      QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString();
      QString complexType =
          complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString();
      if (itemType != VisualMissionItem::jsonTypeComplexItemValue ||
          complexType != CircularSurveyName) {
    
        errorString = tr("%1 does not support loading this complex mission item "
                         "type: %2:%3")
                          .arg(qgcApp()->applicationName())
                          .arg(itemType)
                          .arg(complexType);
    
        return false;
      }
    
      _ignoreRecalc = true;
    
      setSequenceNumber(sequenceNumber);
    
      if (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */,
                                           errorString)) {
        _surveyAreaPolygon.clear();
        return false;
      }
    
      if (!_load(complexObject, errorString)) {
        _ignoreRecalc = false;
        return false;
      }
    
    
      _type.setRawValue(complexObject[typeName].toInt());
    
      _variant.setRawValue(complexObject[variantName].toInt());
    
      _numRuns.setRawValue(complexObject[numRunsName].toInt());
      _run.setRawValue(complexObject[runName].toInt());
    
      _referencePoint.setLongitude(complexObject[refPointLongitudeName].toDouble());
      _referencePoint.setLatitude(complexObject[refPointLatitudeName].toDouble());
      _referencePoint.setAltitude(complexObject[refPointAltitudeName].toDouble());
    
      _ignoreRecalc = false;
    
      _recalcComplexDistance();
      if (_cameraShots == 0) {
        // Shot count was possibly not available from plan file
        _recalcCameraShots();
      }
    
      return true;
    }
    
    QString CircularSurvey::mapVisualQML() const {
      return QStringLiteral("CircularSurveyMapVisual.qml");
    }
    
    void CircularSurvey::save(QJsonArray &planItems) {
      QJsonObject saveObject;
    
      _save(saveObject);
    
      saveObject[JsonHelper::jsonVersionKey] = 1;
      saveObject[VisualMissionItem::jsonTypeKey] =
          VisualMissionItem::jsonTypeComplexItemValue;
      saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = CircularSurveyName;
    
    
      saveObject[typeName] = double(_type.rawValue().toUInt());
    
      saveObject[variantName] = double(_variant.rawValue().toUInt());
    
      saveObject[numRunsName] = double(_numRuns.rawValue().toUInt());
      saveObject[runName] = double(_numRuns.rawValue().toUInt());
    
      saveObject[refPointLongitudeName] = _referencePoint.longitude();
      saveObject[refPointLatitudeName] = _referencePoint.latitude();
      saveObject[refPointAltitudeName] = _referencePoint.altitude();
    
      // Polygon shape
      _surveyAreaPolygon.saveToJson(saveObject);
    
      planItems.append(saveObject);
    }
    
    bool CircularSurvey::specifiesCoordinate() const { return true; }
    
    void CircularSurvey::appendMissionItems(QList<MissionItem *> &items,
                                            QObject *missionItemParent) {
      if (_transectsDirty)
        return;
      if (_loadedMissionItems.count()) {
        // We have mission items from the loaded plan, use those
        _appendLoadedMissionItems(items, missionItemParent);
      } else {
        // Build the mission items on the fly
        _buildAndAppendMissionItems(items, missionItemParent);
      }
    }
    
    void CircularSurvey::_appendLoadedMissionItems(QList<MissionItem *> &items,
                                                   QObject *missionItemParent) {
      if (_transectsDirty)
        return;
      int seqNum = _sequenceNumber;
    
      for (const MissionItem *loadedMissionItem : _loadedMissionItems) {
        MissionItem *item = new MissionItem(*loadedMissionItem, missionItemParent);
        item->setSequenceNumber(seqNum++);
        items.append(item);
      }
    }
    
    void CircularSurvey::_buildAndAppendMissionItems(QList<MissionItem *> &items,
                                                     QObject *missionItemParent) {
    
      if (_transectsDirty || _transects.count() == 0)
    
        return;
    
      MissionItem *item;
      int seqNum = _sequenceNumber;
    
      MAV_FRAME mavFrame =
          followTerrain() || !_cameraCalc.distanceToSurfaceRelative()
              ? MAV_FRAME_GLOBAL
              : MAV_FRAME_GLOBAL_RELATIVE_ALT;
    
      for (const QList<TransectStyleComplexItem::CoordInfo_t> &transect :
           _transects) {
        // bool transectEntry = true;
    
        for (const CoordInfo_t &transectCoordInfo : transect) {
          item = new MissionItem(
              seqNum++, MAV_CMD_NAV_WAYPOINT, mavFrame,
    
              0,   // Hold time (delay for hover and capture to settle vehicle
                   // before image is taken)
    
              0.0, // No acceptance radius specified
              0.0, // Pass through waypoint
              std::numeric_limits<double>::quiet_NaN(), // Yaw unchanged
              transectCoordInfo.coord.latitude(),
              transectCoordInfo.coord.longitude(),
              transectCoordInfo.coord.altitude(),
              true,  // autoContinue
              false, // isCurrentItem
              missionItemParent);
          items.append(item);
        }
      }
    }
    
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    bool CircularSurvey::_switchToGenerator(
        const CircularSurvey::PtrGenerator &newG) {
      if (this->_pGenerator != newG) {
        if (this->_pGenerator != nullptr) {
          disconnect(this->_pGenerator.get(), &GeneratorBase::generatorChanged,
                     this, &CircularSurvey::_rebuildTransects);
        }
    
        this->_pGenerator = newG;
        connect(this->_pGenerator.get(), &GeneratorBase::generatorChanged, this,
                &CircularSurvey::_rebuildTransects);
    
        this->_state = STATE::IDLE;
        _rebuildTransects();
    
        return true;
      } else {
        return false;
      }
    }
    
    
    void CircularSurvey::_changeVariant() {
      this->_state = STATE::VARIANT_CHANGE;
      this->_rebuildTransects();
    
    void CircularSurvey::_changeRun() {
      this->_state = STATE::RUN_CHANGE;
      this->_rebuildTransects();
    }
    
    
    void CircularSurvey::_updateWorker() {
    
      // Mark transects as dirty.
      this->_transectsDirty = true;
    
      // Reset data.
      this->_transects.clear();
    
      this->_variantVector.clear();
    
      this->_variantNames.clear();
      emit variantNamesChanged();
    
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
      if (this->_areaData->isValid()) {
    
        // Prepare data.
        auto origin = this->_areaData->origin();
        // Convert safe area.
        auto geoSafeArea = this->_jArea.coordinateList();
        if (!(geoSafeArea.size() >= 3)) {
          qCWarning(CircularSurveyLog)
              << "_updateWorker(): safe area invalid." << geoSafeArea;
          return;
        }
        for (auto &v : geoSafeArea) {
          if (v.isValid()) {
            v.setAltitude(0);
          } else {
            qCWarning(CircularSurveyLog)
                << "_updateWorker(): safe area contains invalid coordinate."
                << geoSafeArea;
            return;
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        // Routing par.
        RoutingParameter par;
        par.numSolutions = 5;
        auto &safeAreaENU = par.safeArea;
        snake::areaToEnu(origin, geoSafeArea, safeAreaENU);
    
        // Create generator.
        GeneratorBase::Generator g; // Transect generator.
        if (_pGenerator->get(g)) {
          // Start/Restart routing worker.
          this->_pWorker->route(par, g);
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          qCWarning(CircularSurveyLog)
              << "_updateWorker(): generator creation failed.";
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        qCWarning(CircularSurveyLog) << "_updateWorker(): plan data invalid.";
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    void CircularSurvey::_changeVariantWorker() {
    
      auto variant = this->_variant.rawValue().toUInt();
    
      auto run = this->_run.rawValue().toUInt();
    
      // Find old variant and run. Old run corresponts with empty list.
      std::size_t old_variant = std::numeric_limits<std::size_t>::max();
      std::size_t old_run = std::numeric_limits<std::size_t>::max();
      for (std::size_t i = 0; i < std::size_t(this->_variantVector.size()); ++i) {
        const auto &solution = this->_variantVector.at(i);
        for (std::size_t j = 0; j < std::size_t(solution.size()); ++j) {
          const auto &r = solution[j];
          if (r.isEmpty()) {
            old_variant = i;
            old_run = j;
            // break
            i = std::numeric_limits<std::size_t>::max() - 1;
            j = std::numeric_limits<std::size_t>::max() - 1;
          }
    
      // Swap route.
      if (variant != old_variant || run != old_run) {
        // Swap in new variant, if condition.
        if (variant < std::size_t(this->_variantVector.size()) &&
            run < std::size_t(this->_variantVector.at(variant).size())) {
          if (old_variant != std::numeric_limits<std::size_t>::max()) {
            // this->_transects containes a route, swap it back to
            // this->_solutionVector
            auto &old_solution = this->_variantVector[old_variant];
            auto &old_route = old_solution[old_run];
            old_route.swap(this->_transects);
          }
          auto &solution = this->_variantVector[variant];
          auto &route = solution[run];
          this->_transects.swap(route);
    
          if (variant != old_variant) {
            // Add run names.
            this->_runNames.clear();
            for (std::size_t i = 1; i <= std::size_t(solution.size()); ++i) {
              this->_runNames.append(QString::number(i));
            }
            emit runNamesChanged();
          }
    
        } else { // error
    
          qCWarning(CircularSurveyLog)
              << "Variant or run out of bounds (variant = " << variant
              << ", run = " << run << ").";
          qCWarning(CircularSurveyLog) << "Resetting variant and run.";
    
    
          disconnect(&this->_variant, &Fact::rawValueChanged, this,
                     &CircularSurvey::_changeVariant);
          disconnect(&this->_run, &Fact::rawValueChanged, this,
                     &CircularSurvey::_changeRun);
          if (old_variant < std::size_t(this->_variantVector.size())) {
            this->_variant.setCookedValue(QVariant::fromValue(old_variant));
            auto &solution = this->_variantVector[old_variant];
            if (old_run < std::size_t(solution.size())) {
              this->_run.setCookedValue(QVariant::fromValue(old_run));
            } else {
              this->_run.setCookedValue(QVariant(0));
            }
          } else {
            this->_variant.setCookedValue(QVariant(0));
            this->_run.setCookedValue(QVariant(0));
          }
          connect(&this->_variant, &Fact::rawValueChanged, this,
                  &CircularSurvey::_changeVariant);
          connect(&this->_run, &Fact::rawValueChanged, this,
                  &CircularSurvey::_changeRun);
          if (this->_variantVector.size() > 0 &&
              this->_variantVector.front().size() > 0) {
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
            this->_changeVariantWorker();
    
    void CircularSurvey::_reverseWorker() {
      if (this->_transects.size() > 0) {
        auto &t = this->_transects.front();
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        std::reverse(t.begin(), t.end());
    
    void CircularSurvey::_storeWorker() {
      // If the transects are getting rebuilt then any previously loaded
      // mission items are now invalid.
      if (_loadedMissionItemsParent) {
        _loadedMissionItems.clear();
        _loadedMissionItemsParent->deleteLater();
        _loadedMissionItemsParent = nullptr;
      }
    
      // Store raw transects.
    
      const auto &pRoutingData = this->_pRoutingData;
    
      const auto &ori = this->_referencePoint;
      const auto &transectsENU = pRoutingData->transects;
      QList<QList<QGeoCoordinate>> rawTransects;
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
      for (std::size_t i = 1; i < transectsENU.size(); ++i) {
    
        const auto &t = transectsENU[i];
        rawTransects.append(QList<QGeoCoordinate>());
        auto trGeo = rawTransects.back();
        for (auto &v : t) {
          QGeoCoordinate c;
          snake::fromENU(ori, v, c);
          trGeo.append(c);
    
      // Store solutions.
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
      QVector<Variant> solutionVector;
    
      const auto nSolutions = pRoutingData->solutionVector.size();
      for (std::size_t j = 0; j < nSolutions; ++j) {
        const auto &solution = pRoutingData->solutionVector.at(j);
        const auto nRuns = solution.size();
        // Store runs.
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        Variant runs(nRuns, Transects{QList<CoordInfo_t>()});
    
        for (std::size_t k = 0; k < nRuns; ++k) {
          const auto &route = solution.at(k);
          const auto &path = route.path;
          const auto &info = route.info;
          if (info.size() > 1) {
            // Find index of first waypoint.
            std::size_t idxFirst = 0;
    
            const auto &infoFirst = info.at(1);
    
            const auto &firstTransect = transectsENU[infoFirst.index];
            if (firstTransect.size() > 0) {
              const auto &firstWaypoint =
                  infoFirst.reversed ? firstTransect.back() : firstTransect.front();
              double th = 0.01;
              for (std::size_t i = 0; i < path.size(); ++i) {
                auto dist = bg::distance(path[i], firstWaypoint);
    
                if (dist < th) {
    
                  idxFirst = i;
    
              // Find index of last waypoint.
              std::size_t idxLast = path.size() - 1;
              const auto &infoLast = info.at(info.size() - 2);
              const auto &lastTransect = transectsENU[infoLast.index];
              if (lastTransect.size() > 0) {
                const auto &lastWaypoint =
                    infoLast.reversed ? lastTransect.front() : lastTransect.back();
                for (long i = path.size() - 1; i >= 0; --i) {
                  auto dist = bg::distance(path[i], lastWaypoint);
                  if (dist < th) {
                    idxLast = i;
                    break;
                  }
                }
    
                // Convert to geo coordinates.
                auto &list = runs[k].front();
                for (std::size_t i = idxFirst; i <= idxLast; ++i) {
                  auto &vertex = path[i];
                  QGeoCoordinate c;
                  snake::fromENU(ori, vertex, c);
                  list.append(CoordInfo_t{c, CoordTypeInterior});
                }
              } else {
    
                qCWarning(CircularSurveyLog)
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
                    << "_storeWorker(): lastTransect.size() == 0";
    
              qCWarning(CircularSurveyLog)
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
                  << "_storeWorker(): firstTransect.size() == 0";
    
            qCWarning(CircularSurveyLog)
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
                << "_storeWorker(): transectsInfo.size() <= 1";
    
          }
        }
        // Remove empty runs.
        bool error = true;
        for (auto it = runs.begin(); it < runs.end();) {
          if (it->size() > 0 && it->front().size() > 0) {
            error = false;
            ++it;
          } else {
            it = runs.erase(it);
    
        }
        if (!error) {
          solutionVector.push_back(std::move(runs));
    
      // Remove empty solutions.
      std::size_t nSol = 0;
      for (auto it = solutionVector.begin(); it < solutionVector.end();) {
    
        if (it->size() > 0 && it->front().size() > 0) {
          ++it;
    
          it = solutionVector.erase(it);
    
    
      // Assign routes if no error occured.
    
      if (nSol > 0) {
    
        // Swap first route to _transects.
    
        this->_variantVector.swap(solutionVector);
    
    
        // Add route variant names.
    
        this->_variantNames.clear();
        for (std::size_t i = 1; i <= std::size_t(this->_variantVector.size());
             ++i) {
    
          this->_variantNames.append(QString::number(i));
    
        emit variantNamesChanged();
    
        // Swap in rawTransects.
        this->_rawTransects.swap(rawTransects);
    
    
        disconnect(&this->_variant, &Fact::rawValueChanged, this,
                   &CircularSurvey::_changeVariant);
        disconnect(&this->_run, &Fact::rawValueChanged, this,
                   &CircularSurvey::_changeRun);
        this->_variant.setCookedValue(QVariant(0));
        this->_run.setCookedValue(QVariant(0));
        connect(&this->_variant, &Fact::rawValueChanged, this,
                &CircularSurvey::_changeVariant);
        connect(&this->_run, &Fact::rawValueChanged, this,
                &CircularSurvey::_changeRun);
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        this->_changeVariantWorker();
    
        // Mark transect as stored and ready.
        this->_transectsDirty = false;
    
    }
    
    void CircularSurvey::applyNewAltitude(double newAltitude) {
      _cameraCalc.valueSetIsDistance()->setRawValue(true);
      _cameraCalc.distanceToSurface()->setRawValue(newAltitude);
      _cameraCalc.setDistanceToSurfaceRelative(true);
    }
    
    double CircularSurvey::timeBetweenShots() { return 1; }
    
    QString CircularSurvey::commandDescription() const {
      return tr("Circular Survey");
    }
    
    QString CircularSurvey::commandName() const { return tr("Circular Survey"); }
    
    QString CircularSurvey::abbreviation() const { return tr("C.S."); }
    
    bool CircularSurvey::readyForSave() const {
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
      return TransectStyleComplexItem::readyForSave() && !_transectsDirty &&
             this->_state == STATE::IDLE;
    
    }
    
    double CircularSurvey::additionalTimeDelay() const { return 0; }
    
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    bool CircularSurvey::registerGenerator(const QString &name,
                                           std::shared_ptr<GeneratorBase> g) {
      if (name.isEmpty()) {
        qCWarning(CircularSurveyLog) << "registerGenerator(): empty name string.";
        return false;
      }
    
      if (g) {
        qCWarning(CircularSurveyLog) << "registerGenerator(): empty generator.";
        return false;
      }
    
      if (this->_generatorNameList.contains(name)) {
        qCWarning(CircularSurveyLog) << "registerGenerator(): generator "
                                        "already registered.";
        return false;
      } else {
        this->_generatorNameList.push_back(name);
        this->_generatorList.push_back(g);
        if (this->_generatorList.size() == 1) {
          this->_pGenerator = g;
        }
    
        emit generatorNameListChanged();
        return true;
      }
    }
    
    bool CircularSurvey::unregisterGenerator(const QString &name) {
      auto index = this->_generatorNameList.indexOf(name);
      if (index >= 0) {
        // Is this the current generator?
        const auto &g = this->_generatorList.at(index);
        if (g.get() == this->_pGenerator.get()) {
          if (index > 0) {
            _switchToGenerator(this->_generatorList.at(index - 1));
          } else {
            _switchToGenerator(nullptr);
            qCWarning(CircularSurveyLog)
                << "unregisterGenerator(): last generator unregistered.";
          }
        }
    
        this->_generatorNameList.removeAt(index);
        this->_generatorList.removeAt(index);
    
        emit generatorNameListChanged();
        return true;
      } else {
        qCWarning(CircularSurveyLog)
            << "unregisterGenerator(): generator " << name << " not registered.";
        return false;
      }
    }
    
    bool CircularSurvey::unregisterGenerator(int index) {
      if (index > 0 && index < this->_generatorNameList.size()) {
        return unregisterGenerator(this->_generatorNameList.at(index));
      } else {
        qCWarning(CircularSurveyLog) << "unregisterGenerator(): index (" << index
                                     << ") out"
                                        "of bounds ( "
                                     << this->_generatorList.size() << " ).";
        return false;
      }
    }
    
    bool CircularSurvey::switchToGenerator(const QString &name) {
      auto index = this->_generatorNameList.indexOf(name);
      if (index >= 0) {
        _switchToGenerator(this->_generatorList.at(index));
        return true;
      } else {
        qCWarning(CircularSurveyLog)
            << "switchToGenerator(): generator " << name << " not registered.";
        return false;
      }
    }
    
    bool CircularSurvey::switchToGenerator(int index) {
      if (index >= 0) {
        _switchToGenerator(this->_generatorList.at(index));
        return true;
      } else {
        qCWarning(CircularSurveyLog) << "unregisterGenerator(): index (" << index
                                     << ") out"
                                        "of bounds ( "
                                     << this->_generatorNameList.size() << " ).";
        return false;
      }
    }
    
    QList<QString> CircularSurvey::generatorNameList() {
      return this->_generatorNameList;
    }
    
    
    void CircularSurvey::_rebuildTransectsPhase1(void) {
      auto start = std::chrono::high_resolution_clock::now();
    
      switch (this->_state) {
      case STATE::STORE:
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: store.";
    
        this->_storeWorker();
        break;
      case STATE::VARIANT_CHANGE:
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: variant change.";
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        this->_changeVariantWorker();
    
        break;
      case STATE::RUN_CHANGE:
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: run change.";
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        this->_changeVariantWorker();
    
        break;
      case STATE::REVERSE:
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: reverse.";
    
        this->_reverseWorker();
        break;
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
      case STATE::IDLE:
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
        qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: update.";
    
        this->_updateWorker();
        break;
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
      this->_state = STATE::IDLE;
    
      qCWarning(CircularSurveyLog)
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          << "rebuildTransectsPhase1(): "
    
          << std::chrono::duration_cast<std::chrono::milliseconds>(
                 std::chrono::high_resolution_clock::now() - start)
                 .count()
          << " ms";
    
    }
    
    void CircularSurvey::_recalcComplexDistance() {
      _complexDistance = 0;
      if (_transectsDirty)
        return;
      for (int i = 0; i < _visualTransectPoints.count() - 1; i++) {
        _complexDistance +=
            _visualTransectPoints[i].value<QGeoCoordinate>().distanceTo(
                _visualTransectPoints[i + 1].value<QGeoCoordinate>());
      }
      emit complexDistanceChanged();
    }
    
    // no cameraShots in Circular Survey, add if desired
    void CircularSurvey::_recalcCameraShots() { _cameraShots = 0; }
    
    
    void CircularSurvey::_setTransects(CircularSurvey::PtrRoutingData pRoute) {
    
      this->_pRoutingData = pRoute;
    
      this->_state = STATE::STORE;
    
      this->_rebuildTransects();
    
    Fact *CircularSurvey::type() { return &_type; }
    
    
    Fact *CircularSurvey::variant() { return &_variant; }
    
    
    Fact *CircularSurvey::numRuns() { return &_numRuns; }
    
    Fact *CircularSurvey::run() { return &_run; }
    
    
    int CircularSurvey::typeCount() const { return int(integral(Type::Count)); }
    
    bool CircularSurvey::calculating() const {
      return this->_pWorker->calculating();
    }
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    bool circularTransects(const snake::FPolygon &polygon,
                           const std::vector<snake::FPolygon> &tiles,
                           snake::Length deltaR, snake::Angle deltaAlpha,
                           snake::Length minLength, snake::Transects &transects) {
    
      auto s1 = std::chrono::high_resolution_clock::now();
    
      // Check preconitions
    
      if (polygon.outer().size() >= 3) {
    
        using namespace boost::units;
        // Convert geo polygon to ENU polygon.
    
        snake::FPoint origin{0, 0};
    
        std::string error;
        // Check validity.
    
        if (!bg::is_valid(polygon, error)) {
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          qCWarning(CircularSurveyLog) << "circularTransects(): "
    
                                          "invalid polygon.";
          qCWarning(CircularSurveyLog) << error.c_str();
    
          std::stringstream ss;
    
          ss << bg::wkt(polygon);
          qCWarning(CircularSurveyLog) << ss.str().c_str();
    
        } else {
          // Calculate polygon distances and angles.
          std::vector<snake::Length> distances;
    
          distances.reserve(polygon.outer().size());
    
          std::vector<snake::Angle> angles;
    
          angles.reserve(polygon.outer().size());
    
          //#ifdef DEBUG_CIRCULAR_SURVEY
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          //      qCWarning(CircularSurveyLog) << "circularTransects():";
    
          for (const auto &p : polygon.outer()) {
            snake::Length distance = bg::distance(origin, p) * si::meter;
    
            distances.push_back(distance);
            snake::Angle alpha = (std::atan2(p.get<1>(), p.get<0>())) * si::radian;
            alpha = alpha < 0 * si::radian ? alpha + 2 * M_PI * si::radian : alpha;
            angles.push_back(alpha);
            //#ifdef DEBUG_CIRCULAR_SURVEY
    
            //        qCWarning(CircularSurveyLog) << "distances, angles,
            //        coordinates:"; qCWarning(CircularSurveyLog) <<
            //        to_string(distance).c_str(); qCWarning(CircularSurveyLog) <<
            //        to_string(snake::Degree(alpha)).c_str();
            //        qCWarning(CircularSurveyLog) << "x = " << p.get<0>() << "y = "
            //        << p.get<1>();
    
            //#endif
          }
    
          auto rMin = deltaR; // minimal circle radius
          snake::Angle alpha1(0 * degree::degree);
          snake::Angle alpha2(360 * degree::degree);
          // Determine r_min by successive approximation
    
          if (!bg::within(origin, polygon.outer())) {
            rMin = bg::distance(origin, polygon) * si::meter;
    
          }
    
          auto rMax = (*std::max_element(distances.begin(),
                                         distances.end())); // maximal circle radius
    
          // Scale parameters and coordinates.
          const auto rMinScaled =
              ClipperLib::cInt(std::round(rMin.value() * CLIPPER_SCALE));
          const auto deltaRScaled =
              ClipperLib::cInt(std::round(deltaR.value() * CLIPPER_SCALE));
    
          auto originScaled =
              ClipperLib::IntPoint{ClipperLib::cInt(std::round(origin.get<0>())),
                                   ClipperLib::cInt(std::round(origin.get<1>()))};
    
    
          // Generate circle sectors.
          auto rScaled = rMinScaled;
          const auto nTran = long(std::ceil(((rMax - rMin) / deltaR).value()));
          vector<ClipperLib::Path> sectors(nTran, ClipperLib::Path());
          const auto nSectors =
              long(std::round(((alpha2 - alpha1) / deltaAlpha).value()));
          //#ifdef DEBUG_CIRCULAR_SURVEY
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          //      qCWarning(CircularSurveyLog) << "circularTransects(): sector
    
          //      parameres:"; qCWarning(CircularSurveyLog) << "alpha1: " <<
          //      to_string(snake::Degree(alpha1)).c_str();
          //      qCWarning(CircularSurveyLog) << "alpha2:
    
          //      << to_string(snake::Degree(alpha2)).c_str();
          //      qCWarning(CircularSurveyLog) << "n: "
          //      << to_string((alpha2 - alpha1) / deltaAlpha).c_str();
          //      qCWarning(CircularSurveyLog)
          //      << "nSectors: " << nSectors; qCWarning(CircularSurveyLog) <<
          //      "rMin: " << to_string(rMin).c_str(); qCWarning(CircularSurveyLog)
          //      << "rMax: " << to_string(rMax).c_str();
          //      qCWarning(CircularSurveyLog) << "nTran: " << nTran;
    
          //#endif
          using ClipperCircle =
              GenericCircle<ClipperLib::cInt, ClipperLib::IntPoint>;
          for (auto &sector : sectors) {
            ClipperCircle circle(rScaled, originScaled);
            approximate(circle, nSectors, sector);
            rScaled += deltaRScaled;
          }
          // Clip sectors to polygonENU.
          ClipperLib::Path polygonClipper;
    
          snake::FPolygon shrinked;
    
          snake::offsetPolygon(polygon, shrinked, -0.3);
    
          auto &outer = shrinked.outer();
    
          polygonClipper.reserve(outer.size());
    
          for (auto it = outer.begin(); it < outer.end() - 1; ++it) {
            auto x = ClipperLib::cInt(std::round(it->get<0>() * CLIPPER_SCALE));
            auto y = ClipperLib::cInt(std::round(it->get<1>() * CLIPPER_SCALE));
            polygonClipper.push_back(ClipperLib::IntPoint{x, y});
          }
          ClipperLib::Clipper clipper;
          clipper.AddPath(polygonClipper, ClipperLib::ptClip, true);
          clipper.AddPaths(sectors, ClipperLib::ptSubject, false);
          ClipperLib::PolyTree transectsClipper;
          clipper.Execute(ClipperLib::ctIntersection, transectsClipper,
                          ClipperLib::pftNonZero, ClipperLib::pftNonZero);
    
    
          // Subtract holes.
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          if (tiles.size() > 0) {
    
            vector<ClipperLib::Path> processedTiles;
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
            for (const auto &tile : tiles) {
    
              ClipperLib::Path path;
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
              for (const auto &v : tile.outer()) {
    
                path.push_back(ClipperLib::IntPoint{
                    static_cast<ClipperLib::cInt>(v.get<0>() * CLIPPER_SCALE),
                    static_cast<ClipperLib::cInt>(v.get<1>() * CLIPPER_SCALE)});
              }
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
              processedTiles.push_back(std::move(path));
    
            }
    
            clipper.Clear();
            for (const auto &child : transectsClipper.Childs) {
              clipper.AddPath(child->Contour, ClipperLib::ptSubject, false);
            }
            clipper.AddPaths(processedTiles, ClipperLib::ptClip, true);
            transectsClipper.Clear();
            clipper.Execute(ClipperLib::ctDifference, transectsClipper,
                            ClipperLib::pftNonZero, ClipperLib::pftNonZero);
          }
    
    
          // Extract transects from  PolyTree and convert them to
          // BoostLineString
          for (const auto &child : transectsClipper.Childs) {
    
            snake::FLineString transect;
    
            transect.reserve(child->Contour.size());
            for (const auto &vertex : child->Contour) {
              auto x = static_cast<double>(vertex.X) / CLIPPER_SCALE;
              auto y = static_cast<double>(vertex.Y) / CLIPPER_SCALE;
    
              transect.push_back(snake::FPoint(x, y));
    
            }
            transects.push_back(transect);
          }
    
          // Join sectors which where slit due to clipping.
          const double th = 0.01;
          for (auto ito = transects.begin(); ito < transects.end(); ++ito) {
            for (auto iti = ito + 1; iti < transects.end(); ++iti) {
              auto dist1 = bg::distance(ito->front(), iti->front());
              if (dist1 < th) {
    
                snake::FLineString temp;
    
                for (auto it = iti->end() - 1; it >= iti->begin(); --it) {
                  temp.push_back(*it);
                }
                temp.insert(temp.end(), ito->begin(), ito->end());
                *ito = temp;
                transects.erase(iti);
                break;
              }
              auto dist2 = bg::distance(ito->front(), iti->back());
              if (dist2 < th) {
    
                snake::FLineString temp;
    
                temp.insert(temp.end(), iti->begin(), iti->end());
                temp.insert(temp.end(), ito->begin(), ito->end());
                *ito = temp;
                transects.erase(iti);
                break;
              }
              auto dist3 = bg::distance(ito->back(), iti->front());
              if (dist3 < th) {
    
                snake::FLineString temp;
    
                temp.insert(temp.end(), ito->begin(), ito->end());
                temp.insert(temp.end(), iti->begin(), iti->end());
                *ito = temp;
                transects.erase(iti);
                break;
              }
              auto dist4 = bg::distance(ito->back(), iti->back());
              if (dist4 < th) {
    
                snake::FLineString temp;
    
                temp.insert(temp.end(), ito->begin(), ito->end());
                for (auto it = iti->end() - 1; it >= iti->begin(); --it) {
                  temp.push_back(*it);
                }
                *ito = temp;
                transects.erase(iti);
                break;
              }
            }
          }
    
          // Remove short transects
    
          for (auto it = transects.begin(); it < transects.end();) {
    
            if (bg::length(*it) < minLength.value()) {
              it = transects.erase(it);
            } else {
              ++it;
            }
          }
    
    
          qCWarning(CircularSurveyLog)
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
              << "circularTransects(): transect gen. time: "
    
              << std::chrono::duration_cast<std::chrono::milliseconds>(
                     std::chrono::high_resolution_clock::now() - s1)
                     .count()
              << " ms";
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
    bool linearTransects(const snake::FPolygon &polygon,
                         const std::vector<snake::FPolygon> &tiles,
                         snake::Length distance, snake::Angle angle,
                         snake::Length minLength, snake::Transects &transects) {
    
      namespace tr = bg::strategy::transform;
      auto s1 = std::chrono::high_resolution_clock::now();
    
      // Check preconitions
    
      if (polygon.outer().size() >= 3) {
    
        // Convert to ENU system.
        std::string error;
        // Check validity.
    
        if (!bg::is_valid(polygon, error)) {
    
          std::stringstream ss;
    
          ss << bg::wkt(polygon);
    
    
    Valentin Platzgummer's avatar
    Valentin Platzgummer committed
          qCWarning(CircularSurveyLog) << "linearTransects(): "