From 6fd60bb54b072b7bb127c82b9d697d3d2573274c Mon Sep 17 00:00:00 2001 From: Valentin Platzgummer Date: Thu, 24 Sep 2020 08:18:47 +0200 Subject: [PATCH] CircularSurvey concurr. update improved --- .../constraint_solver/search_limit.pb.h | 2 + qgroundcontrol.pro | 2 + src/PlanView/CircularSurveyItemEditor.qml | 125 ++---- src/Wima/CSWorker.cpp | 368 +++++++++++++++ src/Wima/CSWorker.h | 57 +++ src/Wima/CircularSurvey.cc | 420 ++++-------------- src/Wima/CircularSurvey.h | 32 +- src/Wima/Snake/snake.cpp | 45 +- src/Wima/Snake/snake.h | 7 +- src/Wima/WimaPlaner.cc | 13 +- src/Wima/WimaPlaner.h | 42 +- src/WimaView/CircularSurveyMapVisual.qml | 2 - 12 files changed, 628 insertions(+), 487 deletions(-) create mode 100644 src/Wima/CSWorker.cpp create mode 100644 src/Wima/CSWorker.h diff --git a/libs/or-tools-src-ubuntu/include/ortools/constraint_solver/search_limit.pb.h b/libs/or-tools-src-ubuntu/include/ortools/constraint_solver/search_limit.pb.h index 8fb77a22d..0d705f1ca 100644 --- a/libs/or-tools-src-ubuntu/include/ortools/constraint_solver/search_limit.pb.h +++ b/libs/or-tools-src-ubuntu/include/ortools/constraint_solver/search_limit.pb.h @@ -1,4 +1,6 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! + this->_calculating = false; + emit calculatingChanged(); // source: ortools/constraint_solver/search_limit.proto #ifndef GOOGLE_PROTOBUF_INCLUDED_ortools_2fconstraint_5fsolver_2fsearch_5flimit_2eproto diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 056e40947..176b25cd2 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -413,6 +413,7 @@ FORMS += \ # HEADERS += \ + src/Wima/CSWorker.h \ src/Wima/CircularSurvey.h \ src/Wima/Geometry/GenericCircle.h \ src/Wima/Snake/clipper/clipper.hpp \ @@ -502,6 +503,7 @@ HEADERS += \ src/comm/ros_bridge/include/topic_subscriber.h \ src/comm/utilities.h SOURCES += \ + src/Wima/CSWorker.cpp \ src/Wima/CircularSurvey.cc \ src/Wima/Snake/clipper/clipper.cpp \ src/Wima/Snake/snake.cpp \ diff --git a/src/PlanView/CircularSurveyItemEditor.qml b/src/PlanView/CircularSurveyItemEditor.qml index 6fa9671a7..f0c28fbdd 100644 --- a/src/PlanView/CircularSurveyItemEditor.qml +++ b/src/PlanView/CircularSurveyItemEditor.qml @@ -92,9 +92,6 @@ Rectangle { columns: 2 visible: transectsHeader.checked - - - QGCLabel { text: qsTr("Delta R") } FactTextField { fact: missionItem.deltaR @@ -131,12 +128,26 @@ Rectangle { } } - Column { + GridLayout { anchors.left: parent.left anchors.right: parent.right - spacing: _margin + columnSpacing: _margin + rowSpacing: _margin + columns: 2 visible: transectsHeader.checked + QGCButton { + text: qsTr("Reverse") + onClicked: missionItem.reverse(); + Layout.fillWidth: true + } + + QGCButton { + text: qsTr("Reset Ref.") + onClicked: missionItem.resetReference(); + Layout.fillWidth: true + } + QGCCheckBox { id: relAlt text: qsTr("Relative altitude") @@ -144,6 +155,8 @@ Rectangle { enabled: missionItem.cameraCalc.isManualCamera && !missionItem.followTerrain visible: QGroundControl.corePlugin.options.showMissionAbsoluteAltitude || (!missionItem.cameraCalc.distanceToSurfaceRelative && !missionItem.followTerrain) onClicked: missionItem.cameraCalc.distanceToSurfaceRelative = checked + Layout.fillWidth: true + Layout.columnSpan: 2 Connections { target: missionItem.cameraCalc @@ -151,96 +164,38 @@ Rectangle { } } - FactCheckBox { - text: qsTr("Reverse Path") - fact: missionItem.reverse - } - - QGCButton { - text: qsTr("Reset Reference") - onClicked: missionItem.resetReference(); - Layout.fillWidth: true - } } - SectionHeader { - id: miscellaneousHeader - text: qsTr("Miscellaneous") - } - - ColumnLayout { + Column{ anchors.left: parent.left anchors.right: parent.right spacing: _margin - visible: miscellaneousHeader.checked - GridLayout { - Layout.fillWidth: true - columnSpacing: _margin - rowSpacing: _margin - columns: 2 - - QGCLabel { text: qsTr("Max Waypoints") } - FactTextField { - fact: missionItem.maxWaypoints - Layout.fillWidth: true + BusyIndicator{ + id: indicator + anchors.horizontalCenter: parent.horizontalCenter + running: missionItem.calculating + + onRunningChanged: { + if(running){ + visible = true + } else { + timer.restart() + } } - } // GridLayout - } // ColumnLayout - /* - // The following code causes seg. faults from time to time - SectionHeader { - id: terrainHeader - text: qsTr("Terrain") - checked: missionItem.followTerrain - } + Timer{ + id: timer + interval: 2000 + repeat: false - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - spacing: _margin - visible: terrainHeader.checked - - QGCCheckBox { - id: followsTerrainCheckBox - text: qsTr("Vehicle follows terrain") - checked: missionItem.followTerrain - onClicked: missionItem.followTerrain = checked - } - - GridLayout { - Layout.fillWidth: true - columnSpacing: _margin - rowSpacing: _margin - columns: 2 - visible: followsTerrainCheckBox.checked - - QGCLabel { text: qsTr("Tolerance") } - FactTextField { - fact: missionItem.terrainAdjustTolerance - Layout.fillWidth: true - } - - QGCLabel { text: qsTr("Max Climb Rate") } - FactTextField { - fact: missionItem.terrainAdjustMaxClimbRate - Layout.fillWidth: true - } - - QGCLabel { text: qsTr("Max Descent Rate") } - FactTextField { - fact: missionItem.terrainAdjustMaxDescentRate - Layout.fillWidth: true + onTriggered: { + if (indicator.running == false){ + indicator.visible = false + } + } } } - }*/ - - /*SectionHeader { - id: statsHeader - text: qsTr("Statistics") - }*/ - - //TransectStyleComplexItemStats { } + } } // Column } // Rectangle diff --git a/src/Wima/CSWorker.cpp b/src/Wima/CSWorker.cpp new file mode 100644 index 000000000..c2511d991 --- /dev/null +++ b/src/Wima/CSWorker.cpp @@ -0,0 +1,368 @@ +#include "CSWorker.h" +// Wima +#define CLIPPER_SCALE 10000 +#include "clipper/clipper.hpp" +template ClipperLib::cInt get(ClipperLib::IntPoint &p); +#include "Geometry/GenericCircle.h" +// std +#include +// Qt +#include + +template <> ClipperLib::cInt get<0>(ClipperLib::IntPoint &p) { return p.X; } +template <> ClipperLib::cInt get<1>(ClipperLib::IntPoint &p) { return p.Y; } + +CSWorker::CSWorker(QObject *parent) + : QThread(parent), _deltaR(2 * bu::si::meter), + _deltaAlpha(3 * bu::degree::degree), _minLength(10 * bu::si::meter), + _calculating(false), _stop(false), _restart(false) {} + +CSWorker::~CSWorker() { + this->_stop = true; + Lock lk(this->_mutex); + this->_restart = true; + this->_cv.notify_one(); + lk.unlock(); + this->wait(); +} + +bool CSWorker::calculating() { return this->_calculating; } + +void CSWorker::update(const QList &polygon, + const QGeoCoordinate &origin, snake::Length deltaR, + snake::Length minLength, snake::Angle deltaAlpha) { + // Sample input. + Lock lk(this->_mutex); + this->_polygon = polygon; + this->_origin = origin; + this->_deltaR = deltaR; + this->_deltaAlpha = deltaAlpha; + this->_minLength = minLength; + lk.unlock(); + + if (!this->isRunning()) { + this->start(); + } else { + Lock lk(this->_mutex); + this->_restart = true; + this->_cv.notify_one(); + } +} + +void CSWorker::run() { + qWarning() << "CSWorker::run(): thread start."; + while (!this->_stop) { + // Copy input. + Lock lk(this->_mutex); + const auto polygon = this->_polygon; + const auto origin = this->_origin; + const auto deltaR = this->_deltaR; + const auto deltaAlpha = this->_deltaAlpha; + const auto minLength = this->_minLength; + lk.unlock(); + // Check preconitions + if (polygon.size() >= 3) { +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): calculation " + "started."; +#endif +#ifdef SHOW_CIRCULAR_SURVEY_TIME + const auto start = std::chrono::high_resolution_clock::now(); +#endif + using namespace boost::units; + this->_calculating = true; + emit calculatingChanged(); + // Convert geo polygon to ENU polygon. + snake::BoostPolygon polygonENU; + snake::BoostPoint originENU{0, 0}; + snake::areaToEnu(origin, polygon, polygonENU); + std::string error; + // Check validity. + if (!bg::is_valid(polygonENU, error)) { +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): " + "invalid polygon."; + qWarning() << error.c_str(); +#endif + } else { + // Calculate polygon distances and angles. + std::vector distances; + distances.reserve(polygonENU.outer().size()); + std::vector angles; + angles.reserve(polygonENU.outer().size()); +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run():"; +#endif + for (const auto &p : polygonENU.outer()) { + snake::Length distance = bg::distance(originENU, 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 + qWarning() << "distances, angles, coordinates:"; + qWarning() << to_string(distance).c_str(); + qWarning() << to_string(snake::Degree(alpha)).c_str(); + qWarning() << "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(originENU, polygonENU)) { + rMin = bg::distance(originENU, polygonENU) * 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(originENU.get<0>())), + ClipperLib::cInt(std::round(originENU.get<1>()))}; + +#ifdef SHOW_CIRCULAR_SURVEY_TIME + auto s1 = std::chrono::high_resolution_clock::now(); +#endif + // Generate circle sectors. + auto rScaled = rMinScaled; + const auto nTran = long(std::ceil(((rMax - rMin) / deltaR).value())); + vector sectors(nTran, ClipperLib::Path()); + const auto nSectors = + long(std::round(((alpha2 - alpha1) / deltaAlpha).value())); +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): sector parameres:"; + qWarning() << "alpha1: " << to_string(snake::Degree(alpha1)).c_str(); + qWarning() << "alpha2: " << to_string(snake::Degree(alpha2)).c_str(); + qWarning() << "n: " + << to_string((alpha2 - alpha1) / deltaAlpha).c_str(); + qWarning() << "nSectors: " << nSectors; + qWarning() << "rMin: " << to_string(rMin).c_str(); + qWarning() << "rMax: " << to_string(rMax).c_str(); + qWarning() << "nTran: " << nTran; +#endif + using ClipperCircle = + GenericCircle; + for (auto §or : sectors) { + ClipperCircle circle(rScaled, originScaled); + approximate(circle, nSectors, sector); + rScaled += deltaRScaled; + } + // Clip sectors to polygonENU. + ClipperLib::Path polygonClipper; + auto &outer = polygonENU.outer(); + polygonClipper.reserve(outer.size() - 1); + 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); + + // Extract transects from PolyTree and convert them to + // BoostLineString + snake::Transects transectsENU; + for (const auto &child : transectsClipper.Childs) { + snake::BoostLineString transect; + transect.reserve(child->Contour.size()); + for (const auto &vertex : child->Contour) { + auto x = static_cast(vertex.X) / CLIPPER_SCALE; + auto y = static_cast(vertex.Y) / CLIPPER_SCALE; + transect.push_back(snake::BoostPoint(x, y)); + } + transectsENU.push_back(transect); + } + // Join sectors which where slit due to clipping. + const double th = 0.01; + for (auto ito = transectsENU.begin(); ito < transectsENU.end(); ++ito) { + for (auto iti = ito + 1; iti < transectsENU.end(); ++iti) { + auto dist1 = bg::distance(ito->front(), iti->front()); + if (dist1 < th) { + snake::BoostLineString 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; + transectsENU.erase(iti); + break; + } + auto dist2 = bg::distance(ito->front(), iti->back()); + if (dist2 < th) { + snake::BoostLineString temp; + temp.insert(temp.end(), iti->begin(), iti->end()); + temp.insert(temp.end(), ito->begin(), ito->end()); + *ito = temp; + transectsENU.erase(iti); + break; + } + auto dist3 = bg::distance(ito->back(), iti->front()); + if (dist3 < th) { + snake::BoostLineString temp; + temp.insert(temp.end(), ito->begin(), ito->end()); + temp.insert(temp.end(), iti->begin(), iti->end()); + *ito = temp; + transectsENU.erase(iti); + break; + } + auto dist4 = bg::distance(ito->back(), iti->back()); + if (dist4 < th) { + snake::BoostLineString 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; + transectsENU.erase(iti); + break; + } + } + } + // Remove short transects + for (auto it = transectsENU.begin(); it < transectsENU.end();) { + if (bg::length(*it) < minLength.value()) { + it = transectsENU.erase(it); + } else { + ++it; + } + } + // Move transect with min. distance to the front. + auto minDist = std::numeric_limits::max(); + auto minIt = transectsENU.begin(); + bool reverse = false; + for (auto it = transectsENU.begin(); it < transectsENU.end(); ++it) { + auto distFront = bg::distance(originENU, it->front()); + auto distBack = bg::distance(originENU, it->back()); + if (distFront < minDist) { + minDist = distFront; + minIt = it; + reverse = false; + } + if (distBack < minDist) { + minDist = distBack; + minIt = it; + reverse = true; + } + } + // Swap and reverse (if necessary). + if (minIt != transectsENU.begin()) { + auto minTransect = *minIt; + if (reverse) { + snake::BoostLineString rev; + for (auto it = minTransect.end() - 1; it >= minTransect.begin(); + --it) { + rev.push_back(*it); + } + minTransect = rev; + } + *minIt = *transectsENU.begin(); + *transectsENU.begin() = minTransect; + } +#ifdef SHOW_CIRCULAR_SURVEY_TIME + qWarning() << "CSWorker::run(): transect gen. time: " + << std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - s1) + .count() + << " ms"; +#endif + + if (transectsENU.size() == 0) { +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): " + "not able to generate transects."; +#endif + } else if (this->_restart) { +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): " + "restart requested."; +#endif + } else { + // Prepare data for routing. + std::vector transectsInfo; + snake::Route route; + const auto routingStart = std::chrono::high_resolution_clock::now(); + const auto maxRoutingTime = std::chrono::minutes(1); + const auto routingEnd = routingStart + maxRoutingTime; + const auto &restart = this->_restart; + auto stopLambda = [&restart, routingEnd] { + bool expired = + std::chrono::high_resolution_clock::now() > routingEnd; + return restart || expired; + }; + std::string errorString; + // Route transects; + bool success = snake::route(polygonENU, transectsENU, transectsInfo, + route, stopLambda, errorString); + if (!success && !this->_restart) { +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): " + "routing failed."; +#endif + } else if (this->_restart) { +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): " + "restart requested."; +#endif + } else { + // Remove return path. + const auto &info = transectsInfo.back(); + const auto &lastTransect = transectsENU[info.index]; + const auto &lastWaypoint = + info.reversed ? lastTransect.front() : lastTransect.back(); + auto &wp = route.back(); + while (wp != lastWaypoint) { + route.pop_back(); + wp = route.back(); + } + // Convert to geo coordinates and notify main thread. + auto pRoute = PtrRoute(new Route()); + for (const auto &vertex : route) { + QGeoCoordinate c; + snake::fromENU(origin, vertex, c); + pRoute->append(c); + } + emit ready(pRoute); +#ifdef DEBUG_CIRCULAR_SURVEY + qWarning() << "CSWorker::run(): " + "concurrent update success."; +#endif + } + } + } +#ifdef SHOW_CIRCULAR_SURVEY_TIME + qWarning() << "CSWorker::run(): execution time: " + << std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - start) + .count() + << " ms"; +#endif + this->_calculating = false; + emit calculatingChanged(); + } +#ifdef DEBUG_CIRCULAR_SURVEY + else { + qWarning() << "CSWorker::run(): preconditions failed."; + } +#endif + Lock lk2(this->_mutex); + if (!this->_restart) { + this->_cv.wait(lk2, [this] { return this->_restart.load(); }); + } + this->_restart = false; + } + qWarning() << "CSWorker::run(): thread end."; +} diff --git a/src/Wima/CSWorker.h b/src/Wima/CSWorker.h new file mode 100644 index 000000000..a6644e893 --- /dev/null +++ b/src/Wima/CSWorker.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include + +#include "snake.h" +#include +#include +#include + +//! +//! \brief The CSWorker class +//! \note Don't call QThread::start, QThread::quit, etc. onyl use Worker +//! members! +class CSWorker : public QThread { + Q_OBJECT + using Lock = std::unique_lock; + +public: + using Route = QList; + using PtrRoute = QSharedPointer; + + CSWorker(QObject *parent = nullptr); + ~CSWorker() override; + + bool calculating(); + +public slots: + void update(const QList &polygon, + const QGeoCoordinate &origin, snake::Length deltaR, + snake::Length minLength, snake::Angle deltaAlpha); + +signals: + void ready(PtrRoute pTransects); + void calculatingChanged(); + +protected: + void run() override; + +private: + mutable std::mutex _mutex; + mutable std::condition_variable _cv; + // Internal data + QList _polygon; + QGeoCoordinate _origin; + snake::Length _deltaR; + snake::Angle _deltaAlpha; + snake::Length _minLength; + std::size_t _maxWaypoints; + + // State + std::atomic_bool _calculating; + std::atomic_bool _stop; + std::atomic_bool _restart; +}; diff --git a/src/Wima/CircularSurvey.cc b/src/Wima/CircularSurvey.cc index 4a406ec2a..96fd7dc53 100644 --- a/src/Wima/CircularSurvey.cc +++ b/src/Wima/CircularSurvey.cc @@ -1,28 +1,14 @@ #include "CircularSurvey.h" - -#include - +#include "CSWorker.h" +// QGC #include "JsonHelper.h" #include "QGCApplication.h" - -#include - -#include "clipper/clipper.hpp" +// Wima #include "snake.h" -#define CLIPPER_SCALE 10000 - -template ClipperLib::cInt get(ClipperLib::IntPoint &p); -#include "Geometry/GenericCircle.h" -#include "Geometry/GeoUtilities.h" -#include "Geometry/PlanimetryCalculus.h" -#include "Geometry/PolygonCalculus.h" - +// boost #include #include -template <> ClipperLib::cInt get<0>(ClipperLib::IntPoint &p) { return p.X; } -template <> ClipperLib::cInt get<1>(ClipperLib::IntPoint &p) { return p.Y; } - template class CommandRAII { public: CommandRAII(Functor f) : fun(f) {} @@ -36,8 +22,6 @@ const char *CircularSurvey::settingsGroup = "CircularSurvey"; const char *CircularSurvey::deltaRName = "DeltaR"; const char *CircularSurvey::deltaAlphaName = "DeltaAlpha"; const char *CircularSurvey::transectMinLengthName = "TransectMinLength"; -const char *CircularSurvey::reverseName = "Reverse"; -const char *CircularSurvey::maxWaypointsName = "MaxWaypoints"; const char *CircularSurvey::CircularSurveyName = "CircularSurvey"; const char *CircularSurvey::refPointLatitudeName = "ReferencePointLat"; const char *CircularSurvey::refPointLongitudeName = "ReferencePointLong"; @@ -52,51 +36,40 @@ CircularSurvey::CircularSurvey(Vehicle *vehicle, bool flyView, _deltaR(settingsGroup, _metaDataMap[deltaRName]), _deltaAlpha(settingsGroup, _metaDataMap[deltaAlphaName]), _minLength(settingsGroup, _metaDataMap[transectMinLengthName]), - _reverse(settingsGroup, _metaDataMap[reverseName]), - _maxWaypoints(settingsGroup, _metaDataMap[maxWaypointsName]), - _isInitialized(false), _calculating(false), _cancle(false) { + _isInitialized(false), _pWorker(std::make_unique()), + _needsStoring(false), _needsReversal(false) { Q_UNUSED(kmlOrShpFile) _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml"; - // Defer update if facts or ref. changes. - connect(&_deltaR, &Fact::valueChanged, this, &CircularSurvey::_deferUpdate); + // Connect facts. + connect(&_deltaR, &Fact::valueChanged, this, + &CircularSurvey::_rebuildTransects); connect(&_deltaAlpha, &Fact::valueChanged, this, - &CircularSurvey::_deferUpdate); + &CircularSurvey::_rebuildTransects); connect(&_minLength, &Fact::valueChanged, this, - &CircularSurvey::_deferUpdate); - connect(&_maxWaypoints, &Fact::valueChanged, [this] { - this->CircularSurvey::_deferUpdate(); - qWarning() << "max waypoints implementaion missing"; - }); - connect(&_reverse, &Fact::valueChanged, [this] { - this->CircularSurvey::_deferUpdate(); - qWarning() << "reverse implementaion missing"; - }); + &CircularSurvey::_rebuildTransects); connect(this, &CircularSurvey::refPointChanged, this, - &CircularSurvey::_deferUpdate); - connect(&this->_surveyAreaPolygon, &QGCMapPolygon::pathChanged, this, - &CircularSurvey::_deferUpdate); - // Setup Timer. - _timer.setSingleShot(true); - connect(&_timer, &QTimer::timeout, [this] { this->_rebuildTransects(); }); - - // Future watcher. - connect(&_watcher, &Watcher::finished, [this] { - this->_calculating = false; - emit calculatingChanged(); - if (!_cancle) { - this->_transectsDirty = false; - } else { - _cancle = false; - } - this->_rebuildTransects(); - }); + &CircularSurvey::_rebuildTransects); + // Connect worker. + qRegisterMetaType("PtrRoute"); + connect(this->_pWorker.get(), &CSWorker::ready, this, + &CircularSurvey::_setTransects); + connect(this->_pWorker.get(), &CSWorker::calculatingChanged, this, + &CircularSurvey::calculatingChanged); + this->_transectsDirty = false; } +CircularSurvey::~CircularSurvey() {} + void CircularSurvey::resetReference() { setRefPoint(_surveyAreaPolygon.center()); } +void CircularSurvey::reverse() { + this->_needsReversal = true; + this->_rebuildTransects(); +} + void CircularSurvey::setRefPoint(const QGeoCoordinate &refPt) { if (refPt != _referencePoint) { _referencePoint = refPt; @@ -123,8 +96,8 @@ bool CircularSurvey::isInitialized() { return _isInitialized; } 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 + // We need to pull version first to determine what validation/conversion + // needs to be performed QList versionKeyInfoList = { {JsonHelper::jsonVersionKey, QJsonValue::Double, true}, }; @@ -145,7 +118,6 @@ bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber, {deltaRName, QJsonValue::Double, true}, {deltaAlphaName, QJsonValue::Double, true}, {transectMinLengthName, QJsonValue::Double, true}, - {reverseName, QJsonValue::Bool, true}, {refPointLatitudeName, QJsonValue::Double, true}, {refPointLongitudeName, QJsonValue::Double, true}, {refPointAltitudeName, QJsonValue::Double, true}, @@ -160,11 +132,11 @@ bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber, 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); + errorString = tr("%1 does not support loading this complex mission item " + "type: %2:%3") + .arg(qgcApp()->applicationName()) + .arg(itemType) + .arg(complexType); return false; } @@ -189,7 +161,6 @@ bool CircularSurvey::load(const QJsonObject &complexObject, int sequenceNumber, _referencePoint.setLongitude(complexObject[refPointLongitudeName].toDouble()); _referencePoint.setLatitude(complexObject[refPointLatitudeName].toDouble()); _referencePoint.setAltitude(complexObject[refPointAltitudeName].toDouble()); - _reverse.setRawValue(complexObject[reverseName].toBool()); setIsInitialized(true); _ignoreRecalc = false; @@ -220,7 +191,6 @@ void CircularSurvey::save(QJsonArray &planItems) { saveObject[deltaRName] = _deltaR.rawValue().toDouble(); saveObject[deltaAlphaName] = _deltaAlpha.rawValue().toDouble(); saveObject[transectMinLengthName] = _minLength.rawValue().toDouble(); - saveObject[reverseName] = _reverse.rawValue().toBool(); saveObject[refPointLongitudeName] = _referencePoint.longitude(); saveObject[refPointLatitudeName] = _referencePoint.latitude(); saveObject[refPointAltitudeName] = _referencePoint.altitude(); @@ -279,8 +249,8 @@ void CircularSurvey::_buildAndAppendMissionItems(QList &items, 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, // 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::quiet_NaN(), // Yaw unchanged @@ -318,260 +288,43 @@ bool CircularSurvey::readyForSave() const { double CircularSurvey::additionalTimeDelay() const { return 0; } void CircularSurvey::_rebuildTransectsPhase1(void) { - if (_calculating) - return; - if (!_transectsDirty) { - auto pTransects = _watcher.result(); - if (pTransects) { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() - << "CircularSurvey::_rebuildTransectsPhase1(): storing transects."; -#endif - // If the transects are getting rebuilt then any previously loaded - // mission items are now invalid. - if (_loadedMissionItemsParent) { - _loadedMissionItems.clear(); - _loadedMissionItemsParent->deleteLater(); - _loadedMissionItemsParent = nullptr; - } - // Store new transects; - _transects = *pTransects; + if (this->_needsStoring) { + // If the transects are getting rebuilt then any previously loaded + // mission items are now invalid. + if (_loadedMissionItemsParent) { + _loadedMissionItems.clear(); + _loadedMissionItemsParent->deleteLater(); + _loadedMissionItemsParent = nullptr; } - } else { - _transects.clear(); - // Check preconitions -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() - << "CircularSurvey::_rebuildTransectsPhase1(): checking preconditions."; -#endif - if (_isInitialized && _surveyAreaPolygon.count() >= 3 && - _deltaAlpha.rawValue() <= _deltaAlpha.rawMax() && - _deltaAlpha.rawValue() >= _deltaAlpha.rawMin() && - _deltaR.rawValue() <= _deltaR.rawMax() && - _deltaR.rawValue() >= _deltaR.rawMin()) { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() - << "CircularSurvey::_rebuildTransectsPhase1(): preconditions ok."; -#endif - using namespace boost::units; - _calculating = true; - emit calculatingChanged(); - // Copy input. - const auto &polygon = this->_surveyAreaPolygon.coordinateList(); - const auto &origin = this->_referencePoint; - const snake::Length deltaR(this->_deltaR.rawValue().toDouble() * - si::meter); - const snake::Angle deltaAlpha(this->_deltaAlpha.rawValue().toDouble() * - degree::degree); - const snake::Length minLength(this->_minLength.rawValue().toDouble() * - si::meter); - const auto maxWaypoints(this->_maxWaypoints.rawValue().toUInt()); - auto future = QtConcurrent::run([polygon, origin, deltaAlpha, minLength, - maxWaypoints, deltaR] { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "CircularSurvey::_rebuildTransectsPhase1(): calculation " - "started."; -#endif -#ifdef SHOW_CIRCULAR_SURVEY_TIME - auto start = std::chrono::high_resolution_clock::now(); - auto onExit = [&start] { - qWarning() << "CircularSurvey: concurrent update execution time: " - << std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - start) - .count() - << " ms"; - }; - CommandRAII commandRAII(onExit); -#endif - // Convert geo polygon to ENU polygon. - snake::BoostPolygon polygonENU; - snake::BoostPoint originENU{0, 0}; - snake::areaToEnu(origin, polygon, polygonENU); - std::string error; - // Check validity. - if (!bg::is_valid(polygonENU, error)) { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "CircularSurvey::_rebuildTransectsPhase1(): " - "invalid polygon."; - qWarning() << error.c_str(); -#endif - return PtrTransects(); - } else { - // Calculate polygon distances and angles. - std::vector distances; - distances.reserve(polygonENU.outer().size()); - std::vector angles; - angles.reserve(polygonENU.outer().size()); - for (const auto &p : polygonENU.outer()) { - snake::Length distance = bg::distance(originENU, 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 - qWarning() << "distances, angles, coordinates:"; - qWarning() << to_string(distance).c_str(); - qWarning() << to_string(snake::Degree(alpha)).c_str(); - qWarning() << "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(originENU, polygonENU)) { - rMin = bg::distance(originENU, polygonENU) * 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(originENU.get<0>())), - ClipperLib::cInt(std::round(originENU.get<1>()))}; - -#ifdef SHOW_CIRCULAR_SURVEY_TIME - auto s1 = std::chrono::high_resolution_clock::now(); -#endif - // Generate circle sectors. - auto rScaled = rMinScaled; - const auto nTran = long(std::ceil(((rMax - rMin) / deltaR).value())); - vector sectors(nTran, ClipperLib::Path()); - const auto nSectors = - long(std::round(((alpha2 - alpha1) / deltaAlpha).value())); -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "sector parameres:"; - qWarning() << "alpha1: " << to_string(snake::Degree(alpha1)).c_str(); - qWarning() << "alpha2: " << to_string(snake::Degree(alpha2)).c_str(); - qWarning() << "n: " - << to_string((alpha2 - alpha1) / deltaAlpha).c_str(); - qWarning() << "nSectors: " << nSectors; - qWarning() << "rMin: " << to_string(rMin).c_str(); - qWarning() << "rMax: " << to_string(rMax).c_str(); - qWarning() << "nTran: " << nTran; -#endif - using ClipperCircle = - GenericCircle; - for (auto §or : sectors) { - ClipperCircle circle(rScaled, originScaled); - approximate(circle, nSectors, sector); - rScaled += deltaRScaled; - } - // Clip sectors to polygonENU. - ClipperLib::Path polygonClipper; - auto &outer = polygonENU.outer(); - polygonClipper.reserve(outer.size() - 1); - 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); - - // Extract transects from PolyTree and convert them to - // BoostLineString - snake::Transects transectsENU; - for (const auto &child : transectsClipper.Childs) { - snake::BoostLineString transect; - transect.reserve(child->Contour.size()); - for (const auto &vertex : child->Contour) { - auto x = static_cast(vertex.X) / CLIPPER_SCALE; - auto y = static_cast(vertex.Y) / CLIPPER_SCALE; - transect.push_back(snake::BoostPoint(x, y)); - } - if (bg::length(transect) >= minLength.value()) { - transectsENU.push_back(transect); - } - } - // Join sectors which where slit due to clipping. - static_assert(false, "continue here."); - for (std::size_t i = 0; i < transectsENU.size() - 1; ++i) { - const auto &t1 = transectsENU[i]; - for (std::size_t j = i + 1; j < transectsENU.size(); ++j) { - const auto &t2 = transectsENU[j]; - } - } -#ifdef SHOW_CIRCULAR_SURVEY_TIME - qWarning() << "CircularSurvey: concurrent update transect gen. time: " - << std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - s1) - .count() - << " ms"; -#endif - - if (transectsENU.size() == 0) { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "CircularSurvey::_rebuildTransectsPhase1(): " - "not able to generate transects."; -#endif - return PtrTransects(); - } - - // Route transects; - std::vector transectsInfo; - snake::Route route; - std::string errorString; - bool success = snake::route(polygonENU, transectsENU, transectsInfo, - route, errorString); - if (!success) { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "CircularSurvey::_rebuildTransectsPhase1(): " - "routing failed."; -#endif - return PtrTransects(); - } - - // Remove return path. - const auto &info = transectsInfo.back(); - const auto &lastTransect = transectsENU[info.index]; - const auto &lastWaypoint = - info.reversed ? lastTransect.front() : lastTransect.back(); - auto &wp = route.back(); - while (wp != lastWaypoint) { - route.pop_back(); - wp = route.back(); - } - - // Convert to geo coordinates. - QList transectList; - transectList.reserve(route.size()); - for (const auto &vertex : route) { - QGeoCoordinate c; - snake::fromENU(origin, vertex, c); - CoordInfo_t coordinfo = {c, CoordTypeInterior}; - transectList.append(coordinfo); - } - PtrTransects pTransects(new Transects()); - pTransects->append(transectList); -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "CircularSurvey::_rebuildTransectsPhase1(): " - "concurrent update success."; -#endif - return pTransects; - } - }); // QtConcurrent::run() - _watcher.setFuture(future); + // Copy Transects. + QList list; + for (const auto c : *this->_pRoute) { + list.append(CoordInfo_t{c, CoordTypeInterior}); } -#ifdef DEBUG_CIRCULAR_SURVEY - else { - qWarning() - << "CircularSurvey::_rebuildTransectsPhase1(): preconditions failed."; + this->_transects.append(list); + // Mark transect as stored; + this->_needsStoring = false; + } else if (this->_needsReversal) { + if (this->_transects.size() > 0) { + auto &t = this->_transects.front(); + QList list; + list.reserve(t.size()); + for (auto it = t.end() - 1; it >= t.begin(); --it) { + list.append(*it); + } + this->_transects.clear(); + this->_transects.append(list); } -#endif + this->_needsReversal = false; + } else { + this->_transects.clear(); + auto polygon = this->_surveyAreaPolygon.coordinateList(); + this->_pWorker->update( + polygon, this->_referencePoint, + this->_deltaR.rawValue().toDouble() * bu::si::meter, + this->_minLength.rawValue().toDouble() * bu::si::meter, + snake::Angle(this->_deltaAlpha.rawValue().toDouble() * + bu::degree::degree)); } } @@ -590,40 +343,27 @@ void CircularSurvey::_recalcComplexDistance() { // no cameraShots in Circular Survey, add if desired void CircularSurvey::_recalcCameraShots() { _cameraShots = 0; } -void CircularSurvey::_deferUpdate() { - if (!_calculating) { -#ifdef DEBUG_CIRCULAR_SURVEY - qWarning() << "CircularSurvey::_deferUpdate(): defer update."; -#endif - _transectsDirty = true; - if (_timer.isActive()) { - _timer.stop(); - } - _timer.start(100 /*ms*/); - } else { - _cancle = true; - } +void CircularSurvey::_setTransects(CircularSurvey::PtrRoute pRoute) { + this->_pRoute = pRoute; + this->_needsStoring = true; + this->_rebuildTransects(); } Fact *CircularSurvey::transectMinLength() { return &_minLength; } -Fact *CircularSurvey::reverse() { return &_reverse; } - -Fact *CircularSurvey::maxWaypoints() { return &_maxWaypoints; } - -bool CircularSurvey::calculating() { return _calculating; } +bool CircularSurvey::calculating() { return this->_pWorker->calculating(); } /*! \class CircularSurveyComplexItem \inmodule Wima - \brief The \c CircularSurveyComplexItem class provides a survey mission item - with circular transects around a point of interest. + \brief The \c CircularSurveyComplexItem class provides a survey mission + item with circular transects around a point of interest. - 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). + 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). \sa WimaArea */ diff --git a/src/Wima/CircularSurvey.h b/src/Wima/CircularSurvey.h index 4cadb5c96..752b423c4 100644 --- a/src/Wima/CircularSurvey.h +++ b/src/Wima/CircularSurvey.h @@ -6,9 +6,14 @@ #include "SettingsFact.h" #include "TransectStyleComplexItem.h" +class CSWorker; + class CircularSurvey : public TransectStyleComplexItem { Q_OBJECT public: + using Route = QList; + using PtrRoute = QSharedPointer; + /// @param vehicle Vehicle which this is being contructed for /// @param flyView true: Created for use in the Fly View, false: Created for /// use in the Plan View @@ -16,19 +21,19 @@ public: /// polygon CircularSurvey(Vehicle *vehicle, bool flyView, const QString &kmlOrShpFile, QObject *parent); + ~CircularSurvey(); Q_PROPERTY(QGeoCoordinate refPoint READ refPoint WRITE setRefPoint NOTIFY refPointChanged) Q_PROPERTY(Fact *deltaR READ deltaR CONSTANT) Q_PROPERTY(Fact *deltaAlpha READ deltaAlpha CONSTANT) Q_PROPERTY(Fact *transectMinLength READ transectMinLength CONSTANT) - Q_PROPERTY(Fact *reverse READ reverse CONSTANT) - Q_PROPERTY(Fact *maxWaypoints READ maxWaypoints CONSTANT) Q_PROPERTY(bool isInitialized READ isInitialized WRITE setIsInitialized NOTIFY isInitializedChanged) Q_PROPERTY(bool calculating READ calculating NOTIFY calculatingChanged) Q_INVOKABLE void resetReference(void); + Q_INVOKABLE void reverse(void); // Property setters void setRefPoint(const QGeoCoordinate &refPt); @@ -41,8 +46,6 @@ public: Fact *deltaR(); Fact *deltaAlpha(); Fact *transectMinLength(); - Fact *reverse(); - Fact *maxWaypoints(); bool calculating(); // Is true if survey was automatically generated, prevents initialisation from // gui. @@ -72,8 +75,6 @@ public: static const char *deltaRName; static const char *deltaAlphaName; static const char *transectMinLengthName; - static const char *reverseName; - static const char *maxWaypointsName; static const char *CircularSurveyName; static const char *refPointLongitudeName; static const char *refPointLatitudeName; @@ -89,7 +90,7 @@ private slots: void _rebuildTransectsPhase1(void) final; void _recalcComplexDistance(void) final; void _recalcCameraShots(void) final; - void _deferUpdate(); + void _setTransects(PtrRoute pRoute); private: void _appendLoadedMissionItems(QList &items, @@ -108,21 +109,14 @@ private: // minimal transect lenght, transects are rejected if they are shorter than // this value SettingsFact _minLength; - // reverses the _transects path - SettingsFact _reverse; - // the maximum number of waypoints _transects (TransectStyleComplexItem) can - // contain (to avoid performance hits) - SettingsFact _maxWaypoints; - // Timer to defer recalc - QTimer _timer; // indicates if the polygon and refpoint etc. are initialized, prevents // reinitialisation from gui and execution of _rebuildTransectsPhase1 during // init from gui bool _isInitialized; - using PtrTransects = std::shared_ptr; - using Watcher = QFutureWatcher; - Watcher _watcher; - bool _calculating; - bool _cancle; + using PtrWorker = std::shared_ptr; + PtrWorker _pWorker; + PtrRoute _pRoute; + bool _needsStoring; + bool _needsReversal; }; diff --git a/src/Wima/Snake/snake.cpp b/src/Wima/Snake/snake.cpp index eca906455..fe8a4a8cf 100644 --- a/src/Wima/Snake/snake.cpp +++ b/src/Wima/Snake/snake.cpp @@ -781,8 +781,8 @@ void generateRoutingModel(const BoostLineString &vertices, } bool route(const BoostPolygon &area, const Transects &transects, - std::vector &transectInfo, Route &route, - string &errorString) { + std::vector &transectInfo, Route &r, + std::function stop, string &errorString) { //======================================= // Route Transects using Google or-tools. //======================================= @@ -819,9 +819,17 @@ bool route(const BoostPolygon &area, const Transects &transects, vertices.push_back(vertex); } size_t n1 = vertices.size(); + if (stop()) { + errorString = "User termination."; + return false; + } // Generate routing model. RoutingDataModel dataModel; Matrix connectionGraph(n1, n1); + if (stop()) { + errorString = "User termination."; + return false; + } // Offset joined area. BoostPolygon areaOffset; offsetPolygon(area, areaOffset, detail::offsetConstant); @@ -835,6 +843,10 @@ bool route(const BoostPolygon &area, const Transects &transects, cout << "Execution time _generateRoutingModel(): " << delta.count() << " ms" << endl; #endif + if (stop()) { + errorString = "User termination."; + return false; + } // Create Routing Index Manager. RoutingIndexManager manager(dataModel.distanceMatrix.getN(), @@ -883,16 +895,13 @@ bool route(const BoostPolygon &area, const Transects &transects, index += 1; } } - - // Setting first solution heuristic. - RoutingSearchParameters searchParameters = DefaultRoutingSearchParameters(); + // Set first solution heuristic. + auto searchParameters = DefaultRoutingSearchParameters(); searchParameters.set_first_solution_strategy( FirstSolutionStrategy::PATH_CHEAPEST_ARC); - google::protobuf::Duration *tMax = - new google::protobuf::Duration(); // seconds - tMax->set_seconds(10); - searchParameters.set_allocated_time_limit(tMax); - + // Set costume limit. + auto *limit = solver->MakeCustomLimit(stop); + routing.AddSearchMonitor(limit); // Solve the problem. #ifdef SNAKE_SHOW_TIME start = std::chrono::high_resolution_clock::now(); @@ -908,6 +917,9 @@ bool route(const BoostPolygon &area, const Transects &transects, if (!solution || solution->Size() <= 1) { errorString = "Not able to solve the routing problem."; return false; + } else if (stop()) { + errorString = "User terminated."; + return false; } // Extract index list from solution. @@ -944,12 +956,12 @@ bool route(const BoostPolygon &area, const Transects &transects, } for (auto it = reversedTransect.begin(); it < reversedTransect.end() - 1; ++it) { - route.push_back(*it); + r.push_back(*it); } } else { const auto &t = transects[info1.index]; for (auto it = t.begin(); it < t.end() - 1; ++it) { - route.push_back(*it); + r.push_back(*it); } } } else { @@ -958,12 +970,19 @@ bool route(const BoostPolygon &area, const Transects &transects, if (i != route_idx.size() - 2) { idxList.pop_back(); } - idx2Vertex(idxList, route); + idx2Vertex(idxList, r); } } return true; } +bool route(const BoostPolygon &area, const Transects &transects, + std::vector &transectInfo, Route &r, + string &errorString) { + auto stop = [] { return false; }; + return route(area, transects, transectInfo, r, stop, errorString); +} + } // namespace snake bool boost::geometry::model::operator==(snake::BoostPoint p1, diff --git a/src/Wima/Snake/snake.h b/src/Wima/Snake/snake.h index 7b710c9b3..7f8b844af 100644 --- a/src/Wima/Snake/snake.h +++ b/src/Wima/Snake/snake.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -185,8 +187,11 @@ struct TransectInfo { bool reversed; }; bool route(const BoostPolygon &area, const Transects &transects, - std::vector &transectInfo, Route &route, + std::vector &transectInfo, Route &r, string &errorString); +bool route(const BoostPolygon &area, const Transects &transects, + std::vector &transectInfo, Route &r, + std::function stop, string &errorString); namespace detail { const double offsetConstant = diff --git a/src/Wima/WimaPlaner.cc b/src/Wima/WimaPlaner.cc index 3c159e46a..8d42d4a3d 100644 --- a/src/Wima/WimaPlaner.cc +++ b/src/Wima/WimaPlaner.cc @@ -21,12 +21,13 @@ const char *WimaPlaner::areaItemsName = "AreaItems"; const char *WimaPlaner::missionItemsName = "MissionItems"; WimaPlaner::WimaPlaner(QObject *parent) - : QObject(parent), _currentAreaIndex(-1), _wimaBridge(nullptr), - _joinedArea(this), _joinedAreaValid(false), _measurementArea(this), - _serviceArea(this), _corridor(this), _TSComplexItem(nullptr), - _surveyRefChanging(false), _measurementAreaChanging(false), - _corridorChanging(false), _serviceAreaChanging(false), - _syncronizedWithController(false), _readyForSync(false) { + : QObject(parent), _masterController(nullptr), _missionController(nullptr), + _currentAreaIndex(-1), _wimaBridge(nullptr), _joinedArea(this), + _joinedAreaValid(false), _arrivalPathLength(0), _returnPathLength(0), + _TSComplexItem(nullptr), _surveyRefChanging(false), + _measurementAreaChanging(false), _corridorChanging(false), + _serviceAreaChanging(false), _syncronizedWithController(false), + _readyForSync(false) { _lastMeasurementAreaPath = _measurementArea.path(); _lastServiceAreaPath = _serviceArea.path(); _lastCorridorPath = _corridor.path(); diff --git a/src/Wima/WimaPlaner.h b/src/Wima/WimaPlaner.h index 03439cad2..ec5729dff 100644 --- a/src/Wima/WimaPlaner.h +++ b/src/Wima/WimaPlaner.h @@ -49,16 +49,16 @@ public: Q_PROPERTY(bool readyForSync READ readyForSync NOTIFY readyForSyncChanged) // Property accessors - PlanMasterController *masterController(void) { return _masterController; } - MissionController *missionController(void) { return _missionController; } + PlanMasterController *masterController(void); + MissionController *missionController(void); QmlObjectListModel *visualItems(void); - int currentPolygonIndex(void) const { return _currentAreaIndex; } - QString currentFile(void) const { return _currentFile; } + int currentPolygonIndex(void) const; + QString currentFile(void) const; QStringList loadNameFilters(void) const; QStringList saveNameFilters(void) const; - QString fileExtension(void) const { return wimaFileExtension; } + QString fileExtension(void) const; QGeoCoordinate joinedAreaCenter(void) const; - WimaBridge *wimaBridge(void) { return _wimaBridge; } + WimaBridge *wimaBridge(void); // Property setters void setMasterController(PlanMasterController *masterController); @@ -145,22 +145,22 @@ private: PlanMasterController *_masterController; MissionController *_missionController; int _currentAreaIndex; - QString _currentFile; // file for saveing - WimaBridge *_wimaBridge; // container for data exchange with WimaController - QmlObjectListModel _visualItems; // contains all visible areas - WimaJoinedArea _joinedArea; // joined area fromed by _measurementArea, - // _serviceArea, _corridor + QString _currentFile; + // container for data exchange with WimaController + WimaBridge *_wimaBridge; + bool _joinedAreaValid; - WimaMeasurementArea _measurementArea; // measurement area - WimaServiceArea _serviceArea; // area for supplying - WimaCorridor - _corridor; // corridor connecting _measurementArea and _serviceArea - unsigned long - _arrivalPathLength; // the number waypoints the arrival path consists of - // (path from takeoff to first measurement point) - unsigned long - _returnPathLength; // the number waypoints the return path consists of - // (path from last measurement point to land) + WimaMeasurementArea _measurementArea; + WimaServiceArea _serviceArea; + WimaCorridor _corridor; + // contains all visible areas + QmlObjectListModel _visualItems; + // joined area fromed by _measurementArea, _serviceArea, _corridor + WimaJoinedArea _joinedArea; + // path from takeoff to first measurement point + unsigned long _arrivalPathLength; + // path from last measurement point to land + unsigned long _returnPathLength; CircularSurvey *_TSComplexItem; // pointer to the CircularSurvey item in // _missionController.visualItems() diff --git a/src/WimaView/CircularSurveyMapVisual.qml b/src/WimaView/CircularSurveyMapVisual.qml index 4ad393dad..62c7a3045 100644 --- a/src/WimaView/CircularSurveyMapVisual.qml +++ b/src/WimaView/CircularSurveyMapVisual.qml @@ -7,8 +7,6 @@ * ****************************************************************************/ -// original file: SurveyMapVisual.qml - import QtQuick 2.3 import QtQuick.Controls 1.2 import QtLocation 5.3 -- 2.22.0