CircularSurvey.cc 43.9 KB
Newer Older
1
#include "CircularSurvey.h"
2
#include "RoutingThread.h"
3
// QGC
4 5
#include "JsonHelper.h"
#include "QGCApplication.h"
6
#include "QGCLoggingCategory.h"
7
// Wima
8
#include "snake.h"
9
#define CLIPPER_SCALE 1000000
10 11
#include "clipper/clipper.hpp"

12 13 14 15
using namespace ClipperLib;
template <> inline auto get<0>(const IntPoint &p) { return p.X; }
template <> inline auto get<1>(const IntPoint &p) { return p.Y; }

16
#include "Geometry/GenericCircle.h"
17 18
#include "Snake/SnakeTile.h"

19
// boost
20 21 22
#include <boost/units/io.hpp>
#include <boost/units/systems/si.hpp>

23 24
QGC_LOGGING_CATEGORY(CircularSurveyLog, "CircularSurveyLog")

25 26 27 28 29
template <typename T>
constexpr typename std::underlying_type<T>::type integral(T value) {
  return static_cast<typename std::underlying_type<T>::type>(value);
}

Valentin Platzgummer's avatar
Valentin Platzgummer committed
30 31 32 33
bool circularTransects(const snake::FPolygon &polygon,
                       const std::vector<snake::FPolygon> &tiles,
                       snake::Length deltaR, snake::Angle deltaAlpha,
                       snake::Length minLength, snake::Transects &transects);
34

Valentin Platzgummer's avatar
Valentin Platzgummer committed
35 36 37 38
bool linearTransects(const snake::FPolygon &polygon,
                     const std::vector<snake::FPolygon> &tiles,
                     snake::Length distance, snake::Angle angle,
                     snake::Length minLength, snake::Transects &transects);
39

40
const char *CircularSurvey::settingsGroup = "CircularSurvey";
41 42 43 44
const char *CircularSurvey::transectDistanceName = "TransectDistance";
const char *CircularSurvey::alphaName = "Alpha";
const char *CircularSurvey::minLengthName = "MinLength";
const char *CircularSurvey::typeName = "Type";
45 46 47 48
const char *CircularSurvey::CircularSurveyName = "CircularSurvey";
const char *CircularSurvey::refPointLatitudeName = "ReferencePointLat";
const char *CircularSurvey::refPointLongitudeName = "ReferencePointLong";
const char *CircularSurvey::refPointAltitudeName = "ReferencePointAlt";
49
const char *CircularSurvey::variantName = "Variant";
50 51
const char *CircularSurvey::numRunsName = "NumRuns";
const char *CircularSurvey::runName = "Run";
52 53 54 55 56 57 58

CircularSurvey::CircularSurvey(Vehicle *vehicle, bool flyView,
                               const QString &kmlOrShpFile, QObject *parent)
    : TransectStyleComplexItem(vehicle, flyView, settingsGroup, parent),
      _referencePoint(QGeoCoordinate(0, 0, 0)),
      _metaDataMap(FactMetaData::createMapFromJsonFile(
          QStringLiteral(":/json/CircularSurvey.SettingsGroup.json"), this)),
59 60 61 62
      _transectDistance(settingsGroup, _metaDataMap[transectDistanceName]),
      _alpha(settingsGroup, _metaDataMap[alphaName]),
      _minLength(settingsGroup, _metaDataMap[minLengthName]),
      _type(settingsGroup, _metaDataMap[typeName]),
63
      _variant(settingsGroup, _metaDataMap[variantName]),
64 65
      _numRuns(settingsGroup, _metaDataMap[numRunsName]),
      _run(settingsGroup, _metaDataMap[runName]),
