diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index c9145d77f6d6606391f71db3c6241ab7e75e4cab..7311ba688953276a9e277ffd3deb9f0e86668473 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -522,6 +522,7 @@ HEADERS += \ src/MissionManager/SimpleMissionItem.h \ src/MissionManager/Section.h \ src/MissionManager/SpeedSection.h \ + src/MissionManager/StructureScanComplexItem.h \ src/MissionManager/SurveyMissionItem.h \ src/MissionManager/VisualMissionItem.h \ src/PositionManager/PositionManager.h \ @@ -707,6 +708,7 @@ SOURCES += \ src/MissionManager/RallyPointManager.cc \ src/MissionManager/SimpleMissionItem.cc \ src/MissionManager/SpeedSection.cc \ + src/MissionManager/StructureScanComplexItem.cc \ src/MissionManager/SurveyMissionItem.cc \ src/MissionManager/VisualMissionItem.cc \ src/PositionManager/PositionManager.cpp \ diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index ce13d5a07c6f47b1338cb42d0acc832678dce46b..e603cdff6a128a6670a46effd6ef23ea91587a62 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -115,6 +115,7 @@ src/ui/toolbar/SignalStrength.qml src/PlanView/SimpleItemMapVisual.qml src/QmlControls/SliderSwitch.qml + src/PlanView/StructureScanMapVisual.qml src/QmlControls/SubMenuButton.qml src/PlanView/SurveyMapVisual.qml src/QmlControls/VehicleRotationCal.qml @@ -179,7 +180,7 @@ src/VehicleSetup/SetupParameterEditor.qml src/VehicleSetup/SetupView.qml src/PlanView/SimpleItemEditor.qml - src/PlanView/SurveyItemEditor.qml + src/PlanView/StructureScanEditor.qml src/PlanView/FWLandingPatternEditor.qml src/PlanView/MissionSettingsEditor.qml src/ui/preferences/TcpSettings.qml @@ -206,6 +207,7 @@ src/MissionManager/QGCMapCircle.Facts.json src/Settings/RTK.SettingsGroup.json src/MissionManager/Survey.SettingsGroup.json + src/MissionManager/StructureScan.SettingsGroup.json src/Settings/Units.SettingsGroup.json src/Settings/Video.SettingsGroup.json src/MissionManager/RallyPoint.FactMetaData.json diff --git a/src/MissionManager/ComplexMissionItem.h b/src/MissionManager/ComplexMissionItem.h index caf83426a0628128618fb58a94fd640e6690aeca..10ae81c92314137999302b2ef05c67095a5d5336 100644 --- a/src/MissionManager/ComplexMissionItem.h +++ b/src/MissionManager/ComplexMissionItem.h @@ -24,6 +24,7 @@ public: Q_PROPERTY(double complexDistance READ complexDistance NOTIFY complexDistanceChanged) /// @return The distance covered the complex mission item in meters. + /// Signals complexDistanceChanged virtual double complexDistance(void) const = 0; /// @return Amount of additional time delay in seconds needed to fly the complex item @@ -39,6 +40,7 @@ public: /// Get the point of complex mission item furthest away from a coordinate /// @param other QGeoCoordinate to which distance is calculated /// @return the greatest distance from any point of the complex item to some coordinate + /// Signals greatestDistanceToChanged virtual double greatestDistanceTo(const QGeoCoordinate &other) const = 0; /// This mission item attribute specifies the type of the complex item. @@ -46,6 +48,7 @@ public: signals: void complexDistanceChanged (double complexDistance); + void greatestDistanceToChanged (void); void additionalTimeDelayChanged (double additionalTimeDelay); }; diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index 45f26917b66e50965585c0b5a4970a2018152933..05565c971eab76df6009c038c5e23744a87ff48b 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -18,6 +18,7 @@ #include "SimpleMissionItem.h" #include "SurveyMissionItem.h" #include "FixedWingLandingComplexItem.h" +#include "StructureScanComplexItem.h" #include "JsonHelper.h" #include "ParameterManager.h" #include "QGroundControlQmlGlobal.h" @@ -60,6 +61,7 @@ MissionController::MissionController(PlanMasterController* masterController, QOb , _itemsRequested(false) , _surveyMissionItemName(tr("Survey")) , _fwLandingMissionItemName(tr("Fixed Wing Landing")) + , _structureScanMissionItemName(tr("Structure Scan")) , _appSettings(qgcApp()->toolbox()->settingsManager()->appSettings()) , _progressPct(0) { @@ -379,6 +381,8 @@ int MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate } } else if (itemName == _fwLandingMissionItemName) { newItem = new FixedWingLandingComplexItem(_controllerVehicle, _visualItems); + } else if (itemName == _structureScanMissionItemName) { + newItem = new StructureScanComplexItem(_controllerVehicle, _visualItems); } else { qWarning() << "Internal error: Unknown complex item:" << itemName; return sequenceNumber; @@ -1475,6 +1479,7 @@ void MissionController::_initVisualItem(VisualMissionItem* visualItem) ComplexMissionItem* complexItem = qobject_cast(visualItem); if (complexItem) { connect(complexItem, &ComplexMissionItem::complexDistanceChanged, this, &MissionController::_recalcMissionFlightStatus); + connect(complexItem, &ComplexMissionItem::greatestDistanceToChanged, this, &MissionController::_recalcMissionFlightStatus); connect(complexItem, &ComplexMissionItem::additionalTimeDelayChanged, this, &MissionController::_recalcMissionFlightStatus); } else { qWarning() << "ComplexMissionItem not found"; @@ -1769,6 +1774,9 @@ QStringList MissionController::complexMissionItemNames(void) const if (_controllerVehicle->fixedWing()) { complexItems.append(_fwLandingMissionItemName); } + if (_controllerVehicle->multiRotor() || _controllerVehicle->vtol()) { + complexItems.append(_structureScanMissionItemName); + } return complexItems; } diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h index abc553c3781869a44ec18e64c164ce84ce9aefa2..9889466410154964b19fe3b16d38787083f8acae 100644 --- a/src/MissionManager/MissionController.h +++ b/src/MissionManager/MissionController.h @@ -231,6 +231,7 @@ private: MissionFlightStatus_t _missionFlightStatus; QString _surveyMissionItemName; QString _fwLandingMissionItemName; + QString _structureScanMissionItemName; AppSettings* _appSettings; double _progressPct; diff --git a/src/MissionManager/QGCMapPolygon.cc b/src/MissionManager/QGCMapPolygon.cc index aef9372ab181be432975d30b04e182f8e396caed..5beefa9b128b3a70fc73d005780a42aadfb51b16 100644 --- a/src/MissionManager/QGCMapPolygon.cc +++ b/src/MissionManager/QGCMapPolygon.cc @@ -354,3 +354,13 @@ void QGCMapPolygon::setInteractive(bool interactive) emit interactiveChanged(interactive); } } + +QGeoCoordinate QGCMapPolygon::vertexCoordinate(int vertex) const +{ + if (vertex >= 0 && vertex < _polygonPath.count()) { + return _polygonPath[vertex].value(); + } else { + qWarning() << "QGCMapPolygon::vertexCoordinate bad vertex requested"; + return QGeoCoordinate(); + } +} diff --git a/src/MissionManager/QGCMapPolygon.h b/src/MissionManager/QGCMapPolygon.h index b2990dbe3f11ec4d59754871860db04f1875ee25..e9abaa2eacb064e123783a4af3546e87ad09b2b4 100644 --- a/src/MissionManager/QGCMapPolygon.h +++ b/src/MissionManager/QGCMapPolygon.h @@ -55,6 +55,9 @@ public: /// Returns the path in a list of QGeoCoordinate's format QList coordinateList(void) const; + /// Returns the QGeoCoordinate for the vertex specified + QGeoCoordinate vertexCoordinate(int vertex) const; + /// Saves the polygon to the json object. /// @param json Json object to save to void saveToJson(QJsonObject& json); diff --git a/src/MissionManager/StructureScan.SettingsGroup.json b/src/MissionManager/StructureScan.SettingsGroup.json new file mode 100644 index 0000000000000000000000000000000000000000..3a0fbbcd158abe88b97ec74b108d99d50d144c94 --- /dev/null +++ b/src/MissionManager/StructureScan.SettingsGroup.json @@ -0,0 +1,35 @@ +[ +{ + "name": "Altitude", + "shortDescription": "Altitude for the bottom layer of the structure scan.", + "type": "double", + "units": "m", + "decimalPlaces": 1, + "defaultValue": 50 +}, +{ + "name": "Layers", + "shortDescription": "Number of scan layers.", + "type": "uint32", + "min": 1, + "defaultValue": 1 +}, +{ + "name": "Layer distance", + "shortDescription": "Distance between each layer.", + "type": "double", + "decimalPlaces": 2, + "min": 0, + "units": "m", + "defaultValue": 25 +}, +{ + "name": "Trigger distance", + "shortDescription": "Distance between each triggering of the camera. 0 specifies not camera trigger.", + "type": "double", + "decimalPlaces": 2, + "min": 0, + "units": "m", + "defaultValue": 25 +} +] diff --git a/src/MissionManager/StructureScanComplexItem.cc b/src/MissionManager/StructureScanComplexItem.cc new file mode 100644 index 0000000000000000000000000000000000000000..5c0257e375aaada626180baebd231b618847709d --- /dev/null +++ b/src/MissionManager/StructureScanComplexItem.cc @@ -0,0 +1,519 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "StructureScanComplexItem.h" +#include "JsonHelper.h" +#include "MissionController.h" +#include "QGCGeo.h" +#include "QGroundControlQmlGlobal.h" +#include "QGCQGeoCoordinate.h" +#include "SettingsManager.h" +#include "AppSettings.h" +#include "QGCQGeoCoordinate.h" + +#include + +QGC_LOGGING_CATEGORY(StructureScanComplexItemLog, "StructureScanComplexItemLog") + +const char* StructureScanComplexItem::jsonComplexItemTypeValue = "StructureScan-WIP"; + +const char* StructureScanComplexItem::_jsonGridObjectKey = "grid"; +const char* StructureScanComplexItem::_jsonGridAltitudeKey = "altitude"; +const char* StructureScanComplexItem::_jsonGridAltitudeRelativeKey = "relativeAltitude"; +const char* StructureScanComplexItem::_jsonGridAngleKey = "angle"; +const char* StructureScanComplexItem::_jsonGridSpacingKey = "spacing"; +const char* StructureScanComplexItem::_jsonGridEntryLocationKey = "entryLocation"; +const char* StructureScanComplexItem::_jsonTurnaroundDistKey = "turnAroundDistance"; +const char* StructureScanComplexItem::_jsonCameraTriggerDistanceKey = "cameraTriggerDistance"; +const char* StructureScanComplexItem::_jsonCameraTriggerInTurnaroundKey = "cameraTriggerInTurnaround"; +const char* StructureScanComplexItem::_jsonHoverAndCaptureKey = "hoverAndCapture"; +const char* StructureScanComplexItem::_jsonGroundResolutionKey = "groundResolution"; +const char* StructureScanComplexItem::_jsonFrontalOverlapKey = "imageFrontalOverlap"; +const char* StructureScanComplexItem::_jsonSideOverlapKey = "imageSideOverlap"; +const char* StructureScanComplexItem::_jsonCameraSensorWidthKey = "sensorWidth"; +const char* StructureScanComplexItem::_jsonCameraSensorHeightKey = "sensorHeight"; +const char* StructureScanComplexItem::_jsonCameraResolutionWidthKey = "resolutionWidth"; +const char* StructureScanComplexItem::_jsonCameraResolutionHeightKey = "resolutionHeight"; +const char* StructureScanComplexItem::_jsonCameraFocalLengthKey = "focalLength"; +const char* StructureScanComplexItem::_jsonCameraMinTriggerIntervalKey = "minTriggerInterval"; +const char* StructureScanComplexItem::_jsonCameraObjectKey = "camera"; +const char* StructureScanComplexItem::_jsonCameraNameKey = "name"; +const char* StructureScanComplexItem::_jsonManualGridKey = "manualGrid"; +const char* StructureScanComplexItem::_jsonCameraOrientationLandscapeKey = "orientationLandscape"; +const char* StructureScanComplexItem::_jsonFixedValueIsAltitudeKey = "fixedValueIsAltitude"; +const char* StructureScanComplexItem::_jsonRefly90DegreesKey = "refly90Degrees"; + +const char* StructureScanComplexItem::_altitudeFactName = "Altitude"; +const char* StructureScanComplexItem::_layersFactName = "Layers"; +const char* StructureScanComplexItem::_layerDistanceFactName = "Layer distance"; +const char* StructureScanComplexItem::_cameraTriggerDistanceFactName = "Trigger distance"; + +QMap StructureScanComplexItem::_metaDataMap; + +StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, QObject* parent) + : ComplexMissionItem (vehicle, parent) + , _sequenceNumber (0) + , _dirty (false) + , _altitudeRelative (true) + , _entryVertex (0) + , _ignoreRecalc (false) + , _scanDistance (0.0) + , _cameraShots (0) + , _cameraMinTriggerInterval (0) + , _altitudeFact (0, _altitudeFactName, FactMetaData::valueTypeDouble) + , _layersFact (0, _layersFactName, FactMetaData::valueTypeUint32) + , _layerDistanceFact (0, _layerDistanceFactName, FactMetaData::valueTypeDouble) + , _cameraTriggerDistanceFact(0, _cameraTriggerDistanceFactName, FactMetaData::valueTypeDouble) +{ + _editorQml = "qrc:/qml/StructureScanEditor.qml"; + + if (_metaDataMap.isEmpty()) { + _metaDataMap = FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/StructureScan.SettingsGroup.json"), this); + } + + _altitudeFact.setMetaData (_metaDataMap[_altitudeFactName]); + _layersFact.setMetaData (_metaDataMap[_layersFactName]); + _layerDistanceFact.setMetaData (_metaDataMap[_layerDistanceFactName]); + _cameraTriggerDistanceFact.setMetaData (_metaDataMap[_cameraTriggerDistanceFactName]); + + _altitudeFact.setRawValue (_altitudeFact.rawDefaultValue()); + _layersFact.setRawValue (_layersFact.rawDefaultValue()); + _layerDistanceFact.setRawValue (_layerDistanceFact.rawDefaultValue()); + _cameraTriggerDistanceFact.setRawValue (_cameraTriggerDistanceFact.rawDefaultValue()); + + _altitudeFact.setRawValue(qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue()); + + connect(&_altitudeFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); + connect(&_layersFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); + connect(&_layerDistanceFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); + connect(&_cameraTriggerDistanceFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); + + connect(this, &StructureScanComplexItem::altitudeRelativeChanged, this, &StructureScanComplexItem::_setDirty); + connect(this, &StructureScanComplexItem::altitudeRelativeChanged, this, &StructureScanComplexItem::coordinateHasRelativeAltitudeChanged); + connect(this, &StructureScanComplexItem::altitudeRelativeChanged, this, &StructureScanComplexItem::exitCoordinateHasRelativeAltitudeChanged); + + connect(&_altitudeFact, &Fact::valueChanged, this, &StructureScanComplexItem::_updateCoordinateAltitudes); + + connect(&_mapPolygon, &QGCMapPolygon::dirtyChanged, this, &StructureScanComplexItem::_polygonDirtyChanged); + connect(&_mapPolygon, &QGCMapPolygon::countChanged, this, &StructureScanComplexItem::_polygonCountChanged); + connect(&_mapPolygon, &QGCMapPolygon::pathChanged, this, &StructureScanComplexItem::_polygonPathChanged); +} + +void StructureScanComplexItem::_setScanDistance(double scanDistance) +{ + if (!qFuzzyCompare(_scanDistance, scanDistance)) { + _scanDistance = scanDistance; + emit complexDistanceChanged(_scanDistance); + } +} + +void StructureScanComplexItem::_setCameraShots(int cameraShots) +{ + if (_cameraShots != cameraShots) { + _cameraShots = cameraShots; + emit cameraShotsChanged(this->cameraShots()); + } +} + +void StructureScanComplexItem::_clearInternal(void) +{ + setDirty(true); + + emit specifiesCoordinateChanged(); + emit lastSequenceNumberChanged(lastSequenceNumber()); +} + +void StructureScanComplexItem::_polygonCountChanged(int count) +{ + Q_UNUSED(count); + emit lastSequenceNumberChanged(lastSequenceNumber()); +} + +int StructureScanComplexItem::lastSequenceNumber(void) const +{ + return _sequenceNumber + + ((_mapPolygon.count() + 1) * _layersFact.rawValue().toInt()) + // 1 waypoint for each polygon vertex + 1 to go back to first polygon vertex + 1; // Gimbal yaw command +} + +void StructureScanComplexItem::setDirty(bool dirty) +{ + if (_dirty != dirty) { + _dirty = dirty; + emit dirtyChanged(_dirty); + } +} + +void StructureScanComplexItem::save(QJsonArray& missionItems) +{ + Q_UNUSED(missionItems); +#if 0 + QJsonObject saveObject; + + saveObject[JsonHelper::jsonVersionKey] = 3; + saveObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + saveObject[_jsonManualGridKey] = _manualGridFact.rawValue().toBool(); + saveObject[_jsonFixedValueIsAltitudeKey] = _fixedValueIsAltitudeFact.rawValue().toBool(); + saveObject[_jsonHoverAndCaptureKey] = _hoverAndCaptureFact.rawValue().toBool(); + saveObject[_jsonRefly90DegreesKey] = _refly90Degrees; + saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble(); + + QJsonObject gridObject; + gridObject[_jsonGridAltitudeKey] = _gridAltitudeFact.rawValue().toDouble(); + gridObject[_jsonGridAltitudeRelativeKey] = _gridAltitudeRelativeFact.rawValue().toBool(); + gridObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); + gridObject[_jsonGridSpacingKey] = _gridSpacingFact.rawValue().toDouble(); + gridObject[_jsonGridEntryLocationKey] = _gridEntryLocationFact.rawValue().toDouble(); + gridObject[_jsonTurnaroundDistKey] = _turnaroundDistFact.rawValue().toDouble(); + + saveObject[_jsonGridObjectKey] = gridObject; + + if (!_manualGridFact.rawValue().toBool()) { + QJsonObject cameraObject; + cameraObject[_jsonCameraNameKey] = _cameraFact.rawValue().toString(); + cameraObject[_jsonCameraOrientationLandscapeKey] = _cameraOrientationLandscapeFact.rawValue().toBool(); + cameraObject[_jsonCameraSensorWidthKey] = _cameraSensorWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraSensorHeightKey] = _cameraSensorHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionWidthKey] = _cameraResolutionWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionHeightKey] = _cameraResolutionHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraFocalLengthKey] = _cameraFocalLengthFact.rawValue().toDouble(); + cameraObject[_jsonCameraMinTriggerIntervalKey] = _cameraMinTriggerInterval; + cameraObject[_jsonGroundResolutionKey] = _groundResolutionFact.rawValue().toDouble(); + cameraObject[_jsonFrontalOverlapKey] = _frontalOverlapFact.rawValue().toInt(); + cameraObject[_jsonSideOverlapKey] = _sideOverlapFact.rawValue().toInt(); + + saveObject[_jsonCameraObjectKey] = cameraObject; + } + + // Polygon shape + _mapPolygon.saveToJson(saveObject); + + missionItems.append(saveObject); +#endif +} + +void StructureScanComplexItem::setSequenceNumber(int sequenceNumber) +{ + if (_sequenceNumber != sequenceNumber) { + _sequenceNumber = sequenceNumber; + emit sequenceNumberChanged(sequenceNumber); + emit lastSequenceNumberChanged(lastSequenceNumber()); + } +} + +bool StructureScanComplexItem::load(const QJsonObject& complexObject, int sequenceNumber, QString& errorString) +{ +#if 0 + QJsonObject v2Object = complexObject; + + // 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(v2Object, versionKeyInfoList, errorString)) { + return false; + } + + int version = v2Object[JsonHelper::jsonVersionKey].toInt(); + if (version != 2 && version != 3) { + errorString = tr("%1 does not support this version of survey items").arg(qgcApp()->applicationName()); + return false; + } + if (version == 2) { + // Convert to v3 + if (v2Object.contains(VisualMissionItem::jsonTypeKey) && v2Object[VisualMissionItem::jsonTypeKey].toString() == QStringLiteral("survey")) { + v2Object[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + v2Object[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + } + } + + QList mainKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { QGCMapPolygon::jsonPolygonKey, QJsonValue::Array, true }, + { _jsonGridObjectKey, QJsonValue::Object, true }, + { _jsonCameraObjectKey, QJsonValue::Object, false }, + { _jsonCameraTriggerDistanceKey, QJsonValue::Double, true }, + { _jsonManualGridKey, QJsonValue::Bool, true }, + { _jsonFixedValueIsAltitudeKey, QJsonValue::Bool, true }, + { _jsonHoverAndCaptureKey, QJsonValue::Bool, false }, + { _jsonRefly90DegreesKey, QJsonValue::Bool, false }, + }; + if (!JsonHelper::validateKeys(v2Object, mainKeyInfoList, errorString)) { + return false; + } + + QString itemType = v2Object[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = v2Object[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; + + _mapPolygon.clear(); + + setSequenceNumber(sequenceNumber); + + _manualGridFact.setRawValue (v2Object[_jsonManualGridKey].toBool(true)); + _fixedValueIsAltitudeFact.setRawValue (v2Object[_jsonFixedValueIsAltitudeKey].toBool(true)); + _gridAltitudeRelativeFact.setRawValue (v2Object[_jsonGridAltitudeRelativeKey].toBool(true)); + _hoverAndCaptureFact.setRawValue (v2Object[_jsonHoverAndCaptureKey].toBool(false)); + + _refly90Degrees = v2Object[_jsonRefly90DegreesKey].toBool(false); + + QList gridKeyInfoList = { + { _jsonGridAltitudeKey, QJsonValue::Double, true }, + { _jsonGridAltitudeRelativeKey, QJsonValue::Bool, true }, + { _jsonGridAngleKey, QJsonValue::Double, true }, + { _jsonGridSpacingKey, QJsonValue::Double, true }, + { _jsonGridEntryLocationKey, QJsonValue::Double, false }, + { _jsonTurnaroundDistKey, QJsonValue::Double, true }, + }; + QJsonObject gridObject = v2Object[_jsonGridObjectKey].toObject(); + if (!JsonHelper::validateKeys(gridObject, gridKeyInfoList, errorString)) { + return false; + } + _gridAltitudeFact.setRawValue (gridObject[_jsonGridAltitudeKey].toDouble()); + _gridAngleFact.setRawValue (gridObject[_jsonGridAngleKey].toDouble()); + _gridSpacingFact.setRawValue (gridObject[_jsonGridSpacingKey].toDouble()); + _turnaroundDistFact.setRawValue (gridObject[_jsonTurnaroundDistKey].toDouble()); + _cameraTriggerDistanceFact.setRawValue (v2Object[_jsonCameraTriggerDistanceKey].toDouble()); + if (gridObject.contains(_jsonGridEntryLocationKey)) { + _gridEntryLocationFact.setRawValue(gridObject[_jsonGridEntryLocationKey].toDouble()); + } else { + _gridEntryLocationFact.setRawValue(_gridEntryLocationFact.rawDefaultValue()); + } + + if (!_manualGridFact.rawValue().toBool()) { + if (!v2Object.contains(_jsonCameraObjectKey)) { + errorString = tr("%1 but %2 object is missing").arg("manualGrid = false").arg("camera"); + return false; + } + + QJsonObject cameraObject = v2Object[_jsonCameraObjectKey].toObject(); + + // Older code had typo on "imageSideOverlap" incorrectly being "imageSizeOverlap" + QString incorrectImageSideOverlap = "imageSizeOverlap"; + if (cameraObject.contains(incorrectImageSideOverlap)) { + cameraObject[_jsonSideOverlapKey] = cameraObject[incorrectImageSideOverlap]; + cameraObject.remove(incorrectImageSideOverlap); + } + + QList cameraKeyInfoList = { + { _jsonGroundResolutionKey, QJsonValue::Double, true }, + { _jsonFrontalOverlapKey, QJsonValue::Double, true }, + { _jsonSideOverlapKey, QJsonValue::Double, true }, + { _jsonCameraSensorWidthKey, QJsonValue::Double, true }, + { _jsonCameraSensorHeightKey, QJsonValue::Double, true }, + { _jsonCameraResolutionWidthKey, QJsonValue::Double, true }, + { _jsonCameraResolutionHeightKey, QJsonValue::Double, true }, + { _jsonCameraFocalLengthKey, QJsonValue::Double, true }, + { _jsonCameraNameKey, QJsonValue::String, true }, + { _jsonCameraOrientationLandscapeKey, QJsonValue::Bool, true }, + { _jsonCameraMinTriggerIntervalKey, QJsonValue::Double, false }, + }; + if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, errorString)) { + return false; + } + + _cameraFact.setRawValue(cameraObject[_jsonCameraNameKey].toString()); + _cameraOrientationLandscapeFact.setRawValue(cameraObject[_jsonCameraOrientationLandscapeKey].toBool(true)); + + _groundResolutionFact.setRawValue (cameraObject[_jsonGroundResolutionKey].toDouble()); + _frontalOverlapFact.setRawValue (cameraObject[_jsonFrontalOverlapKey].toInt()); + _sideOverlapFact.setRawValue (cameraObject[_jsonSideOverlapKey].toInt()); + _cameraSensorWidthFact.setRawValue (cameraObject[_jsonCameraSensorWidthKey].toDouble()); + _cameraSensorHeightFact.setRawValue (cameraObject[_jsonCameraSensorHeightKey].toDouble()); + _cameraResolutionWidthFact.setRawValue (cameraObject[_jsonCameraResolutionWidthKey].toDouble()); + _cameraResolutionHeightFact.setRawValue (cameraObject[_jsonCameraResolutionHeightKey].toDouble()); + _cameraFocalLengthFact.setRawValue (cameraObject[_jsonCameraFocalLengthKey].toDouble()); + _cameraMinTriggerInterval = cameraObject[_jsonCameraMinTriggerIntervalKey].toDouble(0); + } + + // Polygon shape + /// Load a polygon from json + /// @param json Json object to load from + /// @param required true: no polygon in object will generate error + /// @param errorString Error string if return is false + /// @return true: success, false: failure (errorString set) + if (!_mapPolygon.loadFromJson(v2Object, true /* required */, errorString)) { + _mapPolygon.clear(); + return false; + } + + _ignoreRecalc = false; + _generateGrid(); + + return true; +#else + Q_UNUSED(complexObject); + Q_UNUSED(sequenceNumber); + Q_UNUSED(errorString); + + return false; +#endif +} + +void StructureScanComplexItem::_polygonPathChanged(void) +{ + emit coordinateChanged(coordinate()); + emit exitCoordinateChanged(exitCoordinate()); + emit greatestDistanceToChanged(); +} + +double StructureScanComplexItem::greatestDistanceTo(const QGeoCoordinate &other) const +{ + double greatestDistance = 0.0; + QList vertices = _mapPolygon.coordinateList(); + + for (int i=0; i greatestDistance) { + greatestDistance = distance; + } + } + + return greatestDistance; +} + +bool StructureScanComplexItem::specifiesCoordinate(void) const +{ + return _mapPolygon.count() > 2; +} + +void StructureScanComplexItem::appendMissionItems(QList& items, QObject* missionItemParent) +{ + int seqNum = _sequenceNumber; + double baseAltitude = _altitudeFact.rawValue().toDouble(); + + MissionItem* item = new MissionItem(seqNum++, + MAV_CMD_DO_MOUNT_CONTROL, + MAV_FRAME_MISSION, + 0, // Gimbal pitch + 0, // Gimbal roll + 90, // Gimbal yaw + 0, 0, 0, // param 4-6 not used + MAV_MOUNT_MODE_MAVLINK_TARGETING, + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + + for (int layer=0; layer<_layersFact.rawValue().toInt(); layer++) { + double layerAltitude = baseAltitude + (layer * _layerDistanceFact.rawValue().toDouble()); + + for (int i=0; i<_mapPolygon.count(); i++) { + QGeoCoordinate vertexCoord = _mapPolygon.pathModel().value(i)->coordinate(); + + MissionItem* item = new MissionItem(seqNum++, + MAV_CMD_NAV_WAYPOINT, + _altitudeRelative ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL, + 0, // No hold time + 0.0, // No acceptance radius specified + 0.0, // Pass through waypoint + 90, //std::numeric_limits::quiet_NaN(), // Yaw unchanged + vertexCoord.latitude(), + vertexCoord.longitude(), + layerAltitude, + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + } + + QGeoCoordinate vertexCoord = _mapPolygon.pathModel().value(0)->coordinate(); + + MissionItem* item = new MissionItem(seqNum++, + MAV_CMD_NAV_WAYPOINT, + _altitudeRelative ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL, + 0, // No hold time + 0.0, // No acceptance radius specified + 0.0, // Pass through waypoint + std::numeric_limits::quiet_NaN(), // Yaw unchanged + vertexCoord.latitude(), + vertexCoord.longitude(), + layerAltitude, + true, // autoContinue + false, // isCurrentItem + missionItemParent); + items.append(item); + } +} + +int StructureScanComplexItem::cameraShots(void) const +{ + return true /*_triggerCamera()*/ ? _cameraShots : 0; +} + +void StructureScanComplexItem::setMissionFlightStatus(MissionController::MissionFlightStatus_t& missionFlightStatus) +{ + ComplexMissionItem::setMissionFlightStatus(missionFlightStatus); + if (!qFuzzyCompare(_cruiseSpeed, missionFlightStatus.vehicleSpeed)) { + _cruiseSpeed = missionFlightStatus.vehicleSpeed; + emit timeBetweenShotsChanged(); + } +} + +void StructureScanComplexItem::_setDirty(void) +{ + setDirty(true); +} + +void StructureScanComplexItem::applyNewAltitude(double newAltitude) +{ + _altitudeFact.setRawValue(newAltitude); +} + +void StructureScanComplexItem::_polygonDirtyChanged(bool dirty) +{ + if (dirty) { + setDirty(true); + } +} + +double StructureScanComplexItem::timeBetweenShots(void) const +{ + return _cruiseSpeed == 0 ? 0 :_cameraTriggerDistanceFact.rawValue().toDouble() / _cruiseSpeed; +} + +QGeoCoordinate StructureScanComplexItem::coordinate(void) const +{ + if (_mapPolygon.count() > 0) { + int entryVertex = qMax(qMin(_entryVertex, _mapPolygon.count() - 1), 0); + return _mapPolygon.vertexCoordinate(entryVertex); + } else { + return QGeoCoordinate(); + } +} + +QGeoCoordinate StructureScanComplexItem::exitCoordinate(void) const +{ + return coordinate(); +} + +void StructureScanComplexItem::_updateCoordinateAltitudes(void) +{ + emit coordinateChanged(coordinate()); + emit exitCoordinateChanged(exitCoordinate()); +} + +void StructureScanComplexItem::rotateEntryPoint(void) +{ + _entryVertex++; + if (_entryVertex >= _mapPolygon.count()) { + _entryVertex = 0; + } + emit coordinateChanged(coordinate()); + emit exitCoordinateChanged(exitCoordinate()); +} diff --git a/src/MissionManager/StructureScanComplexItem.h b/src/MissionManager/StructureScanComplexItem.h new file mode 100644 index 0000000000000000000000000000000000000000..342874301a251777608922f68122495c2fee87c3 --- /dev/null +++ b/src/MissionManager/StructureScanComplexItem.h @@ -0,0 +1,162 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + + +#ifndef StructureScanComplexItem_H +#define StructureScanComplexItem_H + +#include "ComplexMissionItem.h" +#include "MissionItem.h" +#include "SettingsFact.h" +#include "QGCLoggingCategory.h" +#include "QGCMapPolygon.h" + +Q_DECLARE_LOGGING_CATEGORY(StructureScanComplexItemLog) + +class StructureScanComplexItem : public ComplexMissionItem +{ + Q_OBJECT + +public: + StructureScanComplexItem(Vehicle* vehicle, QObject* parent = NULL); + + Q_PROPERTY(Fact* altitude READ altitude CONSTANT) + Q_PROPERTY(Fact* layers READ layers CONSTANT) + Q_PROPERTY(Fact* layerDistance READ layerDistance CONSTANT) + Q_PROPERTY(Fact* cameraTriggerDistance READ cameraTriggerDistance CONSTANT) + Q_PROPERTY(bool altitudeRelative MEMBER _altitudeRelative NOTIFY altitudeRelativeChanged) + Q_PROPERTY(int cameraShots READ cameraShots NOTIFY cameraShotsChanged) + Q_PROPERTY(double timeBetweenShots READ timeBetweenShots NOTIFY timeBetweenShotsChanged) + Q_PROPERTY(double cameraMinTriggerInterval MEMBER _cameraMinTriggerInterval NOTIFY cameraMinTriggerIntervalChanged) + Q_PROPERTY(QGCMapPolygon* mapPolygon READ mapPolygon CONSTANT) + + Fact* altitude (void) { return &_altitudeFact; } + Fact* layers (void) { return &_layersFact; } + Fact* layerDistance (void) { return &_layerDistanceFact; } + Fact* cameraTriggerDistance (void) { return &_cameraTriggerDistanceFact; } + + int cameraShots (void) const; + double timeBetweenShots(void) const; + QGCMapPolygon* mapPolygon (void) { return &_mapPolygon; } + + Q_INVOKABLE void rotateEntryPoint(void); + + // Overrides from ComplexMissionItem + + double complexDistance (void) const final { return _scanDistance; } + int lastSequenceNumber (void) const final; + bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final; + double greatestDistanceTo (const QGeoCoordinate &other) const final; + QString mapVisualQML (void) const final { return QStringLiteral("StructureScanMapVisual.qml"); } + + // Overrides from VisualMissionItem + + bool dirty (void) const final { return _dirty; } + bool isSimpleItem (void) const final { return false; } + bool isStandaloneCoordinate (void) const final { return false; } + bool specifiesCoordinate (void) const final; + bool specifiesAltitudeOnly (void) const final { return false; } + QString commandDescription (void) const final { return tr("Structure Scan"); } + QString commandName (void) const final { return tr("Structure Scan"); } + QString abbreviation (void) const final { return "S"; } + QGeoCoordinate coordinate (void) const final; + QGeoCoordinate exitCoordinate (void) const final; + int sequenceNumber (void) const final { return _sequenceNumber; } + double specifiedFlightSpeed (void) final { return std::numeric_limits::quiet_NaN(); } + double specifiedGimbalYaw (void) final { return std::numeric_limits::quiet_NaN(); } + void appendMissionItems (QList& items, QObject* missionItemParent) final; + void setMissionFlightStatus (MissionController::MissionFlightStatus_t& missionFlightStatus) final; + void applyNewAltitude (double newAltitude) final; + + bool coordinateHasRelativeAltitude (void) const final { return _altitudeRelative; } + bool exitCoordinateHasRelativeAltitude (void) const final { return _altitudeRelative; } + bool exitCoordinateSameAsEntry (void) const final { return true; } + + void setDirty (bool dirty) final; + void setCoordinate (const QGeoCoordinate& coordinate) final { Q_UNUSED(coordinate); } + void setSequenceNumber (int sequenceNumber) final; + void save (QJsonArray& missionItems) final; + + static const char* jsonComplexItemTypeValue; + +signals: + void cameraShotsChanged (int cameraShots); + void timeBetweenShotsChanged (void); + void cameraMinTriggerIntervalChanged(double cameraMinTriggerInterval); + void altitudeRelativeChanged (bool altitudeRelative); + +private slots: + void _setDirty(void); + void _polygonDirtyChanged(bool dirty); + void _polygonCountChanged(int count); + void _polygonPathChanged(void); + void _clearInternal(void); + void _updateCoordinateAltitudes(void); + +private: + void _setExitCoordinate(const QGeoCoordinate& coordinate); + void _setScanDistance(double scanDistance); + void _setCameraShots(int cameraShots); + double _triggerDistance(void) const; + + int _sequenceNumber; + bool _dirty; + QGCMapPolygon _mapPolygon; + bool _altitudeRelative; + int _entryVertex; // Polygon vertext which is used as the mission entry point + + bool _ignoreRecalc; + double _scanDistance; + int _cameraShots; + double _timeBetweenShots; + double _cameraMinTriggerInterval; + double _cruiseSpeed; + + static QMap _metaDataMap; + + Fact _altitudeFact; + Fact _layersFact; + Fact _layerDistanceFact; + Fact _cameraTriggerDistanceFact; + + static const char* _altitudeFactName; + static const char* _layersFactName; + static const char* _layerDistanceFactName; + static const char* _cameraTriggerDistanceFactName; + + static const char* _jsonGridObjectKey; + static const char* _jsonGridAltitudeKey; + static const char* _jsonGridAltitudeRelativeKey; + static const char* _jsonGridAngleKey; + static const char* _jsonGridSpacingKey; + static const char* _jsonGridEntryLocationKey; + static const char* _jsonTurnaroundDistKey; + static const char* _jsonCameraTriggerDistanceKey; + static const char* _jsonCameraTriggerInTurnaroundKey; + static const char* _jsonHoverAndCaptureKey; + static const char* _jsonGroundResolutionKey; + static const char* _jsonFrontalOverlapKey; + static const char* _jsonSideOverlapKey; + static const char* _jsonCameraSensorWidthKey; + static const char* _jsonCameraSensorHeightKey; + static const char* _jsonCameraResolutionWidthKey; + static const char* _jsonCameraResolutionHeightKey; + static const char* _jsonCameraFocalLengthKey; + static const char* _jsonCameraMinTriggerIntervalKey; + static const char* _jsonManualGridKey; + static const char* _jsonCameraObjectKey; + static const char* _jsonCameraNameKey; + static const char* _jsonCameraOrientationLandscapeKey; + static const char* _jsonFixedValueIsAltitudeKey; + static const char* _jsonRefly90DegreesKey; + + static const int _hoverAndCaptureDelaySeconds = 1; +}; + +#endif diff --git a/src/MissionManager/SurveyMissionItem.cc b/src/MissionManager/SurveyMissionItem.cc index 94520eefee252dd9d2e5d5b371e7ca56e5b3859b..9747b19aa6b8c292aaaad4617c016be106b43051 100644 --- a/src/MissionManager/SurveyMissionItem.cc +++ b/src/MissionManager/SurveyMissionItem.cc @@ -1322,7 +1322,7 @@ double SurveyMissionItem::timeBetweenShots(void) const return _cruiseSpeed == 0 ? 0 : _triggerDistance() / _cruiseSpeed; } -void SurveyMissionItem::setMissionFlightStatus (MissionController::MissionFlightStatus_t& missionFlightStatus) +void SurveyMissionItem::setMissionFlightStatus(MissionController::MissionFlightStatus_t& missionFlightStatus) { ComplexMissionItem::setMissionFlightStatus(missionFlightStatus); if (!qFuzzyCompare(_cruiseSpeed, missionFlightStatus.vehicleSpeed)) { diff --git a/src/PlanView/StructureScanEditor.qml b/src/PlanView/StructureScanEditor.qml new file mode 100644 index 0000000000000000000000000000000000000000..a23009bce6c9061e49fff318f18f1b1183d89452 --- /dev/null +++ b/src/PlanView/StructureScanEditor.qml @@ -0,0 +1,143 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Extras 1.4 +import QtQuick.Layouts 1.2 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Vehicle 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.FlightMap 1.0 + +// Editor for Survery mission items +Rectangle { + id: _root + height: visible ? (editorColumn.height + (_margin * 2)) : 0 + width: availableWidth + color: qgcPal.windowShadeDark + radius: _radius + + // The following properties must be available up the hierarchy chain + //property real availableWidth ///< Width for control + //property var missionItem ///< Mission Item for editor + + property real _margin: ScreenTools.defaultFontPixelWidth / 2 + property real _fieldWidth: ScreenTools.defaultFontPixelWidth * 10.5 + property var _vehicle: QGroundControl.multiVehicleManager.activeVehicle ? QGroundControl.multiVehicleManager.activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle + + + function polygonCaptureStarted() { + missionItem.clearPolygon() + } + + function polygonCaptureFinished(coordinates) { + for (var i=0; i 0 && missionItem.cameraMinTriggerInterval !== 0 && missionItem.cameraMinTriggerInterval > missionItem.timeBetweenShots + } + + GridLayout { + anchors.left: parent.left + anchors.right: parent.right + columnSpacing: _margin + rowSpacing: _margin + columns: 2 + + QGCLabel { text: qsTr("Altitude") } + FactTextField { + fact: missionItem.altitude + Layout.fillWidth: true + } + + QGCLabel { text: qsTr("Layers") } + FactTextField { + fact: missionItem.layers + Layout.fillWidth: true + } + + QGCLabel { text: qsTr("Layer distance") } + FactTextField { + fact: missionItem.layerDistance + Layout.fillWidth: true + } + + QGCLabel { text: qsTr("Trigger Distance") } + FactTextField { + fact: missionItem.cameraTriggerDistance + Layout.fillWidth: true + } + + QGCCheckBox { + text: qsTr("Relative altitude") + checked: missionItem.altitudeRelative + Layout.columnSpan: 2 + onClicked: missionItem.altitudeRelative = checked + } + } + + QGCLabel { text: qsTr("Point camera to structure using:") } + QGCRadioButton { text: qsTr("Vehicle yaw"); enabled: false } + QGCRadioButton { text: qsTr("Gimbal yaw"); checked: true; enabled: false } + + QGCButton { + text: qsTr("Rotate entry point") + onClicked: missionItem.rotateEntryPoint() + } + + SectionHeader { + id: statsHeader + text: qsTr("Statistics") + } + + Grid { + columns: 2 + columnSpacing: ScreenTools.defaultFontPixelWidth + visible: statsHeader.checked + + QGCLabel { text: qsTr("Photo count") } + QGCLabel { text: missionItem.cameraShots } + + QGCLabel { text: qsTr("Photo interval") } + QGCLabel { text: missionItem.timeBetweenShots.toFixed(1) + " " + qsTr("secs") } + } + } +} + diff --git a/src/PlanView/StructureScanMapVisual.qml b/src/PlanView/StructureScanMapVisual.qml new file mode 100644 index 0000000000000000000000000000000000000000..4110ddcbe9e0c675ab50c70c9ac8175c7582400f --- /dev/null +++ b/src/PlanView/StructureScanMapVisual.qml @@ -0,0 +1,151 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtLocation 5.3 +import QtPositioning 5.3 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FlightMap 1.0 + +/// Survey Complex Mission Item visuals +Item { + id: _root + + property var map ///< Map control to place item in + + property var _missionItem: object + property var _mapPolygon: object.mapPolygon + property var _gridComponent + property var _entryCoordinate + property var _exitCoordinate + + signal clicked(int sequenceNumber) + + function _addVisualElements() { + _gridComponent = gridComponent.createObject(map) + _entryCoordinate = entryPointComponent.createObject(map) + _exitCoordinate = exitPointComponent.createObject(map) + map.addMapItem(_gridComponent) + map.addMapItem(_entryCoordinate) + map.addMapItem(_exitCoordinate) + } + + function _destroyVisualElements() { + _gridComponent.destroy() + _entryCoordinate.destroy() + _exitCoordinate.destroy() + } + + /// Add an initial 4 sided polygon if there is none + function _addInitialPolygon() { + if (_mapPolygon.count < 3) { + // Initial polygon is inset to take 2/3rds space + var rect = Qt.rect(map.centerViewport.x, map.centerViewport.y, map.centerViewport.width, map.centerViewport.height) + rect.x += (rect.width * 0.25) / 2 + rect.y += (rect.height * 0.25) / 2 + rect.width *= 0.75 + rect.height *= 0.75 + + var centerCoord = map.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */) + var topLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */) + var topRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */) + var bottomLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */) + var bottomRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */) + + // Initial polygon has max width and height of 3000 meters + var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), 3000) / 2 + var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), 3000) / 2 + topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0) + topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0) + bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180) + bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180) + + _mapPolygon.appendVertex(topLeftCoord) + _mapPolygon.appendVertex(topRightCoord) + _mapPolygon.appendVertex(bottomRightCoord) + _mapPolygon.appendVertex(bottomLeftCoord) + } + } + + Component.onCompleted: { + _addInitialPolygon() + _addVisualElements() + } + + Component.onDestruction: { + _destroyVisualElements() + } + + QGCMapPolygonVisuals { + id: mapPolygonVisuals + mapControl: map + mapPolygon: _mapPolygon + interactive: _missionItem.isCurrentItem + borderWidth: 1 + borderColor: "black" + interiorColor: "green" + interiorOpacity: 0.5 + } + + // Survey grid lines + Component { + id: gridComponent + + MapPolyline { + line.color: "white" + line.width: 2 + path: _missionItem.gridPoints + } + } + + // Entry point + Component { + id: entryPointComponent + + MapQuickItem { + anchorPoint.x: sourceItem.anchorPointX + anchorPoint.y: sourceItem.anchorPointY + z: QGroundControl.zOrderMapItems + coordinate: _missionItem.coordinate + visible: _missionItem.exitCoordinate.isValid + + sourceItem: MissionItemIndexLabel { + index: _missionItem.sequenceNumber + label: "Entry" + checked: _missionItem.isCurrentItem + onClicked: _root.clicked(_missionItem.sequenceNumber) + } + } + } + + // Exit point + Component { + id: exitPointComponent + + MapQuickItem { + anchorPoint.x: sourceItem.anchorPointX + anchorPoint.y: sourceItem.anchorPointY + z: QGroundControl.zOrderMapItems + coordinate: _missionItem.exitCoordinate + visible: _missionItem.exitCoordinate.isValid + + sourceItem: MissionItemIndexLabel { + index: _missionItem.lastSequenceNumber + label: "Exit" + checked: _missionItem.isCurrentItem + onClicked: _root.clicked(_missionItem.sequenceNumber) + } + } + } +}