From de0169abaa94d556cfd29d0c00320291ab630cb8 Mon Sep 17 00:00:00 2001
From: Don Gagne <don@thegagnes.com>
Date: Fri, 30 Sep 2016 11:01:50 -0700
Subject: [PATCH 1/4] Add new validateKeys support

---
 src/JsonHelper.cc | 22 ++++++++++++++++++++++
 src/JsonHelper.h  |  8 ++++++++
 2 files changed, 30 insertions(+)

diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc
index e1c4058..b15220c 100644
--- a/src/JsonHelper.cc
+++ b/src/JsonHelper.cc
@@ -262,3 +262,25 @@ void JsonHelper::saveGeoCoordinateArray(const QList<QGeoCoordinate>&    rgPoints
     }
     return saveGeoCoordinateArray(rgVarPoints, writeAltitude, jsonValue);
 }
+
+bool JsonHelper::validateKeys(const QJsonObject& jsonObject, const QList<JsonHelper::KeyValidateInfo>& keyInfo, QString& errorString)
+{
+    QStringList             keyList;
+    QList<QJsonValue::Type> typeList;
+
+    for (int i=0; i<keyInfo.count(); i++) {
+        if (keyInfo[i].required) {
+            keyList.append(keyInfo[i].key);
+        }
+    }
+    if (!validateRequiredKeys(jsonObject, keyList, errorString)) {
+        return false;
+    }
+
+    keyList.clear();
+    for (int i=0; i<keyInfo.count(); i++) {
+        keyList.append(keyInfo[i].key);
+        typeList.append(keyInfo[i].type);
+    }
+    return validateKeyTypes(jsonObject, keyList, typeList, errorString);
+}
diff --git a/src/JsonHelper.h b/src/JsonHelper.h
index 12e7ccd..546e059 100644
--- a/src/JsonHelper.h
+++ b/src/JsonHelper.h
@@ -37,6 +37,14 @@ public:
     static bool validateRequiredKeys(const QJsonObject& jsonObject, const QStringList& keys, QString& errorString);
     static bool validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList<QJsonValue::Type>& types, QString& errorString);
 