66 67
      _pWorker(std::make_unique<RoutingThread>()), _state(STATE::DEFAULT),
      _hidePolygon(false) {
68 69 70
  Q_UNUSED(kmlOrShpFile)
  _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml";

71
  // Connect facts.
72
  connect(&_transectDistance, &Fact::valueChanged, this,
73
          &CircularSurvey::_rebuildTransects);
74
  connect(&_alpha, &Fact::valueChanged, this,
75
          &CircularSurvey::_rebuildTransects);
76
  connect(&_minLength, &Fact::valueChanged, this,
77
          &CircularSurvey::_rebuildTransects);
78
  connect(this, &CircularSurvey::refPointChanged, this,
79
          &CircularSurvey::_rebuildTransects);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
80 81
  connect(this, &CircularSurvey::depotChanged, this,
          &CircularSurvey::_rebuildTransects);
82 83
  connect(&this->_type, &Fact::rawValueChanged, this,
          &CircularSurvey::_rebuildTransects);
84 85
  connect(&this->_variant, &Fact::rawValueChanged, this,
          &CircularSurvey::_changeVariant);
86 87 88 89 90
  connect(&this->_run, &Fact::rawValueChanged, this,
          &CircularSurvey::_changeRun);
  connect(&this->_numRuns, &Fact::rawValueChanged, this,
          &CircularSurvey::_rebuildTransects);

91 92 93 94 95 96
  // Areas.
  connect(this, &CircularSurvey::measurementAreaChanged, this,
          &CircularSurvey::_rebuildTransects);
  connect(this, &CircularSurvey::joinedAreaChanged, this,
          &CircularSurvey::_rebuildTransects);

97
  // Connect worker.
98
  connect(this->_pWorker.get(), &RoutingThread::result, this,
99
          &CircularSurvey::_setTransects);
100
  connect(this->_pWorker.get(), &RoutingThread::calculatingChanged, this,
101
          &CircularSurvey::calculatingChanged);
102
  this->_transectsDirty = true;
103 104 105 106 107 108

  // Altitude
  connect(&_cameraCalc, &CameraCalc::distanceToSurfaceRelativeChanged, this,
          &CircularSurvey::coordinateHasRelativeAltitudeChanged);
  connect(&_cameraCalc, &CameraCalc::distanceToSurfaceRelativeChanged, this,
          &CircularSurvey::exitCoordinateHasRelativeAltitudeChanged);
109 110
}

111 112
CircularSurvey::~CircularSurvey() {}

113
void CircularSurvey::resetReference() { setRefPoint(_mArea.center()); }
114

115
void CircularSurvey::reverse() {
116
  this->_state = STATE::REVERSE;
117 118 119
  this->_rebuildTransects();
}

120 121 122
void CircularSurvey::setRefPoint(const QGeoCoordinate &refPt) {
  if (refPt != _referencePoint) {
    _referencePoint = refPt;
123
    _referencePoint.setAltitude(0);
124 125 126 127 128 129 130

    emit refPointChanged();
  }
}

QGeoCoordinate CircularSurvey::refPoint() const { return _referencePoint; }

131
Fact *CircularSurvey::transectDistance() { return &_transectDistance; }
132

133
Fact *CircularSurvey::alpha() { return &_alpha; }
134

135 136
bool CircularSurvey::hidePolygon() const { return _hidePolygon; }

137 138
QList<QString> CircularSurvey::variantNames() const { return _variantNames; }

139 140
QList<QString> CircularSurvey::runNames() const { return _runNames; }

Valentin Platzgummer's avatar
Valentin Platzgummer committed
141 142
QGeoCoordinate CircularSurvey::depot() const { return this->_depot; }

143 144 145 146
const QList<QList<QGeoCoordinate>> &CircularSurvey::rawTransects() const {
  return this->_rawTransects;
}

147 148 149 150 151 152 153
void CircularSurvey::setHidePolygon(bool hide) {
  if (this->_hidePolygon != hide) {
    this->_hidePolygon = hide;
    emit hidePolygonChanged();
  }
}

154 155 156 157 158 159 160 161 162 163 164
void CircularSurvey::setMeasurementArea(const WimaMeasurementAreaData &mArea) {
  if (this->_mArea != mArea) {
    this->_mArea = mArea;
    emit measurementAreaChanged();
  }
}

void CircularSurvey::setJoinedArea(const WimaJoinedAreaData &jArea) {
  if (this->_jArea != jArea) {
    this->_jArea = jArea;
    emit joinedAreaChanged();
165 166 167
  }
}

168 169 170 171 172 173 174 175 176 177 178
void CircularSurvey::setMeasurementArea(const WimaMeasurementArea &mArea) {
  if (this->_mArea != mArea) {
    this->_mArea = mArea;
    emit measurementAreaChanged();
  }
}

void CircularSurvey::setJoinedArea(const WimaJoinedArea &jArea) {
  if (this->_jArea != jArea) {
    this->_jArea = jArea;
    emit joinedAreaChanged();
179 180
  }
}
181

