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)
+ }
+ }
+ }
+}