diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 23f50a5f4eb8ff8ddf2d2345c13783409e405bae..d9f799cc1dd93865106ea083a2f713dae524bce0 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -434,6 +434,7 @@ HEADERS += \ src/Wima/Geometry/GenericPolygon.h \ src/Wima/Geometry/GenericPolygonArray.h \ src/Wima/Geometry/GeoPoint3D.h \ + src/Wima/Snake/NemoInterface.h \ src/Wima/Snake/QNemoHeartbeat.h \ src/Wima/Snake/QNemoProgress.h \ src/Wima/Snake/QNemoProgress.h \ @@ -500,6 +501,7 @@ SOURCES += \ src/Snake/clipper/clipper.cpp \ src/Snake/snake.cpp \ src/Wima/Geometry/GeoPoint3D.cpp \ + src/Wima/Snake/NemoInterface.cpp \ src/Wima/Snake/QNemoProgress.cc \ src/Wima/Snake/SnakeDataManager.cc \ src/Wima/Snake/SnakeTile.cpp \ diff --git a/src/Snake/snake.cpp b/src/Snake/snake.cpp index c0c9ecd4eff9c19ecf3d2bbca7750718e82a2477..7bab2fb03a383fe88b9b99bf5ea29e6cd7d66996 100644 --- a/src/Snake/snake.cpp +++ b/src/Snake/snake.cpp @@ -454,7 +454,7 @@ bool Scenario::update() { return true; } -bool Scenario::_calculateBoundingBox() { +bool Scenario::_calculateBoundingBox() const { return minimalBoundingBox(_mArea, _mAreaBoundingBox); } @@ -474,7 +474,7 @@ bool Scenario::_calculateBoundingBox() { * * @return Returns true if successful. */ -bool Scenario::_calculateTiles() { +bool Scenario::_calculateTiles() const { _tiles.clear(); _tileCenterPoints.clear(); @@ -571,7 +571,7 @@ bool Scenario::_calculateTiles() { return true; } -bool Scenario::_calculateJoinedArea() { +bool Scenario::_calculateJoinedArea() const { _jArea.clear(); // Measurement area and service area overlapping? bool overlapingSerMeas = bg::intersects(_mArea, _sArea) ? true : false; @@ -775,7 +775,7 @@ bool flight_plan::transectsFromScenario(Length distance, Length minLength, ClipperLib::PolyTree clippedTransecs; clipper.Execute(ClipperLib::ctIntersection, clippedTransecs, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - auto &transects = clippedTransecs; + const auto *transects = &clippedTransecs; bool ignoreProgress = p.size() != scenario.tiles().size(); ClipperLib::PolyTree clippedTransecs2; @@ -793,9 +793,9 @@ bool flight_plan::transectsFromScenario(Length distance, Length minLength, if (processedTiles.size() != numTiles) { vector processedTilesClipper; - for (auto t : processedTiles) { + for (const auto &t : processedTiles) { ClipperLib::Path path; - for (auto vertex : t.outer()) { + for (const auto &vertex : t.outer()) { path.push_back(ClipperLib::IntPoint{ static_cast(vertex.get<0>() * CLIPPER_SCALE), static_cast(vertex.get<1>() * CLIPPER_SCALE)}); @@ -805,26 +805,31 @@ bool flight_plan::transectsFromScenario(Length distance, Length minLength, // Subtract holes (tiles with measurement_progress == 100) from transects. clipper.Clear(); - for (auto &child : clippedTransecs.Childs) + for (const auto &child : clippedTransecs.Childs) { clipper.AddPath(child->Contour, ClipperLib::ptSubject, false); + } clipper.AddPaths(processedTilesClipper, ClipperLib::ptClip, true); clipper.Execute(ClipperLib::ctDifference, clippedTransecs2, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - transects = clippedTransecs2; + transects = &clippedTransecs2; + } else { + // All tiles processed (t.size() not changed). + return true; } } // Extract transects from PolyTree and convert them to BoostLineString - for (auto &child : transects.Childs) { - auto &clipperTransect = child->Contour; + for (const auto &child : transects->Childs) { + const auto &clipperTransect = child->Contour; BoostPoint v1{static_cast(clipperTransect[0].X) / CLIPPER_SCALE, static_cast(clipperTransect[0].Y) / CLIPPER_SCALE}; BoostPoint v2{static_cast(clipperTransect[1].X) / CLIPPER_SCALE, static_cast(clipperTransect[1].Y) / CLIPPER_SCALE}; BoostLineString transect{v1, v2}; - if (bg::length(transect) >= minLength.value()) + if (bg::length(transect) >= minLength.value()) { t.push_back(transect); + } } if (t.size() == 0) { diff --git a/src/Snake/snake.h b/src/Snake/snake.h index 2e77bf521e2043f44f338d394c5b862761c873dc..145fffdd1e5cee4cfe7e49013716ded8646b7ac7 100644 --- a/src/Snake/snake.h +++ b/src/Snake/snake.h @@ -173,14 +173,14 @@ public: const BoundingBox &measurementAreaBBox() const; const BoostPoint &homePositon() const; - bool update(); + bool update() const; - string errorString; + mutable string errorString; private: - bool _calculateBoundingBox(); - bool _calculateTiles(); - bool _calculateJoinedArea(); + bool _calculateBoundingBox() const; + bool _calculateTiles() const; + bool _calculateJoinedArea() const; Length _tileWidth; Length _tileHeight; @@ -191,12 +191,12 @@ private: BoostPolygon _mArea; BoostPolygon _sArea; BoostPolygon _corridor; - BoostPolygon _jArea; + mutable BoostPolygon _jArea; - BoundingBox _mAreaBoundingBox; - vector _tiles; - BoostLineString _tileCenterPoints; - BoostPoint _homePosition; + mutable BoundingBox _mAreaBoundingBox; + mutable vector _tiles; + mutable BoostLineString _tileCenterPoints; + mutable BoostPoint _homePosition; }; template class Container> diff --git a/src/Wima/Snake/NemoInterface.cpp b/src/Wima/Snake/NemoInterface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eb2c70ebde781fabd1df16a8cd149f6093ae848b --- /dev/null +++ b/src/Wima/Snake/NemoInterface.cpp @@ -0,0 +1,341 @@ +#include "NemoInterface.h" + +#include "QGCApplication.h" +#include "QGCToolbox.h" +#include "SettingsFact.h" +#include "SettingsManager.h" +#include "WimaSettings.h" + +#include + +#include + +#include "QNemoHeartbeat.h" +#include "QNemoProgress.h" + +#include "ros_bridge/include/messages/geographic_msgs/geopoint.h" +#include "ros_bridge/include/messages/jsk_recognition_msgs/polygon_array.h" +#include "ros_bridge/include/messages/nemo_msgs/heartbeat.h" +#include "ros_bridge/include/messages/nemo_msgs/progress.h" +#include "ros_bridge/include/ros_bridge.h" +#include "ros_bridge/rapidjson/include/rapidjson/document.h" +#include "ros_bridge/rapidjson/include/rapidjson/ostreamwrapper.h" +#include "ros_bridge/rapidjson/include/rapidjson/writer.h" + +#define EVENT_TIMER_INTERVAL 100 // ms +auto static timeoutInterval = std::chrono::milliseconds(3000); + +using ROSBridgePtr = std::unique_ptr; +using JsonDocUPtr = ros_bridge::com_private::JsonDocUPtr; +using UniqueLock = std::unique_lock; +using SharedLock = std::shared_lock; +using JsonDocUPtr = ros_bridge::com_private::JsonDocUPtr; + +class NemoInterface::Impl { + using TimePoint = std::chrono::time_point; + +public: + Impl(NemoInterface *p); + + void start(); + void stop(); + void setTilesENU(const SnakeTilesLocal &tilesENU); + void setENUOrigin(const QGeoCoordinate &ENUOrigin); + NemoInterface::NemoStatus status(); + QVector progress(); + +private: + bool doTopicServiceSetup(); + void loop(); + //! + //! \brief Publishes tilesENU + //! \pre this->tilesENUMutex must be locked + //! + void publishTilesENU(); + //! + //! \brief Publishes ENUOrigin + //! \pre this->ENUOriginMutex must be locked + //! + void publishENUOrigin(); + + // Data. + SnakeTilesLocal tilesENU; + mutable std::shared_timed_mutex tilesENUMutex; + QGeoCoordinate ENUOrigin; + mutable std::shared_timed_mutex ENUOriginMutex; + QNemoProgress progress; + mutable std::shared_timed_mutex progressMutex; + QNemoHeartbeat heartbeat; + TimePoint nextTimeout; + mutable std::shared_timed_mutex heartbeatMutex; + + // Internals + bool running; + std::atomic_bool topicServiceSetupDone; + ROSBridgePtr pRosBridge; + QTimer loopTimer; + NemoInterface *parent; +}; + +NemoInterface::Impl::Impl(NemoInterface *p) + : nextTimeout(TimePoint::max()), running(false), + topicServiceSetupDone(false), parent(p) { + + // ROS Bridge. + WimaSettings *wimaSettings = + qgcApp()->toolbox()->settingsManager()->wimaSettings(); + auto connectionStringFact = wimaSettings->rosbridgeConnectionString(); + auto setConnectionString = [connectionStringFact, this] { + auto connectionString = connectionStringFact->rawValue().toString(); + if (ros_bridge::isValidConnectionString( + connectionString.toLocal8Bit().data())) { + this->pRosBridge.reset( + new ros_bridge::ROSBridge(connectionString.toLocal8Bit().data())); + } else { + QString defaultString("localhost:9090"); + qgcApp()->showMessage("ROS Bridge connection string invalid: " + + connectionString); + qgcApp()->showMessage("Resetting connection string to: " + defaultString); + connectionStringFact->setRawValue( + QVariant(defaultString)); // calls this function recursively + } + }; + connect(connectionStringFact, &SettingsFact::rawValueChanged, + setConnectionString); + setConnectionString(); + + // Periodic. + connect(&this->loopTimer, &QTimer::timeout, this, &NemoInterface::Impl::loop); + this->loopTimer.start(EVENT_TIMER_INTERVAL); +} + +void NemoInterface::Impl::start() { this->running = true; } + +void NemoInterface::Impl::stop() { this->running = false; } + +void NemoInterface::Impl::setTilesENU(const SnakeTilesLocal &tilesENU) { + UniqueLock lk(this->tilesENUMutex); + this->tilesENU = tilesENU; + lk.unlock(); + if (this->running && this->topicServiceSetupDone) { + lk.lock(); + this->publishTilesENU(); + } +} + +void NemoInterface::Impl::setENUOrigin(const QGeoCoordinate &ENUOrigin) { + UniqueLock lk(this->ENUOriginMutex); + this->ENUOrigin = ENUOrigin; + lk.unlock(); + if (this->running && this->topicServiceSetupDone) { + lk.lock(); + this->publishENUOrigin(); + } +} + +NemoInterface::NemoStatus NemoInterface::Impl::status() { + SharedLock lk(this->heartbeatMutex); + return NemoInterface::NemoStatus(heartbeat.status()); +} + +QVector NemoInterface::Impl::progress() { + SharedLock lk(this->progressMutex); + return this->progress.progress(); +} + +bool NemoInterface::Impl::doTopicServiceSetup() { + using namespace ros_bridge::messages; + + // Publish snake tiles. + UniqueLock lk(this->mutex); + { + SharedLock lk(this->tilesENUMutex); + + if (this->tilesENU.polygons().size() == 0) + return false; + this->pRosBridge->advertiseTopic( + "/snake/tiles", + jsk_recognition_msgs::polygon_array::messageType().c_str()); + this->publishTilesENU(); + } + + // Publish snake origin. + { + SharedLock lk(this->ENUOriginMutex); + + this->pRosBridge->advertiseTopic( + "/snake/origin", geographic_msgs::geo_point::messageType().c_str()); + this->publishENUOrigin(); + } + + // Subscribe nemo progress. + this->pRosBridge->subscribe( + "/nemo/progress", + /* callback */ [this](JsonDocUPtr pDoc) { + std::lock(this->progressMutex, this->tilesENUMutex, + this->ENUOriginMutex); + UniqueLock lk1(this->progressMutex, std::adopt_lock); + UniqueLock lk2(this->tilesENUMutex, std::adopt_lock); + UniqueLock lk3(this->ENUOriginMutex, std::adopt_lock); + + int requiredSize = this->tilesENU.polygons().size(); + auto &progressMsg = this->progress; + if (!nemo_msgs::progress::fromJson(*pDoc, progressMsg) || + progressMsg.progress().size() != + requiredSize) { // Some error occured. + progressMsg.progress().clear(); + + // Publish snake origin. + JsonDocUPtr jOrigin( + std::make_unique(rapidjson::kObjectType)); + bool ret = geographic_msgs::geo_point::toJson( + this->ENUOrigin, *jOrigin, jOrigin->GetAllocator()); + Q_ASSERT(ret); + (void)ret; + this->pRosBridge->publish(std::move(jOrigin), "/snake/origin"); + + // Publish snake tiles. + JsonDocUPtr jSnakeTiles( + std::make_unique(rapidjson::kObjectType)); + ret = jsk_recognition_msgs::polygon_array::toJson( + this->tilesENU, *jSnakeTiles, jSnakeTiles->GetAllocator()); + Q_ASSERT(ret); + (void)ret; + this->pRosBridge->publish(std::move(jSnakeTiles), "/snake/tiles"); + } + lk1.unlock(); + lk2.unlock(); + lk3.unlock(); + + emit this->parent->nemoProgressChanged(); + }); + + // Subscribe /nemo/heartbeat. + this->pRosBridge->subscribe( + "/nemo/heartbeat", + /* callback */ [this](JsonDocUPtr pDoc) { + UniqueLock lk(this->heartbeatMutex); + + auto &heartbeatMsg = this->heartbeat; + if (!nemo_msgs::heartbeat::fromJson(*pDoc, heartbeatMsg)) { + heartbeatMsg.setStatus(integral(NemoStatus::InvalidHeartbeat)); + this->nextTimeout = TimePoint::max(); + } else { + this->nextTimeout = + std::chrono::high_resolution_clock::now() + timeoutInterval; + } + + emit this->parent->nemoStatusChanged(heartbeatMsg.status()); + }); + + // Advertise /snake/get_origin. + this->pRosBridge->advertiseService( + "/snake/get_origin", "snake_msgs/GetOrigin", + [this](JsonDocUPtr) -> JsonDocUPtr { + using namespace ros_bridge::messages; + SharedLock lk(this->mutex); + + JsonDocUPtr pDoc( + std::make_unique(rapidjson::kObjectType)); + auto &origin = this->ENUOrigin; + rapidjson::Value jOrigin(rapidjson::kObjectType); + bool ret = geographic_msgs::geo_point::toJson(origin, jOrigin, + pDoc->GetAllocator()); + lk.unlock(); + Q_ASSERT(ret); + (void)ret; + pDoc->AddMember("origin", jOrigin, pDoc->GetAllocator()); + return pDoc; + }); + + // Advertise /snake/get_tiles. + this->pRosBridge->advertiseService( + "/snake/get_tiles", "snake_msgs/GetTiles", + [this](JsonDocUPtr) -> JsonDocUPtr { + SharedLock lk(this->mutex); + + JsonDocUPtr pDoc( + std::make_unique(rapidjson::kObjectType)); + rapidjson::Value jSnakeTiles(rapidjson::kObjectType); + bool ret = jsk_recognition_msgs::polygon_array::toJson( + this->tilesENU, jSnakeTiles, pDoc->GetAllocator()); + Q_ASSERT(ret); + (void)ret; + pDoc->AddMember("tiles", jSnakeTiles, pDoc->GetAllocator()); + return pDoc; + }); + + return true; +} + +void NemoInterface::Impl::loop() { + + // Check ROS Bridge status and do setup if necessary. + if (this->running) { + if (!this->pRosBridge->isRunning()) { + this->pRosBridge->start(); + } else if (this->pRosBridge->isRunning() && this->pRosBridge->connected() && + !this->topicServiceSetupDone) { + if (this->doTopicServiceSetup()) + this->topicServiceSetupDone = true; + } else if (this->pRosBridge->isRunning() && + !this->pRosBridge->connected() && this->topicServiceSetupDone) { + this->pRosBridge->reset(); + this->pRosBridge->start(); + this->topicServiceSetupDone = false; + } + } else if (this->pRosBridge->isRunning()) { + this->pRosBridge->reset(); + this->topicServiceSetupDone = false; + } + + // Check if heartbeat timeout occured. + if (this->running && this->topicServiceSetupDone && + this->nextTimeout != TimePoint::max()) { + if (this->nextTimeout < std::chrono::high_resolution_clock::now()) { + UniqueLock lk(this->heartbeatMutex); + this->heartbeat.setStatus(NemoStatus::Timeout); + emit this->parent->statusChanged(); + } + } +} + +void NemoInterface::Impl::publishTilesENU() { + JsonDocUPtr jSnakeTiles( + std::make_unique(rapidjson::kObjectType)); + ret = jsk_recognition_msgs::polygon_array::toJson( + this->tilesENU, *jSnakeTiles, jSnakeTiles->GetAllocator()); + Q_ASSERT(ret); + (void)ret; + this->pRosBridge->publish(std::move(jSnakeTiles), "/snake/tiles"); +} + +void NemoInterface::Impl::publishENUOrigin() { + JsonDocUPtr jOrigin( + std::make_unique(rapidjson::kObjectType)); + bool ret = geographic_msgs::geo_point::toJson(this->ENUOrigin, *jOrigin, + jOrigin->GetAllocator()); + Q_ASSERT(ret); + (void)ret; + this->pRosBridge->publish(std::move(jOrigin), "/snake/origin"); +} + +void NemoInterface::start() { this->pImpl->start(); } + +void NemoInterface::stop() { this->pImpl->stop(); } + +void NemoInterface::setTilesENU(const SnakeTilesLocal &tilesENU) { + this->pImpl->setTilesENU(tilesENU); +} + +void NemoInterface::setENUOrigin(const QGeoCoordinate &ENUOrigin) { + this->pImpl->setENUOrigin(ENUOrigin); +} + +NemoInterface::NemoStatus NemoInterface::status() { + return this->pImpl->status(); +} + +QVector NemoInterface::progress() { + return this->pImpl->progress.progress(); +} diff --git a/src/Wima/Snake/NemoInterface.h b/src/Wima/Snake/NemoInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..bcf44f8ca7f96348f98cb4bcbac073bab1c04d48 --- /dev/null +++ b/src/Wima/Snake/NemoInterface.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "SnakeTilesLocal.h" + +#include + +class NemoInterface : public QObject { + Q_OBJECT + class Impl; + using PImpl = std::unique_ptr; + +public: + enum class NemoStatus { + NotConnected = 0, + Connected = 1, + Timeout = -1, + InvalidHeartbeat = -2 + }; + + explicit NemoInterface(QObject *parent = nullptr); + + void start(); + void stop(); + + void setTilesENU(const SnakeTilesLocal &tilesENU); + void setENUOrigin(const QGeoCoordinate &ENUOrigin); + + NemoStatus status(); + QVector progress(); + +signals: + void statusChanged(); + void progressChanged(); + +private: + PImpl pImpl; +}; diff --git a/src/Wima/Snake/SnakeDataManager.cc b/src/Wima/Snake/SnakeDataManager.cc index 1e9cfd0a1b71fc10c9ee54b55d42640565257d0f..6e5ae45ea93e2724a5a210b1881d3b8f9db8c440 100644 --- a/src/Wima/Snake/SnakeDataManager.cc +++ b/src/Wima/Snake/SnakeDataManager.cc @@ -17,274 +17,76 @@ #include "Wima/Snake/SnakeTilesLocal.h" #include "snake.h" -#include "ros_bridge/include/messages/geographic_msgs/geopoint.h" -#include "ros_bridge/include/messages/jsk_recognition_msgs/polygon_array.h" -#include "ros_bridge/include/messages/nemo_msgs/heartbeat.h" -#include "ros_bridge/include/messages/nemo_msgs/progress.h" -#include "ros_bridge/include/ros_bridge.h" -#include "ros_bridge/rapidjson/include/rapidjson/document.h" -#include "ros_bridge/rapidjson/include/rapidjson/ostreamwrapper.h" -#include "ros_bridge/rapidjson/include/rapidjson/writer.h" - -#define EVENT_TIMER_INTERVAL 500 // ms -#define TIMEOUT 3000 // ms - using QVariantList = QList; -using ROSBridgePtr = std::unique_ptr; using UniqueLock = std::unique_lock; using SharedLock = std::shared_lock; -using JsonDocUPtr = ros_bridge::com_private::JsonDocUPtr; class SnakeDataManager::SnakeImpl { public: - SnakeImpl(SnakeDataManager *p) - : rosBridgeEnabeled(false), topicServiceSetupDone(false), - lineDistance(1 * si::meter), minTransectLength(1 * si::meter), - calcInProgress(false), parent(p) { - // ROS Bridge. - WimaSettings *wimaSettings = - qgcApp()->toolbox()->settingsManager()->wimaSettings(); - auto connectionStringFact = wimaSettings->rosbridgeConnectionString(); - auto setConnectionString = [connectionStringFact, this] { - auto connectionString = connectionStringFact->rawValue().toString(); - if (ros_bridge::isValidConnectionString( - connectionString.toLocal8Bit().data())) { - this->pRosBridge.reset( - new ros_bridge::ROSBridge(connectionString.toLocal8Bit().data())); - } else { - QString defaultString("localhost:9090"); - qgcApp()->showMessage("ROS Bridge connection string invalid: " + - connectionString); - qgcApp()->showMessage("Resetting connection string to: " + - defaultString); - connectionStringFact->setRawValue( - QVariant(defaultString)); // calls this function recursively - } - }; - connect(connectionStringFact, &SettingsFact::rawValueChanged, - setConnectionString); - setConnectionString(); - - // Periodic. - connect(&this->eventTimer, &QTimer::timeout, [this] { - if (this->rosBridgeEnabeled) { - if (!this->pRosBridge->isRunning()) { - this->pRosBridge->start(); - } else if (this->pRosBridge->isRunning() && - this->pRosBridge->connected() && - !this->topicServiceSetupDone) { - if (this->doTopicServiceSetup()) - this->topicServiceSetupDone = true; - } else if (this->pRosBridge->isRunning() && - !this->pRosBridge->connected() && - this->topicServiceSetupDone) { - this->pRosBridge->reset(); - this->pRosBridge->start(); - this->topicServiceSetupDone = false; - } - } else if (this->pRosBridge->isRunning()) { - this->pRosBridge->reset(); - this->topicServiceSetupDone = false; - } - }); - this->eventTimer.start(EVENT_TIMER_INTERVAL); - } + struct Input { + + Input() + : tileWidth(5 * si::meter), tileHeight(5 * si::meter), + minTileArea(1 * si::meter * si::meter), lineDistance(2 * si::meter), + minTransectLength(1 * si::meter), scenarioChanged(true), + routeParametersChanged(true) {} + + QList mArea; + QGeoCoordinate ENUOrigin; + QList sArea; + QList corridor; + Length tileWidth; + Length tileHeight; + Area minTileArea; + std::atomic_bool scenarioChanged; + + Length lineDistance; + Length minTransectLength; + std::atomic_bool routeParametersChanged; + + mutable std::shared_timed_mutex mutex; + }; + struct Output { + SnakeTiles tiles; + QmlObjectListModel tilesQml; + QVariantList tileCenterPoints; + SnakeTilesLocal tilesENU; + QVector tileCenterPointsENU; + + QVector waypoints; + QVector arrivalPath; + QVector returnPath; + QVector waypointsENU; + QVector arrivalPathENU; + QVector returnPathENU; + + QString errorMessage; + + std::atomic_bool calcInProgress; + + mutable std::shared_timed_mutex mutex; + }; + + SnakeImpl(SnakeDataManager *p); bool precondition() const; - void resetOutput(); bool doTopicServiceSetup(); - // Private data. - ROSBridgePtr pRosBridge; - bool rosBridgeEnabeled; - bool topicServiceSetupDone; - QTimer eventTimer; - QTimer timeout; - mutable std::shared_timed_mutex mutex; - - // Scenario + // Internal data. snake::Scenario scenario; - Length lineDistance; - Length minTransectLength; - QList mArea; - QList sArea; - QList corridor; - QGeoCoordinate ENUOrigin; - SnakeTiles tiles; - QmlObjectListModel tilesQml; - QVariantList tileCenterPoints; - SnakeTilesLocal tilesENU; - QVector tileCenterPointsENU; - - // Waypoints - QVector waypoints; - QVector arrivalPath; - QVector returnPath; - QVector waypointsENU; - QVector arrivalPathENU; - QVector returnPathENU; - QString errorMessage; - - // Other - std::atomic_bool calcInProgress; - QNemoProgress qProgress; - std::vector progress; - QNemoHeartbeat heartbeat; SnakeDataManager *parent; + + Input input; + // Output + Output output; }; -bool SnakeDataManager::SnakeImpl::doTopicServiceSetup() { - using namespace ros_bridge::messages; - UniqueLock lk(this->mutex); - - if (this->tilesENU.polygons().size() == 0) - return false; - - // Publish snake origin. - this->pRosBridge->advertiseTopic( - "/snake/origin", geographic_msgs::geo_point::messageType().c_str()); - JsonDocUPtr jOrigin( - std::make_unique(rapidjson::kObjectType)); - bool ret = geographic_msgs::geo_point::toJson(this->ENUOrigin, *jOrigin, - jOrigin->GetAllocator()); - Q_ASSERT(ret); - (void)ret; - this->pRosBridge->publish(std::move(jOrigin), "/snake/origin"); - - // Publish snake tiles. - this->pRosBridge->advertiseTopic( - "/snake/tiles", - jsk_recognition_msgs::polygon_array::messageType().c_str()); - JsonDocUPtr jSnakeTiles( - std::make_unique(rapidjson::kObjectType)); - ret = jsk_recognition_msgs::polygon_array::toJson( - this->tilesENU, *jSnakeTiles, jSnakeTiles->GetAllocator()); - Q_ASSERT(ret); - (void)ret; - this->pRosBridge->publish(std::move(jSnakeTiles), "/snake/tiles"); - - // Subscribe nemo progress. - this->pRosBridge->subscribe( - "/nemo/progress", - /* callback */ [this](JsonDocUPtr pDoc) { - UniqueLock lk(this->mutex); - int requiredSize = this->tilesENU.polygons().size(); - auto &progressMsg = this->qProgress; - if (!nemo_msgs::progress::fromJson(*pDoc, progressMsg) || - progressMsg.progress().size() != - requiredSize) { // Some error occured. - // Set progress to default. - progressMsg.progress().fill(0, requiredSize); - - // Publish snake origin. - JsonDocUPtr jOrigin( - std::make_unique(rapidjson::kObjectType)); - bool ret = geographic_msgs::geo_point::toJson( - this->ENUOrigin, *jOrigin, jOrigin->GetAllocator()); - Q_ASSERT(ret); - (void)ret; - this->pRosBridge->publish(std::move(jOrigin), "/snake/origin"); - - // Publish snake tiles. - JsonDocUPtr jSnakeTiles( - std::make_unique(rapidjson::kObjectType)); - ret = jsk_recognition_msgs::polygon_array::toJson( - this->tilesENU, *jSnakeTiles, jSnakeTiles->GetAllocator()); - Q_ASSERT(ret); - (void)ret; - this->pRosBridge->publish(std::move(jSnakeTiles), "/snake/tiles"); - } - this->progress.clear(); - for (const auto &p : progressMsg.progress()) { - this->progress.push_back(p); - } - lk.unlock(); - - emit this->parent->nemoProgressChanged(); - }); - - // Subscribe /nemo/heartbeat. - this->pRosBridge->subscribe( - "/nemo/heartbeat", - /* callback */ [this](JsonDocUPtr pDoc) { - UniqueLock lk(this->mutex); - if (this->timeout.isActive()) { - this->timeout.stop(); - } - auto &heartbeatMsg = this->heartbeat; - if (!nemo_msgs::heartbeat::fromJson(*pDoc, heartbeatMsg)) { - heartbeatMsg.setStatus(integral(NemoStatus::InvalidHeartbeat)); - } - this->timeout.singleShot(TIMEOUT, [this] { - UniqueLock lk(this->mutex); - this->heartbeat.setStatus(integral(NemoStatus::Timeout)); - emit this->parent->nemoStatusChanged(integral(NemoStatus::Timeout)); - }); - emit this->parent->nemoStatusChanged(heartbeatMsg.status()); - }); - - // Advertise /snake/get_origin. - this->pRosBridge->advertiseService( - "/snake/get_origin", "snake_msgs/GetOrigin", - [this](JsonDocUPtr) -> JsonDocUPtr { - using namespace ros_bridge::messages; - SharedLock lk(this->mutex); - - JsonDocUPtr pDoc( - std::make_unique(rapidjson::kObjectType)); - auto &origin = this->ENUOrigin; - rapidjson::Value jOrigin(rapidjson::kObjectType); - bool ret = geographic_msgs::geo_point::toJson(origin, jOrigin, - pDoc->GetAllocator()); - lk.unlock(); - Q_ASSERT(ret); - (void)ret; - pDoc->AddMember("origin", jOrigin, pDoc->GetAllocator()); - return pDoc; - }); - - // Advertise /snake/get_tiles. - this->pRosBridge->advertiseService( - "/snake/get_tiles", "snake_msgs/GetTiles", - [this](JsonDocUPtr) -> JsonDocUPtr { - SharedLock lk(this->mutex); - - JsonDocUPtr pDoc( - std::make_unique(rapidjson::kObjectType)); - rapidjson::Value jSnakeTiles(rapidjson::kObjectType); - bool ret = jsk_recognition_msgs::polygon_array::toJson( - this->tilesENU, jSnakeTiles, pDoc->GetAllocator()); - Q_ASSERT(ret); - (void)ret; - pDoc->AddMember("tiles", jSnakeTiles, pDoc->GetAllocator()); - return pDoc; - }); - - return true; -} +SnakeDataManager::SnakeImpl::SnakeImpl(SnakeDataManager *p) + : rosBridgeEnabeled(false), topicServiceSetupDone(false), parent(p) {} bool SnakeDataManager::SnakeImpl::precondition() const { return true; } -//! -//! \brief Resets waypoint data. -//! \pre this->_pImpl->mutex must be locked. -void SnakeDataManager::SnakeImpl::resetOutput() { - this->waypoints.clear(); - this->arrivalPath.clear(); - this->returnPath.clear(); - this->ENUOrigin = QGeoCoordinate(0.0, 0.0, 0.0); - this->waypointsENU.clear(); - this->arrivalPathENU.clear(); - this->returnPathENU.clear(); - this->tilesQml.clearAndDeleteContents(); - this->tiles.polygons().clear(); - this->tileCenterPoints.clear(); - this->tilesENU.polygons().clear(); - this->tileCenterPointsENU.clear(); - this->errorMessage.clear(); -} - template class CommandRAII { public: CommandRAII(Callable &fun) : _fun(fun) {} @@ -300,82 +102,84 @@ SnakeDataManager::SnakeDataManager(QObject *parent) {} -SnakeDataManager::~SnakeDataManager() {} +// SnakeDataManager::~SnakeDataManager() {} void SnakeDataManager::setMeasurementArea( const QList &measurementArea) { - UniqueLock lk(this->pImpl->mutex); - this->pImpl->mArea = measurementArea; - for (auto &vertex : this->pImpl->mArea) { + this.input.scenarioChanged = true; + UniqueLock lk(this->pImpl->input.mutex); + this->pImpl->inptu.mArea = measurementArea; + for (auto &vertex : this->pImpl->input.mArea) { vertex.setAltitude(0); } } void SnakeDataManager::setServiceArea( const QList &serviceArea) { - UniqueLock lk(this->pImpl->mutex); - this->pImpl->sArea = serviceArea; - for (auto &vertex : this->pImpl->sArea) { + this.input.scenarioChanged = true; + UniqueLock lk(this->pImpl->input.mutex); + this->pImpl->input.sArea = serviceArea; + for (auto &vertex : this->pImpl->input.sArea) { vertex.setAltitude(0); } } void SnakeDataManager::setCorridor(const QList &corridor) { - UniqueLock lk(this->pImpl->mutex); - this->pImpl->corridor = corridor; - for (auto &vertex : this->pImpl->corridor) { + this.input.scenarioChanged = true; + UniqueLock lk(this->pImpl->input.mutex); + this->pImpl->input.corridor = corridor; + for (auto &vertex : this->pImpl->input.corridor) { vertex.setAltitude(0); } } const QmlObjectListModel *SnakeDataManager::tiles() const { - SharedLock lk(this->pImpl->mutex); - return &this->pImpl->tilesQml; + SharedLock lk(this->pImpl->output.mutex); + return &this->pImpl->output.tilesQml; } QVariantList SnakeDataManager::tileCenterPoints() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->tileCenterPoints; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.tileCenterPoints; } QNemoProgress SnakeDataManager::nemoProgress() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->qProgress; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.qProgress; } int SnakeDataManager::nemoStatus() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->heartbeat.status(); + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.heartbeat.status(); } bool SnakeDataManager::calcInProgress() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->calcInProgress.load(); + return this->pImpl->output.calcInProgress.load(); } QString SnakeDataManager::errorMessage() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->errorMessage; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.errorMessage; } bool SnakeDataManager::success() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->errorMessage.isEmpty() ? true : false; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.errorMessage.isEmpty() ? true : false; } QVector SnakeDataManager::waypoints() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->waypoints; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.waypoints; } QVector SnakeDataManager::arrivalPath() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->arrivalPath; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.arrivalPath; } QVector SnakeDataManager::returnPath() const { - SharedLock lk(this->pImpl->mutex); - return this->pImpl->returnPath; + SharedLock lk(this->pImpl->output.mutex); + return this->pImpl->output.returnPath; } Length SnakeDataManager::lineDistance() const { @@ -385,6 +189,7 @@ Length SnakeDataManager::lineDistance() const { void SnakeDataManager::setLineDistance(Length lineDistance) { UniqueLock lk(this->pImpl->mutex); + routeParametersChanged = true; this->pImpl->lineDistance = lineDistance; } @@ -394,6 +199,7 @@ Area SnakeDataManager::minTileArea() const { } void SnakeDataManager::setMinTileArea(Area minTileArea) { + scenarioChanged = true; UniqueLock lk(this->pImpl->mutex); this->pImpl->scenario.setMinTileArea(minTileArea); } @@ -404,6 +210,7 @@ Length SnakeDataManager::tileHeight() const { } void SnakeDataManager::setTileHeight(Length tileHeight) { + scenarioChanged = true; UniqueLock lk(this->pImpl->mutex); this->pImpl->scenario.setTileHeight(tileHeight); } @@ -415,6 +222,7 @@ Length SnakeDataManager::tileWidth() const { void SnakeDataManager::setTileWidth(Length tileWidth) { UniqueLock lk(this->pImpl->mutex); + scenarioChanged = true; this->pImpl->scenario.setTileWidth(tileWidth); } @@ -429,7 +237,6 @@ void SnakeDataManager::disableRosBride() { void SnakeDataManager::run() { #ifndef NDEBUG auto startTime = std::chrono::high_resolution_clock::now(); - qDebug() << "SnakeDataManager::run()"; #endif this->pImpl->calcInProgress.store(true); @@ -447,8 +254,8 @@ void SnakeDataManager::run() { }; CommandRAII onExitRAII(onExit); - UniqueLock lk(this->pImpl->mutex); - this->pImpl->resetOutput(); + // Check preconditions. + SharedLock sLock(this->pImpl->mutex); if (!this->pImpl->precondition()) return; @@ -462,55 +269,160 @@ void SnakeDataManager::run() { return; } - this->pImpl->ENUOrigin = this->pImpl->mArea.front(); - auto &origin = this->pImpl->ENUOrigin; - qDebug() << "SnakeDataManager::run(): origin: " << origin.latitude() << " " - << origin.longitude() << " " << origin.altitude(); - // Update measurement area. - auto &mAreaEnu = this->pImpl->scenario.measurementArea(); - auto &mArea = this->pImpl->mArea; - mAreaEnu.clear(); - for (auto geoVertex : mArea) { - snake::BoostPoint p; - snake::toENU(origin, geoVertex, p); - mAreaEnu.outer().push_back(p); - } + // Update Scenario if necessary + if (this->pImpl->scenarioChanged) { + // Set Origin + this->pImpl->ENUOrigin = this->pImpl->mArea.front(); + auto &origin = this->pImpl->ENUOrigin; + + // Update measurement area. + auto &mAreaEnu = this->pImpl->scenario.measurementArea(); + auto &mArea = this->pImpl->mArea; + mAreaEnu.clear(); + for (auto geoVertex : mArea) { + snake::BoostPoint p; + snake::toENU(origin, geoVertex, p); + mAreaEnu.outer().push_back(p); + } - // Update service area. - auto &sAreaEnu = this->pImpl->scenario.serviceArea(); - auto &sArea = this->pImpl->sArea; - sAreaEnu.clear(); - for (auto geoVertex : sArea) { - snake::BoostPoint p; - snake::toENU(origin, geoVertex, p); - sAreaEnu.outer().push_back(p); - } + // Update service area. + auto &sAreaEnu = this->pImpl->scenario.serviceArea(); + auto &sArea = this->pImpl->sArea; + sAreaEnu.clear(); + for (auto geoVertex : sArea) { + snake::BoostPoint p; + snake::toENU(origin, geoVertex, p); + sAreaEnu.outer().push_back(p); + } - // Update corridor. - auto &corridorEnu = this->pImpl->scenario.corridor(); - auto &corridor = this->pImpl->corridor; - corridor.clear(); - for (auto geoVertex : corridor) { - snake::BoostPoint p; - snake::toENU(origin, geoVertex, p); - corridorEnu.outer().push_back(p); + // Update corridor. + auto &corridorEnu = this->pImpl->scenario.corridor(); + auto &corridor = this->pImpl->corridor; + corridor.clear(); + for (auto geoVertex : corridor) { + snake::BoostPoint p; + snake::toENU(origin, geoVertex, p); + corridorEnu.outer().push_back(p); + } + + if (!this->pImpl->scenario.update()) { + this->pImpl->errorMessage = this->pImpl->scenario.errorString.c_str(); + return; + } } + sLock.unlock; + + bool storeProgress = false; + bool storeRoute = false; + if (this->pImpl->scenarioChanged || this->pImpl->routeParametersChanged) { + storeRoute = true; + // Sample data + SharedLock lk(this->pImpl->mutex); + size_t nTiles = this->pImpl->tiles.polygons().size(); + if (this->pImpl->progress.size() != nTiles) { + } + const auto scenario = this->pImpl->scenario; + std::vector progress; + size_t nTiles = this->pImpl->tiles.polygons().size(); + if (this->pImpl->progress.size() != nTiles) { + progress.reserve(nTiles); + for (size_t i = 0; i < nTiles; ++i) { + progress.push_back(0); + } + storeProgress = true; + } else { + progress = this->pImpl->progress; + } + const auto lineDistance = this->pImpl->lineDistance; + const auto minTransectLength = this->pImpl->minTransectLength; + lk.unlock(); + + // Create transects. + std::string errorString; + snake::Angle alpha(-scenario.mAreaBoundingBox().angle * degree::degree); + snake::flight_plan::Transects transects; + transects.push_back( + bg::model::linestring{scenario.homePositon()}); + + bool value = snake::flight_plan::transectsFromScenario( + lineDistance, minTransectLength, alpha, scenario, progress, transects, + errorString); + if (!value) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->errorMessage = errorString.c_str(); + storeRoute = false; + } - if (!this->pImpl->scenario.update()) { - this->pImpl->errorMessage = this->pImpl->scenario.errorString.c_str(); - return; + // Route transects + if (transects.size() > 1) { + snake::flight_plan::Transects transectsRouted; + snake::flight_plan::Route route; + value = snake::flight_plan::route(scenario.joinedArea(), transects, + transectsRouted, route, errorString); + if (!value) { + this->pImpl->errorMessage = errorString.c_str(); + return; + } + + // Store arrival path. + const auto &firstWaypoint = transectsRouted.front().front(); + long startIdx = 0; + for (long i = 0; i < long(route.size()); ++i) { + const auto &boostVertex = route[i]; + if (boostVertex == firstWaypoint) { + startIdx = i; + break; + } + QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostVertex, geoVertex); + this->pImpl->arrivalPathENU.push_back(enuVertex); + this->pImpl->arrivalPath.push_back(geoVertex); + } + // Store return path. + long endIdx = 0; + const auto &lastWaypoint = transectsRouted.back().back(); + for (long i = route.size() - 1; i >= 0; --i) { + const auto &boostVertex = route[i]; + if (boostVertex == lastWaypoint) { + endIdx = i; + break; + } + QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostVertex, geoVertex); + this->pImpl->returnPathENU.push_back(enuVertex); + this->pImpl->returnPath.push_back(geoVertex); + } + // Store waypoints. + for (long i = startIdx; i <= endIdx; ++i) { + const auto &boostVertex = route[i]; + QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostVertex, geoVertex); + this->pImpl->waypointsENU.push_back(enuVertex); + this->pImpl->waypoints.push_back(geoVertex); + } + } } - // Store scenario data. - { - // Get tiles. + UniqueLock uLock(this->pImpl->mutex); + // Store scenario. + if (this->pImpl->scenarioChanged) { + // Clear some output data. + this->pImpl->tiles.polygons().clear(); + this->pImpl->tilesQml.clearAndDeleteContents(); + this->pImpl->tileCenterPoints.clear(); + this->pImpl->tilesENU.polygons().clear(); + this->pImpl->tileCenterPointsENU.clear(); + // Convert and store scenario data. const auto &tiles = this->pImpl->scenario.tiles(); const auto ¢erPoints = this->pImpl->scenario.tileCenterPoints(); for (unsigned int i = 0; i < tiles.size(); ++i) { const auto &tile = tiles[i]; SnakeTile geoTile; SnakeTileLocal enuTile; - for (size_t i = tile.outer().size(); i < tile.outer().size() - 1; ++i) { + for (size_t i = 0; i < tile.outer().size() - 1; ++i) { auto &p = tile.outer()[i]; QPointF enuVertex(p.get<0>(), p.get<1>()); QGeoCoordinate geoVertex; @@ -523,77 +435,19 @@ void SnakeDataManager::run() { QGeoCoordinate geoVertex; snake::fromENU(origin, boostPoint, geoVertex); geoTile.setCenter(geoVertex); - this->pImpl->tilesQml.append(new SnakeTile(geoTile)); this->pImpl->tiles.polygons().push_back(geoTile); + auto *geoTileCopy = new SnakeTile(geoTile); + geoTileCopy->moveToThread(this->pImpl->tilesQml.thread()); + this->pImpl->tilesQml.append(geoTileCopy); this->pImpl->tileCenterPoints.push_back(QVariant::fromValue(geoVertex)); this->pImpl->tilesENU.polygons().push_back(enuTile); this->pImpl->tileCenterPointsENU.push_back(enuVertex); } } - - // Create transects. - std::string errorString; - snake::Angle alpha(-this->pImpl->scenario.mAreaBoundingBox().angle * - degree::degree); - snake::flight_plan::Transects transects; - transects.push_back(bg::model::linestring{ - this->pImpl->scenario.homePositon()}); - bool value = snake::flight_plan::transectsFromScenario( - this->pImpl->lineDistance, this->pImpl->minTransectLength, alpha, - this->pImpl->scenario, this->pImpl->progress, transects, errorString); - if (!value) { - this->pImpl->errorMessage = errorString.c_str(); - return; - } - - // Route transects - snake::flight_plan::Transects transectsRouted; - snake::flight_plan::Route route; - value = - snake::flight_plan::route(this->pImpl->scenario.joinedArea(), transects, - transectsRouted, route, errorString); - if (!value) { - this->pImpl->errorMessage = errorString.c_str(); - return; - } - - // Store arrival path. - const auto &firstWaypoint = transectsRouted.front().front(); - long startIdx = 0; - for (long i = 0; i < long(route.size()); ++i) { - const auto &boostVertex = route[i]; - if (boostVertex == firstWaypoint) { - startIdx = i; - break; - } - QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; - QGeoCoordinate geoVertex; - snake::fromENU(origin, boostVertex, geoVertex); - this->pImpl->arrivalPathENU.push_back(enuVertex); - this->pImpl->arrivalPath.push_back(geoVertex); - } - // Store return path. - long endIdx = 0; - const auto &lastWaypoint = transectsRouted.back().back(); - for (long i = route.size() - 1; i >= 0; --i) { - const auto &boostVertex = route[i]; - if (boostVertex == lastWaypoint) { - endIdx = i; - break; - } - QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; - QGeoCoordinate geoVertex; - snake::fromENU(origin, boostVertex, geoVertex); - this->pImpl->returnPathENU.push_back(enuVertex); - this->pImpl->returnPath.push_back(geoVertex); - } - // Store waypoints. - for (long i = startIdx; i <= endIdx; ++i) { - const auto &boostVertex = route[i]; - QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; - QGeoCoordinate geoVertex; - snake::fromENU(origin, boostVertex, geoVertex); - this->pImpl->waypointsENU.push_back(enuVertex); - this->pImpl->waypoints.push_back(geoVertex); + // Store route etc. + if (this->pImpl->scenarioChanged || this->pImpl->routeParametersChanged) { } + uLock.unlock; + this->pImpl->scenarioChanged = false; + this->pImpl->routeParametersChanged = false; } diff --git a/src/Wima/Snake/SnakeDataManager.h b/src/Wima/Snake/SnakeDataManager.h index 9bc74e5983e23a3416bba313f2e2d8499a1386ad..6b6711ab0a1344afafe619802ecfbec9380e6ba5 100644 --- a/src/Wima/Snake/SnakeDataManager.h +++ b/src/Wima/Snake/SnakeDataManager.h @@ -15,13 +15,6 @@ using namespace boost::units; using Length = quantity; using Area = quantity; -enum class NemoStatus { - NotConnected = 0, - Connected = 1, - Timeout = -1, - InvalidHeartbeat = -2 -}; - class SnakeDataManager : public QThread { Q_OBJECT @@ -39,7 +32,6 @@ public: const QmlObjectListModel *tiles() const; QVariantList tileCenterPoints() const; QNemoProgress nemoProgress() const; - int nemoStatus() const; bool calcInProgress() const; QString errorMessage() const; bool success() const; @@ -63,12 +55,7 @@ public: Length tileWidth() const; void setTileWidth(Length tileWidth); - void enableRosBridge(); - void disableRosBride(); - signals: - void nemoProgressChanged(); - void nemoStatusChanged(int status); void calcInProgressChanged(bool inProgress); protected: diff --git a/src/Wima/Snake/SnakeDataManager_old.cc b/src/Wima/Snake/SnakeDataManager_old.cc new file mode 100644 index 0000000000000000000000000000000000000000..aab26b00a5472dd0907e27ae1ecff08f09ce5968 --- /dev/null +++ b/src/Wima/Snake/SnakeDataManager_old.cc @@ -0,0 +1,425 @@ +#include "SnakeDataManager.h" + +#include +#include + +#include "QGCApplication.h" +#include "QGCToolbox.h" +#include "SettingsFact.h" +#include "SettingsManager.h" +#include "WimaSettings.h" + +#include +#include +#include + +#include "Wima/Snake/SnakeTiles.h" +#include "Wima/Snake/SnakeTilesLocal.h" +#include "snake.h" + +#include "ros_bridge/include/messages/geographic_msgs/geopoint.h" +#include "ros_bridge/include/messages/jsk_recognition_msgs/polygon_array.h" +#include "ros_bridge/include/messages/nemo_msgs/heartbeat.h" +#include "ros_bridge/include/messages/nemo_msgs/progress.h" +#include "ros_bridge/include/ros_bridge.h" +#include "ros_bridge/rapidjson/include/rapidjson/document.h" +#include "ros_bridge/rapidjson/include/rapidjson/ostreamwrapper.h" +#include "ros_bridge/rapidjson/include/rapidjson/writer.h" + +#define EVENT_TIMER_INTERVAL 500 // ms +#define TIMEOUT 3000 // ms + +using QVariantList = QList; +using ROSBridgePtr = std::unique_ptr; + +using UniqueLock = std::unique_lock; +using SharedLock = std::shared_lock; +using JsonDocUPtr = ros_bridge::com_private::JsonDocUPtr; + +class SnakeDataManager::SnakeImpl { +public: + SnakeImpl(SnakeDataManager *p) + : rosBridgeEnabeled(false), topicServiceSetupDone(false), + lineDistance(1 * si::meter), minTransectLength(1 * si::meter), + calcInProgress(false), parent(p) { + + } + + bool precondition() const; + void resetOutput(); + bool doTopicServiceSetup(); + + // Private data. + ROSBridgePtr pRosBridge; + bool rosBridgeEnabeled; + bool topicServiceSetupDone; + QTimer eventTimer; + QTimer timeout; + mutable std::shared_timed_mutex mutex; + + // Scenario + snake::Scenario scenario; + Length lineDistance; + Length minTransectLength; + QList mArea; + QList sArea; + QList corridor; + QGeoCoordinate ENUOrigin; + SnakeTiles tiles; + QmlObjectListModel tilesQml; + QVariantList tileCenterPoints; + SnakeTilesLocal tilesENU; + QVector tileCenterPointsENU; + + // Waypoints + QVector waypoints; + QVector arrivalPath; + QVector returnPath; + QVector waypointsENU; + QVector arrivalPathENU; + QVector returnPathENU; + QString errorMessage; + + // Other + std::atomic_bool calcInProgress; + QNemoProgress qProgress; + std::vector progress; + QNemoHeartbeat heartbeat; + SnakeDataManager *parent; +}; + + + +bool SnakeDataManager::SnakeImpl::precondition() const { return true; } + +//! +//! \brief Resets waypoint data. +//! \pre this->_pImpl->mutex must be locked. +void SnakeDataManager::SnakeImpl::resetOutput() { + this->waypoints.clear(); + this->arrivalPath.clear(); + this->returnPath.clear(); + this->ENUOrigin = QGeoCoordinate(0.0, 0.0, 0.0); + this->waypointsENU.clear(); + this->arrivalPathENU.clear(); + this->returnPathENU.clear(); + this->tilesQml.clearAndDeleteContents(); + this->tiles.polygons().clear(); + this->tileCenterPoints.clear(); + this->tilesENU.polygons().clear(); + this->tileCenterPointsENU.clear(); + this->errorMessage.clear(); +} + +template class CommandRAII { +public: + CommandRAII(Callable &fun) : _fun(fun) {} + + ~CommandRAII() { _fun(); } + +private: + Callable _fun; +}; + +SnakeDataManager::SnakeDataManager(QObject *parent) + : QThread(parent), pImpl(std::make_unique(this)) + +{} + +SnakeDataManager::~SnakeDataManager() {} + +void SnakeDataManager::setMeasurementArea( + const QList &measurementArea) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->mArea = measurementArea; + for (auto &vertex : this->pImpl->mArea) { + vertex.setAltitude(0); + } +} + +void SnakeDataManager::setServiceArea( + const QList &serviceArea) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->sArea = serviceArea; + for (auto &vertex : this->pImpl->sArea) { + vertex.setAltitude(0); + } +} + +void SnakeDataManager::setCorridor(const QList &corridor) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->corridor = corridor; + for (auto &vertex : this->pImpl->corridor) { + vertex.setAltitude(0); + } +} + +const QmlObjectListModel *SnakeDataManager::tiles() const { + SharedLock lk(this->pImpl->mutex); + return &this->pImpl->tilesQml; +} + +QVariantList SnakeDataManager::tileCenterPoints() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->tileCenterPoints; +} + +QNemoProgress SnakeDataManager::nemoProgress() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->qProgress; +} + +int SnakeDataManager::nemoStatus() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->heartbeat.status(); +} + +bool SnakeDataManager::calcInProgress() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->calcInProgress.load(); +} + +QString SnakeDataManager::errorMessage() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->errorMessage; +} + +bool SnakeDataManager::success() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->errorMessage.isEmpty() ? true : false; +} + +QVector SnakeDataManager::waypoints() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->waypoints; +} + +QVector SnakeDataManager::arrivalPath() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->arrivalPath; +} + +QVector SnakeDataManager::returnPath() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->returnPath; +} + +Length SnakeDataManager::lineDistance() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->lineDistance; +} + +void SnakeDataManager::setLineDistance(Length lineDistance) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->lineDistance = lineDistance; +} + +Area SnakeDataManager::minTileArea() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->scenario.minTileArea(); +} + +void SnakeDataManager::setMinTileArea(Area minTileArea) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->scenario.setMinTileArea(minTileArea); +} + +Length SnakeDataManager::tileHeight() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->scenario.tileHeight(); +} + +void SnakeDataManager::setTileHeight(Length tileHeight) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->scenario.setTileHeight(tileHeight); +} + +Length SnakeDataManager::tileWidth() const { + SharedLock lk(this->pImpl->mutex); + return this->pImpl->scenario.tileWidth(); +} + +void SnakeDataManager::setTileWidth(Length tileWidth) { + UniqueLock lk(this->pImpl->mutex); + this->pImpl->scenario.setTileWidth(tileWidth); +} + +void SnakeDataManager::enableRosBridge() { + this->pImpl->rosBridgeEnabeled = true; +} + +void SnakeDataManager::disableRosBride() { + this->pImpl->rosBridgeEnabeled = false; +} + +void SnakeDataManager::run() { +#ifndef NDEBUG + auto startTime = std::chrono::high_resolution_clock::now(); + qDebug() << "SnakeDataManager::run()"; +#endif + + this->pImpl->calcInProgress.store(true); + emit calcInProgressChanged(this->pImpl->calcInProgress.load()); + auto onExit = [this, &startTime] { +#ifndef NDEBUG + qDebug() << "SnakeDataManager::run() execution time: " + << std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - startTime) + .count() + << " ms"; +#endif + this->pImpl->calcInProgress.store(false); + emit calcInProgressChanged(this->pImpl->calcInProgress.load()); + }; + CommandRAII onExitRAII(onExit); + + UniqueLock lk(this->pImpl->mutex); + this->pImpl->resetOutput(); + + if (!this->pImpl->precondition()) + return; + + if (this->pImpl->mArea.size() < 3) { + this->pImpl->errorMessage = "Measurement area invalid: size < 3."; + return; + } + if (this->pImpl->sArea.size() < 3) { + this->pImpl->errorMessage = "Service area invalid: size < 3."; + return; + } + + this->pImpl->ENUOrigin = this->pImpl->mArea.front(); + auto &origin = this->pImpl->ENUOrigin; + qDebug() << "SnakeDataManager::run(): origin: " << origin.latitude() << " " + << origin.longitude() << " " << origin.altitude(); + // Update measurement area. + auto &mAreaEnu = this->pImpl->scenario.measurementArea(); + auto &mArea = this->pImpl->mArea; + mAreaEnu.clear(); + for (auto geoVertex : mArea) { + snake::BoostPoint p; + snake::toENU(origin, geoVertex, p); + mAreaEnu.outer().push_back(p); + } + + // Update service area. + auto &sAreaEnu = this->pImpl->scenario.serviceArea(); + auto &sArea = this->pImpl->sArea; + sAreaEnu.clear(); + for (auto geoVertex : sArea) { + snake::BoostPoint p; + snake::toENU(origin, geoVertex, p); + sAreaEnu.outer().push_back(p); + } + + // Update corridor. + auto &corridorEnu = this->pImpl->scenario.corridor(); + auto &corridor = this->pImpl->corridor; + corridor.clear(); + for (auto geoVertex : corridor) { + snake::BoostPoint p; + snake::toENU(origin, geoVertex, p); + corridorEnu.outer().push_back(p); + } + + if (!this->pImpl->scenario.update()) { + this->pImpl->errorMessage = this->pImpl->scenario.errorString.c_str(); + return; + } + + // Store scenario data. + { + // Get tiles. + const auto &tiles = this->pImpl->scenario.tiles(); + const auto ¢erPoints = this->pImpl->scenario.tileCenterPoints(); + for (unsigned int i = 0; i < tiles.size(); ++i) { + const auto &tile = tiles[i]; + SnakeTile geoTile; + SnakeTileLocal enuTile; + for (size_t i = tile.outer().size(); i < tile.outer().size() - 1; ++i) { + auto &p = tile.outer()[i]; + QPointF enuVertex(p.get<0>(), p.get<1>()); + QGeoCoordinate geoVertex; + snake::fromENU(origin, p, geoVertex); + enuTile.polygon().points().push_back(enuVertex); + geoTile.push_back(geoVertex); + } + const auto &boostPoint = centerPoints[i]; + QPointF enuVertex(boostPoint.get<0>(), boostPoint.get<1>()); + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostPoint, geoVertex); + geoTile.setCenter(geoVertex); + this->pImpl->tilesQml.append(new SnakeTile(geoTile)); + this->pImpl->tiles.polygons().push_back(geoTile); + this->pImpl->tileCenterPoints.push_back(QVariant::fromValue(geoVertex)); + this->pImpl->tilesENU.polygons().push_back(enuTile); + this->pImpl->tileCenterPointsENU.push_back(enuVertex); + } + } + + // Create transects. + std::string errorString; + snake::Angle alpha(-this->pImpl->scenario.mAreaBoundingBox().angle * + degree::degree); + snake::flight_plan::Transects transects; + transects.push_back(bg::model::linestring{ + this->pImpl->scenario.homePositon()}); + bool value = snake::flight_plan::transectsFromScenario( + this->pImpl->lineDistance, this->pImpl->minTransectLength, alpha, + this->pImpl->scenario, this->pImpl->progress, transects, errorString); + if (!value) { + this->pImpl->errorMessage = errorString.c_str(); + return; + } + + // Route transects + snake::flight_plan::Transects transectsRouted; + snake::flight_plan::Route route; + value = + snake::flight_plan::route(this->pImpl->scenario.joinedArea(), transects, + transectsRouted, route, errorString); + if (!value) { + this->pImpl->errorMessage = errorString.c_str(); + return; + } + + // Store arrival path. + const auto &firstWaypoint = transectsRouted.front().front(); + long startIdx = 0; + for (long i = 0; i < long(route.size()); ++i) { + const auto &boostVertex = route[i]; + if (boostVertex == firstWaypoint) { + startIdx = i; + break; + } + QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostVertex, geoVertex); + this->pImpl->arrivalPathENU.push_back(enuVertex); + this->pImpl->arrivalPath.push_back(geoVertex); + } + // Store return path. + long endIdx = 0; + const auto &lastWaypoint = transectsRouted.back().back(); + for (long i = route.size() - 1; i >= 0; --i) { + const auto &boostVertex = route[i]; + if (boostVertex == lastWaypoint) { + endIdx = i; + break; + } + QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostVertex, geoVertex); + this->pImpl->returnPathENU.push_back(enuVertex); + this->pImpl->returnPath.push_back(geoVertex); + } + // Store waypoints. + for (long i = startIdx; i <= endIdx; ++i) { + const auto &boostVertex = route[i]; + QPointF enuVertex{boostVertex.get<0>(), boostVertex.get<1>()}; + QGeoCoordinate geoVertex; + snake::fromENU(origin, boostVertex, geoVertex); + this->pImpl->waypointsENU.push_back(enuVertex); + this->pImpl->waypoints.push_back(geoVertex); + } +} diff --git a/src/Wima/WimaController.cc b/src/Wima/WimaController.cc index 5fbc7ae66ce853e8968b857ce9ab62a2406939a9..44fd014b3ee4462938101b8942b774a64381d58f 100644 --- a/src/Wima/WimaController.cc +++ b/src/Wima/WimaController.cc @@ -121,6 +121,8 @@ WimaController::WimaController(QObject *parent) &WimaController::nemoStatusChanged); connect(_currentDM, &SnakeDataManager::nemoStatusChanged, this, &WimaController::nemoStatusStringChanged); + connect(_currentDM, &SnakeDataManager::calcInProgressChanged, this, + &WimaController::snakeCalcInProgressChanged); connect(this, &QObject::destroyed, &this->_snakeDM, &SnakeDataManager::quit); connect(this, &QObject::destroyed, &this->_emptyDM, &SnakeDataManager::quit); @@ -726,15 +728,15 @@ void WimaController::_DMFinishedHandler() { } // Do update. - auto fut = QtConcurrent::run([this] { - this->_snakeWM.update(); // this can take a while (ca. 200ms) + this->_snakeWM.update(); // this can take a while (ca. 200ms) - emit this->missionItemsChanged(); - emit this->currentMissionItemsChanged(); - emit this->currentWaypointPathChanged(); - emit this->waypointPathChanged(); - }); - (void)fut; + emit snakeTilesChanged(); + emit snakeTileCenterPointsChanged(); + emit nemoProgressChanged(); + emit missionItemsChanged(); + emit currentMissionItemsChanged(); + emit currentWaypointPathChanged(); + emit waypointPathChanged(); } void WimaController::_switchToSnakeWaypointManager(QVariant variant) { @@ -756,6 +758,8 @@ void WimaController::_switchDataManager(SnakeDataManager &dataManager) { &WimaController::nemoStatusChanged); disconnect(_currentDM, &SnakeDataManager::nemoStatusChanged, this, &WimaController::nemoStatusStringChanged); + disconnect(_currentDM, &SnakeDataManager::calcInProgressChanged, this, + &WimaController::snakeCalcInProgressChanged); _currentDM = &dataManager; @@ -767,8 +771,9 @@ void WimaController::_switchDataManager(SnakeDataManager &dataManager) { &WimaController::nemoStatusChanged); connect(_currentDM, &SnakeDataManager::nemoStatusChanged, this, &WimaController::nemoStatusStringChanged); + connect(_currentDM, &SnakeDataManager::calcInProgressChanged, this, + &WimaController::snakeCalcInProgressChanged); - emit snakeConnectionStatusChanged(); emit snakeCalcInProgressChanged(); emit snakeTilesChanged(); emit snakeTileCenterPointsChanged(); @@ -778,16 +783,13 @@ void WimaController::_switchDataManager(SnakeDataManager &dataManager) { } } -void WimaController::_progressChangedHandler() { - emit this->nemoProgressChanged(); - this->_currentDM->start(); -} +void WimaController::_progressChangedHandler() { _snakeDM.start(); } void WimaController::_enableSnakeChangedHandler() { if (this->_enableSnake.rawValue().toBool()) { qDebug() << "WimaController: enabling snake."; + _switchDataManager(this->_snakeDM); this->_snakeDM.enableRosBridge(); - _switchDataManager(_snakeDM); _currentDM->start(); } else { qDebug() << "WimaController: disabling snake."; diff --git a/src/Wima/WimaController.h b/src/Wima/WimaController.h index ba8e3ea3b4c24c0ce3865de8a969326d90d95303..3684a9c6a098e7b907be29e641ea07c1a1a77bd5 100644 --- a/src/Wima/WimaController.h +++ b/src/Wima/WimaController.h @@ -205,7 +205,6 @@ signals: void phaseDistanceChanged(void); void phaseDurationChanged(void); // Snake. - void snakeConnectionStatusChanged(void); void snakeCalcInProgressChanged(void); void snakeTilesChanged(void); void snakeTileCenterPointsChanged(void);