Valentin Platzgummer's avatar
Valentin Platzgummer committed
182 183 184 185 186 187 188 189 190
void CircularSurvey::setDepot(const QGeoCoordinate &depot) {
  if (this->_depot.latitude() != depot.latitude() ||
      this->_depot.longitude() != depot.longitude()) {
    this->_depot = depot;
    this->_depot.setAltitude(0);
    emit depotChanged();
  }
}

191 192
bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber,
                          QString &errorString) {
193 194
  // We need to pull version first to determine what validation/conversion
  // needs to be performed
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
  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},
212 213 214 215
      {transectDistanceName, QJsonValue::Double, true},
      {alphaName, QJsonValue::Double, true},
      {minLengthName, QJsonValue::Double, true},
      {typeName, QJsonValue::Double, true},
216
      {variantName, QJsonValue::Double, false},
217 218
      {numRunsName, QJsonValue::Double, false},
      {runName, QJsonValue::Double, false},
219 220 221 222 223 224 225 226 227 228 229 230 231 232
      {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) {
233 234 235 236 237
    errorString = tr("%1 does not support loading this complex mission item "
                     "type: %2:%3")
                      .arg(qgcApp()->applicationName())
                      .arg(itemType)
                      .arg(complexType);
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    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;
  }

256 257 258 259
  _transectDistance.setRawValue(complexObject[transectDistanceName].toDouble());
  _alpha.setRawValue(complexObject[alphaName].toDouble());
  _minLength.setRawValue(complexObject[minLengthName].toDouble());
  _type.setRawValue(complexObject[typeName].toInt());
260
  _variant.setRawValue(complexObject[variantName].toInt());
261 262
  _numRuns.setRawValue(complexObject[numRunsName].toInt());
  _run.setRawValue(complexObject[runName].toInt());
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
  _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;

292 293 294 295
  saveObject[transectDistanceName] = _transectDistance.rawValue().toDouble();
  saveObject[alphaName] = _alpha.rawValue().toDouble();
  saveObject[minLengthName] = _minLength.rawValue().toDouble();
  saveObject[typeName] = double(_type.rawValue().toUInt());
296
  saveObject[variantName] = double(_variant.rawValue().toUInt());
297 298
  saveObject[numRunsName] = double(_numRuns.rawValue().toUInt());
  saveObject[runName] = double(_numRuns.rawValue().toUInt());
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
  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) {
339
  if (_transectsDirty || _transects.count() == 0)
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
    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,
357 358
          0,   // Hold time (delay for hover and capture to settle vehicle
               // before image is taken)
359 360 361 362 363 364 365 366 367 368 369 370 371 372
          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);
    }
  }
}

373 374 375
void CircularSurvey::_changeVariant() {
  this->_state = STATE::VARIANT_CHANGE;
  this->_rebuildTransects();
376 377
}

378 379 380 381 382
void CircularSurvey::_changeRun() {
  this->_state = STATE::RUN_CHANGE;
  this->_rebuildTransects();
}