+    typedef struct {
+        const char*         key;        ///< json key name
+        QJsonValue::Type    type;       ///< type of key
+        bool                required;   ///< true: key must be present
+    } KeyValidateInfo;
+
+    static bool validateKeys(const QJsonObject& jsonObject, const QList<KeyValidateInfo>& keyInfo, QString& errorString);
+
     /// Loads a QGeoCoordinate
     /// @return false: validation failed
     static bool loadGeoCoordinate(const QJsonValue& jsonValue,          ///< json value to load from

From 23e95926a7cb95d0e0ecf1aa78b6e70ada123066 Mon Sep 17 00:00:00 2001
From: Don Gagne <don@thegagnes.com>
Date: Fri, 30 Sep 2016 11:02:02 -0700
Subject: [PATCH 2/4] Add new fact controls

---
 qgroundcontrol.qrc                                |  2 ++
 src/FactSystem/FactControls/FactTextFieldGrid.qml | 27 +++++++++++++++++++++++
 src/FactSystem/FactControls/FactTextFieldRow.qml  | 19 ++++++++++++++++
 src/FactSystem/FactControls/qmldir                | 14 +++++++-----
 4 files changed, 56 insertions(+), 6 deletions(-)
 create mode 100644 src/FactSystem/FactControls/FactTextFieldGrid.qml
 create mode 100644 src/FactSystem/FactControls/FactTextFieldRow.qml

diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc
index f21a19d..ebf701b 100644
--- a/qgroundcontrol.qrc
+++ b/qgroundcontrol.qrc
@@ -103,6 +103,8 @@
         <file alias="QGroundControl/FactControls/FactLabel.qml">src/FactSystem/FactControls/FactLabel.qml</file>
         <file alias="QGroundControl/FactControls/FactPanel.qml">src/FactSystem/FactControls/FactPanel.qml</file>
         <file alias="QGroundControl/FactControls/FactTextField.qml">src/FactSystem/FactControls/FactTextField.qml</file>
+        <file alias="QGroundControl/FactControls/FactTextFieldGrid.qml">src/FactSystem/FactControls/FactTextFieldGrid.qml</file>
+        <file alias="QGroundControl/FactControls/FactTextFieldRow.qml">src/FactSystem/FactControls/FactTextFieldRow.qml</file>
         <file alias="QGroundControl/FactControls/qmldir">src/FactSystem/FactControls/qmldir</file>
         <file alias="QGroundControl/FlightDisplay/qmldir">src/FlightDisplay/qmldir</file>
         <file alias="QGroundControl/FlightDisplay/FlightDisplayView.qml">src/FlightDisplay/FlightDisplayView.qml</file>
diff --git a/src/FactSystem/FactControls/FactTextFieldGrid.qml b/src/FactSystem/FactControls/FactTextFieldGrid.qml
new file mode 100644
index 0000000..90097c7
--- /dev/null
+++ b/src/FactSystem/FactControls/FactTextFieldGrid.qml
@@ -0,0 +1,27 @@
+import QtQuick          2.2
+import QtQuick.Layouts  1.2
+
+import QGroundControl.FactSystem    1.0
+import QGroundControl.Controls      1.0
+
+GridLayout {
+    property var factList   ///< List of Facts to show
+
+    rows: factList.length
+    flow: GridLayout.TopToBottom
+
+    Repeater {
+        model: parent.factList
+
+        QGCLabel { text: modelData.name + ":" }
+    }
+
+    Repeater {
+        model: parent.factList
+
+        FactTextField {
+            Layout.fillWidth:   true
+            fact:               modelData
+        }
+    }
+}
diff --git a/src/FactSystem/FactControls/FactTextFieldRow.qml b/src/FactSystem/FactControls/FactTextFieldRow.qml
new file mode 100644
index 0000000..36ea7ca
--- /dev/null
+++ b/src/FactSystem/FactControls/FactTextFieldRow.qml
@@ -0,0 +1,19 @@
+import QtQuick          2.2
+import QtQuick.Layouts  1.2
+
+import QGroundControl.FactSystem    1.0
+import QGroundControl.Controls      1.0
+
+RowLayout {
+    property var fact: Fact { }
+
+    QGCLabel {
+        text: fact.name + ":"
+    }
+
+    FactTextField {
+        Layout.fillWidth:   true
+        showUnits:          true
+        fact:               parent.fact
+    }
+}
diff --git a/src/FactSystem/FactControls/qmldir b/src/FactSystem/FactControls/qmldir
index a919ae3..6bf7d23 100644
--- a/src/FactSystem/FactControls/qmldir
+++ b/src/FactSystem/FactControls/qmldir
@@ -1,8 +1,10 @@
 Module QGroundControl.FactControls
 
-FactBitmask     1.0 FactBitmask.qml
-FactCheckBox    1.0 FactCheckBox.qml
-FactComboBox    1.0 FactComboBox.qml
-FactLabel       1.0 FactLabel.qml
-FactPanel       1.0 FactPanel.qml
-FactTextField   1.0 FactTextField.qml
+FactBitmask         1.0 FactBitmask.qml
+FactCheckBox        1.0 FactCheckBox.qml
+FactComboBox        1.0 FactComboBox.qml
+FactLabel           1.0 FactLabel.qml
+FactPanel           1.0 FactPanel.qml
+FactTextField       1.0 FactTextField.qml
+FactTextFieldGrid   1.0 FactTextFieldGrid.qml
+FactTextFieldRow    1.0 FactTextFieldRow.qml

From fd28bf0307fce94abdcc18929bad2a5a6c092cab Mon Sep 17 00:00:00 2001
From: Don Gagne <don@thegagnes.com>
Date: Fri, 30 Sep 2016 11:02:11 -0700
Subject: [PATCH 3/4] Major rework of Survey ui

---
 src/MissionEditor/SurveyItemEditor.qml      | 756 +++++++++++++++-------------
 src/MissionManager/Survey.FactMetaData.json |  67 ++-
 src/MissionManager/SurveyMissionItem.cc     | 285 ++++++++---
 src/MissionManager/SurveyMissionItem.h      | 143 ++++--
 4 files changed, 784 insertions(+), 467 deletions(-)

diff --git a/src/MissionEditor/SurveyItemEditor.qml b/src/MissionEditor/SurveyItemEditor.qml
index 70266d9..0192ed3 100644
--- a/src/MissionEditor/SurveyItemEditor.qml
+++ b/src/MissionEditor/SurveyItemEditor.qml
@@ -1,6 +1,7 @@
-import QtQuick                  2.2
-import QtQuick.Controls         1.2
-import QtQuick.Dialogs          1.2
+import QtQuick          2.2
+import QtQuick.Controls 1.2
+import QtQuick.Dialogs  1.2
+import QtQuick.Layouts  1.2
 
 import QGroundControl               1.0
 import QGroundControl.ScreenTools   1.0
@@ -21,127 +22,165 @@ Rectangle {
     //property real   availableWidth    ///< Width for control
     //property var    missionItem       ///< Mission Item for editor
 
-    property real _margin: ScreenTools.defaultFontPixelWidth / 2
-
-    property int cameraIndex: 1
+    property real   _margin:        ScreenTools.defaultFontPixelWidth / 2
+    property int    _cameraIndex:   1
+
+    readonly property int _gridTypeManual:          0
+    readonly property int _gridTypeCustomCamera:    1
+    readonly property int _gridTypeCamera:          2
+
+    Component.onCompleted: {
+        console.log("gridAltitude", missionItem.gridAltitude.value)
+        console.log("gridAltitudeRelative", missionItem.gridAltitudeRelative)
+        console.log("gridAngle", missionItem.gridAngle.value)
+        console.log("gridSpacing", missionItem.gridSpacing.value)
+        console.log("turnaroundDist", missionItem.turnaroundDist.value)
+        console.log("cameraTrigger", missionItem.cameraTrigger)
+        console.log("cameraTriggerDistance", missionItem.cameraTriggerDistance.value)
+        console.log("groundResolution", missionItem.groundResolution.value)
+        console.log("frontalOverlap", missionItem.frontalOverlap.value)
+        console.log("sideOverlap", missionItem.sideOverlap.value)
+        console.log("cameraSensorWidth", missionItem.cameraSensorWidth.value)
+        console.log("cameraSensorHeight", missionItem.cameraSensorHeight.value)
+        console.log("cameraResolutionWidth", missionItem.cameraResolutionWidth.value)
+        console.log("cameraResolutionHeight", missionItem.cameraResolutionHeight.value)
+        console.log("cameraFocalLength", missionItem.cameraFocalLength.value)
+        console.log("fixedValueIsAltitude", missionItem.fixedValueIsAltitude)
+        console.log("cameraOrientationLandscape", missionItem.cameraOrientationLandscape)
+        console.log("manualGrid", missionItem.manualGrid)
+        console.log("camera", missionItem.camera)
+    }
 
     ListModel {
-           id: cameraModelList
-           ListElement {
-               text:           qsTr("Custom")
-               sensorWidth:    0
-               sensorHeight:   0
-               imageWidth:     0
-               imageHeight:    0
-               focalLength:    0
-           }
-           ListElement {
-               text:           qsTr("Sony ILCE-QX1") //http://www.sony.co.uk/electronics/interchangeable-lens-cameras/ilce-qx1-body-kit/specifications
-               sensorWidth:    23.2                  //http://www.sony.com/electronics/camera-lenses/sel16f28/specifications
-               sensorHeight:   15.4
-               imageWidth:     5456
-               imageHeight:    3632
-               focalLength:    16
-           }
-           ListElement {
-               text:           qsTr("Canon S100 PowerShot")
-               sensorWidth:    7.6
-               sensorHeight:   5.7
-               imageWidth:     4000
-               imageHeight:    3000
-               focalLength:    5.2
-           }
-           ListElement {
-               text:           qsTr("Canon SX260 HS PowerShot")
-               sensorWidth:    6.17
-               sensorHeight:   4.55
-               imageWidth:     4000
-               imageHeight:    3000
-               focalLength:    4.5
-           }
-           ListElement {
-               text:           qsTr("Canon EOS-M 22mm")
-               sensorWidth:    22.3
-               sensorHeight:   14.9
-               imageWidth:     5184
-               imageHeight:    3456
-               focalLength:    22
-           }
-           ListElement {
-               text:           qsTr("Sony a6000 16mm") //http://www.sony.co.uk/electronics/interchangeable-lens-cameras/ilce-6000-body-kit#product_details_default
-               sensorWidth:    23.5
-               sensorHeight:   15.6
-               imageWidth:     6000
-               imageHeight:    4000
-               focalLength:    16
-           }
-   }
+        id: cameraModelList
+
+        Component.onCompleted: {
+            cameraModelList.setProperty(_gridTypeCustomCamera, "sensorWidth", missionItem.cameraSensorWidth.rawValue)
+            cameraModelList.setProperty(_gridTypeCustomCamera, "sensorHeight", missionItem.cameraSensorHeight.rawValue)
+            cameraModelList.setProperty(_gridTypeCustomCamera, "imageWidth", missionItem.cameraResolutionWidth.rawValue)
+            cameraModelList.setProperty(_gridTypeCustomCamera, "imageHeight", missionItem.cameraResolutionHeight.rawValue)
+            cameraModelList.setProperty(_gridTypeCustomCamera, "focalLength", missionItem.cameraFocalLength.rawValue)
+        }
+
+        ListElement {
+            text:           qsTr("Manual Grid (no camera specs)")
+        }
+        ListElement {
+            text:           qsTr("Custom Camera Grid")
+        }
+        ListElement {
+            text:           qsTr("Sony ILCE-QX1") //http://www.sony.co.uk/electronics/interchangeable-lens-cameras/ilce-qx1-body-kit/specifications
+            sensorWidth:    23.2                  //http://www.sony.com/electronics/camera-lenses/sel16f28/specifications
+            sensorHeight:   15.4
+            imageWidth:     5456
+            imageHeight:    3632
+            focalLength:    16
+        }
+        ListElement {
+            text:           qsTr("Canon S100 PowerShot")
+            sensorWidth:    7.6
+            sensorHeight:   5.7
+            imageWidth:     4000
+            imageHeight:    3000
+            focalLength:    5.2
+        }
+        ListElement {
+            text:           qsTr("Canon SX260 HS PowerShot")
+            sensorWidth:    6.17
+            sensorHeight:   4.55
+            imageWidth:     4000
+            imageHeight:    3000
+            focalLength:    4.5
+        }
+        ListElement {
+            text:           qsTr("Canon EOS-M 22mm")
+            sensorWidth:    22.3
+            sensorHeight:   14.9
+            imageWidth:     5184
+            imageHeight:    3456
+            focalLength:    22
+        }
+        ListElement {
+            text:           qsTr("Sony a6000 16mm") //http://www.sony.co.uk/electronics/interchangeable-lens-cameras/ilce-6000-body-kit#product_details_default
+            sensorWidth:    23.5
+            sensorHeight:   15.6
+            imageWidth:     6000
+            imageHeight:    4000
+            focalLength:    16
+        }
+    }
 
     function recalcFromCameraValues() {
-        var focalLength = cameraModelList.get(cameraIndex).focalLength
-        var sensorWidth = cameraModelList.get(cameraIndex).sensorWidth
-        var sensorHeight = cameraModelList.get(cameraIndex).sensorHeight
-        var imageWidth = cameraModelList.get(cameraIndex).imageWidth
-        var imageHeight = cameraModelList.get(cameraIndex).imageHeight
-
-        var gsd = Number(gsdField.text)
-        var frontalOverlap = Number(frontalOverlapField.text)
-        var sideOverlap = Number(sideOverlapField.text)
-
-        if (focalLength <= 0.0 || sensorWidth <= 0.0 || sensorHeight <= 0.0 || imageWidth < 0 || imageHeight < 0 || gsd < 0.0 || frontalOverlap < 0 || sideOverlap < 0) {
-            missionItem.gridAltitude.rawValue = 0
-            missionItem.gridSpacing.rawValue = 0
-            missionItem.cameraTriggerDistance.rawValue = 0
+        var focalLength = missionItem.cameraFocalLength.rawValue
+        var sensorWidth = missionItem.cameraSensorWidth.rawValue
+        var sensorHeight = missionItem.cameraSensorHeight.rawValue
+        var imageWidth = missionItem.cameraResolutionWidth.rawValue
+        var imageHeight = missionItem.cameraResolutionHeight.rawValue
+
+        var altitude = missionItem.gridAltitude.rawValue
+        var groundResolution = missionItem.groundResolution.rawValue
+        var frontalOverlap = missionItem.frontalOverlap.rawValue
+        var sideOverlap = missionItem.sideOverlap.rawValue
+
+        if (focalLength <= 0 || sensorWidth <= 0 || sensorHeight <= 0 || imageWidth <= 0 || imageHeight <= 0 || groundResolution <= 0) {
             return
         }
 
-        var altitude
-        var imageSizeSideGround //size in side (non flying) direction of the image on the ground
-        var imageSizeFrontGround //size in front (flying) direction of the image on the ground
+        var imageSizeSideGround     //size in side (non flying) direction of the image on the ground
+        var imageSizeFrontGround    //size in front (flying) direction of the image on the ground
         var gridSpacing
         var cameraTriggerDistance
 
-        altitude = (imageWidth * gsd * focalLength) / (sensorWidth * 100)
+        if (missionItem.fixedValueIsAltitude) {
+            groundResolution = (altitude * sensorWidth * 100) /  (imageWidth * focalLength)
+        } else {
+            altitude = (imageWidth * groundResolution * focalLength) / (sensorWidth * 100)
+        }
 
         if (cameraOrientationLandscape.checked) {
-            imageSizeSideGround = (imageWidth * gsd) / 100
-            imageSizeFrontGround = (imageHeight * gsd) / 100
+            imageSizeSideGround = (imageWidth * groundResolution) / 100
+            imageSizeFrontGround = (imageHeight * groundResolution) / 100
         } else {
-            imageSizeSideGround = (imageHeight * gsd) / 100
-            imageSizeFrontGround = (imageWidth * gsd) / 100
+            imageSizeSideGround = (imageHeight * groundResolution) / 100
+            imageSizeFrontGround = (imageWidth * groundResolution) / 100
         }
 
         gridSpacing = imageSizeSideGround * ( (100-sideOverlap) / 100 )
         cameraTriggerDistance = imageSizeFrontGround * ( (100-frontalOverlap) / 100 )
 
-        missionItem.gridAltitude.rawValue = altitude
+        if (missionItem.fixedValueIsAltitude) {
+            missionItem.groundResolution.rawValue = groundResolution
+        } else {
+            missionItem.gridAltitude.rawValue = altitude
+        }
         missionItem.gridSpacing.rawValue = gridSpacing
         missionItem.cameraTriggerDistance.rawValue = cameraTriggerDistance
     }
 
+    /*
     function recalcFromMissionValues() {
-        var focalLength = cameraModelList.get(cameraIndex).focalLength
-        var sensorWidth = cameraModelList.get(cameraIndex).sensorWidth
-        var sensorHeight = cameraModelList.get(cameraIndex).sensorHeight
-        var imageWidth = cameraModelList.get(cameraIndex).imageWidth
-        var imageHeight = cameraModelList.get(cameraIndex).imageHeight
+        var focalLength = missionItem.cameraFocalLength.rawValue
+        var sensorWidth = missionItem.cameraSensorWidth.rawValue
+        var sensorHeight = missionItem.cameraSensorHeight.rawValue
+        var imageWidth = missionItem.cameraResolutionWidth.rawValue
+        var imageHeight = missionItem.cameraResolutionHeight.rawValue
 
         var altitude = missionItem.gridAltitude.rawValue
         var gridSpacing = missionItem.gridSpacing.rawValue
         var cameraTriggerDistance = missionItem.cameraTriggerDistance.rawValue
 
         if (focalLength <= 0.0 || sensorWidth <= 0.0 || sensorHeight <= 0.0 || imageWidth < 0 || imageHeight < 0 || altitude < 0.0 || gridSpacing < 0.0 || cameraTriggerDistance < 0.0) {
-            gsdField.text = "0.0"
-            sideOverlapField.text = "0"
-            frontalOverlapField.text = "0"
+            missionItem.groundResolution.rawValue = 0
+            missionItem.sideOverlap = 0
+            missionItem.frontalOverlap = 0
             return
         }
 
-        var gsd
-        var imageSizeSideGround //size in side (non flying) direction of the image on the ground
-        var imageSizeFrontGround //size in front (flying) direction of the image on the ground
+        var groundResolution
+        var imageSizeSideGround     //size in side (non flying) direction of the image on the ground
+        var imageSizeFrontGround    //size in front (flying) direction of the image on the ground
 
-        gsd = (altitude * sensorWidth * 100) / (imageWidth * focalLength)
+        groundResolution = (altitude * sensorWidth * 100) / (imageWidth * focalLength)
 
         if (cameraOrientationLandscape.checked) {
             imageSizeSideGround = (imageWidth * gsd) / 100
@@ -154,13 +193,14 @@ Rectangle {
         var sideOverlap = (imageSizeSideGround == 0 ? 0 : 100 - (gridSpacing*100 / imageSizeSideGround))
         var frontOverlap = (imageSizeFrontGround == 0 ? 0 : 100 - (cameraTriggerDistance*100 / imageSizeFrontGround))
 
-        gsdField.text = gsd.toFixed(1)
-        sideOverlapField.text = sideOverlap.toFixed(0)
-        frontalOverlapField.text = frontOverlap.toFixed(0)
+        missionItem.groundResolution.rawValue = groundResolution
+        missionItem.sideOverlap.rawValue = sideOverlap
+        missionItem.frontalOverlap.rawValue = frontOverlap
     }
+    */
 
     function polygonCaptureStarted() {
-    	missionItem.clearPolygon()
+        missionItem.clearPolygon()
     }
 
     function polygonCaptureFinished(coordinates) {
@@ -176,15 +216,42 @@ Rectangle {
     function polygonAdjustStarted() { }
     function polygonAdjustFinished() { }
 
+    property bool _noCameraValueRecalc: false   ///< Prevents uneeded recalcs
+
+    Connections {
+        target: missionItem
+
+        onCameraValueChanged: {
+            if (gridTypeCombo.currentIndex >= _gridTypeCustomCamera && !_noCameraValueRecalc) {
+                recalcFromCameraValues()
+            }
+        }
+    }
+
+    Connections {
+        target: missionItem.gridAltitude
+
+        onValueChanged: {
+            if (gridTypeCombo.currentIndex >= _gridTypeCustomCamera && missionItem.fixedValueIsAltitude && !_noCameraValueRecalc) {
+                recalcFromCameraValues()
+            }
+        }
+    }
+
     QGCPalette { id: qgcPal; colorGroupEnabled: true }
 
     ExclusiveGroup {
-        id:                 cameraOrientationGroup
-        onCurrentChanged:   {
-            recalcFromMissionValues()
+        id: cameraOrientationGroup
+
+        onCurrentChanged: {
+            if (gridTypeCombo.currentIndex >= _gridTypeCustomCamera) {
+                recalcFromCameraValues()
+            }
         }
     }
 
+    ExclusiveGroup { id: fixedValueGroup }
+
     Column {
         id:                 editorColumn
         anchors.margins:    _margin
@@ -198,307 +265,292 @@ Rectangle {
             anchors.right:  parent.right
             wrapMode:       Text.WordWrap
             font.pointSize: ScreenTools.smallFontPointSize
-            text:           qsTr("Create a flight path to fully cover a polygonal area with a camera.")
+            text:           gridTypeCombo.currentIndex == 0 ?
+                                qsTr("Create a flight path which covers a polygonal area by specifying all grid parameters.") :
+                                qsTr("Create a flight path which fully covers a polygonal area using camera specifications.")
         }
 
-        Repeater {
-            model: [ missionItem.gridAngle, missionItem.gridSpacing, missionItem.gridAltitude, missionItem.turnaroundDist ]
+        QGCLabel { text: qsTr("Camera:") }
 
-            Item {
-                anchors.left:   parent.left
-                anchors.right:  parent.right
-                height:         textField.height
+        Rectangle {
+            anchors.left:   parent.left
+            anchors.right:  parent.right
+            height:         1
+            color:          qgcPal.text
+        }
 
-                QGCLabel {
-                    anchors.baseline:   textField.baseline
-                    anchors.left:       parent.left
-                    text:               modelData.name + ":"
+        QGCComboBox {
+            id:             gridTypeCombo
+            anchors.left:   parent.left
+            anchors.right:  parent.right
+            model:          cameraModelList
+            currentIndex:   -1
+
+            Component.onCompleted: {
+                if (missionItem.manualGrid) {
+                    gridTypeCombo.currentIndex = _gridTypeManual
+                } else {
+                    var index = gridTypeCombo.find(missionItem.camera)
+                    if (index == -1) {
+                        console.log("Couldn't find camera", missionItem.camera)
+                        gridTypeCombo.currentIndex = _gridTypeManual
+                    } else {
+                        gridTypeCombo.currentIndex = index
+                    }
                 }
+            }
 
-                FactTextField {
-                    id:             textField
-                    anchors.right:  parent.right
-                    width:          _editFieldWidth
-                    showUnits:      true
-                    fact:           modelData
-                    onEditingFinished: recalcFromMissionValues()
-                    validator:      DoubleValidator{bottom:0.0; decimals:2}
+            onActivated: {
+                if (index == _gridTypeManual) {
+                    missionItem.manualGrid = true
+                } else {
+                    missionItem.manualGrid = false
+                    missionItem.camera = gridTypeCombo.textAt(index)
+                    _noCameraValueRecalc = true
+                    missionItem.cameraSensorWidth.rawValue = cameraModelList.get(index).sensorWidth
+                    missionItem.cameraSensorHeight.rawValue = cameraModelList.get(index).sensorHeight
+                    missionItem.cameraResolutionWidth.rawValue = cameraModelList.get(index).imageWidth
+                    missionItem.cameraResolutionHeight.rawValue = cameraModelList.get(index).imageHeight
+                    missionItem.cameraFocalLength.rawValue = cameraModelList.get(index).focalLength
+                    _noCameraValueRecalc = false
+                    recalcFromCameraValues()
                 }
             }
         }
 
-        QGCCheckBox {
+        // Camera based grid ui
+        Column {
             anchors.left:   parent.left
-            text:           qsTr("Relative altitude")
-            checked:        missionItem.gridAltitudeRelative
-            onClicked:      missionItem.gridAltitudeRelative = checked
-        }
+            anchors.right:  parent.right
+            spacing:        _margin
+            visible:        gridTypeCombo.currentIndex != _gridTypeManual
 
-        Grid {
-            columns: 2
-            columnSpacing:  ScreenTools.defaultFontPixelWidth
-            rowSpacing:     _margin
-            verticalItemAlignment: Grid.AlignVCenter
+            Row {
+                spacing: _margin
 
-            QGCLabel {
-                text: qsTr("GSD:")
-                width:      _editFieldWidth
-            }
-            QGCTextField {
-                id:         gsdField
-                width:      _editFieldWidth
-                unitsLabel: "cm/px"
-                showUnits:  true
-                onEditingFinished:  recalcFromCameraValues()
-                validator:  DoubleValidator{bottom:0.0; decimals:2}
-            }
+                QGCRadioButton {
+                    id:             cameraOrientationLandscape
+                    width:          _editFieldWidth
+                    text:           "Landscape"
+                    checked:        true
+                    exclusiveGroup: cameraOrientationGroup
+                }
 
-            QGCLabel { text: qsTr("Frontal Overlap:") }
-            QGCTextField {
-                id:         frontalOverlapField
-                width:      _editFieldWidth
-                unitsLabel: "%"
-                showUnits:  true
-                onEditingFinished: recalcFromCameraValues()
-                validator:  IntValidator {bottom:0}
+                QGCRadioButton {
+                    id:             cameraOrientationPortrait
+                    text:           "Portrait"
+                    exclusiveGroup: cameraOrientationGroup
+                }
             }
 
-            QGCLabel { text: qsTr("Side Overlap:") }
-            QGCTextField {
-                id:         sideOverlapField
-                width:      _editFieldWidth
-                unitsLabel: "%"
-                showUnits:  true
-                onEditingFinished: recalcFromCameraValues()
-                validator:  IntValidator {bottom:0}
-            }
+            Column {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                spacing:        _margin
+                visible:        gridTypeCombo.currentIndex == _gridTypeCustomCamera
 
-            Component.onCompleted: recalcFromMissionValues()
-        }
+                GridLayout {
+                    columns:        3
+                    columnSpacing:  _margin
+                    rowSpacing:     _margin
 
-        QGCLabel { text: qsTr("Camera:") }
+                    property real _fieldWidth: ScreenTools.defaultFontPixelWidth * 10
 
-        Rectangle {
-            anchors.left:   parent.left
-            anchors.right:  parent.right
-            height:         1
-            color:          qgcPal.text
-        }
+                    QGCLabel { }
+                    QGCLabel { text: qsTr("Width") }
+                    QGCLabel { text: qsTr("Height") }
 
-        Grid {
-            columns: 2
-            spacing: ScreenTools.defaultFontPixelWidth
-            verticalItemAlignment: Grid.AlignVCenter
-
-            QGCRadioButton {
-                id:             cameraOrientationLandscape
-                width:          _editFieldWidth
-                text:           "Landscape"
-                checked:        true
-                exclusiveGroup: cameraOrientationGroup
+                    QGCLabel { text: qsTr("Sensor:") }
+                    FactTextField {
+                        Layout.preferredWidth:  parent._fieldWidth
+                        fact:                   missionItem.cameraSensorWidth
+                    }
+                    FactTextField {
+                        Layout.preferredWidth:  parent._fieldWidth
+                        fact:                   missionItem.cameraSensorHeight
+                    }
+
+                    QGCLabel { text: qsTr("Image:") }
+                    FactTextField {
+                        Layout.preferredWidth:  parent._fieldWidth
+                        fact:                   missionItem.cameraResolutionWidth
+                    }
+                    FactTextField {
+                        Layout.preferredWidth:  parent._fieldWidth
+                        fact:                   missionItem.cameraResolutionHeight
+                    }
+                }
+
+                FactTextFieldRow {
+                    spacing: _margin
+                    fact:       missionItem.cameraFocalLength
+                }
+            } // Column - custom camera
+
+            QGCLabel { text: qsTr("Image Overlap") }
+
+            Row {
+                spacing:        _margin
+
+                Item {
+                    width:  ScreenTools.defaultFontPixelWidth * 2
+                    height: 1
+                }
+
+                QGCLabel {
+                    anchors.baseline:   frontalOverlapField.baseline
+                    text:               qsTr("Frontal:")
+                }
+
+                FactTextField {
+                    id:     frontalOverlapField
+                    width:  ScreenTools.defaultFontPixelWidth * 7
+                    fact:   missionItem.frontalOverlap
+                }
+
+                QGCLabel {
+                    anchors.baseline:   frontalOverlapField.baseline
+                    text:               qsTr("Side:")
+                }
+
+                FactTextField {
+                    width:  frontalOverlapField.width
+                    fact:   missionItem.sideOverlap
+                }
             }
 
-            QGCRadioButton {
-                id:             cameraOrientationPortrait
-                text:           "Portrait"
-                exclusiveGroup: cameraOrientationGroup
+            QGCLabel { text: qsTr("Grid:") }
+
+            Rectangle {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                height:         1
+                color:          qgcPal.text
             }
 
-            QGCCheckBox {
-                id:         cameraTrigger
-                width:      _editFieldWidth
-                text:       qsTr("Trigger:")
-                checked:    missionItem.cameraTrigger
-                onClicked:  missionItem.cameraTrigger = checked
+            FactTextFieldGrid {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                columnSpacing:  _margin
+                rowSpacing:     _margin
+                factList:       [ missionItem.gridAngle, missionItem.turnaroundDist ]
             }
 
-            FactTextField {
-                width:      _editFieldWidth
-                showUnits:  true
-                fact:       missionItem.cameraTriggerDistance
-                enabled:    missionItem.cameraTrigger
-                onEditingFinished: recalcFromMissionValues()
-                validator:  DoubleValidator{bottom:0.0; decimals:2}
+            QGCLabel {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                wrapMode:       Text.WordWrap
+                font.pointSize: ScreenTools.smallFontPointSize
+                text:           qsTr("Which value would you like to keep constant as you adjust other settings:")
             }
-        }
 
-        Component {
-            id: cameraFields
-
-            QGCViewDialog {
-
-                Column {
-                    id:                 dialogColumn
-                    anchors.margins:    _margin
-                    anchors.top:        parent.top
-                    anchors.left:       parent.left
-                    anchors.right:      parent.right
-                    spacing:            _margin * 5
-
-                    Row {
-                        spacing: ScreenTools.defaultFontPixelWidth
-
-                        QGCLabel {
-                            id:                 selectCameraModelText
-                            text:               qsTr("Select Camera Model:")
-                        }
-
-                        QGCComboBox {
-                            id:                 cameraModelCombo
-                            model:              cameraModelList
-                            width:              dialogColumn.width - selectCameraModelText.width - ScreenTools.defaultFontPixelWidth
-
-                            onActivated: {
-                                cameraIndex = index
-                            }
-
-                            Component.onCompleted: {
-                                var index = cameraIndex
-                                if (index === -1) {
-                                    console.warn("Active camera model name not in combo", cameraIndex)
-                                } else {
-                                    cameraModelCombo.currentIndex = index
-                                }
-                            }
-                        }
-                    }
+            RowLayout {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                spacing:        _margin
+
+                QGCRadioButton {
+                    id:                 fixedAltitudeRadio
+                    anchors.baseline:   gridAltitudeField.baseline
+                    text:               qsTr("Altitude:")
+                    checked:            missionItem.fixedValueIsAltitude
+                    exclusiveGroup:     fixedValueGroup
+                    onClicked:          missionItem.fixedValueIsAltitude = true
+                }
 
-                    Grid {
-                        columns: 2
-                        spacing: ScreenTools.defaultFontPixelWidth
-                        verticalItemAlignment: Grid.AlignVCenter
-
-                        QGCLabel { text: qsTr("Sensor Width:") }
-                        QGCTextField {
-                            id:                 sensorWidthField
-                            unitsLabel:         "mm"
-                            showUnits:          true
-                            text:               cameraModelList.get(cameraIndex).sensorWidth.toFixed(2)
-                            readOnly:           cameraIndex != 0
-                            enabled:            cameraIndex == 0
-                            validator:          DoubleValidator{bottom:0.0; decimals:2}
-                            onEditingFinished:  {
-                                if (cameraIndex == 0) {
-                                    cameraModelList.setProperty(cameraIndex, "sensorWidth", Number(text))
-                                }
-                            }
-                        }
-
-                        QGCLabel { text: qsTr("Sensor Height:") }
-                        QGCTextField {
-                            id:                 sensorHeightField
-                            unitsLabel:         "mm"
-                            showUnits:          true
-                            text:               cameraModelList.get(cameraIndex).sensorHeight.toFixed(2)
-                            readOnly:           cameraIndex != 0
-                            enabled:            cameraIndex == 0
-                            validator:          DoubleValidator{bottom:0.0; decimals:2}
-                            onEditingFinished:  {
-                                if (cameraIndex == 0) {
-                                    cameraModelList.setProperty(cameraIndex, "sensorHeight", Number(text))
-                                }
-                            }
-                        }
-
-                        QGCLabel { text: qsTr("Image Width:") }
-                        QGCTextField {
-                            id:                 imageWidthField
-                            unitsLabel:         "px"
-                            showUnits:          true
-                            text:               cameraModelList.get(cameraIndex).imageWidth.toFixed(0)
-                            readOnly:           cameraIndex != 0
-                            enabled:            cameraIndex == 0
-                            validator:          IntValidator {bottom:0}
-                            onEditingFinished:  {
-                                if (cameraIndex == 0) {
-                                    cameraModelList.setProperty(cameraIndex, "imageWidth", Number(text))
-                                }
-                            }
-                        }
-
-                        QGCLabel { text: qsTr("Image Height:") }
-                        QGCTextField {
-                            id:                 imageHeightField
-                            unitsLabel:         "px"
-                            showUnits:          true
-                            text:               cameraModelList.get(cameraIndex).imageHeight.toFixed(0)
-                            readOnly:           cameraIndex != 0
-                            enabled:            cameraIndex == 0
-                            validator:          IntValidator {bottom:0}
-                            onEditingFinished:  {
-                                if (cameraIndex == 0) {
-                                    cameraModelList.setProperty(cameraIndex, "imageHeight", Number(text))
-                                }
-                            }
-                        }
-
-                        QGCLabel { text: qsTr("Focal Length:") }
-                        QGCTextField {
-                            id:                 focalLengthField
-                            unitsLabel:         "mm"
-                            showUnits:          true
-                            text:               cameraModelList.get(cameraIndex).focalLength.toFixed(2)
-                            readOnly:           cameraIndex != 0
-                            enabled:            cameraIndex == 0
-                            validator:          DoubleValidator{bottom:0.0; decimals:2}
-                            onEditingFinished:  {
-                                if (cameraIndex == 0) {
-                                    cameraModelList.setProperty(cameraIndex, "focalLength", Number(text))
-                                }
-                            }
-                        }
-                    }
+                FactTextField {
+                    id:                 gridAltitudeField
+                    Layout.fillWidth:   true
+                    fact:               missionItem.gridAltitude
+                    enabled:            fixedAltitudeRadio.checked
                 }
+            }
 
-                function accept() {
-                    hideDialog()
-                    recalcFromCameraValues()
+            RowLayout {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                spacing:        _margin
+
+                QGCRadioButton {
+                    id:                 fixedGroundResolutionRadio
+                    anchors.baseline:   groundResolutionField.baseline
+                    text:               qsTr("Ground res:")
+                    checked:            !missionItem.fixedValueIsAltitude
+                    exclusiveGroup:     fixedValueGroup
+                    onClicked:          missionItem.fixedValueIsAltitude = false
                 }
-            }//QGCViewDialog
-        }//Component
 
+                FactTextField {
+                    id:                 groundResolutionField
+                    Layout.fillWidth:   true
+                    fact:               missionItem.groundResolution
+                    enabled:            fixedGroundResolutionRadio.checked
+                }
+            }
+        }
+
+        // Manual grid ui
         Column {
-            spacing: ScreenTools.defaultFontPixelHeight*0.01
+            anchors.left:   parent.left
+            anchors.right:  parent.right
+            spacing:        _margin
+            visible:        gridTypeCombo.currentIndex == _gridTypeManual
 
-            Row {
-                spacing: ScreenTools.defaultFontPixelWidth
+            QGCLabel { text: qsTr("Grid:") }
 
-                QGCLabel { text: qsTr("Model:") }
-                QGCLabel { text: cameraModelList.get(cameraIndex).text }
+            Rectangle {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                height:         1
+                color:          qgcPal.text
             }
 
-            Grid {
-                columns: 2
-                columnSpacing: ScreenTools.defaultFontPixelWidth
-                rowSpacing: ScreenTools.defaultFontPixelHeight*0.01
+            FactTextFieldGrid {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                columnSpacing:  _margin
+                rowSpacing:     _margin
+                factList:       [ missionItem.gridAngle, missionItem.gridSpacing, missionItem.gridAltitude, missionItem.turnaroundDist ]
+            }
 
-                QGCLabel {
-                    text: qsTr("Sensor Size:")
-                    width: _editFieldWidth
-                }
-                QGCLabel {
-                    text: cameraModelList.get(cameraIndex).sensorWidth.toFixed(2) + qsTr(" x ") +  cameraModelList.get(cameraIndex).sensorHeight.toFixed(2)
-                    width: _editFieldWidth
-                }
+            QGCCheckBox {
+                anchors.left:   parent.left
+                text:           qsTr("Relative altitude")
+                checked:        missionItem.gridAltitudeRelative
+                onClicked:      missionItem.gridAltitudeRelative = checked
+            }
 
-                QGCLabel { text: qsTr("Image Size:") }
-                QGCLabel { text: cameraModelList.get(cameraIndex).imageWidth.toFixed(0) + qsTr(" x ") +  cameraModelList.get(cameraIndex).imageHeight.toFixed(0) }
+            QGCLabel { text: qsTr("Camera:") }
 
-                QGCLabel { text: qsTr("Focal length:") }
-                QGCLabel { text: cameraModelList.get(cameraIndex).focalLength.toFixed(2) }
+            Rectangle {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                height:         1
+                color:          qgcPal.text
             }
-        }
 
-        QGCButton {
-            id:         cameraModelChange
-            text:       qsTr("Change")
+            RowLayout {
+                anchors.left:   parent.left
+                anchors.right:  parent.right
+                spacing:        _margin
+
+                QGCCheckBox {
+                    id:                 cameraTrigger
+                    anchors.baseline:   cameraTriggerDistanceField.baseline
+                    text:               qsTr("Trigger Distance:")
+                    checked:            missionItem.cameraTrigger
+                    onClicked:          missionItem.cameraTrigger = checked
+                }
 
-            onClicked: {
-                qgcView.showDialog(cameraFields, qsTr("Set Camera Model"), qgcView.showDialogDefaultWidth, StandardButton.Save)
+                FactTextField {
+                    id:                 cameraTriggerDistanceField
+                    Layout.fillWidth:   true
+                    fact:               missionItem.cameraTriggerDistance
+                    enabled:            missionItem.cameraTrigger
+                }
             }
         }
 
-
         QGCLabel { text: qsTr("Polygon:") }
 
         Rectangle {
diff --git a/src/MissionManager/Survey.FactMetaData.json b/src/MissionManager/Survey.FactMetaData.json
index d50d12b..bb341a6 100644
--- a/src/MissionManager/Survey.FactMetaData.json
+++ b/src/MissionManager/Survey.FactMetaData.json
@@ -18,20 +18,85 @@
     "shortDescription": "Amount of spacing in between parallel grid lines.",
     "type":             "double",
     "decimalPlaces":    2,
+    "min":              0.1,
     "units":            "m"
 },
 {
-    "name":             "Turnaround dist.",
+    "name":             "Turnaround dist",
     "shortDescription": "Amount of additional distance to add outside the grid area for vehicle turnaround.",
     "type":             "double",
     "decimalPlaces":    2,
+    "min":              0,
     "units":            "m"
 },
 {
+    "name":             "Ground resolution",
+    "shortDescription": "Resolution of image in relationship to ground distance.",
+    "type":             "double",
+    "decimalPlaces":    2,
+    "min":              0,
+    "units":            "cm/px"
+},
+{
+    "name":             "Frontal overlap",
+    "shortDescription": "Amount of overlap between images in the forward facing direction.",
+    "type":             "double",
+    "decimalPlaces":    0,
+    "max":              75,
+    "units":            "%"
+},
+{
+    "name":             "Side overlap",
+    "shortDescription": "Amount of overlap between images in the side facing direction.",
+    "type":             "double",
+    "decimalPlaces":    0,
+    "max":              75,
+    "units":            "%"
+},
+{
+    "name":             "Camera sensor width",
+    "shortDescription": "Amount of overlap between images in the side facing direction.",
+    "type":             "double",
+    "decimalPlaces":    2,
+    "min":              1,
+    "units":            "mm"
+},
+{
+    "name":             "Camera sensor height",
+    "shortDescription": "Amount of overlap between images in the side facing direction.",
+    "type":             "double",
+    "decimalPlaces":    2,
+    "min":              1,
+    "units":            "mm"
+},
+{
+    "name":             "Camera resolution width",
+    "shortDescription": "Amount of overlap between images in the side facing direction.",
+    "type":             "uint32",
+    "min":              1,
+    "units":            "px"
+},
+{
+    "name":             "Camera resolution height",
+    "shortDescription": "Amount of overlap between images in the side facing direction.",
+    "type":             "uint32",
+    "min":              1,
+    "units":            "px"
+},
+{
+    "name":             "Focal length",
+    "shortDescription": "Amount of overlap between images in the side facing direction.",
+    "type":             "double",
+    "decimalPlaces":    1,
+    "min":              1,
+    "units":            "mm"
+},
+{
     "name":             "Camera trigger distance",
     "shortDescription": "Distance between each triggering of the camera.",
     "type":             "double",
     "decimalPlaces":    2,
+    "min":              0.1,
     "units":            "m"
 }
 ]
diff --git a/src/MissionManager/SurveyMissionItem.cc b/src/MissionManager/SurveyMissionItem.cc
index a4531e5..e112271 100644
--- a/src/MissionManager/SurveyMissionItem.cc
+++ b/src/MissionManager/SurveyMissionItem.cc
@@ -17,22 +17,44 @@
 
 QGC_LOGGING_CATEGORY(SurveyMissionItemLog, "SurveyMissionItemLog")
 
-const char* SurveyMissionItem::_jsonTypeKey =                  "type";
-const char* SurveyMissionItem::_jsonPolygonKey =               "polygon";
-const char* SurveyMissionItem::_jsonIdKey =                    "id";
-const char* SurveyMissionItem::_jsonGridAltitudeKey =          "gridAltitude";
-const char* SurveyMissionItem::_jsonGridAltitudeRelativeKey =  "gridAltitudeRelative";
-const char* SurveyMissionItem::_jsonGridAngleKey =             "gridAngle";
-const char* SurveyMissionItem::_jsonGridSpacingKey =           "gridSpacing";
-const char* SurveyMissionItem::_jsonTurnaroundDistKey =        "turnaroundDist";
-const char* SurveyMissionItem::_jsonCameraTriggerKey =         "cameraTrigger";
-const char* SurveyMissionItem::_jsonCameraTriggerDistanceKey = "cameraTriggerDistance";
-
-const char* SurveyMissionItem::_gridAltitudeFactName =          "Altitude";
-const char* SurveyMissionItem::_gridAngleFactName =             "Grid angle";
-const char* SurveyMissionItem::_gridSpacingFactName =           "Grid spacing";
-const char* SurveyMissionItem::_turnaroundDistFactName =        "Turnaround dist.";
-const char* SurveyMissionItem::_cameraTriggerDistanceFactName = "Camera trigger distance";
+const char* SurveyMissionItem::_jsonTypeKey =                       "type";
+const char* SurveyMissionItem::_jsonPolygonObjectKey =              "polygon";
+const char* SurveyMissionItem::_jsonIdKey =                         "id";
+const char* SurveyMissionItem::_jsonGridObjectKey =                 "grid";
+const char* SurveyMissionItem::_jsonGridAltitudeKey =               "altitude";
+const char* SurveyMissionItem::_jsonGridAltitudeRelativeKey =       "relativeAltitude";
+const char* SurveyMissionItem::_jsonGridAngleKey =                  "angle";
+const char* SurveyMissionItem::_jsonGridSpacingKey =                "spacing";
+const char* SurveyMissionItem::_jsonTurnaroundDistKey =             "turnAroundDistance";
+const char* SurveyMissionItem::_jsonCameraTriggerKey =              "cameraTrigger";
+const char* SurveyMissionItem::_jsonCameraTriggerDistanceKey =      "cameraTriggerDistance";
+const char* SurveyMissionItem::_jsonGroundResolutionKey =           "groundResolution";
+const char* SurveyMissionItem::_jsonFrontalOverlapKey =             "imageFrontalOverlap";
+const char* SurveyMissionItem::_jsonSideOverlapKey =                "imageSizeOverlap";
+const char* SurveyMissionItem::_jsonCameraSensorWidthKey =          "sensorWidth";
+const char* SurveyMissionItem::_jsonCameraSensorHeightKey =         "sensorHeight";
+const char* SurveyMissionItem::_jsonCameraResolutionWidthKey =      "resolutionWidth";
+const char* SurveyMissionItem::_jsonCameraResolutionHeightKey =     "resolutionHeight";
+const char* SurveyMissionItem::_jsonCameraFocalLengthKey =          "focalLength";
+const char* SurveyMissionItem::_jsonCameraObjectKey =               "camera";
+const char* SurveyMissionItem::_jsonCameraNameKey =                 "name";
+const char* SurveyMissionItem::_jsonManualGridKey =                 "manualGrid";
+const char* SurveyMissionItem::_jsonCameraOrientationLandscapeKey = "orientationLandscape";
+const char* SurveyMissionItem::_jsonFixedValueIsAltitudeKey =       "fixedValueIsAltitude";
+
+const char* SurveyMissionItem::_gridAltitudeFactName =              "Altitude";
+const char* SurveyMissionItem::_gridAngleFactName =                 "Grid angle";
+const char* SurveyMissionItem::_gridSpacingFactName =               "Grid spacing";
+const char* SurveyMissionItem::_turnaroundDistFactName =            "Turnaround dist";
+const char* SurveyMissionItem::_cameraTriggerDistanceFactName =     "Camera trigger distance";
+const char* SurveyMissionItem::_groundResolutionFactName =          "Ground resolution";
+const char* SurveyMissionItem::_frontalOverlapFactName =            "Frontal overlap";
+const char* SurveyMissionItem::_sideOverlapFactName =               "Side overlap";
+const char* SurveyMissionItem::_cameraSensorWidthFactName =         "Camera sensor width";
+const char* SurveyMissionItem::_cameraSensorHeightFactName =        "Camera sensor height";
+const char* SurveyMissionItem::_cameraResolutionWidthFactName =     "Camera resolution width";
+const char* SurveyMissionItem::_cameraResolutionHeightFactName =    "Camera resolution height";
+const char* SurveyMissionItem::_cameraFocalLengthFactName =         "Focal length";
 
 const char* SurveyMissionItem::_complexType = "survey";
 
@@ -44,29 +66,57 @@ SurveyMissionItem::SurveyMissionItem(Vehicle* vehicle, QObject* parent)
     , _dirty(false)
     , _cameraTrigger(true)
     , _gridAltitudeRelative(true)
+    , _manualGrid(true)
+    , _cameraOrientationLandscape(true)
+    , _fixedValueIsAltitude(false)
     , _surveyDistance(0.0)
     , _cameraShots(0)
     , _coveredArea(0.0)
-    , _gridAltitudeFact         (0, _gridAltitudeFactName,          FactMetaData::valueTypeDouble)
-    , _gridAngleFact            (0, _gridAngleFactName,             FactMetaData::valueTypeDouble)
-    , _gridSpacingFact          (0, _gridSpacingFactName,           FactMetaData::valueTypeDouble)
-    , _turnaroundDistFact       (0, _turnaroundDistFactName,        FactMetaData::valueTypeDouble)
-    , _cameraTriggerDistanceFact(0, _cameraTriggerDistanceFactName, FactMetaData::valueTypeDouble)
+    , _gridAltitudeFact             (0, _gridAltitudeFactName,              FactMetaData::valueTypeDouble)
+    , _gridAngleFact                (0, _gridAngleFactName,                 FactMetaData::valueTypeDouble)
+    , _gridSpacingFact              (0, _gridSpacingFactName,               FactMetaData::valueTypeDouble)
+    , _turnaroundDistFact           (0, _turnaroundDistFactName,            FactMetaData::valueTypeDouble)
+    , _cameraTriggerDistanceFact    (0, _cameraTriggerDistanceFactName,     FactMetaData::valueTypeDouble)
+    , _groundResolutionFact         (0, _groundResolutionFactName,          FactMetaData::valueTypeDouble)
+    , _frontalOverlapFact           (0, _frontalOverlapFactName,            FactMetaData::valueTypeDouble)
+    , _sideOverlapFact              (0, _sideOverlapFactName,               FactMetaData::valueTypeDouble)
+    , _cameraSensorWidthFact        (0, _cameraSensorWidthFactName,         FactMetaData::valueTypeDouble)
+    , _cameraSensorHeightFact       (0, _cameraSensorHeightFactName,        FactMetaData::valueTypeDouble)
+    , _cameraResolutionWidthFact    (0, _cameraResolutionWidthFactName,     FactMetaData::valueTypeUint32)
+    , _cameraResolutionHeightFact   (0, _cameraResolutionHeightFactName,    FactMetaData::valueTypeUint32)
+    , _cameraFocalLengthFact        (0, _cameraFocalLengthFactName,         FactMetaData::valueTypeDouble)
 {
     if (_metaDataMap.isEmpty()) {
         _metaDataMap = FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/Survey.FactMetaData.json"), NULL /* metaDataParent */);
     }
 
     _gridAltitudeFact.setRawValue(50);
-    _gridSpacingFact.setRawValue(10);
-    _turnaroundDistFact.setRawValue(60);
+    _gridSpacingFact.setRawValue(30);
+    _turnaroundDistFact.setRawValue(_vehicle->multiRotor() ? 0 : 60);
     _cameraTriggerDistanceFact.setRawValue(25);
+    _groundResolutionFact.setRawValue(3);
+    _frontalOverlapFact.setRawValue(10);
+    _sideOverlapFact.setRawValue(10);
+
+    _cameraSensorWidthFact.setRawValue(6.17);
+    _cameraSensorHeightFact.setRawValue(4.55);
+    _cameraResolutionWidthFact.setRawValue(4000);
+    _cameraResolutionHeightFact.setRawValue(3000);
+    _cameraFocalLengthFact.setRawValue(4.5);
 
     _gridAltitudeFact.setMetaData(_metaDataMap[_gridAltitudeFactName]);
     _gridAngleFact.setMetaData(_metaDataMap[_gridAngleFactName]);
     _gridSpacingFact.setMetaData(_metaDataMap[_gridSpacingFactName]);
     _turnaroundDistFact.setMetaData(_metaDataMap[_turnaroundDistFactName]);
     _cameraTriggerDistanceFact.setMetaData(_metaDataMap[_cameraTriggerDistanceFactName]);
+    _groundResolutionFact.setMetaData(_metaDataMap[_groundResolutionFactName]);
+    _frontalOverlapFact.setMetaData(_metaDataMap[_frontalOverlapFactName]);
+    _sideOverlapFact.setMetaData(_metaDataMap[_sideOverlapFactName]);
+    _cameraSensorWidthFact.setMetaData(_metaDataMap[_cameraSensorWidthFactName]);
+    _cameraSensorHeightFact.setMetaData(_metaDataMap[_cameraSensorHeightFactName]);
+    _cameraResolutionWidthFact.setMetaData(_metaDataMap[_cameraResolutionWidthFactName]);
+    _cameraResolutionHeightFact.setMetaData(_metaDataMap[_cameraResolutionHeightFactName]);
+    _cameraFocalLengthFact.setMetaData(_metaDataMap[_cameraFocalLengthFactName]);
 
     connect(&_gridSpacingFact,              &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid);
     connect(&_gridAngleFact,                &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid);
@@ -74,18 +124,17 @@ SurveyMissionItem::SurveyMissionItem(Vehicle* vehicle, QObject* parent)
     connect(&_cameraTriggerDistanceFact,    &Fact::valueChanged, this, &SurveyMissionItem::_generateGrid);
     connect(&_gridAltitudeFact,             &Fact::valueChanged, this, &SurveyMissionItem::_updateCoordinateAltitude);
 
-    connect(this, &SurveyMissionItem::cameraTriggerChanged, this, &SurveyMissionItem::_cameraTriggerChanged);
-}
-
-const SurveyMissionItem& SurveyMissionItem::operator=(const SurveyMissionItem& other)
-{
-    ComplexMissionItem::operator=(other);
-
-    _setSurveyDistance(other._surveyDistance);
-    _setCameraShots(other._cameraShots);
-    _setCoveredArea(other._coveredArea);
+    // Signal to Qml when camera value changes to it can recalc
+    connect(&_groundResolutionFact,         &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_frontalOverlapFact,           &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_sideOverlapFact,              &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_cameraSensorWidthFact,        &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_cameraSensorHeightFact,       &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_cameraResolutionWidthFact,    &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_cameraResolutionHeightFact,   &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
+    connect(&_cameraFocalLengthFact,        &Fact::valueChanged, this, &SurveyMissionItem::_cameraValueChanged);
 
-    return *this;
+    connect(this, &SurveyMissionItem::cameraTriggerChanged, this, &SurveyMissionItem::_cameraTriggerChanged);
 }
 
 void SurveyMissionItem::_setSurveyDistance(double surveyDistance)
@@ -190,16 +239,41 @@ void SurveyMissionItem::setDirty(bool dirty)
 
 void SurveyMissionItem::save(QJsonObject& saveObject) const
 {
-    saveObject[JsonHelper::jsonVersionKey] =    1;
+    saveObject[JsonHelper::jsonVersionKey] =    2;
     saveObject[_jsonTypeKey] =                  _complexType;
     saveObject[_jsonIdKey] =                    sequenceNumber();
-    saveObject[_jsonGridAltitudeKey] =          _gridAltitudeFact.rawValue().toDouble();
-    saveObject[_jsonGridAltitudeRelativeKey] =  _gridAltitudeRelative;
-    saveObject[_jsonGridAngleKey] =             _gridAngleFact.rawValue().toDouble();
-    saveObject[_jsonGridSpacingKey] =           _gridSpacingFact.rawValue().toDouble();
-    saveObject[_jsonTurnaroundDistKey] =        _turnaroundDistFact.rawValue().toDouble();
     saveObject[_jsonCameraTriggerKey] =         _cameraTrigger;
-    saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble();
+    saveObject[_jsonManualGridKey] =            _manualGrid;
+    saveObject[_jsonFixedValueIsAltitudeKey] =  _fixedValueIsAltitude;
+
+    if (_cameraTrigger) {
+        saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble();
+    }
+
+    QJsonObject gridObject;
+    gridObject[_jsonGridAltitudeKey] =          _gridAltitudeFact.rawValue().toDouble();
+    gridObject[_jsonGridAltitudeRelativeKey] =  _gridAltitudeRelative;
+    gridObject[_jsonGridAngleKey] =             _gridAngleFact.rawValue().toDouble();
+    gridObject[_jsonGridSpacingKey] =           _gridSpacingFact.rawValue().toDouble();
+    gridObject[_jsonTurnaroundDistKey] =        _turnaroundDistFact.rawValue().toDouble();
+
+    saveObject[_jsonGridObjectKey] = gridObject;
+
+    if (!_manualGrid) {
+        QJsonObject cameraObject;
+        cameraObject[_jsonCameraNameKey] =                  _camera;
+        cameraObject[_jsonCameraOrientationLandscapeKey] =  _cameraOrientationLandscape;
+        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[_jsonGroundResolutionKey] =            _groundResolutionFact.rawValue().toDouble();
+        cameraObject[_jsonFrontalOverlapKey] =              _frontalOverlapFact.rawValue().toInt();
+        cameraObject[_jsonSideOverlapKey] =                 _sideOverlapFact.rawValue().toInt();
+
+        saveObject[_jsonCameraObjectKey] = cameraObject;
+    }
 
     // Polygon shape
 
@@ -213,7 +287,7 @@ void SurveyMissionItem::save(QJsonObject& saveObject) const
         polygonArray += jsonValue;
     }
 
-    saveObject[_jsonPolygonKey] = polygonArray;
+    saveObject[_jsonPolygonObjectKey] = polygonArray;
 }
 
 void SurveyMissionItem::setSequenceNumber(int sequenceNumber)
@@ -234,55 +308,115 @@ void SurveyMissionItem::_clear(void)
 
 bool SurveyMissionItem::load(const QJsonObject& complexObject, QString& errorString)
 {
-    _clear();
-
-    // Validate requires keys
-    QStringList requiredKeys;
-    requiredKeys << JsonHelper::jsonVersionKey << _jsonTypeKey << _jsonIdKey << _jsonPolygonKey << _jsonGridAltitudeKey << _jsonGridAngleKey << _jsonGridSpacingKey <<
-                    _jsonCameraTriggerKey << _jsonCameraTriggerDistanceKey << _jsonGridAltitudeRelativeKey;
-    if (!JsonHelper::validateRequiredKeys(complexObject, requiredKeys, errorString)) {
-        _clear();
+    struct jsonKeyInfo_s {
+        const char*         key;
+        QJsonValue::Type    type;
+        bool                required;
+    };
+
+    QList<JsonHelper::KeyValidateInfo> mainKeyInfoList = {
+        { JsonHelper::jsonVersionKey,           QJsonValue::Double, true },
+        { _jsonTypeKey,                         QJsonValue::String, true },
+        { _jsonPolygonObjectKey,                QJsonValue::Array,  true },
+        { _jsonIdKey,                           QJsonValue::Double, true },
+        { _jsonGridObjectKey,                   QJsonValue::Object, true },
+        { _jsonCameraObjectKey,                 QJsonValue::Object, false },
+        { _jsonCameraTriggerKey,                QJsonValue::Bool,   true },
+        { _jsonCameraTriggerDistanceKey,        QJsonValue::Double, false },
+        { _jsonManualGridKey,                   QJsonValue::Bool,   true },
+        { _jsonFixedValueIsAltitudeKey,         QJsonValue::Bool,   true },
+    };
+
+    QList<JsonHelper::KeyValidateInfo> gridKeyInfoList = {
+        { _jsonGridAltitudeKey,                 QJsonValue::Double, true },
+        { _jsonGridAltitudeRelativeKey,         QJsonValue::Bool,   true },
+        { _jsonGridAngleKey,                    QJsonValue::Double, true },
+        { _jsonGridSpacingKey,                  QJsonValue::Double, true },
+        { _jsonTurnaroundDistKey,               QJsonValue::Double, true },
+    };
+
+    QList<JsonHelper::KeyValidateInfo> 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 },
+    };
+
+    if (!JsonHelper::validateKeys(complexObject, mainKeyInfoList, errorString)) {
         return false;
     }
-
-    // Validate types
-    QStringList keyList;
-    QList<QJsonValue::Type> typeList;
-    keyList << JsonHelper::jsonVersionKey << _jsonTypeKey << _jsonIdKey << _jsonPolygonKey << _jsonGridAltitudeKey << _jsonGridAngleKey << _jsonGridSpacingKey << _jsonTurnaroundDistKey <<
-               _jsonCameraTriggerKey << _jsonCameraTriggerDistanceKey << _jsonGridAltitudeRelativeKey;
-    typeList << QJsonValue::Double << QJsonValue::String << QJsonValue::Double << QJsonValue::Array << QJsonValue::Double << QJsonValue::Double<< QJsonValue::Double << QJsonValue::Double <<
-                QJsonValue::Bool << QJsonValue::Double << QJsonValue::Bool;
-    if (!JsonHelper::validateKeyTypes(complexObject, keyList, typeList, errorString)) {
-        _clear();
+    if (!JsonHelper::validateKeys(complexObject[_jsonGridObjectKey].toObject(), gridKeyInfoList, errorString)) {
         return false;
     }
 
     // Version check
-    if (complexObject[JsonHelper::jsonVersionKey].toInt() != 1) {
+    if (complexObject[JsonHelper::jsonVersionKey].toInt() != 2) {
         errorString = tr("QGroundControl does not support this version of survey items");
-        _clear();
         return false;
     }
     QString complexType = complexObject[_jsonTypeKey].toString();
     if (complexType != _complexType) {
         errorString = tr("QGroundControl does not support loading this complex mission item type: %1").arg(complexType);
-        _clear();
         return false;
     }
 
+    _clear();
+
     setSequenceNumber(complexObject[_jsonIdKey].toInt());
 
-    _cameraTrigger =        complexObject[_jsonCameraTriggerKey].toBool();
-    _gridAltitudeRelative = complexObject[_jsonGridAltitudeRelativeKey].toBool();
+    _manualGrid =           complexObject[_jsonManualGridKey].toBool(true);
+    _cameraTrigger =        complexObject[_jsonCameraTriggerKey].toBool(false);
+    _fixedValueIsAltitude = complexObject[_jsonFixedValueIsAltitudeKey].toBool(true);
+    _gridAltitudeRelative = complexObject[_jsonGridAltitudeRelativeKey].toBool(true);
 
-    _gridAltitudeFact.setRawValue           (complexObject[_jsonGridAltitudeKey].toDouble());
-    _gridAngleFact.setRawValue              (complexObject[_jsonGridAngleKey].toDouble());
-    _gridSpacingFact.setRawValue            (complexObject[_jsonGridSpacingKey].toDouble());
-    _turnaroundDistFact.setRawValue         (complexObject[_jsonTurnaroundDistKey].toDouble());
-    _cameraTriggerDistanceFact.setRawValue  (complexObject[_jsonCameraTriggerDistanceKey].toDouble());
+    QJsonObject gridObject = complexObject[_jsonGridObjectKey].toObject();
+
+    _gridAltitudeFact.setRawValue   (gridObject[_jsonGridAltitudeKey].toDouble());
+    _gridAngleFact.setRawValue      (gridObject[_jsonGridAngleKey].toDouble());
+    _gridSpacingFact.setRawValue    (gridObject[_jsonGridSpacingKey].toDouble());
+    _turnaroundDistFact.setRawValue (gridObject[_jsonTurnaroundDistKey].toDouble());
+
+    if (_cameraTrigger) {
+        if (!complexObject.contains(_jsonCameraTriggerDistanceKey)) {
+            errorString = tr("%1 but %2 is missing").arg("cameraTrigger = true").arg("cameraTriggerDistance");
+            return false;
+        }
+        _cameraTriggerDistanceFact.setRawValue(complexObject[_jsonCameraTriggerDistanceKey].toDouble());
+    }
+
+    if (!_manualGrid) {
+        if (!complexObject.contains(_jsonCameraObjectKey)) {
+            errorString = tr("%1 but %2 object is missing").arg("manualGrid = false").arg("camera");
+            return false;
+        }
+
+        QJsonObject cameraObject = complexObject[_jsonCameraObjectKey].toObject();
+
+        if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, errorString)) {
+            return false;
+        }
+
+        _camera =                       cameraObject[_jsonCameraNameKey].toString();
+        _cameraOrientationLandscape =   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());
+    }
 
     // Polygon shape
-    QJsonArray polygonArray(complexObject[_jsonPolygonKey].toArray());
+    QJsonArray polygonArray(complexObject[_jsonPolygonObjectKey].toArray());
     for (int i=0; i<polygonArray.count(); i++) {
         const QJsonValue& pointValue = polygonArray[i];
 
@@ -528,6 +662,9 @@ void SurveyMissionItem::_adjustLineDirection(const QList<QLineF>& lineList, QLis
 void SurveyMissionItem::_gridGenerator(const QList<QPointF>& polygonPoints,  QList<QPointF>& gridPoints)
 {
     double gridAngle = _gridAngleFact.rawValue().toDouble();
+    double gridSpacing = _gridSpacingFact.rawValue().toDouble();
+
+    qCDebug(SurveyMissionItemLog) << "SurveyMissionItem::_gridGenerator gridSpacing:gridAngle" << gridSpacing << gridAngle;
 
     gridPoints.clear();
 
@@ -557,7 +694,6 @@ void SurveyMissionItem::_gridGenerator(const QList<QPointF>& polygonPoints,  QLi
     // Create set of rotated parallel lines within the expanded bounding rect. Make the lines larger than the
     // bounding box to guarantee intersection.
     QList<QLineF> lineList;
-    float gridSpacing = _gridSpacingFact.rawValue().toDouble();
     float x = largeBoundRect.topLeft().x() - (gridSpacing / 2);
     while (x < largeBoundRect.bottomRight().x()) {
         float yTop =    largeBoundRect.topLeft().y() - 100.0;
@@ -671,3 +807,8 @@ int SurveyMissionItem::cameraShots(void) const
 {
     return _cameraTrigger ? _cameraShots : 0;
 }
+
+void SurveyMissionItem::_cameraValueChanged(void)
+{
+    emit cameraValueChanged();
+}
diff --git a/src/MissionManager/SurveyMissionItem.h b/src/MissionManager/SurveyMissionItem.h
index ed34cb8..58bed7e 100644
--- a/src/MissionManager/SurveyMissionItem.h
+++ b/src/MissionManager/SurveyMissionItem.h
@@ -25,19 +25,29 @@ class SurveyMissionItem : public ComplexMissionItem
 public:
     SurveyMissionItem(Vehicle* vehicle, QObject* parent = NULL);
 
-    const SurveyMissionItem& operator=(const SurveyMissionItem& other);
-
-    Q_PROPERTY(Fact*                gridAltitude            READ gridAltitude               CONSTANT)
-    Q_PROPERTY(bool                 gridAltitudeRelative    MEMBER _gridAltitudeRelative    NOTIFY gridAltitudeRelativeChanged)
-    Q_PROPERTY(Fact*                gridAngle               READ gridAngle                  CONSTANT)
-    Q_PROPERTY(Fact*                gridSpacing             READ gridSpacing                CONSTANT)
-    Q_PROPERTY(Fact*                turnaroundDist          READ turnaroundDist             CONSTANT)
-    Q_PROPERTY(bool                 cameraTrigger           MEMBER _cameraTrigger           NOTIFY cameraTriggerChanged)
-    Q_PROPERTY(Fact*                cameraTriggerDistance   READ cameraTriggerDistance      CONSTANT)
-    Q_PROPERTY(QVariantList         polygonPath             READ polygonPath                NOTIFY polygonPathChanged)
-    Q_PROPERTY(QVariantList         gridPoints              READ gridPoints                 NOTIFY gridPointsChanged)
-    Q_PROPERTY(int                  cameraShots             READ cameraShots                NOTIFY cameraShotsChanged)
-    Q_PROPERTY(double               coveredArea             READ coveredArea                NOTIFY coveredAreaChanged)
+    Q_PROPERTY(Fact*                gridAltitude                READ gridAltitude                   CONSTANT)
+    Q_PROPERTY(bool                 gridAltitudeRelative        MEMBER _gridAltitudeRelative        NOTIFY gridAltitudeRelativeChanged)
+    Q_PROPERTY(Fact*                gridAngle                   READ gridAngle                      CONSTANT)
+    Q_PROPERTY(Fact*                gridSpacing                 READ gridSpacing                    CONSTANT)
+    Q_PROPERTY(Fact*                turnaroundDist              READ turnaroundDist                 CONSTANT)
+    Q_PROPERTY(bool                 cameraTrigger               MEMBER _cameraTrigger               NOTIFY cameraTriggerChanged)
+    Q_PROPERTY(Fact*                cameraTriggerDistance       READ cameraTriggerDistance          CONSTANT)
+    Q_PROPERTY(Fact*                groundResolution            READ groundResolution               CONSTANT)
+    Q_PROPERTY(Fact*                frontalOverlap              READ frontalOverlap                 CONSTANT)
+    Q_PROPERTY(Fact*                sideOverlap                 READ sideOverlap                    CONSTANT)
+    Q_PROPERTY(Fact*                cameraSensorWidth           READ cameraSensorWidth              CONSTANT)
+    Q_PROPERTY(Fact*                cameraSensorHeight          READ cameraSensorHeight             CONSTANT)
+    Q_PROPERTY(Fact*                cameraResolutionWidth       READ cameraResolutionWidth          CONSTANT)
+    Q_PROPERTY(Fact*                cameraResolutionHeight      READ cameraResolutionHeight         CONSTANT)
+    Q_PROPERTY(Fact*                cameraFocalLength           READ cameraFocalLength              CONSTANT)
+    Q_PROPERTY(QVariantList         polygonPath                 READ polygonPath                    NOTIFY polygonPathChanged)
+    Q_PROPERTY(QVariantList         gridPoints                  READ gridPoints                     NOTIFY gridPointsChanged)
+    Q_PROPERTY(int                  cameraShots                 READ cameraShots                    NOTIFY cameraShotsChanged)
+    Q_PROPERTY(double               coveredArea                 READ coveredArea                    NOTIFY coveredAreaChanged)
+    Q_PROPERTY(bool                 fixedValueIsAltitude        MEMBER _fixedValueIsAltitude        NOTIFY fixedValueIsAltitudeChanged)
+    Q_PROPERTY(bool                 cameraOrientationLandscape  MEMBER _cameraOrientationLandscape  NOTIFY cameraOrientationLandscapeChanged)
+    Q_PROPERTY(bool                 manualGrid                  MEMBER _manualGrid                  NOTIFY manualGridChanged)
+    Q_PROPERTY(QString              camera                      MEMBER _camera                      NOTIFY cameraChanged)
 
     Q_INVOKABLE void clearPolygon(void);
     Q_INVOKABLE void addPolygonCoordinate(const QGeoCoordinate coordinate);
@@ -46,11 +56,19 @@ public:
     QVariantList polygonPath(void) { return _polygonPath; }
     QVariantList gridPoints (void) { return _gridPoints; }
 
-    Fact* gridAltitude(void)    { return &_gridAltitudeFact; }
-    Fact* gridAngle(void)       { return &_gridAngleFact; }
-    Fact* gridSpacing(void)     { return &_gridSpacingFact; }
-    Fact* turnaroundDist(void)  { return &_turnaroundDistFact; }
-    Fact* cameraTriggerDistance(void) { return &_cameraTriggerDistanceFact; }
+    Fact* gridAltitude              (void) { return &_gridAltitudeFact; }
+    Fact* gridAngle                 (void) { return &_gridAngleFact; }
+    Fact* gridSpacing               (void) { return &_gridSpacingFact; }
+    Fact* turnaroundDist            (void) { return &_turnaroundDistFact; }
+    Fact* cameraTriggerDistance     (void) { return &_cameraTriggerDistanceFact; }
+    Fact* groundResolution          (void) { return &_groundResolutionFact; }
+    Fact* frontalOverlap            (void) { return &_frontalOverlapFact; }
+    Fact* sideOverlap               (void) { return &_sideOverlapFact; }
+    Fact* cameraSensorWidth         (void) { return &_cameraSensorWidthFact; }
+    Fact* cameraSensorHeight        (void) { return &_cameraSensorHeightFact; }
+    Fact* cameraResolutionWidth     (void) { return &_cameraResolutionWidthFact; }
+    Fact* cameraResolutionHeight    (void) { return &_cameraResolutionHeightFact; }
+    Fact* cameraFocalLength         (void) { return &_cameraFocalLengthFact; }
 
     int     cameraShots(void) const;
     double  coveredArea(void) const { return _coveredArea; }
@@ -87,14 +105,20 @@ public:
     void save               (QJsonObject& saveObject) const final;
 
 signals:
-    void polygonPathChanged             (void);
-    void altitudeChanged                (double altitude);
-    void gridAngleChanged               (double gridAngle);
-    void gridPointsChanged              (void);
-    void cameraTriggerChanged           (bool cameraTrigger);
-    void gridAltitudeRelativeChanged    (bool gridAltitudeRelative);
-    void cameraShotsChanged             (int cameraShots);
-    void coveredAreaChanged             (double coveredArea);
+    void polygonPathChanged                 (void);
+    void altitudeChanged                    (double altitude);
+    void gridAngleChanged                   (double gridAngle);
+    void gridPointsChanged                  (void);
+    void cameraTriggerChanged               (bool cameraTrigger);
+    void gridAltitudeRelativeChanged        (bool gridAltitudeRelative);
+    void cameraShotsChanged                 (int cameraShots);
+    void coveredAreaChanged                 (double coveredArea);
+    void cameraValueChanged                 (void);
+    void fixedValueIsAltitudeChanged        (bool fixedValueIsAltitude);
+    void gridTypeChanged                    (QString gridType);
+    void cameraOrientationLandscapeChanged  (bool cameraOrientationLandscape);
+    void cameraChanged                      (QString camera);
+    void manualGridChanged                  (bool manualGrid);
 
 private slots:
     void _cameraTriggerChanged(void);
@@ -113,33 +137,46 @@ private:
     void _setSurveyDistance(double surveyDistance);
     void _setCameraShots(int cameraShots);
     void _setCoveredArea(double coveredArea);
-
-    int                 _sequenceNumber;
-    bool                _dirty;
-    QVariantList        _polygonPath;
-    QVariantList        _gridPoints;
-    QGeoCoordinate      _coordinate;
-    QGeoCoordinate      _exitCoordinate;
-    double              _altitude;
-    double              _gridAngle;
-    bool                _cameraTrigger;
-    bool                _gridAltitudeRelative;
-
-    double              _surveyDistance;
-    int                 _cameraShots;
-    double              _coveredArea;
+    void _cameraValueChanged(void);
+
+    int             _sequenceNumber;
+    bool            _dirty;
+    QVariantList    _polygonPath;
+    QVariantList    _gridPoints;
+    QGeoCoordinate  _coordinate;
+    QGeoCoordinate  _exitCoordinate;
+    double          _altitude;
+    bool            _cameraTrigger;
+    bool            _gridAltitudeRelative;
+    bool            _manualGrid;
+    QString         _camera;
+    bool            _cameraOrientationLandscape;
+    bool            _fixedValueIsAltitude;
+
+    double          _surveyDistance;
+    int             _cameraShots;
+    double          _coveredArea;
 
     Fact            _gridAltitudeFact;
     Fact            _gridAngleFact;
     Fact            _gridSpacingFact;
     Fact            _turnaroundDistFact;
     Fact            _cameraTriggerDistanceFact;
+    Fact            _groundResolutionFact;
+    Fact            _frontalOverlapFact;
+    Fact            _sideOverlapFact;
+    Fact            _cameraSensorWidthFact;
+    Fact            _cameraSensorHeightFact;
+    Fact            _cameraResolutionWidthFact;
+    Fact            _cameraResolutionHeightFact;
+    Fact            _cameraFocalLengthFact;
 
     static QMap<QString, FactMetaData*> _metaDataMap;
 
     static const char* _jsonTypeKey;
-    static const char* _jsonPolygonKey;
+    static const char* _jsonPolygonObjectKey;
     static const char* _jsonIdKey;
+    static const char* _jsonGridObjectKey;
     static const char* _jsonGridAltitudeKey;
     static const char* _jsonGridAltitudeRelativeKey;
     static const char* _jsonGridAngleKey;
@@ -147,12 +184,34 @@ private:
     static const char* _jsonTurnaroundDistKey;
     static const char* _jsonCameraTriggerKey;
     static const char* _jsonCameraTriggerDistanceKey;
+    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* _jsonManualGridKey;
+    static const char* _jsonCameraObjectKey;
+    static const char* _jsonCameraNameKey;
+    static const char* _jsonCameraOrientationLandscapeKey;
+    static const char* _jsonFixedValueIsAltitudeKey;
+
 
     static const char* _gridAltitudeFactName;
     static const char* _gridAngleFactName;
     static const char* _gridSpacingFactName;
     static const char* _turnaroundDistFactName;
     static const char* _cameraTriggerDistanceFactName;
+    static const char* _groundResolutionFactName;
+    static const char* _frontalOverlapFactName;
+    static const char* _sideOverlapFactName;
+    static const char* _cameraSensorWidthFactName;
+    static const char* _cameraSensorHeightFactName;
+    static const char* _cameraResolutionWidthFactName;
+    static const char* _cameraResolutionHeightFactName;
+    static const char* _cameraFocalLengthFactName;
 
     static const char* _complexType;
 };

From 1fc04bf40a4b4defcde82cfbe2f7dc44a1a01dea Mon Sep 17 00:00:00 2001
From: Don Gagne <don@thegagnes.com>
Date: Fri, 30 Sep 2016 14:11:21 -0700
Subject: [PATCH 4/4] Fix unit test usage

---
 src/MissionManager/SurveyMissionItem.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/MissionManager/SurveyMissionItem.cc b/src/MissionManager/SurveyMissionItem.cc
index e112271..aae975f 100644
--- a/src/MissionManager/SurveyMissionItem.cc
+++ b/src/MissionManager/SurveyMissionItem.cc
@@ -92,7 +92,7 @@ SurveyMissionItem::SurveyMissionItem(Vehicle* vehicle, QObject* parent)
 
     _gridAltitudeFact.setRawValue(50);
     _gridSpacingFact.setRawValue(30);
-    _turnaroundDistFact.setRawValue(_vehicle->multiRotor() ? 0 : 60);
+    _turnaroundDistFact.setRawValue((_vehicle && _vehicle->multiRotor()) ? 0 : 60);
     _cameraTriggerDistanceFact.setRawValue(25);
     _groundResolutionFact.setRawValue(3);
     _frontalOverlapFact.setRawValue(10);