diff --git a/SurveyComplexItem_delLater.cc b/SurveyComplexItem_delLater.cc new file mode 100644 index 0000000000000000000000000000000000000000..ca613e297db6224a830916be1aa06fa495f4436b --- /dev/null +++ b/SurveyComplexItem_delLater.cc @@ -0,0 +1,713 @@ +#include "CircularSurveyComplexItem.h" +#include "JsonHelper.h" +#include "QGCApplication.h" +#include + + +const char* CircularSurveyComplexItem::settingsGroup = "CircularSurvey"; +const char* CircularSurveyComplexItem::deltaRName = "DeltaR"; +const char* CircularSurveyComplexItem::deltaAlphaName = "DeltaAlpha"; +const char* CircularSurveyComplexItem::transectMinLengthName = "TransectMinLength"; +const char* CircularSurveyComplexItem::isSnakePathName = "IsSnakePath"; +const char* CircularSurveyComplexItem::reverseName = "Reverse"; +const char* CircularSurveyComplexItem::maxWaypointsName = "MaxWaypoints"; + + +const char* CircularSurveyComplexItem::jsonComplexItemTypeValue = "circularSurvey"; +const char* CircularSurveyComplexItem::jsonDeltaRKey = "deltaR"; +const char* CircularSurveyComplexItem::jsonDeltaAlphaKey = "deltaAlpha"; +const char* CircularSurveyComplexItem::jsonTransectMinLengthKey = "transectMinLength"; +const char* CircularSurveyComplexItem::jsonIsSnakePathKey = "isSnakePath"; +const char* CircularSurveyComplexItem::jsonReverseKey = "reverse"; +const char* CircularSurveyComplexItem::jsonReferencePointLatKey = "referencePointLat"; +const char* CircularSurveyComplexItem::jsonReferencePointLongKey = "referencePointLong"; +const char* CircularSurveyComplexItem::jsonReferencePointAltKey = "referencePointAlt"; + +CircularSurveyComplexItem::CircularSurveyComplexItem(Vehicle *vehicle, bool flyView, const QString &kmlOrShpFile, QObject *parent) + : TransectStyleComplexItem (vehicle, flyView, settingsGroup, parent) + , _referencePoint (QGeoCoordinate(0, 0,0)) + , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CircularSurvey.SettingsGroup.json"), this)) + , _deltaR (settingsGroup, _metaDataMap[deltaRName]) + , _deltaAlpha (settingsGroup, _metaDataMap[deltaAlphaName]) + , _transectMinLength (settingsGroup, _metaDataMap[transectMinLengthName]) + , _isSnakePath (settingsGroup, _metaDataMap[isSnakePathName]) + , _reverse (settingsGroup, _metaDataMap[reverseName]) + , _maxWaypoints (settingsGroup, _metaDataMap[maxWaypointsName]) + , _isInitialized (false) + , _reverseOnly (false) + , _referencePointBeingChanged (false) + , _updateCounter (0) +{ + Q_UNUSED(kmlOrShpFile) + _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml"; + connect(&_deltaR, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(&_deltaAlpha, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(&_transectMinLength, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(&_isSnakePath, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(&_maxWaypoints, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(&_reverse, &Fact::valueChanged, this, &CircularSurveyComplexItem::_reverseTransects); + connect(this, &CircularSurveyComplexItem::refPointChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + //connect(&_cameraCalc.distanceToSurface(), &Fact::rawValueChanged, this->) + +} + +void CircularSurveyComplexItem::resetReference() +{ + setRefPoint(_surveyAreaPolygon.center()); +} + +void CircularSurveyComplexItem::setReferencePointBeingChanged(bool changeing) +{ + _referencePointBeingChanged = changeing; +} + +void CircularSurveyComplexItem::setRefPoint(const QGeoCoordinate &refPt) +{ + if (refPt != _referencePoint){ + _referencePoint = refPt; + + emit refPointChanged(); + } +} + +void CircularSurveyComplexItem::setIsInitialized(bool isInitialized) +{ + if (isInitialized != _isInitialized) { + _isInitialized = isInitialized; + + emit isInitializedChanged(); + } +} + +QGeoCoordinate CircularSurveyComplexItem::refPoint() const +{ + return _referencePoint; +} + +Fact *CircularSurveyComplexItem::deltaR() +{ + return &_deltaR; +} + +Fact *CircularSurveyComplexItem::deltaAlpha() +{ + return &_deltaAlpha; +} + +bool CircularSurveyComplexItem::isInitialized() +{ + return _isInitialized; +} + +bool CircularSurveyComplexItem::referencePointBeingChanged() +{ + return _referencePointBeingChanged; +} + +bool CircularSurveyComplexItem::load(const QJsonObject &complexObject, int sequenceNumber, QString &errorString) +{ + // We need to pull version first to determine what validation/conversion needs to be performed + QList versionKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(complexObject, versionKeyInfoList, errorString)) { + return false; + } + + int version = complexObject[JsonHelper::jsonVersionKey].toInt(); + if (version != 1) { + errorString = tr("Survey items do not support version %1").arg(version); + return false; + } + + QList keyInfoList = { + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { jsonDeltaRKey, QJsonValue::Double, true }, + { jsonDeltaAlphaKey, QJsonValue::Double, true }, + { jsonTransectMinLengthKey, QJsonValue::Double, true }, + { jsonIsSnakePathKey, QJsonValue::Bool, true }, + { jsonReverseKey, QJsonValue::Bool, true }, + { jsonReferencePointLatKey, QJsonValue::Double, true }, + { jsonReferencePointLongKey, QJsonValue::Double, true }, + { jsonReferencePointAltKey, QJsonValue::Double, true }, + }; + + if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) { + return false; + } + + QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString(); + if (itemType != VisualMissionItem::jsonTypeComplexItemValue || complexType != jsonComplexItemTypeValue) { + errorString = tr("%1 does not support loading this complex mission item type: %2:%3").arg(qgcApp()->applicationName()).arg(itemType).arg(complexType); + return false; + } + + _ignoreRecalc = true; + + setSequenceNumber(sequenceNumber); + + if (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, errorString)) { + _surveyAreaPolygon.clear(); + return false; + } + + if (!_load(complexObject, errorString)) { + _ignoreRecalc = false; + return false; + } + + _deltaR.setRawValue (complexObject[jsonDeltaRKey].toDouble()); + _deltaAlpha.setRawValue (complexObject[jsonDeltaAlphaKey].toDouble()); + _transectMinLength.setRawValue (complexObject[jsonTransectMinLengthKey].toDouble()); + _referencePoint.setLongitude (complexObject[jsonReferencePointLongKey].toDouble()); + _referencePoint.setLatitude (complexObject[jsonReferencePointLatKey].toDouble()); + _referencePoint.setAltitude (complexObject[jsonReferencePointAltKey].toDouble()); + _isSnakePath.setRawValue (complexObject[jsonIsSnakePathKey].toBool()); + _reverse.setRawValue (complexObject[jsonReverseKey].toBool()); + setIsInitialized(true); + + _ignoreRecalc = false; + + _recalcComplexDistance(); + if (_cameraShots == 0) { + // Shot count was possibly not available from plan file + _recalcCameraShots(); + } + + return true; +} + +void CircularSurveyComplexItem::save(QJsonArray &planItems) +{ + QJsonObject saveObject; + + _save(saveObject); + + saveObject[JsonHelper::jsonVersionKey] = 1; + saveObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + + saveObject[jsonDeltaRKey] = _deltaR.rawValue().toDouble(); + saveObject[jsonDeltaAlphaKey] = _deltaAlpha.rawValue().toDouble(); + saveObject[jsonTransectMinLengthKey] = _transectMinLength.rawValue().toDouble(); + saveObject[jsonIsSnakePathKey] = _isSnakePath.rawValue().toBool(); + saveObject[jsonReverseKey] = _reverse.rawValue().toBool(); + saveObject[jsonReferencePointLongKey] = _referencePoint.longitude(); + saveObject[jsonReferencePointLatKey] = _referencePoint.latitude(); + saveObject[jsonReferencePointAltKey] = _referencePoint.altitude(); + + // Polygon shape + _surveyAreaPolygon.saveToJson(saveObject); + + planItems.append(saveObject); +} + +void CircularSurveyComplexItem::appendMissionItems(QList &items, QObject *missionItemParent) +{ + if (_loadedMissionItems.count()) { + // We have mission items from the loaded plan, use those + _appendLoadedMissionItems(items, missionItemParent); + } else { + // Build the mission items on the fly + _buildAndAppendMissionItems(items, missionItemParent); + } +} + +void CircularSurveyComplexItem::_appendLoadedMissionItems(QList& items, QObject* missionItemParent) +{ + //qCDebug(SurveyComplexItemLog) << "_appendLoadedMissionItems"; + + int seqNum = _sequenceNumber; + + for (const MissionItem* loadedMissionItem: _loadedMissionItems) { + MissionItem* item = new MissionItem(*loadedMissionItem, missionItemParent); + item->setSequenceNumber(seqNum++); + items.append(item); + } +} + +void CircularSurveyComplexItem::_buildAndAppendMissionItems(QList& items, QObject* missionItemParent) +{ + // original code: SurveyComplexItem::_buildAndAppendMissionItems() + //qCDebug(SurveyComplexItemLog) << "_buildAndAppendMissionItems"; + + // Now build the mission items from the transect points + + MissionItem* item; + int seqNum = _sequenceNumber; + // bool imagesEverywhere = _cameraTriggerInTurnAroundFact.rawValue().toBool(); + // bool addTriggerAtBeginning = !hoverAndCaptureEnabled() && imagesEverywhere; + //bool firstOverallPoint = true; + + MAV_FRAME mavFrame = followTerrain() || !_cameraCalc.distanceToSurfaceRelative() ? MAV_FRAME_GLOBAL : MAV_FRAME_GLOBAL_RELATIVE_ALT; + + for (const QList& transect: _transects) { + //bool transectEntry = true; + + for (const CoordInfo_t& transectCoordInfo: transect) { + item = new MissionItem(seqNum++, + MAV_CMD_NAV_WAYPOINT, + mavFrame, + 0, // Hold time (delay for hover and capture to settle vehicle before image is taken) + 0.0, // No acceptance radius specified + 0.0, // Pass through waypoint + std::numeric_limits::quiet_NaN(), // Yaw unchanged + transectCoordInfo.coord.latitude(), + transectCoordInfo.coord.longitude(), + transectCoordInfo.coord.altitude(), + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + // implement capture if desired +// if (hoverAndCaptureEnabled()) { +// item = new MissionItem(seqNum++, +// MAV_CMD_IMAGE_START_CAPTURE, +// MAV_FRAME_MISSION, +// 0, // Reserved (Set to 0) +// 0, // Interval (none) +// 1, // Take 1 photo +// qQNaN(), qQNaN(), qQNaN(), qQNaN(), // param 4-7 reserved +// true, // autoContinue +// false, // isCurrentItem +// missionItemParent); +// items.append(item); +// } + +// if (firstOverallPoint && addTriggerAtBeginning) { +// // Start triggering +// addTriggerAtBeginning = false; +// item = new MissionItem(seqNum++, +// MAV_CMD_DO_SET_CAM_TRIGG_DIST, +// MAV_FRAME_MISSION, +// triggerDistance(), // trigger distance +// 0, // shutter integration (ignore) +// 1, // trigger immediately when starting +// 0, 0, 0, 0, // param 4-7 unused +// true, // autoContinue +// false, // isCurrentItem +// missionItemParent); +// items.append(item); +// } + //firstOverallPoint = false; + +// // Possibly add trigger start/stop to survey area entrance/exit +// if (triggerCamera() && !hoverAndCaptureEnabled() && transectCoordInfo.coordType == TransectStyleComplexItem::CoordTypeSurveyEdge) { +// if (transectEntry) { +// // Start of transect, always start triggering. We do this even if we are taking images everywhere. +// // This allows a restart of the mission in mid-air without losing images from the entire mission. +// // At most you may lose part of a transect. +// item = new MissionItem(seqNum++, +// MAV_CMD_DO_SET_CAM_TRIGG_DIST, +// MAV_FRAME_MISSION, +// triggerDistance(), // trigger distance +// 0, // shutter integration (ignore) +// 1, // trigger immediately when starting +// 0, 0, 0, 0, // param 4-7 unused +// true, // autoContinue +// false, // isCurrentItem +// missionItemParent); +// items.append(item); +// transectEntry = false; +// } else if (!imagesEverywhere && !transectEntry){ +// // End of transect, stop triggering +// item = new MissionItem(seqNum++, +// MAV_CMD_DO_SET_CAM_TRIGG_DIST, +// MAV_FRAME_MISSION, +// 0, // stop triggering +// 0, // shutter integration (ignore) +// 0, // trigger immediately when starting +// 0, 0, 0, 0, // param 4-7 unused +// true, // autoContinue +// false, // isCurrentItem +// missionItemParent); +// items.append(item); +// } +// } + } + } + + // implemetn photo capture if desired +// if (triggerCamera() && !hoverAndCaptureEnabled() && imagesEverywhere) { +// // Stop triggering +// MissionItem* item = new MissionItem(seqNum++, +// MAV_CMD_DO_SET_CAM_TRIGG_DIST, +// MAV_FRAME_MISSION, +// 0, // stop triggering +// 0, // shutter integration (ignore) +// 0, // trigger immediately when starting +// 0, 0, 0, 0, // param 4-7 unused +// true, // autoContinue +// false, // isCurrentItem +// missionItemParent); +// items.append(item); +// } +} + +void CircularSurveyComplexItem::applyNewAltitude(double newAltitude) +{ + _cameraCalc.valueSetIsDistance()->setRawValue(true); + _cameraCalc.distanceToSurface()->setRawValue(newAltitude); + _cameraCalc.setDistanceToSurfaceRelative(true); +} + +double CircularSurveyComplexItem::timeBetweenShots() +{ + return 1; +} + +bool CircularSurveyComplexItem::readyForSave() const +{ + return TransectStyleComplexItem::readyForSave(); +} + +double CircularSurveyComplexItem::additionalTimeDelay() const +{ + return 0; +} + +void CircularSurveyComplexItem::_rebuildTransectsPhase1() +{ + using namespace GeoUtilities; + using namespace PolygonCalculus; + using namespace PlanimetryCalculus; + + // rebuild not necessary? + if (!_isInitialized || _referencePointBeingChanged) + return; + + _updateCounter++; + unsigned int waypointCounter = 0; + + // If the transects are getting rebuilt then any previously loaded mission items are now invalid + if (_loadedMissionItemsParent) { + _loadedMissionItems.clear(); + _loadedMissionItemsParent->deleteLater(); + _loadedMissionItemsParent = nullptr; + } + + + // check if input is valid + if ( _surveyAreaPolygon.count() < 3) { + _transects.clear(); + return; + } + + // reverse transects and return + if (_reverseOnly) { + _reverseOnly = false; + + if (_transects.size() > 1) { + QList> transectsReverse; + transectsReverse.reserve(_transects.size()); + + for (auto list : _transects) { + QList listReverse; + for (auto coordinate : list) + listReverse.prepend(coordinate); + + transectsReverse.prepend(listReverse); + } + _transects = transectsReverse; + + return; + } + } + + _transects.clear(); + QPolygonF surveyPolygon = toQPolygonF(toCartesian2D(_surveyAreaPolygon.coordinateList(), _referencePoint)); + + // some more checks + if (!PolygonCalculus::isSimplePolygon(surveyPolygon)) { + _transects.clear(); + return; + } + + // even more checks + if (!PolygonCalculus::hasClockwiseWinding(surveyPolygon)) + PolygonCalculus::reversePath(surveyPolygon); + + QVector distances; + for (const QPointF &p : surveyPolygon) distances.append(norm(p)); + + // check if input is valid + if ( _deltaAlpha.rawValue() > _deltaAlpha.rawMax() + && _deltaAlpha.rawValue() < _deltaAlpha.rawMin()) + return; + if ( _deltaR.rawValue() > _deltaR.rawMax() + && _deltaR.rawValue() < _deltaR.rawMin()) + return; + + + // fetch input data + double dalpha = _deltaAlpha.rawValue().toDouble()/180.0*M_PI; // radiants + double dr = _deltaR.rawValue().toDouble(); // meter + double lmin = _transectMinLength.rawValue().toDouble(); + double r_min = dr; // meter + double r_max = (*std::max_element(distances.begin(), distances.end())); // meter + unsigned int maxWaypoints = _maxWaypoints.rawValue().toUInt(); + + QPointF origin(0, 0); + IntersectType type; + bool originInside = true; + if (!contains(surveyPolygon, origin, type)) { + QVector angles; + for (const QPointF &p : surveyPolygon) angles.append(truncateAngle(angle(p))); + + // determine r_min by successive approximation + double r = r_min; + while ( r < r_max) { + Circle circle(r, origin); + + if (intersects(circle, surveyPolygon)) { + r_min = r; + break; + } + + r += dr; + } + originInside = false; + } + + + // generate transects + QVector> transectPath; + double r = r_min; + + while (r < r_max) { + Circle circle(r, origin); + QVector intersectPoints; + QVector typeList; + QVector> neighbourList; + if (intersects(circle, surveyPolygon, intersectPoints, neighbourList, typeList)) { + + // intersection Points between circle and polygon, entering polygon + // when walking in counterclockwise direction along circle + QPointFList entryPoints; + // intersection Points between circle and polygon, leaving polygon + // when walking in counterclockwise direction along circle + QPointFList exitPoints; + // determine entryPoints and exit Points + for (int j = 0; j < intersectPoints.size(); j++) { + QVector intersects = intersectPoints[j]; // one pt = tangent, two pt = sekant + + QPointF p1 = surveyPolygon[neighbourList[j].first]; + QPointF p2 = surveyPolygon[neighbourList[j].second]; + QLineF intersetLine(p1, p2); + double lineAngle = angle(intersetLine); + +// int n = 16; +// for (int i = -n; i <= n; i++) { +// double alpha = 2*M_PI*double(i)/double(n); +// qDebug() << i << " " << alpha << " " << truncateAngle(alpha); +// } + + for (QPointF ipt : intersects) { + double circleTangentAngle = angle(ipt)+M_PI_2; + // compare line angle and circle tangent at intersection point + // to determine between exit and entry point +// qDebug() << "lineAngle" << lineAngle*180/M_PI; +// qDebug() << "circleTangentAngle" << circleTangentAngle*180/M_PI; +// qDebug() << "!qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle): " << !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle)); +// qDebug() << "!qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI): " << !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI)); + if ( !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle)) + && !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI) )) + { + if (truncateAngle(circleTangentAngle - lineAngle) > M_PI) { + entryPoints.append(ipt); + } else { + exitPoints.append(ipt); + } + } + } + } + + // sort + std::sort(entryPoints.begin(), entryPoints.end(), [](QPointF p1, QPointF p2) { + return angle(p1) < angle(p2); + }); + std::sort(exitPoints.begin(), exitPoints.end(), [](QPointF p1, QPointF p2) { + return angle(p1) < angle(p2); + }); + + // match entry and exit points + int offset = 0; + double minAngle = std::numeric_limits::infinity(); + for (int k = 0; k < exitPoints.size(); k++) { + QPointF pt = exitPoints[k]; + double alpha = truncateAngle(angle(pt) - angle(entryPoints[0])); + if (minAngle > alpha) { + minAngle = alpha; + offset = k; + } + } + + // generate circle sectors + for (int k = 0; k < entryPoints.size(); k++) { + double alpha1 = angle(entryPoints[k]); + double alpha2 = angle(exitPoints[(k+offset) % entryPoints.size()]); + double dAlpha = truncateAngle(alpha2-alpha1); + int numNodes = int(ceil(dAlpha/dalpha)) + 1; +// qDebug() << "alpha1" << alpha1; +// qDebug() << "alpha2" << alpha2; +// qDebug() << "dAlpha" << dAlpha; +// qDebug() << "numNodes" << numNodes; + + QVector sectorPath = circle.approximateSektor(numNodes, alpha1, alpha2); + // use shortestPath() here if necessary, could be a problem if dr >> + if (sectorPath.size() > 0) { + waypointCounter += uint(sectorPath.size()); + if (waypointCounter > maxWaypoints ) + return; + transectPath.append(sectorPath); + } + } + } else if (originInside) { + // circle fully inside polygon + int numNodes = int(ceil(2*M_PI/dalpha)) + 1; + QVector sectorPath = circle.approximateSektor(numNodes, 0, 2*M_PI); + // use shortestPath() here if necessary, could be a problem if dr >> + waypointCounter += uint(sectorPath.size()); + if (waypointCounter > maxWaypoints ) + return; + transectPath.append(sectorPath); + } + r += dr; + } + + if (transectPath.size() == 0) + return; + + // remove short transects + for (int i = 0; i < transectPath.size(); i++) { + auto transect = transectPath[i]; + double len = 0; + for (int j = 0; j < transect.size()-1; ++j) { + len += PlanimetryCalculus::distance(transect[j], transect[j+1]); + } + + if (len < lmin) + transectPath.removeAt(i--); + } + if (transectPath.size() == 0) + return; + + // optimize path to snake or zig-zag pattern + bool isSnakePattern = _isSnakePath.rawValue().toBool(); + QVector currentSection = transectPath.takeFirst(); + if ( currentSection.isEmpty() ) + return; + QVector optiPath; // optimized path + while( !transectPath.empty() ) { + optiPath.append(currentSection); + QPointF endVertex = currentSection.last(); + double minDist = std::numeric_limits::infinity(); + int index = 0; + bool reversePath = false; + + // iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection + for (int i = 0; i < transectPath.size(); i++) { + auto iteratorPath = transectPath[i]; + double dist = PlanimetryCalculus::distance(endVertex, iteratorPath.first()); + if ( dist < minDist ) { + minDist = dist; + index = i; + reversePath = false; + } + dist = PlanimetryCalculus::distance(endVertex, iteratorPath.last()); + if (dist < minDist) { + minDist = dist; + index = i; + reversePath = true; + } + } + currentSection = transectPath.takeAt(index); + if (reversePath && isSnakePattern) { + PolygonCalculus::reversePath(currentSection); + } + } + + optiPath.append(currentSection); // append last section + + if (optiPath.size() > _maxWaypoints.rawValue().toInt()) + return; + + + // convert to CoordInfo_t + if (_reverse.rawValue().toBool()) + PolygonCalculus::reversePath(optiPath); + + QVector geoPath = toGeo(optiPath, _referencePoint); + QList transectList; + transectList.reserve(optiPath.size()); + for ( const QGeoCoordinate &coordinate : geoPath) { + CoordInfo_t coordinfo = {coordinate, CoordTypeInterior}; + transectList.append(coordinfo); + } + _transects.append(transectList); + + qDebug() << "CircularSurveyComplexItem::_rebuildTransectsPhase1(): calls: " << _updateCounter; + +} + +void CircularSurveyComplexItem::_recalcComplexDistance() +{ + _complexDistance = 0; + for (int i=0; i<_visualTransectPoints.count() - 1; i++) { + _complexDistance += _visualTransectPoints[i].value().distanceTo(_visualTransectPoints[i+1].value()); + } + emit complexDistanceChanged(); +} + +// no cameraShots in Circular Survey, add if desired +void CircularSurveyComplexItem::_recalcCameraShots() +{ + _cameraShots = 0; +} + +void CircularSurveyComplexItem::_reverseTransects() +{ + _reverseOnly = true; + _rebuildTransects(); +} + + + +Fact *CircularSurveyComplexItem::transectMinLength() +{ + return &_transectMinLength; +} + +Fact *CircularSurveyComplexItem::isSnakePath() +{ + return &_isSnakePath; +} + +Fact *CircularSurveyComplexItem::reverse() +{ + return &_reverse; +} + +Fact *CircularSurveyComplexItem::maxWaypoints() +{ + return &_maxWaypoints; +} + + + + +/*! + \class CircularSurveyComplexItem + \inmodule Wima + + \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). + + \sa WimaArea +*/ + + diff --git a/src/Wima/CircularSurveyComplexItem.cc b/src/Wima/CircularSurveyComplexItem.cc index f2ce4b8cf3bc57a73c8c9b2abefb36b069aead1a..1119cb083a3e268f9902360f8fa17d24c94dc2c5 100644 --- a/src/Wima/CircularSurveyComplexItem.cc +++ b/src/Wima/CircularSurveyComplexItem.cc @@ -41,13 +41,13 @@ CircularSurveyComplexItem::CircularSurveyComplexItem(Vehicle *vehicle, bool flyV { Q_UNUSED(kmlOrShpFile) _editorQml = "qrc:/qml/CircularSurveyItemEditor.qml"; - connect(&_deltaR, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); - connect(&_deltaAlpha, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); - connect(&_transectMinLength, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); - connect(&_isSnakePath, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); - connect(&_maxWaypoints, &Fact::valueChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(&_deltaR, &Fact::valueChanged, this, &CircularSurveyComplexItem::_triggerSlowRecalc); + connect(&_deltaAlpha, &Fact::valueChanged, this, &CircularSurveyComplexItem::_triggerSlowRecalc); + connect(&_transectMinLength, &Fact::valueChanged, this, &CircularSurveyComplexItem::_triggerSlowRecalc); + connect(&_isSnakePath, &Fact::valueChanged, this, &CircularSurveyComplexItem::_triggerSlowRecalc); + connect(&_maxWaypoints, &Fact::valueChanged, this, &CircularSurveyComplexItem::_triggerSlowRecalc); connect(&_reverse, &Fact::valueChanged, this, &CircularSurveyComplexItem::_reverseTransects); - connect(this, &CircularSurveyComplexItem::refPointChanged, this, &CircularSurveyComplexItem::_rebuildTransects); + connect(this, &CircularSurveyComplexItem::refPointChanged, this, &CircularSurveyComplexItem::_triggerSlowRecalc); //connect(&_cameraCalc.distanceToSurface(), &Fact::rawValueChanged, this->) } @@ -57,6 +57,11 @@ void CircularSurveyComplexItem::resetReference() setRefPoint(_surveyAreaPolygon.center()); } +void CircularSurveyComplexItem::comprehensiveUpdate() +{ + _triggerSlowRecalc(); +} + void CircularSurveyComplexItem::setRefPoint(const QGeoCoordinate &refPt) { if (refPt != _referencePoint){ @@ -285,22 +290,28 @@ double CircularSurveyComplexItem::additionalTimeDelay() const { return 0; } +void CircularSurveyComplexItem::_rebuildTransectsPhase1(void){ + if (_fastRecalc){ + _rebuildTransectsFast(); + } else { + _rebuildTransectsSlow(); + _fastRecalc = true; + } +} -void CircularSurveyComplexItem::_rebuildTransectsPhase1() +void CircularSurveyComplexItem::_rebuildTransectsFast() { using namespace GeoUtilities; using namespace PolygonCalculus; using namespace PlanimetryCalculus; - _transectsDiry = true; + _transects.clear(); + _updateCounter++; - // rebuild not necessary? - if (!_isInitialized || _referencePointBeingChanged) + QPolygonF surveyPolygon = toQPolygonF(toCartesian2D(_surveyAreaPolygon.coordinateList(), _referencePoint)); + if (!_rebuildTransectsInputCheck(surveyPolygon)) return; - _updateCounter++; - unsigned int waypointCounter = 0; - // If the transects are getting rebuilt then any previously loaded mission items are now invalid if (_loadedMissionItemsParent) { _loadedMissionItems.clear(); @@ -308,226 +319,53 @@ void CircularSurveyComplexItem::_rebuildTransectsPhase1() _loadedMissionItemsParent = nullptr; } - - // check if input is valid - if ( _surveyAreaPolygon.count() < 3) { - _transects.clear(); - return; - } - - // reverse transects and return - if (_reverseOnly) { - _reverseOnly = false; - - if (_transects.size() > 1) { - QList> transectsReverse; - transectsReverse.reserve(_transects.size()); - - for (auto list : _transects) { - QList listReverse; - for (auto coordinate : list) - listReverse.prepend(coordinate); - - transectsReverse.prepend(listReverse); - } - _transects = transectsReverse; - - return; - } - } - - _transects.clear(); - QPolygonF surveyPolygon = toQPolygonF(toCartesian2D(_surveyAreaPolygon.coordinateList(), _referencePoint)); - - // some more checks - if (!PolygonCalculus::isSimplePolygon(surveyPolygon)) { - _transects.clear(); + QVector> transectPath; + if(!_generateTransectPath(transectPath, surveyPolygon)) return; - } - - // even more checks - if (!PolygonCalculus::hasClockwiseWinding(surveyPolygon)) - PolygonCalculus::reversePath(surveyPolygon); - - QVector distances; - for (const QPointF &p : surveyPolygon) distances.append(norm(p)); - // check if input is valid - if ( _deltaAlpha.rawValue() > _deltaAlpha.rawMax() - && _deltaAlpha.rawValue() < _deltaAlpha.rawMin()) - return; - if ( _deltaR.rawValue() > _deltaR.rawMax() - && _deltaR.rawValue() < _deltaR.rawMin()) + /// optimize path to snake or zig-zag pattern + bool isSnakePattern = _isSnakePath.rawValue().toBool(); + QVector currentSection = transectPath.takeFirst(); + if ( currentSection.isEmpty() ) return; + QVector optiPath; // optimized path + while( !transectPath.empty() ) { + optiPath.append(currentSection); + QPointF endVertex = currentSection.last(); + double minDist = std::numeric_limits::infinity(); + int index = 0; + bool reversePath = false; - // fetch input data - double dalpha = _deltaAlpha.rawValue().toDouble()/180.0*M_PI; // radiants - double dr = _deltaR.rawValue().toDouble(); // meter - double lmin = _transectMinLength.rawValue().toDouble(); - double r_min = dr; // meter - double r_max = (*std::max_element(distances.begin(), distances.end())); // meter - unsigned int maxWaypoints = _maxWaypoints.rawValue().toUInt(); - - QPointF origin(0, 0); - IntersectType type; - bool originInside = true; - if (!contains(surveyPolygon, origin, type)) { - QVector angles; - for (const QPointF &p : surveyPolygon) angles.append(truncateAngle(angle(p))); - - // determine r_min by successive approximation - double r = r_min; - while ( r < r_max) { - Circle circle(r, origin); - - if (intersects(circle, surveyPolygon)) { - r_min = r; - break; - } - - r += dr; - } - originInside = false; - } - - - // generate transects - QVector> transectPath; - double r = r_min; - - while (r < r_max) { - Circle circle(r, origin); - QVector intersectPoints; - QVector typeList; - QVector> neighbourList; - if (intersects(circle, surveyPolygon, intersectPoints, neighbourList, typeList)) { - - // intersection Points between circle and polygon, entering polygon - // when walking in counterclockwise direction along circle - QPointFList entryPoints; - // intersection Points between circle and polygon, leaving polygon - // when walking in counterclockwise direction along circle - QPointFList exitPoints; - // determine entryPoints and exit Points - for (int j = 0; j < intersectPoints.size(); j++) { - QVector intersects = intersectPoints[j]; // one pt = tangent, two pt = sekant - - QPointF p1 = surveyPolygon[neighbourList[j].first]; - QPointF p2 = surveyPolygon[neighbourList[j].second]; - QLineF intersetLine(p1, p2); - double lineAngle = angle(intersetLine); - -// int n = 16; -// for (int i = -n; i <= n; i++) { -// double alpha = 2*M_PI*double(i)/double(n); -// qDebug() << i << " " << alpha << " " << truncateAngle(alpha); -// } - - for (QPointF ipt : intersects) { - double circleTangentAngle = angle(ipt)+M_PI_2; - // compare line angle and circle tangent at intersection point - // to determine between exit and entry point -// qDebug() << "lineAngle" << lineAngle*180/M_PI; -// qDebug() << "circleTangentAngle" << circleTangentAngle*180/M_PI; -// qDebug() << "!qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle): " << !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle)); -// qDebug() << "!qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI): " << !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI)); - if ( !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle)) - && !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI) )) - { - if (truncateAngle(circleTangentAngle - lineAngle) > M_PI) { - entryPoints.append(ipt); - } else { - exitPoints.append(ipt); - } - } - } - } - - // sort - std::sort(entryPoints.begin(), entryPoints.end(), [](QPointF p1, QPointF p2) { - return angle(p1) < angle(p2); - }); - std::sort(exitPoints.begin(), exitPoints.end(), [](QPointF p1, QPointF p2) { - return angle(p1) < angle(p2); - }); - - // match entry and exit points - int offset = 0; - double minAngle = std::numeric_limits::infinity(); - for (int k = 0; k < exitPoints.size(); k++) { - QPointF pt = exitPoints[k]; - double alpha = truncateAngle(angle(pt) - angle(entryPoints[0])); - if (minAngle > alpha) { - minAngle = alpha; - offset = k; - } + // iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection + for (int i = 0; i < transectPath.size(); i++) { + auto iteratorPath = transectPath[i]; + double dist = PlanimetryCalculus::distance(endVertex, iteratorPath.first()); + if ( dist < minDist ) { + minDist = dist; + index = i; + reversePath = false; } - - // generate circle sectors - for (int k = 0; k < entryPoints.size(); k++) { - double alpha1 = angle(entryPoints[k]); - double alpha2 = angle(exitPoints[(k+offset) % entryPoints.size()]); - double dAlpha = truncateAngle(alpha2-alpha1); - int numNodes = int(ceil(dAlpha/dalpha)) + 1; -// qDebug() << "alpha1" << alpha1; -// qDebug() << "alpha2" << alpha2; -// qDebug() << "dAlpha" << dAlpha; -// qDebug() << "numNodes" << numNodes; - - QVector sectorPath = circle.approximateSektor(numNodes, alpha1, alpha2); - // use shortestPath() here if necessary, could be a problem if dr >> - if (sectorPath.size() > 0) { - waypointCounter += uint(sectorPath.size()); - if (waypointCounter > maxWaypoints ) - return; - transectPath.append(sectorPath); - } + dist = PlanimetryCalculus::distance(endVertex, iteratorPath.last()); + if (dist < minDist) { + minDist = dist; + index = i; + reversePath = true; } - } else if (originInside) { - // circle fully inside polygon - int numNodes = int(ceil(2*M_PI/dalpha)) + 1; - QVector sectorPath = circle.approximateSektor(numNodes, 0, 2*M_PI); - // use shortestPath() here if necessary, could be a problem if dr >> - waypointCounter += uint(sectorPath.size()); - if (waypointCounter > maxWaypoints ) - return; - transectPath.append(sectorPath); } - r += dr; - } - - if (transectPath.size() == 0) - return; - - // remove short transects - for (int i = 0; i < transectPath.size(); i++) { - auto transect = transectPath[i]; - double len = 0; - for (int j = 0; j < transect.size()-1; ++j) { - len += PlanimetryCalculus::distance(transect[j], transect[j+1]); + currentSection = transectPath.takeAt(index); + if (reversePath && isSnakePattern) { + PolygonCalculus::reversePath(currentSection); } - - if (len < lmin) - transectPath.removeAt(i--); } - if (transectPath.size() == 0) - return; + optiPath.append(currentSection); // append last section - // convert to CoordInfo_t + if (optiPath.size() > _maxWaypoints.rawValue().toInt()) + return; - for (auto path : transectPath){ - QVector geoPath = toGeo(path, _referencePoint); - QList transectList; - transectList.reserve(geoPath.size()); - for ( const QGeoCoordinate &coordinate : geoPath) { - CoordInfo_t coordinfo = {coordinate, CoordTypeInterior}; - transectList.append(coordinfo); - } - _transects.append(transectList); - } + _rebuildTransectsToGeo(optiPath, _referencePoint); qDebug() << "CircularSurveyComplexItem::_rebuildTransectsPhase1(): calls: " << _updateCounter; } @@ -537,14 +375,13 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() { using namespace GeoUtilities; using namespace PolygonCalculus; - using namespace PlanimetryCalculus; + using namespace PlanimetryCalculus; - // rebuild not necessary? - if (!_isInitialized || _referencePointBeingChanged) - return; + _transects.clear(); - _updateCounter++; - unsigned int waypointCounter = 0; + QPolygonF surveyPolygon = toQPolygonF(toCartesian2D(_surveyAreaPolygon.coordinateList(), _referencePoint)); + if (!_rebuildTransectsInputCheck(surveyPolygon)) + return; // If the transects are getting rebuilt then any previously loaded mission items are now invalid if (_loadedMissionItemsParent) { @@ -554,14 +391,7 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() } - // check if input is valid - if ( _surveyAreaPolygon.count() < 3) { - _transects.clear(); - return; - } - - // reverse transects and return - if (_reverseOnly) { + if (_reverseOnly) { // reverse transects and return _reverseOnly = false; if (_transects.size() > 1) { @@ -581,37 +411,123 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() } } - _transects.clear(); - QPolygonF surveyPolygon = toQPolygonF(toCartesian2D(_surveyAreaPolygon.coordinateList(), _referencePoint)); - - // some more checks - if (!PolygonCalculus::isSimplePolygon(surveyPolygon)) { - _transects.clear(); + QVector> transectPath; + if(!_generateTransectPath(transectPath, surveyPolygon)) return; - } - // even more checks - if (!PolygonCalculus::hasClockwiseWinding(surveyPolygon)) - PolygonCalculus::reversePath(surveyPolygon); + // optimize path to snake or zig-zag pattern + const bool isSnakePathBool = _isSnakePath.rawValue().toBool(); + QVector currentSection = transectPath.takeFirst(); if ( currentSection.isEmpty() ) return; + QVector optimizedPath(currentSection); + bool reversePath = true; // controlls if currentSection gets reversed, has nothing todo with _reverseOnly + while( !transectPath.empty() ) { + QPointF endVertex = currentSection.last(); + double minDist = std::numeric_limits::infinity(); + int index = 0; - QVector distances; - for (const QPointF &p : surveyPolygon) distances.append(norm(p)); + // iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection + QVector connectorPath; + for (int i = 0; i < transectPath.size(); i++) { + QVector iteratorPath = transectPath[i]; + QVector tempConnectorPath; + bool retVal; - // check if input is valid - if ( _deltaAlpha.rawValue() > _deltaAlpha.rawMax() - && _deltaAlpha.rawValue() < _deltaAlpha.rawMin()) + if (reversePath && isSnakePathBool) { + retVal = PolygonCalculus::shortestPath(surveyPolygon, endVertex, iteratorPath.last(), tempConnectorPath); + } else { + retVal = PolygonCalculus::shortestPath(surveyPolygon, endVertex, iteratorPath.first(), tempConnectorPath); + } + + if (!retVal) + qWarning("CircularSurveyComplexItem::_rebuildTransectsPhase1: internal error; false shortestPath"); + + double dist = 0; + for (int i = 0; i < tempConnectorPath.size()-1; ++i) + dist += PlanimetryCalculus::distance(tempConnectorPath[i], tempConnectorPath[i+1]); + + if (dist < minDist) { + minDist = dist; + index = i; + connectorPath = tempConnectorPath; + } + } + currentSection = transectPath.takeAt(index); + if (reversePath && isSnakePathBool) { + PolygonCalculus::reversePath(currentSection); + } + + reversePath ^= true; // toggle + + optimizedPath.append(connectorPath); + optimizedPath.append(currentSection); + } + if (optimizedPath.size() > _maxWaypoints.rawValue().toInt()) return; - if ( _deltaR.rawValue() > _deltaR.rawMax() - && _deltaR.rawValue() < _deltaR.rawMin()) + + _rebuildTransectsToGeo(optimizedPath, _referencePoint); + _transectsDiry = false; +} + +void CircularSurveyComplexItem::_triggerSlowRecalc() +{ + _fastRecalc = false; + _rebuildTransects(); +} + + + +void CircularSurveyComplexItem::_recalcComplexDistance() +{ + _complexDistance = 0; + if (_transectsDiry) return; + for (int i=0; i<_visualTransectPoints.count() - 1; i++) { + _complexDistance += _visualTransectPoints[i].value().distanceTo(_visualTransectPoints[i+1].value()); + } + emit complexDistanceChanged(); +} +// no cameraShots in Circular Survey, add if desired +void CircularSurveyComplexItem::_recalcCameraShots() +{ + _cameraShots = 0; +} + +void CircularSurveyComplexItem::_reverseTransects() +{ + _reverseOnly = true; + _rebuildTransectsSlow(); +} + +bool CircularSurveyComplexItem::_shortestPath(const QGeoCoordinate &start, const QGeoCoordinate &destination, QVector &shortestPath) +{ + using namespace GeoUtilities; + using namespace PolygonCalculus; + QVector path2D; + + bool retVal = PolygonCalculus::shortestPath( + toQPolygonF(toCartesian2D(this->surveyAreaPolygon()->coordinateList(), /*origin*/ start)), + /*start point*/ QPointF(0,0), + /*destination*/ toCartesian2D(destination, start), + /*shortest path*/ path2D); + if (retVal) + shortestPath.append(toGeo(path2D, /*origin*/ start)); + + return retVal; +} + +bool CircularSurveyComplexItem::_generateTransectPath(QVector > &transectPath, const QPolygonF &surveyPolygon) +{ + using namespace PlanimetryCalculus; + QVector distances; + for (const QPointF &p : surveyPolygon) distances.append(norm(p)); // fetch input data - double dalpha = _deltaAlpha.rawValue().toDouble()/180.0*M_PI; // radiants - double dr = _deltaR.rawValue().toDouble(); // meter - double lmin = _transectMinLength.rawValue().toDouble(); - double r_min = dr; // meter - double r_max = (*std::max_element(distances.begin(), distances.end())); // meter + double dalpha = _deltaAlpha.rawValue().toDouble()/180.0*M_PI; // angle discretisation of circles + double dr = _deltaR.rawValue().toDouble(); // distance between circles + double lmin = _transectMinLength.rawValue().toDouble(); // minimal transect length + double r_min = dr; // minimal circle radius + double r_max = (*std::max_element(distances.begin(), distances.end())); // maximal circle radius unsigned int maxWaypoints = _maxWaypoints.rawValue().toUInt(); QPointF origin(0, 0); @@ -636,11 +552,9 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() originInside = false; } - - // generate transects - QVector> transectPath; double r = r_min; + unsigned int waypointCounter = 0; while (r < r_max) { Circle circle(r, origin); QVector intersectPoints; @@ -650,10 +564,10 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() // intersection Points between circle and polygon, entering polygon // when walking in counterclockwise direction along circle - QPointFList entryPoints; + QVector entryPoints; // intersection Points between circle and polygon, leaving polygon // when walking in counterclockwise direction along circle - QPointFList exitPoints; + QVector exitPoints; // determine entryPoints and exit Points for (int j = 0; j < intersectPoints.size(); j++) { QVector intersects = intersectPoints[j]; // one pt = tangent, two pt = sekant @@ -663,20 +577,8 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() QLineF intersetLine(p1, p2); double lineAngle = angle(intersetLine); -// int n = 16; -// for (int i = -n; i <= n; i++) { -// double alpha = 2*M_PI*double(i)/double(n); -// qDebug() << i << " " << alpha << " " << truncateAngle(alpha); -// } - for (QPointF ipt : intersects) { double circleTangentAngle = angle(ipt)+M_PI_2; - // compare line angle and circle tangent at intersection point - // to determine between exit and entry point -// qDebug() << "lineAngle" << lineAngle*180/M_PI; -// qDebug() << "circleTangentAngle" << circleTangentAngle*180/M_PI; -// qDebug() << "!qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle): " << !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle)); -// qDebug() << "!qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI): " << !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI)); if ( !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle)) && !qFuzzyIsNull(truncateAngle(lineAngle - circleTangentAngle - M_PI) )) { @@ -715,35 +617,31 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() double alpha2 = angle(exitPoints[(k+offset) % entryPoints.size()]); double dAlpha = truncateAngle(alpha2-alpha1); int numNodes = int(ceil(dAlpha/dalpha)) + 1; -// qDebug() << "alpha1" << alpha1; -// qDebug() << "alpha2" << alpha2; -// qDebug() << "dAlpha" << dAlpha; -// qDebug() << "numNodes" << numNodes; QVector sectorPath = circle.approximateSektor(numNodes, alpha1, alpha2); // use shortestPath() here if necessary, could be a problem if dr >> if (sectorPath.size() > 0) { waypointCounter += uint(sectorPath.size()); if (waypointCounter > maxWaypoints ) - return; + return false; transectPath.append(sectorPath); } } } else if (originInside) { - // circle fully inside polygon + // circle fully inside polygon int numNodes = int(ceil(2*M_PI/dalpha)) + 1; QVector sectorPath = circle.approximateSektor(numNodes, 0, 2*M_PI); // use shortestPath() here if necessary, could be a problem if dr >> waypointCounter += uint(sectorPath.size()); if (waypointCounter > maxWaypoints ) - return; + return false; transectPath.append(sectorPath); } r += dr; } if (transectPath.size() == 0) - return; + return false;; // remove short transects for (int i = 0; i < transectPath.size(); i++) { @@ -757,116 +655,52 @@ void CircularSurveyComplexItem::_rebuildTransectsSlow() transectPath.removeAt(i--); } if (transectPath.size() == 0) - return; - - // optimize path to snake or zig-zag pattern - const bool isSnakePathBool = _isSnakePath.rawValue().toBool(); - QVector currentSection = transectPath.takeFirst(); if ( currentSection.isEmpty() ) return; - QVector optimizedPath(currentSection); - bool reversePath = true; // controlls if currentSection gets reversed, has nothing todo with _reverseOnly - while( !transectPath.empty() ) { - QPointF endVertex = currentSection.last(); - double minDist = std::numeric_limits::infinity(); - int index = 0; - - // iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection - QVector connectorPath; - for (int i = 0; i < transectPath.size(); i++) { - QVector iteratorPath = transectPath[i]; - QVector tempConnectorPath; - bool retVal; - - if (reversePath && isSnakePathBool) { - retVal = PolygonCalculus::shortestPath(surveyPolygon, endVertex, iteratorPath.last(), tempConnectorPath); - } else { - retVal = PolygonCalculus::shortestPath(surveyPolygon, endVertex, iteratorPath.first(), tempConnectorPath); - } + return false; - if (!retVal) - qWarning("CircularSurveyComplexItem::_rebuildTransectsPhase1: internal error; false shortestPath"); + return true; +} - double dist = 0; - for (int i = 0; i < tempConnectorPath.size()-1; ++i) - dist += PlanimetryCalculus::distance(tempConnectorPath[i], tempConnectorPath[i+1]); +bool CircularSurveyComplexItem::_rebuildTransectsInputCheck(QPolygonF &poly) +{ + // rebuild not necessary? + if (!_isInitialized) + return false; - if (dist < minDist) { - minDist = dist; - index = i; - connectorPath = tempConnectorPath; - } - } - currentSection = transectPath.takeAt(index); - if (reversePath && isSnakePathBool) { - PolygonCalculus::reversePath(currentSection); - } + // check if input is valid + if ( _surveyAreaPolygon.count() < 3) + return false; - reversePath ^= true; // toggle + // some more checks + if (!PolygonCalculus::isSimplePolygon(poly)) + return false; - optimizedPath.append(connectorPath); - optimizedPath.append(currentSection); - } + // even more checks + if (!PolygonCalculus::hasClockwiseWinding(poly)) + PolygonCalculus::reversePath(poly); - if (optimizedPath.size() > _maxWaypoints.rawValue().toInt()) - return; + // check if input is valid + if ( _deltaAlpha.rawValue() > _deltaAlpha.rawMax() + && _deltaAlpha.rawValue() < _deltaAlpha.rawMin()) + return false; + if ( _deltaR.rawValue() > _deltaR.rawMax() + && _deltaR.rawValue() < _deltaR.rawMin()) + return false; + return true; +} - // convert to CoordInfo_t - if (_reverse.rawValue().toBool()) - PolygonCalculus::reversePath(optimizedPath); +void CircularSurveyComplexItem::_rebuildTransectsToGeo(const QVector &path, const QGeoCoordinate &reference) +{ + using namespace GeoUtilities; - QVector geoPath = toGeo(optimizedPath, _referencePoint); + QVector geoPath = toGeo(path, reference); QList transectList; - transectList.reserve(optimizedPath.size()); + transectList.reserve(path.size()); for ( const QGeoCoordinate &coordinate : geoPath) { CoordInfo_t coordinfo = {coordinate, CoordTypeInterior}; transectList.append(coordinfo); } _transects.append(transectList); - - qDebug() << "CircularSurveyComplexItem::_rebuildTransectsPhase1(): calls: " << _updateCounter; - _transectsDiry = false; -} - - - -void CircularSurveyComplexItem::_recalcComplexDistance() -{ - _complexDistance = 0; - if (_transectsDiry) - return; - for (int i=0; i<_visualTransectPoints.count() - 1; i++) { - _complexDistance += _visualTransectPoints[i].value().distanceTo(_visualTransectPoints[i+1].value()); - } - emit complexDistanceChanged(); -} - -// no cameraShots in Circular Survey, add if desired -void CircularSurveyComplexItem::_recalcCameraShots() -{ - _cameraShots = 0; -} - -void CircularSurveyComplexItem::_reverseTransects() -{ - _reverseOnly = true; - _rebuildTransects(); -} - -bool CircularSurveyComplexItem::_shortestPath(const QGeoCoordinate &start, const QGeoCoordinate &destination, QVector shortestPath) -{ - using namespace GeoUtilities; - using namespace PolygonCalculus; - QVector path2D; - - bool retVal = PolygonCalculus::shortestPath( - toQPolygonF(toCartesian2D(this->surveyAreaPolygon()->coordinateList(), /*origin*/ start)), - /*start point*/ QPointF(0,0), - /*destination*/ toCartesian2D(destination, start), - /*shortest path*/ path2D); - if (retVal) - shortestPath.append(toGeo(path2D, /*origin*/ start)); - - return retVal; } diff --git a/src/Wima/CircularSurveyComplexItem.h b/src/Wima/CircularSurveyComplexItem.h index 9e6daae058b71f1dae4773f7b5fa85d5e454d6fc..2e9b42183a18aa2c3915c731d787631d539aa539 100644 --- a/src/Wima/CircularSurveyComplexItem.h +++ b/src/Wima/CircularSurveyComplexItem.h @@ -28,6 +28,7 @@ public: Q_PROPERTY(bool isInitialized READ isInitialized WRITE setIsInitialized NOTIFY isInitializedChanged) Q_INVOKABLE void resetReference(void); + Q_INVOKABLE void comprehensiveUpdate(void); // triggers a slow recalculation of the transects // Property setters void setRefPoint(const QGeoCoordinate &refPt); @@ -88,12 +89,18 @@ signals: private slots: // Overrides from TransectStyleComplexItem - void _rebuildTransectsPhase1 (void) final; // do not call this function, it is called by TransectStyleComplexItem::_rebuildTransects() - void _rebuildTransectsSlow (void); // the slow version of _rebuildTransectsPhase1 which properly connects the _transects + void _rebuildTransectsPhase1 (void) final; // calls _rebuildTransectsFast or _rebuildTransectsSlow depending on _fastRecalc + void _rebuildTransectsFast (void); + void _rebuildTransectsSlow (void); // the slow version of _rebuildTransectsFast which properly connects the _transects + void _triggerSlowRecalc (void); + bool _generateTransectPath (QVector> &transectPath, const QPolygonF &surveyPolygon); + bool _rebuildTransectsInputCheck(QPolygonF &poly); + void _rebuildTransectsToGeo (const QVector &path, const QGeoCoordinate &reference); void _recalcComplexDistance (void) final; void _recalcCameraShots (void) final; void _reverseTransects (void); - bool _shortestPath (const QGeoCoordinate &start, const QGeoCoordinate &destination, QVector shortestPath); + bool _shortestPath (const QGeoCoordinate &start, const QGeoCoordinate &destination, QVector &shortestPath); + signals: @@ -120,6 +127,7 @@ private: bool _isInitialized; // indicates if the polygon and refpoint etc. are initialized, prevents reinitialisation from gui and execution of _rebuildTransectsPhase1 during init from gui bool _reverseOnly; // if this is true _rebuildTransectsPhase1() will reverse the path only, _rebuildTransectsPhase1() resets _reverseOnly bool _referencePointBeingChanged; // is set to true by gui, if user is changeing the reference point + bool _fastRecalc; // see _rebuildTransectsPhase1 for explanation int _updateCounter; int _transectsDiry; @@ -127,3 +135,5 @@ private: + + diff --git a/src/WimaView/CircularSurveyMapVisual.qml b/src/WimaView/CircularSurveyMapVisual.qml index 4ad393dad0b8c7650eefd071671a560b8f40dc7e..098fc5ecb423aed99512b872fdb42ae4bd44e67d 100644 --- a/src/WimaView/CircularSurveyMapVisual.qml +++ b/src/WimaView/CircularSurveyMapVisual.qml @@ -113,6 +113,10 @@ Item { borderColor: "black" interiorColor: "green" interiorOpacity: 0.5 + + onDragStop: { + _missionItem.comprehensiveUpdate() + } } // Transect lines diff --git a/src/WimaView/WimaMapPolygonVisuals.qml b/src/WimaView/WimaMapPolygonVisuals.qml index f8b07789a24a0e248bf796d3d2696f04a2827bbd..8ef4264f8f7d1f55a217ff92fb3a7d0117ca6785 100644 --- a/src/WimaView/WimaMapPolygonVisuals.qml +++ b/src/WimaView/WimaMapPolygonVisuals.qml @@ -47,6 +47,8 @@ Item { property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2 property real _zorderCenterHandle: QGroundControl.zOrderMapItems + 1 // Lowest such that drag or split takes precedence + signal dragStop // triggered if node or center handle was stopped dragging + function addVisuals() { _polygonComponent = polygonComponent.createObject(mapControl) mapControl.addMapItem(_polygonComponent) @@ -350,7 +352,10 @@ Item { mapControl: _root.mapControl z: _zorderDragHandle visible: !_circle - onDragStop: mapPolygon.verifyClockwiseWinding() + onDragStop: { + mapPolygon.verifyClockwiseWinding() + _root.dragStop() + } property int polygonVertex @@ -495,7 +500,10 @@ Item { z: _zorderCenterHandle onItemCoordinateChanged: mapPolygon.center = itemCoordinate onDragStart: mapPolygon.centerDrag = true - onDragStop: mapPolygon.centerDrag = false + onDragStop: { + mapPolygon.centerDrag = false + _root.dragStop() + } onClicked: menu.popupCenter()