383
void CircularSurvey::_updateWorker() {
384 385
  // Mark transects as dirty.
  this->_transectsDirty = true;
386 387 388
  // Reset data.
  this->_transects.clear();
  this->_rawTransects.clear();
389
  this->_variantVector.clear();
390
  this->_variantNames.clear();
391
  this->_runNames.clear();
392
  emit variantNamesChanged();
393
  emit runNamesChanged();
394 395 396

  // Prepare data.
  auto ref = this->_referencePoint;
397 398
  auto geoPolygon = this->_mArea.coordinateList();
  for (auto &v : geoPolygon) {
399 400
    v.setAltitude(0);
  }
401 402 403 404 405 406
  auto pPolygon = std::make_shared<snake::FPolygon>();
  snake::areaToEnu(ref, geoPolygon, *pPolygon);

  // Progress and tiles.
  const auto &progress = this->_mArea.progress();
  const auto *tiles = this->_mArea.tiles();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
407
  auto pTiles = std::make_shared<std::vector<snake::FPolygon>>();
408 409 410 411 412 413 414
  if (progress.size() == tiles->count()) {
    for (int i = 0; i < tiles->count(); ++i) {
      if (progress[i] == 100) {
        const auto *tile = tiles->value<const SnakeTile *>(i);
        if (tile != nullptr) {
          snake::FPolygon tileENU;
          snake::areaToEnu(ref, tile->coordinateList(), tileENU);
Valentin Platzgummer's avatar
Valentin Platzgummer committed
415
          pTiles->push_back(std::move(tileENU));
416 417
        } else {
          qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
418
              << "_updateWorker(): progress.size() != tiles->count().";
419 420 421 422 423 424
          return;
        }
      }
    }
  } else {
    qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
425
        << "_updateWorker(): progress.size() != tiles->count().";
426 427 428 429
    return;
  }

  // Convert safe area.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
430
  auto geoDepot = this->_depot;
431
  auto geoSafeArea = this->_jArea.coordinateList();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
432 433 434 435 436 437 438 439
  if (!geoDepot.isValid()) {
    qCWarning(CircularSurveyLog)
        << "_updateWorker(): depot invalid." << geoDepot;
    return;
  }
  if (!(geoSafeArea.size() >= 3)) {
    qCWarning(CircularSurveyLog)
        << "_updateWorker(): safe area invalid." << geoSafeArea;
440 441 442
    return;
  }
  for (auto &v : geoSafeArea) {
443 444
    v.setAltitude(0);
  }
445 446
  snake::FPoint depot;
  snake::toENU(ref, geoDepot, depot);
447 448

  // Routing par.
449
  RoutingParameter par;
450
  par.numSolutions = 5;
451 452 453 454 455 456 457 458 459 460 461
  if (this->_numRuns.rawValue().toUInt() < 1) {
    disconnect(&this->_numRuns, &Fact::rawValueChanged, this,
               &CircularSurvey::_rebuildTransects);

    this->_numRuns.setCookedValue(QVariant(1));

    connect(&this->_numRuns, &Fact::rawValueChanged, this,
            &CircularSurvey::_rebuildTransects);
  }
  par.numRuns = this->_numRuns.rawValue().toUInt();

462
  auto &safeAreaENU = par.safeArea;
463
  snake::areaToEnu(ref, geoSafeArea, safeAreaENU);
464 465

  // Fetch transect parameter.
466 467 468 469 470 471 472 473 474 475 476 477
  auto distance = snake::Length(this->_transectDistance.rawValue().toDouble() *
                                bu::si::meter);
  auto minLength =
      snake::Length(this->_minLength.rawValue().toDouble() * bu::si::meter);
  auto alpha =
      snake::Angle(this->_alpha.rawValue().toDouble() * bu::degree::degree);

  // Select survey type.
  if (this->_type.rawValue().toUInt() == integral(Type::Circular)) {
    // Clip angle.
    if (alpha >= snake::Angle(0.3 * bu::degree::degree) &&
        alpha <= snake::Angle(45 * bu::degree::degree)) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
478
      auto generator = [depot, pPolygon, pTiles, distance, alpha,
479
                        minLength](snake::Transects &transects) -> bool {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
480 481 482 483
        bool value = circularTransects(*pPolygon, *pTiles, distance, alpha,
                                       minLength, transects);
        transects.insert(transects.begin(), snake::FLineString{depot});
        return value;
484 485 486 487 488 489 490 491 492 493 494
      };
      // Start routing worker.
      this->_pWorker->route(par, generator);
    } else {
      if (alpha < snake::Angle(0.3 * bu::degree::degree)) {
        this->_alpha.setCookedValue(QVariant(0.3));
      } else {
        this->_alpha.setCookedValue(QVariant(45));
      }
    }
  } else if (this->_type.rawValue().toUInt() == integral(Type::Linear)) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
495
    auto generator = [depot, pPolygon, pTiles, distance, alpha,
496
                      minLength](snake::Transects &transects) -> bool {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
497 498 499 500
      bool value = linearTransects(*pPolygon, *pTiles, distance, alpha,
                                   minLength, transects);
      transects.insert(transects.begin(), snake::FLineString{depot});
      return value;
501 502 503 504
    };
    // Start routing worker.
    this->_pWorker->route(par, generator);
  } else {
505
    qCWarning(CircularSurveyLog)
506 507 508
        << "CircularSurvey::rebuildTransectsPhase1(): invalid survey type:"
        << this->_type.rawValue().toUInt();
  }
509 510
}

511
void CircularSurvey::_changeVariantRunWorker() {
512
  auto variant = this->_variant.rawValue().toUInt();
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
  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;
      }
529 530
    }
  }
531

532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
  // 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
