diff --git a/src/MissionEditor/SurveyItemEditor.qml b/src/MissionEditor/SurveyItemEditor.qml index 81d88cb7a53f6619021666346ab8868ba87972d6..d184754c1414313fb619012ee05ce1a7d5c9413c 100644 --- a/src/MissionEditor/SurveyItemEditor.qml +++ b/src/MissionEditor/SurveyItemEditor.qml @@ -1,5 +1,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 import QGroundControl 1.0 import QGroundControl.ScreenTools 1.0 @@ -22,40 +23,128 @@ Rectangle { property real _margin: ScreenTools.defaultFontPixelWidth / 2 - property var _cameraInfoCanonSX260: { "focalLength": 4.5, "sensorHeight": 4.55, "sensorWidth": 6.17 } + property int cameraIndex: 1 + + 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 + } + } function recalcFromCameraValues() { - var focalLength = Number(focalLengthField.text) - var sensorWidth = Number(sensorWidthField.text) - var sensorHeight = Number(sensorHeightField.text) - var overlap = Number(imageOverlapField.text) - - if (focalLength <= 0.0 || sensorWidth <= 0.0 || sensorHeight <= 0.0) { + 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 return } - var scaledFocalLengthMM = (1000.0 * missionItem.gridAltitude.rawValue) / focalLength - var imageWidthM = (sensorWidth * scaledFocalLengthMM) / 1000.0; - var imageHeightM = (sensorHeight * scaledFocalLengthMM) / 1000.0; - + 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 gridSpacing var cameraTriggerDistance + + altitude = (imageWidth * gsd * focalLength) / (sensorWidth * 100) + if (cameraOrientationLandscape.checked) { - gridSpacing = imageWidthM - cameraTriggerDistance = imageHeightM + imageSizeSideGround = (imageWidth * gsd) / 100 + imageSizeFrontGround = (imageHeight * gsd) / 100 } else { - gridSpacing = imageHeightM - cameraTriggerDistance = imageWidthM + imageSizeSideGround = (imageHeight * gsd) / 100 + imageSizeFrontGround = (imageWidth * gsd) / 100 } - gridSpacing = (1.0 - (overlap / 100.0)) * gridSpacing - cameraTriggerDistance = (1.0 - (overlap / 100.0)) * cameraTriggerDistance + gridSpacing = imageSizeSideGround * ( (100-sideOverlap) / 100 ) + cameraTriggerDistance = imageSizeFrontGround * ( (100-frontalOverlap) / 100 ) + + 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 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" + 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 + + gsd = (altitude * sensorWidth * 100) / (imageWidth * focalLength) + + if (cameraOrientationLandscape.checked) { + imageSizeSideGround = (imageWidth * gsd) / 100 + imageSizeFrontGround = (imageHeight * gsd) / 100 + } else { + imageSizeSideGround = (imageHeight * gsd) / 100 + imageSizeFrontGround = (imageWidth * gsd) / 100 + } + + 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) + } + function polygonCaptureStarted() { - missionItem.clearPolygon() + missionItem.clearPolygon() } function polygonCaptureFinished(coordinates) { @@ -75,7 +164,9 @@ Rectangle { ExclusiveGroup { id: cameraOrientationGroup - onCurrentChanged: recalcFromCameraValues() + onCurrentChanged: { + recalcFromMissionValues() + } } Column { @@ -112,6 +203,8 @@ Rectangle { width: _editFieldWidth showUnits: true fact: modelData + onEditingFinished: recalcFromMissionValues() + validator: DoubleValidator{bottom:0.0; decimals:2} } } } @@ -123,6 +216,48 @@ Rectangle { onClicked: missionItem.gridAltitudeRelative = checked } + Grid { + columns: 2 + columnSpacing: ScreenTools.defaultFontPixelWidth + rowSpacing: _margin + verticalItemAlignment: Grid.AlignVCenter + + 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} + } + + QGCLabel { text: qsTr("Frontal Overlap:") } + QGCTextField { + id: frontalOverlapField + width: _editFieldWidth + unitsLabel: "%" + showUnits: true + onEditingFinished: recalcFromCameraValues() + validator: IntValidator {bottom:0} + } + + QGCLabel { text: qsTr("Side Overlap:") } + QGCTextField { + id: sideOverlapField + width: _editFieldWidth + unitsLabel: "%" + showUnits: true + onEditingFinished: recalcFromCameraValues() + validator: IntValidator {bottom:0} + } + + Component.onCompleted: recalcFromMissionValues() + } + QGCLabel { text: qsTr("Camera:") } Rectangle { @@ -132,11 +267,14 @@ Rectangle { color: qgcPal.text } - Row { + Grid { + columns: 2 spacing: ScreenTools.defaultFontPixelWidth + verticalItemAlignment: Grid.AlignVCenter QGCRadioButton { id: cameraOrientationLandscape + width: _editFieldWidth text: "Landscape" checked: true exclusiveGroup: cameraOrientationGroup @@ -147,15 +285,10 @@ Rectangle { text: "Portrait" exclusiveGroup: cameraOrientationGroup } - } - - Grid { - columns: 2 - spacing: ScreenTools.defaultFontPixelWidth - verticalItemAlignment: Grid.AlignVCenter QGCCheckBox { id: cameraTrigger + width: _editFieldWidth text: qsTr("Trigger:") checked: missionItem.cameraTrigger onClicked: missionItem.cameraTrigger = checked @@ -166,49 +299,188 @@ Rectangle { showUnits: true fact: missionItem.cameraTriggerDistance enabled: missionItem.cameraTrigger + onEditingFinished: recalcFromMissionValues() + validator: DoubleValidator{bottom:0.0; decimals:2} } + } - QGCLabel { text: qsTr("Focal length:") } - QGCTextField { - id: focalLengthField - unitsLabel: "mm" - showUnits: true - text: _cameraInfoCanonSX260.focalLength.toString() + Component { + id: cameraFields - onEditingFinished: recalcFromCameraValues() - } + QGCViewDialog { - QGCLabel { text: qsTr("Sensor Width:") } - QGCTextField { - id: sensorWidthField - unitsLabel: "mm" - showUnits: true - text: _cameraInfoCanonSX260.sensorWidth.toString() + 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 + } + } + } + } - onEditingFinished: recalcFromCameraValues() + 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)) + } + } + } + } + } + + function accept() { + hideDialog() + recalcFromCameraValues() + } + }//QGCViewDialog + }//Component + + Column { + spacing: ScreenTools.defaultFontPixelHeight*0.01 + + Row { + spacing: ScreenTools.defaultFontPixelWidth + + QGCLabel { text: qsTr("Model:") } + QGCLabel { text: cameraModelList.get(cameraIndex).text } } - QGCLabel { text: qsTr("Sensor height:") } - QGCTextField { - id: sensorHeightField - unitsLabel: "mm" - showUnits: true - text: _cameraInfoCanonSX260.sensorHeight.toString() + Grid { + columns: 2 + columnSpacing: ScreenTools.defaultFontPixelWidth + rowSpacing: ScreenTools.defaultFontPixelHeight*0.01 - onEditingFinished: recalcFromCameraValues() + 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 + } + + QGCLabel { text: qsTr("Image Size:") } + QGCLabel { text: cameraModelList.get(cameraIndex).imageWidth.toFixed(0) + qsTr(" x ") + cameraModelList.get(cameraIndex).imageHeight.toFixed(0) } + + QGCLabel { text: qsTr("Focal length:") } + QGCLabel { text: cameraModelList.get(cameraIndex).focalLength.toFixed(2) } } + } - QGCLabel { text: qsTr("Image overlap:") } - QGCTextField { - id: imageOverlapField - unitsLabel: "%" - showUnits: true - text: "0" + QGCButton { + id: cameraModelChange + text: qsTr("Change") - onEditingFinished: recalcFromCameraValues() + onClicked: { + qgcView.showDialog(cameraFields, qsTr("Set Camera Model"), qgcView.showDialogDefaultWidth, StandardButton.Save) } } + QGCLabel { text: qsTr("Polygon:") } Rectangle { diff --git a/src/MissionManager/SurveyMissionItem.cc b/src/MissionManager/SurveyMissionItem.cc index c105e08e58d409280b0c4326989e17c66608136f..f5f23098f013a8175b1e7c59a4615950b3fe1119 100644 --- a/src/MissionManager/SurveyMissionItem.cc +++ b/src/MissionManager/SurveyMissionItem.cc @@ -53,7 +53,7 @@ SurveyMissionItem::SurveyMissionItem(Vehicle* vehicle, QObject* parent) , _turnaroundDistMetaData (FactMetaData::valueTypeDouble) , _cameraTriggerDistanceMetaData(FactMetaData::valueTypeDouble) { - _gridAltitudeFact.setRawValue(25); + _gridAltitudeFact.setRawValue(50); _gridSpacingFact.setRawValue(10); _turnaroundDistFact.setRawValue(60); _cameraTriggerDistanceFact.setRawValue(25); @@ -344,7 +344,7 @@ void SurveyMissionItem::_clearGrid(void) void SurveyMissionItem::_generateGrid(void) { - if (_polygonPath.count() < 3) { + if (_polygonPath.count() < 3 || _gridSpacingFact.rawValue().toDouble() <= 0) { _clearGrid(); return; } @@ -391,7 +391,11 @@ void SurveyMissionItem::_generateGrid(void) _gridPoints += QVariant::fromValue(geoCoord); } _setSurveyDistance(surveyDistance); - _setCameraShots((int)floor(surveyDistance / _cameraTriggerDistanceFact.rawValue().toDouble())); + if (_cameraTriggerDistanceFact.rawValue().toDouble() > 0) { + _setCameraShots((int)floor(surveyDistance / _cameraTriggerDistanceFact.rawValue().toDouble())); + } else { + _setCameraShots(0); + } emit gridPointsChanged(); emit lastSequenceNumberChanged(lastSequenceNumber());