#include "WimaController.h" #include "utilities.h" #include "MissionController.h" #include "MissionSettingsItem.h" #include "PlanMasterController.h" #include "QGCApplication.h" #include "SettingsManager.h" #include "SimpleMissionItem.h" #include "WimaSettings.h" #include "Snake/QNemoHeartbeat.h" #include "Snake/QNemoProgress.h" #include "Snake/SnakeTile.h" #include "QVector3D" #include #define CLIPPER_SCALE 10000 #include "clipper/clipper.hpp" #include template constexpr typename std::underlying_type::type integral(T value) { return static_cast::type>(value); } #define SMART_RTL_MAX_ATTEMPTS 3 // times #define SMART_RTL_ATTEMPT_INTERVAL 200 // ms #define EVENT_TIMER_INTERVAL 50 // ms const char *WimaController::areaItemsName = "AreaItems"; const char *WimaController::missionItemsName = "MissionItems"; const char *WimaController::settingsGroup = "WimaController"; const char *WimaController::enableWimaControllerName = "EnableWimaController"; const char *WimaController::overlapWaypointsName = "OverlapWaypoints"; const char *WimaController::maxWaypointsPerPhaseName = "MaxWaypointsPerPhase"; const char *WimaController::startWaypointIndexName = "StartWaypointIndex"; const char *WimaController::showAllMissionItemsName = "ShowAllMissionItems"; const char *WimaController::showCurrentMissionItemsName = "ShowCurrentMissionItems"; const char *WimaController::flightSpeedName = "FlightSpeed"; const char *WimaController::arrivalReturnSpeedName = "ArrivalReturnSpeed"; const char *WimaController::altitudeName = "Altitude"; WimaController::StatusMap WimaController::_nemoStatusMap{ std::make_pair(0, "No Heartbeat"), std::make_pair(1, "Connected"), std::make_pair(-1, "Timeout"), std::make_pair(-2, "Error")}; WimaController::WimaController(QObject *parent) : QObject(parent), _joinedArea(), _measurementArea(), _serviceArea(), _corridor(), _planDataValid(false), _areaInterface(&_measurementArea, &_serviceArea, &_corridor, &_joinedArea), _WMSettings(), _defaultWM(_WMSettings, _areaInterface), _snakeWM(_WMSettings, _areaInterface), _rtlWM(_WMSettings, _areaInterface), _currentWM(&_defaultWM), _WMList{&_defaultWM, &_snakeWM, &_rtlWM}, _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/WimaController.SettingsGroup.json"), this)), _enableWimaController(settingsGroup, _metaDataMap[enableWimaControllerName]), _overlapWaypoints(settingsGroup, _metaDataMap[overlapWaypointsName]), _maxWaypointsPerPhase(settingsGroup, _metaDataMap[maxWaypointsPerPhaseName]), _nextPhaseStartWaypointIndex(settingsGroup, _metaDataMap[startWaypointIndexName]), _showAllMissionItems(settingsGroup, _metaDataMap[showAllMissionItemsName]), _showCurrentMissionItems(settingsGroup, _metaDataMap[showCurrentMissionItemsName]), _flightSpeed(settingsGroup, _metaDataMap[flightSpeedName]), _arrivalReturnSpeed(settingsGroup, _metaDataMap[arrivalReturnSpeedName]), _altitude(settingsGroup, _metaDataMap[altitudeName]), _lowBatteryHandlingTriggered(false), _measurementPathLength(-1), _nemoInterface(this), _batteryLevelTicker(EVENT_TIMER_INTERVAL, 1000 /*ms*/) { // Set up facts for waypoint manager. _showAllMissionItems.setRawValue(true); _showCurrentMissionItems.setRawValue(true); connect(&_overlapWaypoints, &Fact::rawValueChanged, this, &WimaController::_updateOverlap); connect(&_maxWaypointsPerPhase, &Fact::rawValueChanged, this, &WimaController::_updateMaxWaypoints); connect(&_nextPhaseStartWaypointIndex, &Fact::rawValueChanged, this, &WimaController::_setStartIndex); connect(&_flightSpeed, &Fact::rawValueChanged, this, &WimaController::_updateflightSpeed); connect(&_arrivalReturnSpeed, &Fact::rawValueChanged, this, &WimaController::_updateArrivalReturnSpeed); connect(&_altitude, &Fact::rawValueChanged, this, &WimaController::_updateAltitude); // Nemo stuff. connect(&_nemoInterface, &NemoInterface::progressChanged, this, &WimaController::_progressChangedHandler); connect(&_nemoInterface, &NemoInterface::statusChanged, this, &WimaController::nemoStatusChanged); connect(&_nemoInterface, &NemoInterface::statusChanged, this, &WimaController::nemoStatusStringChanged); // Init waypoint managers. bool value; size_t overlap = _overlapWaypoints.rawValue().toUInt(&value); Q_ASSERT(value); size_t N = _maxWaypointsPerPhase.rawValue().toUInt(&value); Q_ASSERT(value); size_t startIdx = _nextPhaseStartWaypointIndex.rawValue().toUInt(&value); Q_ASSERT(value); (void)value; for (auto manager : _WMList) { manager->setOverlap(overlap); manager->setN(N); manager->setStartIndex(startIdx); } // Periodic. connect(&_eventTimer, &QTimer::timeout, this, &WimaController::_eventTimerHandler); _eventTimer.start(EVENT_TIMER_INTERVAL); // NemoInterface. connect(&_nemoInterface, &NemoInterface::progressChanged, this, &WimaController::_progressChangedHandler); connect(&_nemoInterface, &NemoInterface::statusChanged, this, &WimaController::nemoStatusChanged); connect(&_nemoInterface, &NemoInterface::statusChanged, this, &WimaController::nemoStatusStringChanged); // Enable/disable snake. connect(&_enableSnake, &Fact::rawValueChanged, this, &WimaController::_enableSnakeChangedHandler); _enableSnakeChangedHandler(); connect(&_enableWimaController, &Fact::rawValueChanged, [this] { if (!this->_enableWimaController.rawValue().toBool()) { this->_enableSnake.setCookedValue(QVariant(false)); } }); // Snake Waypoint Manager. connect(&_enableSnake, &Fact::rawValueChanged, this, &WimaController::_switchToSnakeWaypointManager); _switchToSnakeWaypointManager(_enableSnake.rawValue()); // Routing connect(&_routingThread, &RoutingThread::result, this, &WimaController::_storeRoute); connect(&_routingThread, &RoutingThread::calculatingChanged, this, &WimaController::snakeCalcInProgressChanged); } PlanMasterController *WimaController::masterController() { return _masterController; } MissionController *WimaController::missionController() { return _missionController; } QmlObjectListModel *WimaController::visualItems() { return &_areas; } QmlObjectListModel *WimaController::missionItems() { return const_cast(&_currentWM->missionItems()); } QmlObjectListModel *WimaController::currentMissionItems() { return const_cast(&_currentWM->currentMissionItems()); } QVariantList WimaController::waypointPath() { return const_cast(_currentWM->waypointsVariant()); } QVariantList WimaController::currentWaypointPath() { return const_cast(_currentWM->currentWaypointsVariant()); } Fact *WimaController::enableWimaController() { return &_enableWimaController; } Fact *WimaController::overlapWaypoints() { return &_overlapWaypoints; } Fact *WimaController::maxWaypointsPerPhase() { return &_maxWaypointsPerPhase; } Fact *WimaController::startWaypointIndex() { return &_nextPhaseStartWaypointIndex; } Fact *WimaController::showAllMissionItems() { return &_showAllMissionItems; } Fact *WimaController::showCurrentMissionItems() { return &_showCurrentMissionItems; } Fact *WimaController::flightSpeed() { return &_flightSpeed; } Fact *WimaController::arrivalReturnSpeed() { return &_arrivalReturnSpeed; } Fact *WimaController::altitude() { return &_altitude; } QmlObjectListModel *WimaController::snakeTiles() { return this->_measurementArea.tiles(); } QVariantList WimaController::snakeTileCenterPoints() { return this->_measurementArea.tileCenterPoints(); } QVector WimaController::nemoProgress() { return this->_measurementArea.progress(); } double WimaController::phaseDistance() const { qWarning() << "using phaseDistance dummy"; return 0.0; } double WimaController::phaseDuration() const { qWarning() << "using phaseDuration dummy"; return 0.0; } int WimaController::nemoStatus() const { return integral(_nemoInterface.status()); } QString WimaController::nemoStatusString() const { return _nemoStatusMap.at(nemoStatus()); } bool WimaController::snakeCalcInProgress() const { return _routingThread.calculating(); } void WimaController::setMasterController(PlanMasterController *masterC) { _masterController = masterC; _WMSettings.setMasterController(masterC); emit masterControllerChanged(); } void WimaController::setMissionController(MissionController *missionC) { _missionController = missionC; _WMSettings.setMissionController(missionC); emit missionControllerChanged(); } void WimaController::nextPhase() { _calcNextPhase(); } void WimaController::previousPhase() { if (!_currentWM->previous()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::resetPhase() { if (!_currentWM->reset()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::requestSmartRTL() { #ifdef DEBUG_SRTL qWarning() << "WimaController::requestSmartRTL() called"; #endif QString errorString("Smart RTL requested."); if (!_SRTLPrecondition(errorString)) { qgcApp()->showMessage(errorString); return; } emit smartRTLRequestConfirm(); } bool WimaController::upload() { auto &items = _currentWM->currentMissionItems(); if (_masterController && _masterController->managerVehicle() && items.count() > 0) { if (!_joinedArea.containsCoordinate( _masterController->managerVehicle()->coordinate())) { emit forceUploadConfirm(); return false; } else { return forceUpload(); } } else { return false; } } bool WimaController::forceUpload() { auto ¤tMissionItems = _currentWM->currentMissionItems(); if (currentMissionItems.count() < 1 || !_missionController || !_masterController) { qWarning() << "WimaController::forceUpload(): error:"; qWarning() << "currentMissionItems.count(): " << currentMissionItems.count(); qWarning() << "_missionController: " << _missionController; qWarning() << "_masterController: " << _masterController; return false; } else { _missionController->removeAll(); // Set homeposition of settingsItem. QmlObjectListModel *visuals = _missionController->visualItems(); MissionSettingsItem *settingsItem = visuals->value(0); if (settingsItem == nullptr) { Q_ASSERT(false); qWarning("WimaController::updateCurrentMissionItems(): nullptr"); return false; } else { settingsItem->setCoordinate(_WMSettings.homePosition()); // Copy mission items to _missionController and send them. for (int i = 1; i < currentMissionItems.count(); i++) { auto *item = currentMissionItems.value(i); _missionController->insertSimpleMissionItem(*item, visuals->count()); } _masterController->sendToVehicle(); return true; } } } void WimaController::removeFromVehicle() { if (_masterController && _missionController) { _masterController->removeAllFromVehicle(); _missionController->removeAll(); } } void WimaController::executeSmartRTL() { #ifdef DEBUG_SRTL qWarning() << "WimaController::executeSmartRTL() called"; #endif if (_masterController && _masterController->managerVehicle()) { forceUpload(); _masterController->managerVehicle()->startMission(); } } void WimaController::initSmartRTL() { #ifdef DEBUG_SRTL qWarning() << "WimaController::initSmartRTL() called"; #endif _initSmartRTL(); } void WimaController::removeVehicleTrajectoryHistory() { if (_masterController && _masterController->managerVehicle()) { _masterController->managerVehicle() ->trajectoryPoints() ->clearAndDeleteContents(); } } bool WimaController::_calcShortestPath(const QGeoCoordinate &start, const QGeoCoordinate &destination, QVector &path) { using namespace GeoUtilities; using namespace PolygonCalculus; QPolygonF polygon2D; toCartesianList(_joinedArea.coordinateList(), /*origin*/ start, polygon2D); QPointF start2D(0, 0); QPointF end2D; toCartesian(destination, start, end2D); QVector path2D; bool retVal = PolygonCalculus::shortestPath(polygon2D, start2D, end2D, path2D); toGeoList(path2D, /*origin*/ start, path); return retVal; } bool WimaController::setWimaPlanData(QSharedPointer planData) { // reset visual items _areas.clear(); _defaultWM.clear(); _snakeWM.clear(); _measurementArea = WimaMeasurementAreaData(); _serviceArea = WimaServiceAreaData(); _corridor = WimaCorridorData(); _joinedArea = WimaJoinedAreaData(); emit visualItemsChanged(); emit missionItemsChanged(); emit currentMissionItemsChanged(); emit waypointPathChanged(); emit currentWaypointPathChanged(); emit snakeTilesChanged(); emit nemoProgressChanged(); _planDataValid = false; // extract list with WimaAreas QList areaList = planData->areaList(); int areaCounter = 0; const int numAreas = 4; // extract only numAreas Areas, if there are more // they are invalid and ignored for (int i = 0; i < areaList.size(); i++) { const WimaAreaData *areaData = areaList[i]; if (areaData->type() == WimaServiceAreaData::typeString) { // is it a service area? _serviceArea = *qobject_cast(areaData); areaCounter++; _areas.append(&_serviceArea); continue; } if (areaData->type() == WimaMeasurementAreaData::typeString) { // is it a measurement area? _measurementArea = *qobject_cast(areaData); areaCounter++; _areas.append(&_measurementArea); continue; } if (areaData->type() == WimaCorridorData::typeString) { // is it a corridor? _corridor = *qobject_cast(areaData); areaCounter++; //_visualItems.append(&_corridor); // not needed continue; } if (areaData->type() == WimaJoinedAreaData::typeString) { // is it a corridor? _joinedArea = *qobject_cast(areaData); areaCounter++; _areas.append(&_joinedArea); continue; } if (areaCounter >= numAreas) break; } if (areaCounter != numAreas) { Q_ASSERT(false); return false; } emit visualItemsChanged(); emit snakeTilesChanged(); // Copy raw transects. this->_rawTransects = planData->transects(); // Copy missionItems. auto tempMissionItems = planData->missionItems(); if (tempMissionItems.size() < 1) { qWarning("WimaController: Mission items from WimaPlaner empty!"); return false; } for (auto *item : tempMissionItems) { _snakeWM.push_back(item->coordinate()); _defaultWM.push_back(item->coordinate()); } _WMSettings.setHomePosition(QGeoCoordinate( _serviceArea.depot().latitude(), _serviceArea.depot().longitude(), 0)); // auto tempMissionItems = planData->missionItems(); if (!_defaultWM.reset()) { Q_ASSERT(false); return false; } if (!_snakeWM.reset()) { Q_ASSERT(false); return false; } if (_currentWM == &_defaultWM || _currentWM == &_snakeWM) { emit missionItemsChanged(); emit currentMissionItemsChanged(); emit waypointPathChanged(); emit currentWaypointPathChanged(); } // Publish tiles, set progress if necessary. if (!this->_nemoInterface.hasTileData(this->_measurementArea.tileData())) { if (_enableSnake.rawValue().toBool()) { this->_nemoInterface.publishTileData(this->_measurementArea.tileData()); } this->_measurementArea.progress() = QVector(this->_measurementArea.tiles()->count(), 0); emit nemoProgressChanged(); } else if (this->_enableSnake.rawValue().toBool()) { const auto progress = this->_nemoInterface.progress(); if (progress.size() == this->_measurementArea.tiles()->count()) { this->_measurementArea.progress() = std::move(progress); emit nemoProgressChanged(); this->_updateRoute(); } } _planDataValid = true; return true; } WimaController *WimaController::thisPointer() { return this; } bool WimaController::_calcNextPhase() { if (!_currentWM->next()) { Q_ASSERT(false); return false; } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); return true; } bool WimaController::_setStartIndex() { bool value; _currentWM->setStartIndex( _nextPhaseStartWaypointIndex.rawValue().toUInt(&value) - 1); Q_ASSERT(value); (void)value; if (!_currentWM->update()) { Q_ASSERT(false); return false; } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); return true; } void WimaController::_recalcCurrentPhase() { if (!_currentWM->update()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::_updateOverlap() { bool value; _currentWM->setOverlap(_overlapWaypoints.rawValue().toUInt(&value)); Q_ASSERT(value); (void)value; if (!_currentWM->update()) { assert(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::_updateMaxWaypoints() { bool value; _currentWM->setN(_maxWaypointsPerPhase.rawValue().toUInt(&value)); Q_ASSERT(value); (void)value; if (!_currentWM->update()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::_updateflightSpeed() { bool value; _WMSettings.setFlightSpeed(_flightSpeed.rawValue().toDouble(&value)); Q_ASSERT(value); (void)value; if (!_currentWM->update()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::_updateArrivalReturnSpeed() { bool value; _WMSettings.setArrivalReturnSpeed( _arrivalReturnSpeed.rawValue().toDouble(&value)); Q_ASSERT(value); (void)value; if (!_currentWM->update()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::_updateAltitude() { bool value; _WMSettings.setAltitude(_altitude.rawValue().toDouble(&value)); Q_ASSERT(value); (void)value; if (!_currentWM->update()) { Q_ASSERT(false); } emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } void WimaController::_checkBatteryLevel() { if (_missionController && _masterController && _masterController->managerVehicle()) { Vehicle *managerVehicle = masterController()->managerVehicle(); WimaSettings *wimaSettings = qgcApp()->toolbox()->settingsManager()->wimaSettings(); int threshold = wimaSettings->lowBatteryThreshold()->rawValue().toInt(); bool enabled = _enableWimaController.rawValue().toBool(); unsigned int minTime = wimaSettings->minimalRemainingMissionTime()->rawValue().toUInt(); if (enabled) { Fact *battery1percentRemaining = managerVehicle->battery1FactGroup()->getFact( VehicleBatteryFactGroup::_percentRemainingFactName); Fact *battery2percentRemaining = managerVehicle->battery2FactGroup()->getFact( VehicleBatteryFactGroup::_percentRemainingFactName); if (battery1percentRemaining->rawValue().toDouble() < threshold && battery2percentRemaining->rawValue().toDouble() < threshold) { if (!_lowBatteryHandlingTriggered) { _lowBatteryHandlingTriggered = true; if (!(_missionController->remainingTime() <= minTime)) { requestSmartRTL(); } } } else { _lowBatteryHandlingTriggered = false; } } } } void WimaController::_eventTimerHandler() { // Battery level check necessary? Fact *enableLowBatteryHandling = qgcApp() ->toolbox() ->settingsManager() ->wimaSettings() ->enableLowBatteryHandling(); if (enableLowBatteryHandling->rawValue().toBool() && _batteryLevelTicker.ready()) _checkBatteryLevel(); } void WimaController::_smartRTLCleanUp(bool flying) { if (!flying && _missionController) { // vehicle has landed _switchWaypointManager(_defaultWM); _missionController->removeAll(); disconnect(masterController()->managerVehicle(), &Vehicle::flyingChanged, this, &WimaController::_smartRTLCleanUp); } } void WimaController::_setPhaseDistance(double distance) { (void)distance; // if (!qFuzzyCompare(distance, _phaseDistance)) { // _phaseDistance = distance; // emit phaseDistanceChanged(); // } } void WimaController::_setPhaseDuration(double duration) { (void)duration; // if (!qFuzzyCompare(duration, _phaseDuration)) { // _phaseDuration = duration; // emit phaseDurationChanged(); // } } bool WimaController::_SRTLPrecondition(QString &errorString) { if (!_planDataValid) { errorString.append(tr("No WiMA data available. Please define at least a " "measurement and a service area.")); return false; } return _rtlWM.checkPrecondition(errorString); } void WimaController::_switchWaypointManager( WaypointManager::ManagerBase &manager) { if (_currentWM != &manager) { _currentWM = &manager; disconnect(&_overlapWaypoints, &Fact::rawValueChanged, this, &WimaController::_updateOverlap); disconnect(&_maxWaypointsPerPhase, &Fact::rawValueChanged, this, &WimaController::_updateMaxWaypoints); disconnect(&_nextPhaseStartWaypointIndex, &Fact::rawValueChanged, this, &WimaController::_setStartIndex); _maxWaypointsPerPhase.setRawValue(_currentWM->N()); _overlapWaypoints.setRawValue(_currentWM->overlap()); _nextPhaseStartWaypointIndex.setRawValue(_currentWM->startIndex()); connect(&_overlapWaypoints, &Fact::rawValueChanged, this, &WimaController::_updateOverlap); connect(&_maxWaypointsPerPhase, &Fact::rawValueChanged, this, &WimaController::_updateMaxWaypoints); connect(&_nextPhaseStartWaypointIndex, &Fact::rawValueChanged, this, &WimaController::_setStartIndex); emit missionItemsChanged(); emit currentMissionItemsChanged(); emit waypointPathChanged(); emit currentWaypointPathChanged(); qWarning() << "WimaController::_switchWaypointManager: statistics update " "missing."; } } void WimaController::_initSmartRTL() { static int attemptCounter = 0; attemptCounter++; QString errorString; if (_SRTLPrecondition(errorString)) { if (_missionController && _masterController && _masterController->managerVehicle()) { _masterController->managerVehicle()->pauseVehicle(); connect(_masterController->managerVehicle(), &Vehicle::flyingChanged, this, &WimaController::_smartRTLCleanUp); if (_rtlWM.update()) { // Calculate return path. _switchWaypointManager(_rtlWM); removeFromVehicle(); attemptCounter = 0; emit smartRTLPathConfirm(); } } } else if (attemptCounter > SMART_RTL_MAX_ATTEMPTS) { errorString.append( tr("Smart RTL: No success after maximum number of attempts.")); qgcApp()->showMessage(errorString); attemptCounter = 0; } else { _smartRTLTimer.singleShot(SMART_RTL_ATTEMPT_INTERVAL, this, &WimaController::_initSmartRTL); } } void WimaController::_storeRoute(RoutingThread::PtrRoutingData data) { #ifdef SNAKE_SHOW_TIME auto start = std::chrono::high_resolution_clock::now(); #endif // Copy waypoints to waypoint manager. _snakeWM.clear(); if (data->route.size() > 0) { // Store route. const auto &transectsENU = data->transects; const auto &transectsInfo = data->transectsInfo; const auto &route = data->route; // Find index of first waypoint. std::size_t idxFirst = 0; const auto &infoFirst = transectsInfo.front(); const auto &firstTransect = transectsENU[infoFirst.index]; const auto &firstWaypoint = infoFirst.reversed ? firstTransect.back() : firstTransect.front(); double th = 0.001; for (std::size_t i = 0; i < route.size(); ++i) { auto dist = bg::distance(route[i], firstWaypoint); if (dist < th) { idxFirst = i; break; } } // Find index of last waypoint. std::size_t idxLast = route.size() - 1; const auto &infoLast = transectsInfo.back(); const auto &lastTransect = transectsENU[infoLast.index]; const auto &lastWaypoint = infoLast.reversed ? lastTransect.front() : lastTransect.back(); for (long i = route.size() - 1; i >= 0; --i) { auto dist = bg::distance(route[i], lastWaypoint); if (dist < th) { idxLast = i; break; } } // Convert to geo coordinates and append to waypoint manager. const auto &ori = this->_origin; for (std::size_t i = idxFirst; i <= idxLast; ++i) { auto &vertex = route[i]; QGeoCoordinate c; snake::fromENU(ori, vertex, c); _snakeWM.push_back(c); } // Do update. this->_snakeWM.update(); // this can take a while (ca. 200ms) emit missionItemsChanged(); emit currentMissionItemsChanged(); emit currentWaypointPathChanged(); emit waypointPathChanged(); } #ifdef SNAKE_SHOW_TIME auto end = std::chrono::high_resolution_clock::now(); std::cout << "WimaController::_threadFinishedHandler(): waypoints: " << std::chrono::duration_cast(end - start) .count() << " ms" << std::endl; #endif } void WimaController::_switchToSnakeWaypointManager(QVariant variant) { if (variant.value()) { _switchWaypointManager(_snakeWM); } else { _switchWaypointManager(_defaultWM); } } void WimaController::_progressChangedHandler() { const auto progress = this->_nemoInterface.progress(); if (this->_measurementArea.tiles()->count() == progress.size()) { this->_measurementArea.progress() = std::move(progress); emit nemoProgressChanged(); this->_updateRoute(); } else if (_planDataValid) { this->_nemoInterface.publishTileData(this->_measurementArea.tileData()); } } void WimaController::_enableSnakeChangedHandler() { if (this->_enableSnake.rawValue().toBool()) { qDebug() << "WimaController: enabling snake."; _switchWaypointManager(_snakeWM); this->_nemoInterface.start(); if (_planDataValid) { if (!this->_nemoInterface.hasTileData( this->_measurementArea.tileData())) { this->_nemoInterface.publishTileData(this->_measurementArea.tileData()); } } } else { qDebug() << "WimaController: disabling snake."; _switchWaypointManager(_defaultWM); this->_nemoInterface.stop(); } } void WimaController::_updateRoute() { // precondtion if (_planDataValid && this->_joinedArea.coordinateList().size() > 0) { const auto progress = this->_nemoInterface.progress(); const auto *tiles = this->_measurementArea.tiles(); // precondtion if (tiles->count() > 0 && progress.size() == tiles->count() && this->_rawTransects.size() > 0 && this->_joinedArea.coordinateList().size() > 0) { // Fetch tiles and convert to ENU. this->_origin = this->_joinedArea.coordinateList().first(); this->_origin.setAltitude(0); const auto &origin = this->_origin; auto pTiles = std::make_shared>(); std::size_t numTiles = 0; for (int i = 0; i < progress.size(); ++i) { if (progress[i] == 100) { ++numTiles; const auto *obj = tiles->get(i); const auto *tile = qobject_cast(obj); if (tile != nullptr) { snake::BoostPolygon t; snake::areaToEnu(origin, tile->coordinateList(), t); pTiles->push_back(std::move(t)); } else { return; } } } if (numTiles > 0) { // Fetch safe area and convert to ENU. auto safeArea = this->_joinedArea.coordinateList(); for (auto &v : safeArea) { v.setAltitude(0); } snake::BoostPolygon safeAreaENU; snake::areaToEnu(origin, safeArea, safeAreaENU); const auto &depot = this->_serviceArea.depot(); snake::BoostPoint depotENU; snake::toENU(origin, depot, depotENU); // Fetch geo transects and convert to ENU. const auto &geoTransects = this->_rawTransects; auto pUnclippedTransects = std::make_shared(); for (auto &geoTransect : geoTransects) { snake::BoostLineString t; for (auto &geoVertex : geoTransect) { snake::BoostPoint v; snake::toENU(origin, geoVertex, v); t.push_back(v); } pUnclippedTransects->push_back(t); } std::size_t minLength = 1; // meter qWarning() << "using minLength dummy lkjfdslooij"; auto generator = [depotENU, pUnclippedTransects, pTiles, minLength](snake::Transects &transects) -> bool { // Convert transects to clipper path. vector transectsClipper; for (const auto &t : *pUnclippedTransects) { ClipperLib::Path path; for (const auto &v : t) { ClipperLib::IntPoint c{ static_cast(v.get<0>() * CLIPPER_SCALE), static_cast(v.get<1>() * CLIPPER_SCALE)}; path.push_back(c); } transectsClipper.push_back(std::move(path)); } // Add transects to clipper. ClipperLib::Clipper clipper; clipper.AddPaths(transectsClipper, ClipperLib::ptSubject, false); // Add tiles to clipper. vector tilesClipper; for (const auto &t : *pTiles) { ClipperLib::Path path; for (const auto &v : t.outer()) { path.push_back(ClipperLib::IntPoint{ static_cast(v.get<0>() * CLIPPER_SCALE), static_cast(v.get<1>() * CLIPPER_SCALE)}); } tilesClipper.push_back(std::move(path)); } clipper.AddPaths(tilesClipper, ClipperLib::ptClip, true); // Clip transects. ClipperLib::PolyTree clippedTransecs; clipper.Execute(ClipperLib::ctDifference, clippedTransecs, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // Extract transects from PolyTree and convert them to // BoostLineString transects.push_back(snake::BoostLineString{depotENU}); for (const auto &child : clippedTransecs.Childs) { snake::BoostLineString transect; for (const auto &v : child->Contour) { snake::BoostPoint c{static_cast(v.X) / CLIPPER_SCALE, static_cast(v.Y) / CLIPPER_SCALE}; transect.push_back(c); } if (bg::length(transect) >= minLength) { transects.push_back(std::move(transect)); } } if (transects.size() > 1) { return true; } else { return false; } }; // generator this->_routingThread.route(safeAreaENU, generator); } else { this->_storeRoute(RoutingThread::PtrRoutingData(new RoutingData())); } } } }