558 559 560 561
      qCWarning(CircularSurveyLog)
          << "Variant or run out of bounds (variant = " << variant
          << ", run = " << run << ").";
      qCWarning(CircularSurveyLog) << "Resetting variant and run.";
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587

      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) {
        this->_changeVariantRunWorker();
      }
    }
588 589
  }
}
590

591 592 593
void CircularSurvey::_reverseWorker() {
  if (this->_transects.size() > 0) {
    auto &t = this->_transects.front();
Valentin Platzgummer's avatar
Valentin Platzgummer committed
594
    std::reverse(t.begin(), t.end());
595
  }
596 597
}

598 599 600 601 602 603 604 605
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;
  }
606

607
  // Store raw transects.
608
  const auto &pRoutingData = this->_pRoutingData;
609 610 611
  const auto &ori = this->_referencePoint;
  const auto &transectsENU = pRoutingData->transects;
  QList<QList<QGeoCoordinate>> rawTransects;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
612
  for (std::size_t i = 1; i < transectsENU.size(); ++i) {
613 614 615 616 617 618 619
    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);
620
    }
621 622
  }

623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
  // Store solutions.
  QVector<Runs> 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.
    Runs 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;
638
        const auto &infoFirst = info.at(1);
639 640 641 642 643 644 645
        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);
646
            if (dist < th) {
647
              idxFirst = i;
648 649 650
              break;
            }
          }
651

652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
          // 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 {
676
            qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
677
                << "_storeWorker(): lastTransect.size() == 0";
678 679
          }
        } else {
680
          qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
681
              << "_storeWorker(): firstTransect.size() == 0";
682 683
        }
      } else {
684
        qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
685
            << "_storeWorker(): transectsInfo.size() <= 1";
686 687 688 689 690 691 692 693 694 695
      }
    }
    // 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);
696
      }
697 698 699
    }
    if (!error) {
      solutionVector.push_back(std::move(runs));
700
    }
701
  }
702

703 704 705
  // Remove empty solutions.
  std::size_t nSol = 0;
  for (auto it = solutionVector.begin(); it < solutionVector.end();) {
706 707
    if (it->size() > 0 && it->front().size() > 0) {
      ++it;
708
      ++nSol;
709
    } else {
710
      it = solutionVector.erase(it);
711
    }
712
  }
713 714

  // Assign routes if no error occured.
715
  if (nSol > 0) {
716
    // Swap first route to _transects.
717 718
    this->_variantVector.swap(solutionVector);

719
    // Add route variant names.
720 721 722
    this->_variantNames.clear();
    for (std::size_t i = 1; i <= std::size_t(this->_variantVector.size());
         ++i) {
723
      this->_variantNames.append(QString::number(i));
724
    }
725
    emit variantNamesChanged();
726

727 728
    // Swap in rawTransects.
    this->_rawTransects.swap(rawTransects);
729 730 731 732 733 734 735 736 737 738 739 740

    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);
    this->_changeVariantRunWorker();
741 742
    // Mark transect as stored and ready.
    this->_transectsDirty = false;
743
  }
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
}

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 {
  return TransectStyleComplexItem::readyForSave() && !_transectsDirty;
}

double CircularSurvey::additionalTimeDelay() const { return 0; }

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
773
    qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: store.";
774 775 776
    this->_storeWorker();
    break;
  case STATE::VARIANT_CHANGE:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
777
    qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: variant change.";
778 779 780
    this->_changeVariantRunWorker();
    break;
  case STATE::RUN_CHANGE:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
781
    qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: run change.";
782
    this->_changeVariantRunWorker();
783 784
    break;
  case STATE::REVERSE:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
785
    qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: reverse.";
786 787 788
    this->_reverseWorker();
    break;
  case STATE::DEFAULT:
Valentin Platzgummer's avatar
Valentin Platzgummer committed
789
    qCWarning(CircularSurveyLog) << "rebuildTransectsPhase1: update.";
790 791
    this->_updateWorker();
    break;
792
  }
793 794
  this->_state = STATE::DEFAULT;

795
  qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
796
      << "rebuildTransectsPhase1(): "
797 798 799 800
      << std::chrono::duration_cast<std::chrono::milliseconds>(
             std::chrono::high_resolution_clock::now() - start)
             .count()
      << " ms";
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
}

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

818
void CircularSurvey::_setTransects(CircularSurvey::PtrRoutingData pRoute) {
819
  this->_pRoutingData = pRoute;
820
  this->_state = STATE::STORE;
821
  this->_rebuildTransects();
822 823
}

824 825 826 827
Fact *CircularSurvey::minLength() { return &_minLength; }

Fact *CircularSurvey::type() { return &_type; }

828 829
Fact *CircularSurvey::variant() { return &_variant; }

830 831 832 833
Fact *CircularSurvey::numRuns() { return &_numRuns; }

Fact *CircularSurvey::run() { return &_run; }

834
int CircularSurvey::typeCount() const { return int(integral(Type::Count)); }
835

836 837 838
bool CircularSurvey::calculating() const {
  return this->_pWorker->calculating();
}
839

Valentin Platzgummer's avatar
Valentin Platzgummer committed
840 841 842 843
bool circularTransects(const snake::FPolygon &polygon,
                       const std::vector<snake::FPolygon> &tiles,
                       snake::Length deltaR, snake::Angle deltaAlpha,
                       snake::Length minLength, snake::Transects &transects) {
844
  auto s1 = std::chrono::high_resolution_clock::now();
845

846
  // Check preconitions
847
  if (polygon.outer().size() >= 3) {
848 849
    using namespace boost::units;
    // Convert geo polygon to ENU polygon.
850
    snake::FPoint origin{0, 0};
851 852
    std::string error;
    // Check validity.
853
    if (!bg::is_valid(polygon, error)) {
Valentin Platzgummer's avatar
Valentin Platzgummer committed
854
      qCWarning(CircularSurveyLog) << "circularTransects(): "
855 856
                                      "invalid polygon.";
      qCWarning(CircularSurveyLog) << error.c_str();
857
      std::stringstream ss;
858 859
      ss << bg::wkt(polygon);
      qCWarning(CircularSurveyLog) << ss.str().c_str();
860 861 862
    } else {
      // Calculate polygon distances and angles.
      std::vector<snake::Length> distances;
863
      distances.reserve(polygon.outer().size());
864
      std::vector<snake::Angle> angles;
865
      angles.reserve(polygon.outer().size());
866
      //#ifdef DEBUG_CIRCULAR_SURVEY
Valentin Platzgummer's avatar
Valentin Platzgummer committed
867
      //      qCWarning(CircularSurveyLog) << "circularTransects():";
868
      //#endif
869 870
      for (const auto &p : polygon.outer()) {
        snake::Length distance = bg::distance(origin, p) * si::meter;
871 872 873 874 875
        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
876 877 878 879 880 881
        //        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>();
882 883 884 885 886 887 888
        //#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
889 890
      if (!bg::within(origin, polygon.outer())) {
        rMin = bg::distance(origin, polygon) * si::meter;
891 892 893 894 895 896 897 898 899 900
      }

      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));
901 902 903
      auto originScaled =
          ClipperLib::IntPoint{ClipperLib::cInt(std::round(origin.get<0>())),
                               ClipperLib::cInt(std::round(origin.get<1>()))};
904 905 906 907 908 909 910 911

      // 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
912
      //      qCWarning(CircularSurveyLog) << "circularTransects(): sector
913 914 915
      //      parameres:"; qCWarning(CircularSurveyLog) << "alpha1: " <<
      //      to_string(snake::Degree(alpha1)).c_str();
      //      qCWarning(CircularSurveyLog) << "alpha2:
916
      //      "
917 918 919 920 921 922 923 924
      //      << 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;
925 926 927 928 929 930 931 932 933 934
      //#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;
935
      snake::FPolygon shrinked;
936
      snake::offsetPolygon(polygon, shrinked, -0.3);
937
      auto &outer = shrinked.outer();
938
      polygonClipper.reserve(outer.size());
939 940 941 942 943 944 945 946 947 948 949 950
      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);

951
      // Subtract holes.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
952
      if (tiles.size() > 0) {
953
        vector<ClipperLib::Path> processedTiles;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
954
        for (const auto &tile : tiles) {
955
          ClipperLib::Path path;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
956
          for (const auto &v : tile.outer()) {
957 958 959 960
            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
961
          processedTiles.push_back(std::move(path));
962 963 964 965 966 967 968 969 970 971 972 973
        }

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

974 975 976
      // Extract transects from  PolyTree and convert them to
      // BoostLineString
      for (const auto &child : transectsClipper.Childs) {
977
        snake::FLineString transect;
978 979 980 981
        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;
982
          transect.push_back(snake::FPoint(x, y));
983 984 985
        }
        transects.push_back(transect);
      }
986

987 988 989 990 991 992
      // 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) {
993
            snake::FLineString temp;
994 995 996 997 998 999 1000 1001 1002 1003
            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) {
1004
            snake::FLineString temp;
1005 1006 1007 1008 1009 1010 1011 1012
            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) {
1013
            snake::FLineString temp;
1014 1015 1016 1017 1018 1019 1020 1021
            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) {
1022
            snake::FLineString temp;
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
            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;
          }
        }
      }
1033

1034
      // Remove short transects
1035
      for (auto it = transects.begin(); it < transects.end();) {
1036 1037 1038 1039 1040 1041 1042
        if (bg::length(*it) < minLength.value()) {
          it = transects.erase(it);
        } else {
          ++it;
        }
      }

1043
      qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1044
          << "circularTransects(): transect gen. time: "
1045 1046 1047 1048
          << std::chrono::duration_cast<std::chrono::milliseconds>(
                 std::chrono::high_resolution_clock::now() - s1)
                 .count()
          << " ms";
1049 1050 1051 1052 1053
      return true;
    }
  }
  return false;
}
1054

Valentin Platzgummer's avatar
Valentin Platzgummer committed
1055 1056 1057 1058
bool linearTransects(const snake::FPolygon &polygon,
                     const std::vector<snake::FPolygon> &tiles,
                     snake::Length distance, snake::Angle angle,
                     snake::Length minLength, snake::Transects &transects) {
1059 1060
  namespace tr = bg::strategy::transform;
  auto s1 = std::chrono::high_resolution_clock::now();
1061

1062
  // Check preconitions
1063
  if (polygon.outer().size() >= 3) {
1064 1065 1066
    // Convert to ENU system.
    std::string error;
    // Check validity.
1067
    if (!bg::is_valid(polygon, error)) {
1068
      std::stringstream ss;
1069 1070
      ss << bg::wkt(polygon);

Valentin Platzgummer's avatar
Valentin Platzgummer committed
1071
      qCWarning(CircularSurveyLog) << "linearTransects(): "
1072 1073
                                      "invalid polygon. "
                                   << error.c_str() << ss.str().c_str();
1074 1075 1076 1077
    } else {
      tr::rotate_transformer<bg::degree, double, 2, 2> rotate(angle.value() *
                                                              180 / M_PI);
      // Rotate polygon by angle and calculate bounding box.
1078
      snake::FPolygon polygonENURotated;
1079
      bg::transform(polygon.outer(), polygonENURotated.outer(), rotate);
1080
      snake::FBox box;
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
      boost::geometry::envelope(polygonENURotated, box);
      double x0 = box.min_corner().get<0>();
      double y0 = box.min_corner().get<1>();
      double x1 = box.max_corner().get<0>();
      double y1 = box.max_corner().get<1>();

      // Generate transects and convert them to clipper path.
      size_t num_t = ceil((y1 - y0) / distance.value()); // number of transects
      vector<ClipperLib::Path> transectsClipper;
      transectsClipper.reserve(num_t);
      for (size_t i = 0; i < num_t; ++i) {
        // calculate transect
1093 1094 1095
        snake::FPoint v1{x0, y0 + i * distance.value()};
        snake::FPoint v2{x1, y0 + i * distance.value()};
        snake::FLineString transect;
1096 1097 1098
        transect.push_back(v1);
        transect.push_back(v2);
        // transform back
1099
        snake::FLineString temp_transect;
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
        tr::rotate_transformer<bg::degree, double, 2, 2> rotate_back(
            -angle.value() * 180 / M_PI);
        bg::transform(transect, temp_transect, rotate_back);
        // to clipper
        ClipperLib::IntPoint c1{static_cast<ClipperLib::cInt>(
                                    temp_transect[0].get<0>() * CLIPPER_SCALE),
                                static_cast<ClipperLib::cInt>(
                                    temp_transect[0].get<1>() * CLIPPER_SCALE)};
        ClipperLib::IntPoint c2{static_cast<ClipperLib::cInt>(
                                    temp_transect[1].get<0>() * CLIPPER_SCALE),
                                static_cast<ClipperLib::cInt>(
                                    temp_transect[1].get<1>() * CLIPPER_SCALE)};
        ClipperLib::Path path{c1, c2};
        transectsClipper.push_back(path);
      }

      if (transectsClipper.size() == 0) {
        std::stringstream ss;
        ss << "Not able to generate transects. Parameter: distance = "
           << distance << std::endl;
1120
        qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1121
            << "linearTransects(): " << ss.str().c_str();
1122 1123 1124 1125
        return false;
      }

      // Convert measurement area to clipper path.
1126
      snake::FPolygon shrinked;
1127
      snake::offsetPolygon(polygon, shrinked, -0.2);
1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
      auto &outer = shrinked.outer();
      ClipperLib::Path polygonClipper;
      for (auto vertex : outer) {
        polygonClipper.push_back(ClipperLib::IntPoint{
            static_cast<ClipperLib::cInt>(vertex.get<0>() * CLIPPER_SCALE),
            static_cast<ClipperLib::cInt>(vertex.get<1>() * CLIPPER_SCALE)});
      }

      // Perform clipping.
      // Clip transects to measurement area.
      ClipperLib::Clipper clipper;
      clipper.AddPath(polygonClipper, ClipperLib::ptClip, true);
      clipper.AddPaths(transectsClipper, ClipperLib::ptSubject, false);
      ClipperLib::PolyTree clippedTransecs;
      clipper.Execute(ClipperLib::ctIntersection, clippedTransecs,
                      ClipperLib::pftNonZero, ClipperLib::pftNonZero);

1145
      // Subtract holes.
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1146
      if (tiles.size() > 0) {
1147
        vector<ClipperLib::Path> processedTiles;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1148
        for (const auto &tile : tiles) {
1149
          ClipperLib::Path path;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1150
          for (const auto &v : tile.outer()) {
1151 1152 1153 1154
            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
1155
          processedTiles.push_back(std::move(path));
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
        }

        clipper.Clear();
        for (const auto &child : clippedTransecs.Childs) {
          clipper.AddPath(child->Contour, ClipperLib::ptSubject, false);
        }
        clipper.AddPaths(processedTiles, ClipperLib::ptClip, true);
        clippedTransecs.Clear();
        clipper.Execute(ClipperLib::ctDifference, clippedTransecs,
                        ClipperLib::pftNonZero, ClipperLib::pftNonZero);
1166
      }
1167 1168

      // Extract transects from  PolyTree and convert them to BoostLineString
1169 1170
      for (const auto &child : clippedTransecs.Childs) {
        const auto &clipperTransect = child->Contour;
1171
        snake::FPoint v1{
1172 1173
            static_cast<double>(clipperTransect[0].X) / CLIPPER_SCALE,
            static_cast<double>(clipperTransect[0].Y) / CLIPPER_SCALE};
1174
        snake::FPoint v2{
1175 1176 1177
            static_cast<double>(clipperTransect[1].X) / CLIPPER_SCALE,
            static_cast<double>(clipperTransect[1].Y) / CLIPPER_SCALE};

1178
        snake::FLineString transect{v1, v2};
1179 1180 1181 1182 1183 1184 1185
        if (bg::length(transect) >= minLength.value()) {
          transects.push_back(transect);
        }
      }

      if (transects.size() == 0) {
        std::stringstream ss;
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1186
        ss << "Not able to  generatetransects. Parameter: minLength = "
1187
           << minLength << std::endl;
1188
        qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1189
            << "linearTransects(): " << ss.str().c_str();
1190 1191
        return false;
      }
1192
      qCWarning(CircularSurveyLog)
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1193
          << "linearTransects(): transect gen. time: "
1194 1195 1196 1197
          << std::chrono::duration_cast<std::chrono::milliseconds>(
                 std::chrono::high_resolution_clock::now() - s1)
                 .count()
          << " ms";
1198 1199 1200 1201 1202
      return true;
    }
  }
  return false;
}
1203 1204 1205 1206
/*!
    \class CircularSurveyComplexItem
    \inmodule Wima

1207 1208
    \brief The \c CircularSurveyComplexItem class provides a survey mission
   item with circular transects around a point of interest.
1209

1210 1211 1212 1213
    CircularSurveyComplexItem class provides a survey mission item with
   circular transects around a point of interest. Within the \c Wima module
   it's used to scan a defined area with constant angle (circular transects)
   to the base station (point of interest).
1214 1215 1216

    \sa WimaArea
*/