diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 32c149c5eb8d101e4138a1b7d73a01554b03e1d7..d64e82534a3878baabd36853bfe36cc7e8f7a415 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -505,7 +505,9 @@ HEADERS += \ src/JsonHelper.h \ src/LogCompressor.h \ src/MG.h \ + src/MissionManager/CameraCalc.h \ src/MissionManager/CameraSection.h \ + src/MissionManager/CameraSpec.h \ src/MissionManager/ComplexMissionItem.h \ src/MissionManager/FixedWingLandingComplexItem.h \ src/MissionManager/GeoFenceController.h \ @@ -693,7 +695,9 @@ SOURCES += \ src/Joystick/JoystickManager.cc \ src/JsonHelper.cc \ src/LogCompressor.cc \ + src/MissionManager/CameraCalc.cc \ src/MissionManager/CameraSection.cc \ + src/MissionManager/CameraSpec.cc \ src/MissionManager/ComplexMissionItem.cc \ src/MissionManager/FixedWingLandingComplexItem.cc \ src/MissionManager/GeoFenceController.cc \ diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index a1860945631ba23ee5f676f9f8c77e956add0bd9..5a743baf9caed70fc9b10fd98611dd23a4dd7f83 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -44,6 +44,7 @@ src/VehicleSetup/PX4FlowSensor.qml src/AnalyzeView/AnalyzePage.qml src/QmlControls/AppMessages.qml + src/PlanView/CameraCalc.qml src/PlanView/CameraSection.qml src/QmlControls/ClickableColor.qml src/QmlControls/DropButton.qml @@ -203,6 +204,8 @@ src/MissionManager/FWLandingPattern.FactMetaData.json src/comm/USBBoardInfo.json src/MissionManager/CameraSection.FactMetaData.json + src/MissionManager/CameraCalc.FactMetaData.json + src/MissionManager/CameraSpec.FactMetaData.json src/MissionManager/SpeedSection.FactMetaData.json src/MissionManager/MissionSettings.FactMetaData.json src/Vehicle/VehicleFact.json diff --git a/src/FactSystem/Fact.cc b/src/FactSystem/Fact.cc index a1fcf413e370aadba168302ad4192fe31fd51f0c..83fe83e9223ba126e333c3ea5a1a460e086e642f 100644 --- a/src/FactSystem/Fact.cc +++ b/src/FactSystem/Fact.cc @@ -89,7 +89,7 @@ void Fact::forceSetRawValue(const QVariant& value) emit rawValueChanged(_rawValue); } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } } @@ -109,7 +109,7 @@ void Fact::setRawValue(const QVariant& value) } } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } } @@ -118,7 +118,7 @@ void Fact::setCookedValue(const QVariant& value) if (_metaData) { setRawValue(_metaData->cookedTranslator()(value)); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } } @@ -130,7 +130,7 @@ void Fact::setEnumStringValue(const QString& value) setCookedValue(_metaData->enumValues()[index]); } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } } @@ -139,7 +139,7 @@ void Fact::setEnumIndex(int index) if (_metaData) { setCookedValue(_metaData->enumValues()[index]); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } } @@ -168,7 +168,7 @@ QVariant Fact::cookedValue(void) const if (_metaData) { return _metaData->rawTranslator()(_rawValue); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return _rawValue; } } @@ -181,7 +181,7 @@ QString Fact::enumStringValue(void) return _metaData->enumStrings()[enumIndex]; } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } return QString(); @@ -213,7 +213,7 @@ int Fact::enumIndex(void) return index; } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } return -1; } @@ -223,7 +223,7 @@ QStringList Fact::enumStrings(void) const if (_metaData) { return _metaData->enumStrings(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QStringList(); } } @@ -233,7 +233,7 @@ QVariantList Fact::enumValues(void) const if (_metaData) { return _metaData->enumValues(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariantList(); } } @@ -243,7 +243,7 @@ void Fact::setEnumInfo(const QStringList& strings, const QVariantList& values) if (_metaData) { _metaData->setEnumInfo(strings, values); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } } @@ -252,7 +252,7 @@ QStringList Fact::bitmaskStrings(void) const if (_metaData) { return _metaData->bitmaskStrings(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QStringList(); } } @@ -262,7 +262,7 @@ QVariantList Fact::bitmaskValues(void) const if (_metaData) { return _metaData->bitmaskValues(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariantList(); } } @@ -336,7 +336,7 @@ QVariant Fact::rawDefaultValue(void) const } return _metaData->rawDefaultValue(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariant(0); } } @@ -349,7 +349,7 @@ QVariant Fact::cookedDefaultValue(void) const } return _metaData->cookedDefaultValue(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariant(0); } } @@ -369,7 +369,7 @@ QString Fact::shortDescription(void) const if (_metaData) { return _metaData->shortDescription(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QString(); } } @@ -379,7 +379,7 @@ QString Fact::longDescription(void) const if (_metaData) { return _metaData->longDescription(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QString(); } } @@ -389,7 +389,7 @@ QString Fact::rawUnits(void) const if (_metaData) { return _metaData->rawUnits(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QString(); } } @@ -399,7 +399,7 @@ QString Fact::cookedUnits(void) const if (_metaData) { return _metaData->cookedUnits(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QString(); } } @@ -409,7 +409,7 @@ QVariant Fact::rawMin(void) const if (_metaData) { return _metaData->rawMin(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariant(0); } } @@ -419,7 +419,7 @@ QVariant Fact::cookedMin(void) const if (_metaData) { return _metaData->cookedMin(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariant(0); } } @@ -434,7 +434,7 @@ QVariant Fact::rawMax(void) const if (_metaData) { return _metaData->rawMax(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariant(0); } } @@ -444,7 +444,7 @@ QVariant Fact::cookedMax(void) const if (_metaData) { return _metaData->cookedMax(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QVariant(0); } } @@ -459,7 +459,7 @@ bool Fact::minIsDefaultForType(void) const if (_metaData) { return _metaData->minIsDefaultForType(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } @@ -469,7 +469,7 @@ bool Fact::maxIsDefaultForType(void) const if (_metaData) { return _metaData->maxIsDefaultForType(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } @@ -479,7 +479,7 @@ int Fact::decimalPlaces(void) const if (_metaData) { return _metaData->decimalPlaces(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return FactMetaData::defaultDecimalPlaces; } } @@ -489,14 +489,17 @@ QString Fact::group(void) const if (_metaData) { return _metaData->group(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QString(); } } -void Fact::setMetaData(FactMetaData* metaData) +void Fact::setMetaData(FactMetaData* metaData, bool setDefaultFromMetaData) { _metaData = metaData; + if (setDefaultFromMetaData) { + setRawValue(rawDefaultValue()); + } emit valueChanged(cookedValue()); } @@ -509,7 +512,7 @@ bool Fact::valueEqualsDefault(void) const return false; } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } @@ -519,7 +522,7 @@ bool Fact::defaultValueAvailable(void) const if (_metaData) { return _metaData->defaultValueAvailable(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } @@ -534,7 +537,7 @@ QString Fact::validate(const QString& cookedValue, bool convertOnly) return errorString; } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return QString("Internal error: Meta data pointer missing"); } } @@ -550,7 +553,7 @@ QVariant Fact::clamp(const QString& cookedValue) return rawValue(); } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } return QVariant(); } @@ -560,7 +563,7 @@ bool Fact::rebootRequired(void) const if (_metaData) { return _metaData->rebootRequired(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } @@ -600,7 +603,7 @@ QString Fact::enumOrValueString(void) return cookedValueString(); } } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } return QString(); } @@ -610,7 +613,7 @@ double Fact::increment(void) const if (_metaData) { return _metaData->increment(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); } return std::numeric_limits::quiet_NaN(); } @@ -620,7 +623,7 @@ bool Fact::hasControl(void) const if (_metaData) { return _metaData->hasControl(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } @@ -630,7 +633,7 @@ bool Fact::readOnly(void) const if (_metaData) { return _metaData->readOnly(); } else { - qWarning() << kMissingMetadata; + qWarning() << kMissingMetadata << name(); return false; } } diff --git a/src/FactSystem/Fact.h b/src/FactSystem/Fact.h index b8693b86c3308cf70f5df2b21b39ccf23db5b167..63ee9a584dbc13d1aecd13ba05632bc6d7cac284 100644 --- a/src/FactSystem/Fact.h +++ b/src/FactSystem/Fact.h @@ -136,7 +136,9 @@ public: void forceSetRawValue(const QVariant& value); /// Sets the meta data associated with the Fact. - void setMetaData(FactMetaData* metaData); + /// @param metaData FactMetaData for Fact + /// @param setDefaultFromMetaData true: set the fact value to the default specified in the meta data + void setMetaData(FactMetaData* metaData, bool setDefaultFromMetaData = false); FactMetaData* metaData() { return _metaData; } diff --git a/src/FirmwarePlugin/CameraMetaData.h b/src/FirmwarePlugin/CameraMetaData.h index 50ab89495933c4d311bde0479804f8a99a83f097..2c146f1a33f998562567b55c3b3e562fffde7046 100644 --- a/src/FirmwarePlugin/CameraMetaData.h +++ b/src/FirmwarePlugin/CameraMetaData.h @@ -7,8 +7,7 @@ * ****************************************************************************/ -#ifndef CameraMetaData_H -#define CameraMetaData_H +#pragma once #include @@ -29,15 +28,25 @@ public: double minTriggerInterval, QObject* parent = NULL); - Q_PROPERTY(QString name MEMBER _name CONSTANT) ///< Camera name - Q_PROPERTY(double sensorWidth MEMBER _sensorWidth CONSTANT) ///< Sensor size in millimeters - Q_PROPERTY(double sensorHeight MEMBER _sensorHeight CONSTANT) ///< Sensor size in millimeters - Q_PROPERTY(double imageWidth MEMBER _imageWidth CONSTANT) ///< Image size in pixels - Q_PROPERTY(double imageHeight MEMBER _imageHeight CONSTANT) ///< Image size in pixels - Q_PROPERTY(double focalLength MEMBER _focalLength CONSTANT) ///< Focal length in millimeters - Q_PROPERTY(bool landscape MEMBER _landscape CONSTANT) ///< true: camera is in landscape orientation - Q_PROPERTY(bool fixedOrientation MEMBER _fixedOrientation CONSTANT) ///< true: camera is in fixed orientation - Q_PROPERTY(double minTriggerInterval MEMBER _minTriggerInterval CONSTANT) ///< Minimum time in seconds between each photo taken, 0 for not specified + Q_PROPERTY(QString name READ name CONSTANT) ///< Camera name + Q_PROPERTY(double sensorWidth READ sensorWidth CONSTANT) ///< Sensor size in millimeters + Q_PROPERTY(double sensorHeight READ sensorHeight CONSTANT) ///< Sensor size in millimeters + Q_PROPERTY(double imageWidth READ imageWidth CONSTANT) ///< Image size in pixels + Q_PROPERTY(double imageHeight READ imageHeight CONSTANT) ///< Image size in pixels + Q_PROPERTY(double focalLength READ focalLength CONSTANT) ///< Focal length in millimeters + Q_PROPERTY(bool landscape READ landscape CONSTANT) ///< true: camera is in landscape orientation + Q_PROPERTY(bool fixedOrientation READ fixedOrientation CONSTANT) ///< true: camera is in fixed orientation + Q_PROPERTY(double minTriggerInterval READ minTriggerInterval CONSTANT) ///< Minimum time in seconds between each photo taken, 0 for not specified + + QString name (void) const { return _name; } + double sensorWidth (void) const { return _sensorWidth; } + double sensorHeight (void) const { return _sensorHeight; } + double imageWidth (void) const { return _imageWidth; } + double imageHeight (void) const { return _imageHeight; } + double focalLength (void) const { return _focalLength; } + bool landscape (void) const { return _landscape; } + bool fixedOrientation (void) const { return _fixedOrientation; } + double minTriggerInterval (void) const { return _minTriggerInterval; } private: QString _name; @@ -50,5 +59,3 @@ private: bool _fixedOrientation; double _minTriggerInterval; }; - -#endif diff --git a/src/MissionManager/CameraCalc.FactMetaData.json b/src/MissionManager/CameraCalc.FactMetaData.json new file mode 100644 index 0000000000000000000000000000000000000000..2778a493a127fc7f7cbf134e67f20104919d774f --- /dev/null +++ b/src/MissionManager/CameraCalc.FactMetaData.json @@ -0,0 +1,62 @@ +[ +{ + "name": "ValueSetIsDistance", + "shortDescription": "Value specified is distance to surface.", + "type": "bool", + "defaultValue": 1 +}, +{ + "name": "DistanceToSurface", + "shortDescription": "Distance vehicle is away from surface.", + "type": "double", + "min": 0.1, + "units": "m", + "decimalPlaces": 2, + "defaultValue": 100.0 +}, +{ + "name": "ImageDensity", + "shortDescription": "Image desity at surface.", + "type": "double", + "min": 0, + "units": "cm/px", + "decimalPlaces": 1, + "defaultValue": 25 +}, +{ + "name": "FrontalOverlap", + "shortDescription": "Amount of overlap between images in the forward facing direction.", + "type": "double", + "decimalPlaces": 0, + "min": 0, + "max": 85, + "units": "%", + "defaultValue": 70 +}, +{ + "name": "SideOverlap", + "shortDescription": "Amount of overlap between images in the side facing direction.", + "type": "double", + "decimalPlaces": 0, + "min": 0, + "max": 85, + "units": "%", + "defaultValue": 70 +}, +{ + "name": "AdjustedFootprintFrontal", + "type": "double", + "decimalPlaces": 2, + "min": 0, + "units": "m", + "defaultValue": 25 +}, +{ + "name": "AdjustedFootprintSide", + "type": "double", + "decimalPlaces": 2, + "min": 0, + "units": "m", + "defaultValue": 25 +} +] diff --git a/src/MissionManager/CameraCalc.cc b/src/MissionManager/CameraCalc.cc new file mode 100644 index 0000000000000000000000000000000000000000..8ebe3879255f0726aa8f30d4065481dd18e44230 --- /dev/null +++ b/src/MissionManager/CameraCalc.cc @@ -0,0 +1,351 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "CameraCalc.h" +#include "JsonHelper.h" +#include "Vehicle.h" +#include "CameraMetaData.h" + +#include + +const char* CameraCalc::_valueSetIsDistanceName = "ValueSetIsDistance"; +const char* CameraCalc::_distanceToSurfaceName = "DistanceToSurface"; +const char* CameraCalc::_imageDensityName = "ImageDensity"; +const char* CameraCalc::_frontalOverlapName = "FrontalOverlap"; +const char* CameraCalc::_sideOverlapName = "SideOverlap"; +const char* CameraCalc::_adjustedFootprintFrontalName = "AdjustedFootprintFrontal"; +const char* CameraCalc::_adjustedFootprintSideName = "AdjustedFootprintSide"; + +CameraCalc::CameraCalc(Vehicle* vehicle, QObject* parent) + : CameraSpec (parent) + , _vehicle (vehicle) + , _dirty (false) + , _cameraSpecType (CameraSpecNone) + , _disableRecalc (false) + , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CameraCalc.FactMetaData.json"), this)) + , _valueSetIsDistanceFact (0, _valueSetIsDistanceName, FactMetaData::valueTypeBool) + , _distanceToSurfaceFact (0, _distanceToSurfaceName, FactMetaData::valueTypeDouble) + , _imageDensityFact (0, _imageDensityName, FactMetaData::valueTypeDouble) + , _frontalOverlapFact (0, _frontalOverlapName, FactMetaData::valueTypeDouble) + , _sideOverlapFact (0, _sideOverlapName, FactMetaData::valueTypeDouble) + , _adjustedFootprintSideFact (0, _adjustedFootprintSideName, FactMetaData::valueTypeDouble) + , _adjustedFootprintFrontalFact (0, _adjustedFootprintFrontalName, FactMetaData::valueTypeDouble) + , _imageFootprintSide (0) + , _imageFootprintFrontal (0) + , _knownCameraList (_vehicle->staticCameraList()) +{ + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + + _valueSetIsDistanceFact.setMetaData (_metaDataMap[_valueSetIsDistanceName], true /* setDefaultFromMetaData */); + _distanceToSurfaceFact.setMetaData (_metaDataMap[_distanceToSurfaceName], true); + _imageDensityFact.setMetaData (_metaDataMap[_imageDensityName], true); + _frontalOverlapFact.setMetaData (_metaDataMap[_frontalOverlapName], true); + _sideOverlapFact.setMetaData (_metaDataMap[_sideOverlapName], true); + _adjustedFootprintSideFact.setMetaData (_metaDataMap[_adjustedFootprintSideName], false); + _adjustedFootprintFrontalFact.setMetaData (_metaDataMap[_adjustedFootprintFrontalName], false); + + connect(this, &CameraCalc::knownCameraNameChanged, this, &CameraCalc::_knownCameraNameChanged); + + connect(this, &CameraCalc::cameraSpecTypeChanged, this, &CameraCalc::_recalcTriggerDistance); + + connect(&_distanceToSurfaceFact, &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance); + connect(&_imageDensityFact, &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance); + connect(&_frontalOverlapFact, &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance); + connect(&_sideOverlapFact, &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance); + connect(landscape(), &Fact::rawValueChanged, this, &CameraCalc::_recalcTriggerDistance); + + _recalcTriggerDistance(); +} + +void CameraCalc::setDirty(bool dirty) +{ + if (_dirty != dirty) { + _dirty = dirty; + emit dirtyChanged(_dirty); + } +} + +void CameraCalc::_knownCameraNameChanged(QString knownCameraName) +{ + if (_cameraSpecType == CameraSpecKnown) { + CameraMetaData* cameraMetaData = NULL; + + // Update camera specs to new camera + for (int cameraIndex=0; cameraIndex<_knownCameraList.count(); cameraIndex++) { + cameraMetaData = _knownCameraList[cameraIndex].value(); + if (knownCameraName == cameraMetaData->name()) { + break; + } + } + + _disableRecalc = true; + if (cameraMetaData) { + sensorWidth()->setRawValue (cameraMetaData->sensorWidth()); + sensorHeight()->setRawValue (cameraMetaData->sensorHeight()); + imageWidth()->setRawValue (cameraMetaData->imageWidth()); + imageHeight()->setRawValue (cameraMetaData->imageHeight()); + focalLength()->setRawValue (cameraMetaData->focalLength()); + landscape()->setRawValue (cameraMetaData->landscape()); + fixedOrientation()->setRawValue (cameraMetaData->fixedOrientation()); + minTriggerInterval()->setRawValue (cameraMetaData->minTriggerInterval()); + } else { + // We don't know this camera, switch back to custom + _cameraSpecType = CameraSpecCustom; + emit cameraSpecTypeChanged(_cameraSpecType); + } + _disableRecalc = false; + + _recalcTriggerDistance(); + } +} + +void CameraCalc::_recalcTriggerDistance(void) +{ + if (_disableRecalc || _cameraSpecType == CameraSpecNone) { + return; + } + + _disableRecalc = true; + + double focalLength = this->focalLength()->rawValue().toDouble(); + double sensorWidth = this->sensorWidth()->rawValue().toDouble(); + double sensorHeight = this->sensorHeight()->rawValue().toDouble(); + double imageWidth = this->imageWidth()->rawValue().toDouble(); + double imageHeight = this->imageHeight()->rawValue().toDouble(); + double imageDensity = _imageDensityFact.rawValue().toDouble(); + + if (focalLength <= 0 || sensorWidth <= 0 || sensorHeight <= 0 || imageWidth <= 0 || imageHeight <= 0 || imageDensity <= 0) { + return; + } + + if (_valueSetIsDistanceFact.rawValue().toBool()) { + _imageDensityFact.setRawValue((_distanceToSurfaceFact.rawValue().toDouble() * sensorWidth * 100.0) / (imageWidth * focalLength)); + } else { + _distanceToSurfaceFact.setRawValue((imageWidth * _imageDensityFact.rawValue().toDouble() * focalLength) / (sensorWidth * 100.0)); + } + + imageDensity = _imageDensityFact.rawValue().toDouble(); + + if (landscape()->rawValue().toBool()) { + _imageFootprintSide = (imageWidth * imageDensity) / 100.0; + _imageFootprintFrontal = (imageHeight * imageDensity) / 100.0; + } else { + _imageFootprintSide = (imageHeight * imageDensity) / 100.0; + _imageFootprintFrontal = (imageWidth * imageDensity) / 100.0; + } + _adjustedFootprintSideFact.setRawValue (_imageFootprintSide * ((100.0 - _sideOverlapFact.rawValue().toDouble()) / 100.0)); + _adjustedFootprintFrontalFact.setRawValue (_imageFootprintFrontal * ((100.0 - _frontalOverlapFact.rawValue().toDouble()) / 100.0)); + + emit imageFootprintSideChanged (_imageFootprintSide); + emit imageFootprintFrontalChanged (_imageFootprintFrontal); + + _disableRecalc = false; +} + +void CameraCalc::save(QJsonObject& json) const +{ +#if 0 + QJsonObject saveObject; + + saveObject[JsonHelper::jsonVersionKey] = 3; + saveObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + saveObject[_jsonManualGridKey] = _manualGridFact.rawValue().toBool(); + saveObject[_jsonFixedValueIsAltitudeKey] = _fixedValueIsAltitudeFact.rawValue().toBool(); + saveObject[_jsonHoverAndCaptureKey] = _hoverAndCaptureFact.rawValue().toBool(); + saveObject[_jsonRefly90DegreesKey] = _refly90Degrees; + saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble(); + saveObject[_jsonCameraTriggerInTurnaroundKey] = _cameraTriggerInTurnaroundFact.rawValue().toBool(); + + QJsonObject gridObject; + gridObject[_jsonGridAltitudeKey] = _gridAltitudeFact.rawValue().toDouble(); + gridObject[_jsonGridAltitudeRelativeKey] = _gridAltitudeRelativeFact.rawValue().toBool(); + gridObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); + gridObject[_jsonGridSpacingKey] = _gridSpacingFact.rawValue().toDouble(); + gridObject[_jsonGridEntryLocationKey] = _gridEntryLocationFact.rawValue().toDouble(); + gridObject[_jsonTurnaroundDistKey] = _turnaroundDistFact.rawValue().toDouble(); + + saveObject[_jsonGridObjectKey] = gridObject; + + if (!_manualGridFact.rawValue().toBool()) { + QJsonObject cameraObject; + cameraObject[_jsonCameraNameKey] = _cameraFact.rawValue().toString(); + cameraObject[_jsonCameraOrientationLandscapeKey] = _cameraOrientationLandscapeFact.rawValue().toBool(); + cameraObject[_jsonCameraSensorWidthKey] = _cameraSensorWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraSensorHeightKey] = _cameraSensorHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionWidthKey] = _cameraResolutionWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionHeightKey] = _cameraResolutionHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraFocalLengthKey] = _cameraFocalLengthFact.rawValue().toDouble(); + cameraObject[_jsonCameraMinTriggerIntervalKey] = _cameraMinTriggerInterval; + cameraObject[_jsonGroundResolutionKey] = _groundResolutionFact.rawValue().toDouble(); + cameraObject[_jsonFrontalOverlapKey] = _frontalOverlapFact.rawValue().toInt(); + cameraObject[_jsonSideOverlapKey] = _sideOverlapFact.rawValue().toInt(); + + saveObject[_jsonCameraObjectKey] = cameraObject; + } + + // Polygon shape + _mapPolygon.saveToJson(saveObject); + + missionItems.append(saveObject); +#endif +} + +bool CameraCalc::load(const QJsonObject& complexObject, QString& errorString) +{ +#if 0 + QJsonObject v2Object = complexObject; + + // We need to pull version first to determine what validation/conversion needs to be performed. + QList versionKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(v2Object, versionKeyInfoList, errorString)) { + return false; + } + + int version = v2Object[JsonHelper::jsonVersionKey].toInt(); + if (version != 2 && version != 3) { + errorString = tr("%1 does not support this version of survey items").arg(qgcApp()->applicationName()); + return false; + } + if (version == 2) { + // Convert to v3 + if (v2Object.contains(VisualMissionItem::jsonTypeKey) && v2Object[VisualMissionItem::jsonTypeKey].toString() == QStringLiteral("survey")) { + v2Object[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + v2Object[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + } + } + + QList mainKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { QGCMapPolygon::jsonPolygonKey, QJsonValue::Array, true }, + { _jsonGridObjectKey, QJsonValue::Object, true }, + { _jsonCameraObjectKey, QJsonValue::Object, false }, + { _jsonCameraTriggerDistanceKey, QJsonValue::Double, true }, + { _jsonManualGridKey, QJsonValue::Bool, true }, + { _jsonFixedValueIsAltitudeKey, QJsonValue::Bool, true }, + { _jsonHoverAndCaptureKey, QJsonValue::Bool, false }, + { _jsonRefly90DegreesKey, QJsonValue::Bool, false }, + { _jsonCameraTriggerInTurnaroundKey, QJsonValue::Bool, false }, // Should really be required, but it was missing from initial code due to bug + }; + if (!JsonHelper::validateKeys(v2Object, mainKeyInfoList, errorString)) { + return false; + } + + QString itemType = v2Object[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = v2Object[ComplexMissionItem::jsonComplexItemTypeKey].toString(); + if (itemType != VisualMissionItem::jsonTypeComplexItemValue || complexType != jsonComplexItemTypeValue) { + errorString = tr("%1 does not support loading this complex mission item type: %2:%3").arg(qgcApp()->applicationName()).arg(itemType).arg(complexType); + return false; + } + + _ignoreRecalc = true; + + _mapPolygon.clear(); + + setSequenceNumber(sequenceNumber); + + _manualGridFact.setRawValue (v2Object[_jsonManualGridKey].toBool(true)); + _fixedValueIsAltitudeFact.setRawValue (v2Object[_jsonFixedValueIsAltitudeKey].toBool(true)); + _gridAltitudeRelativeFact.setRawValue (v2Object[_jsonGridAltitudeRelativeKey].toBool(true)); + _hoverAndCaptureFact.setRawValue (v2Object[_jsonHoverAndCaptureKey].toBool(false)); + _cameraTriggerInTurnaroundFact.setRawValue (v2Object[_jsonCameraTriggerInTurnaroundKey].toBool(true)); + + _refly90Degrees = v2Object[_jsonRefly90DegreesKey].toBool(false); + + QList gridKeyInfoList = { + { _jsonGridAltitudeKey, QJsonValue::Double, true }, + { _jsonGridAltitudeRelativeKey, QJsonValue::Bool, true }, + { _jsonGridAngleKey, QJsonValue::Double, true }, + { _jsonGridSpacingKey, QJsonValue::Double, true }, + { _jsonGridEntryLocationKey, QJsonValue::Double, false }, + { _jsonTurnaroundDistKey, QJsonValue::Double, true }, + }; + QJsonObject gridObject = v2Object[_jsonGridObjectKey].toObject(); + if (!JsonHelper::validateKeys(gridObject, gridKeyInfoList, errorString)) { + return false; + } + _gridAltitudeFact.setRawValue (gridObject[_jsonGridAltitudeKey].toDouble()); + _gridAngleFact.setRawValue (gridObject[_jsonGridAngleKey].toDouble()); + _gridSpacingFact.setRawValue (gridObject[_jsonGridSpacingKey].toDouble()); + _turnaroundDistFact.setRawValue (gridObject[_jsonTurnaroundDistKey].toDouble()); + _cameraTriggerDistanceFact.setRawValue (v2Object[_jsonCameraTriggerDistanceKey].toDouble()); + if (gridObject.contains(_jsonGridEntryLocationKey)) { + _gridEntryLocationFact.setRawValue(gridObject[_jsonGridEntryLocationKey].toDouble()); + } else { + _gridEntryLocationFact.setRawValue(_gridEntryLocationFact.rawDefaultValue()); + } + + if (!_manualGridFact.rawValue().toBool()) { + if (!v2Object.contains(_jsonCameraObjectKey)) { + errorString = tr("%1 but %2 object is missing").arg("manualGrid = false").arg("camera"); + return false; + } + + QJsonObject cameraObject = v2Object[_jsonCameraObjectKey].toObject(); + + // Older code had typo on "imageSideOverlap" incorrectly being "imageSizeOverlap" + QString incorrectImageSideOverlap = "imageSizeOverlap"; + if (cameraObject.contains(incorrectImageSideOverlap)) { + cameraObject[_jsonSideOverlapKey] = cameraObject[incorrectImageSideOverlap]; + cameraObject.remove(incorrectImageSideOverlap); + } + + QList cameraKeyInfoList = { + { _jsonGroundResolutionKey, QJsonValue::Double, true }, + { _jsonFrontalOverlapKey, QJsonValue::Double, true }, + { _jsonSideOverlapKey, QJsonValue::Double, true }, + { _jsonCameraSensorWidthKey, QJsonValue::Double, true }, + { _jsonCameraSensorHeightKey, QJsonValue::Double, true }, + { _jsonCameraResolutionWidthKey, QJsonValue::Double, true }, + { _jsonCameraResolutionHeightKey, QJsonValue::Double, true }, + { _jsonCameraFocalLengthKey, QJsonValue::Double, true }, + { _jsonCameraNameKey, QJsonValue::String, true }, + { _jsonCameraOrientationLandscapeKey, QJsonValue::Bool, true }, + { _jsonCameraMinTriggerIntervalKey, QJsonValue::Double, false }, + }; + if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, errorString)) { + return false; + } + + _cameraFact.setRawValue(cameraObject[_jsonCameraNameKey].toString()); + _cameraOrientationLandscapeFact.setRawValue(cameraObject[_jsonCameraOrientationLandscapeKey].toBool(true)); + + _groundResolutionFact.setRawValue (cameraObject[_jsonGroundResolutionKey].toDouble()); + _frontalOverlapFact.setRawValue (cameraObject[_jsonFrontalOverlapKey].toInt()); + _sideOverlapFact.setRawValue (cameraObject[_jsonSideOverlapKey].toInt()); + _cameraSensorWidthFact.setRawValue (cameraObject[_jsonCameraSensorWidthKey].toDouble()); + _cameraSensorHeightFact.setRawValue (cameraObject[_jsonCameraSensorHeightKey].toDouble()); + _cameraResolutionWidthFact.setRawValue (cameraObject[_jsonCameraResolutionWidthKey].toDouble()); + _cameraResolutionHeightFact.setRawValue (cameraObject[_jsonCameraResolutionHeightKey].toDouble()); + _cameraFocalLengthFact.setRawValue (cameraObject[_jsonCameraFocalLengthKey].toDouble()); + _cameraMinTriggerInterval = cameraObject[_jsonCameraMinTriggerIntervalKey].toDouble(0); + } + + // Polygon shape + /// Load a polygon from json + /// @param json Json object to load from + /// @param required true: no polygon in object will generate error + /// @param errorString Error string if return is false + /// @return true: success, false: failure (errorString set) + if (!_mapPolygon.loadFromJson(v2Object, true /* required */, errorString)) { + _mapPolygon.clear(); + return false; + } + + _ignoreRecalc = false; + _generateGrid(); + + return true; +#endif + return false; +} diff --git a/src/MissionManager/CameraCalc.h b/src/MissionManager/CameraCalc.h new file mode 100644 index 0000000000000000000000000000000000000000..32864432058528c030cf86ba0fc5f4640a1caad6 --- /dev/null +++ b/src/MissionManager/CameraCalc.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "CameraSpec.h" + +class Vehicle; + +class CameraCalc : public CameraSpec +{ + Q_OBJECT + +public: + CameraCalc(Vehicle* vehicle, QObject* parent = NULL); + + Q_ENUMS(CameraSpecType) + + Q_PROPERTY(CameraSpecType cameraSpecType MEMBER _cameraSpecType NOTIFY cameraSpecTypeChanged) + Q_PROPERTY(QString knownCameraName MEMBER _knownCameraName NOTIFY knownCameraNameChanged) + Q_PROPERTY(Fact* valueSetIsDistance READ valueSetIsDistance CONSTANT) ///< true: distance specified, resolution calculated + Q_PROPERTY(Fact* distanceToSurface READ distanceToSurface CONSTANT) ///< Distance to surface for image foot print calculation + Q_PROPERTY(Fact* imageDensity READ imageDensity CONSTANT) ///< Image density on surface (cm/px) + Q_PROPERTY(Fact* frontalOverlap READ frontalOverlap CONSTANT) + Q_PROPERTY(Fact* sideOverlap READ sideOverlap CONSTANT) + Q_PROPERTY(Fact* adjustedFootprintSide READ adjustedFootprintSide CONSTANT) ///< Side footprint adjusted down for overlap + Q_PROPERTY(Fact* adjustedFootprintFrontal READ adjustedFootprintFrontal CONSTANT) ///< Frontal footprint adjusted down for overlap + + // The following values are calculated from the camera properties + Q_PROPERTY(double imageFootprintSide READ imageFootprintSide NOTIFY imageFootprintSideChanged) ///< Size of image size side in meters + Q_PROPERTY(double imageFootprintFrontal READ imageFootprintFrontal NOTIFY imageFootprintFrontalChanged) ///< Size of image size frontal in meters + + enum CameraSpecType { + CameraSpecNone, + CameraSpecCustom, + CameraSpecKnown + }; + + Fact* valueSetIsDistance (void) { return &_valueSetIsDistanceFact; } + Fact* distanceToSurface (void) { return &_distanceToSurfaceFact; } + Fact* imageDensity (void) { return &_imageDensityFact; } + Fact* frontalOverlap (void) { return &_frontalOverlapFact; } + Fact* sideOverlap (void) { return &_sideOverlapFact; } + Fact* adjustedFootprintSide (void) { return &_adjustedFootprintSideFact; } + Fact* adjustedFootprintFrontal (void) { return &_adjustedFootprintFrontalFact; } + + double imageFootprintSide (void) const { return _imageFootprintSide; } + double imageFootprintFrontal (void) const { return _imageFootprintFrontal; } + + bool dirty (void) const { return _dirty; } + void setDirty (bool dirty); + + void save(QJsonObject& json) const; + bool load(const QJsonObject& json, QString& errorString); + +signals: + void cameraSpecTypeChanged (CameraSpecType cameraSpecType); + void knownCameraNameChanged (QString knownCameraName); + void dirtyChanged (bool dirty); + void imageFootprintSideChanged (double imageFootprintSide); + void imageFootprintFrontalChanged (double imageFootprintFrontal); + +private slots: + void _knownCameraNameChanged(QString knownCameraName); + void _recalcTriggerDistance(void); + +private: + Vehicle* _vehicle; + bool _dirty; + CameraSpecType _cameraSpecType; + QString _knownCameraName; + bool _disableRecalc; + + QMap _metaDataMap; + + Fact _valueSetIsDistanceFact; + Fact _distanceToSurfaceFact; + Fact _imageDensityFact; + Fact _frontalOverlapFact; + Fact _sideOverlapFact; + Fact _adjustedFootprintSideFact; + Fact _adjustedFootprintFrontalFact; + + double _imageFootprintSide; + double _imageFootprintFrontal; + + QVariantList _knownCameraList; + + static const char* _valueSetIsDistanceName; + static const char* _distanceToSurfaceName; + static const char* _imageDensityName; + static const char* _frontalOverlapName; + static const char* _sideOverlapName; + static const char* _adjustedFootprintSideName; + static const char* _adjustedFootprintFrontalName; +}; diff --git a/src/MissionManager/CameraSpec.FactMetaData.json b/src/MissionManager/CameraSpec.FactMetaData.json new file mode 100644 index 0000000000000000000000000000000000000000..f48ce287f4045180cd6dda3ea610e5a82cdc374a --- /dev/null +++ b/src/MissionManager/CameraSpec.FactMetaData.json @@ -0,0 +1,71 @@ +[ +{ + "name": "Name", + "shortDescription": "Camera name.", + "type": "string", + "defaultValue": "" +}, +{ + "name": "SensorWidth", + "shortDescription": "Width of camera image sensor.", + "type": "double", + "decimalPlaces": 2, + "min": 1, + "units": "mm", + "defaultValue": 6.17 +}, +{ + "name": "SensorHeight", + "shortDescription": "Height of camera image sensor.", + "type": "double", + "decimalPlaces": 2, + "min": 1, + "units": "mm", + "defaultValue": 4.55 +}, +{ + "name": "ImageWidth", + "shortDescription": "Camera image resolution width.", + "type": "uint32", + "min": 1, + "units": "px", + "defaultValue": 4000 +}, +{ + "name": "ImageHeight", + "shortDescription": "Camera image resolution height.", + "type": "uint32", + "min": 1, + "units": "px", + "defaultValue": 3000 +}, +{ + "name": "FocalLength", + "shortDescription": "Focal length of camera lens.", + "type": "double", + "decimalPlaces": 1, + "min": 1, + "units": "mm", + "defaultValue": 4.5 +}, +{ + "name": "Landscape", + "shortDescription": "Camera on vehicle is in landscape orientation.", + "type": "bool", + "defaultValue": 1 +}, +{ + "name": "FixedOrientation", + "shortDescription": "Camera orientation ix fixed and cannot be changed.", + "type": "bool", + "defaultValue": 0 +}, +{ + "name": "MinTriggerInterval", + "shortDescription": "Minimum amount of time between each camera trigger.", + "type": "double", + "min": 0.1, + "units": "secs", + "defaultValue": 1.0 +} +] diff --git a/src/MissionManager/CameraSpec.cc b/src/MissionManager/CameraSpec.cc new file mode 100644 index 0000000000000000000000000000000000000000..f14bde01c9aea2ec25c2b6c148157c1d544ea6a5 --- /dev/null +++ b/src/MissionManager/CameraSpec.cc @@ -0,0 +1,394 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "CameraSpec.h" +#include "JsonHelper.h" + +#include + +const char* CameraSpec::_jsonSensorWidthKey = "sensorWidth"; +const char* CameraSpec::_jsonSensorHeightKey = "sensorHeight"; +const char* CameraSpec::_jsonImageWidthKey = "imageWidth"; +const char* CameraSpec::_jsonImageHeightKey = "imageHeight"; +const char* CameraSpec::_jsonFocalLengthKey = "focalLength"; +const char* CameraSpec::_jsonMinTriggerIntervalKey = "minTriggerInterval"; +const char* CameraSpec::_jsonNameKey = "name"; +const char* CameraSpec::_jsonLandscapeKey = "orientationLandscape"; + +const char* CameraSpec::_nameName = "Camera"; +const char* CameraSpec::_sensorWidthName = "SensorWidth"; +const char* CameraSpec::_sensorHeightName = "SensorHeight"; +const char* CameraSpec::_imageWidthName = "ImageWidth"; +const char* CameraSpec::_imageHeightName = "ImageHeight"; +const char* CameraSpec::_focalLengthName = "FocalLength"; +const char* CameraSpec::_landscapeName = "Landscape"; +const char* CameraSpec::_fixedOrientationName = "FixedOrientation"; +const char* CameraSpec::_minTriggerIntervalName = "MinTriggerInterval"; + +CameraSpec::CameraSpec(QObject* parent) + : QObject (parent) + , _dirty (false) + , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CameraSpec.FactMetaData.json"), this)) + , _nameFact (0, _nameName, FactMetaData::valueTypeString) + , _sensorWidthFact (0, _sensorWidthName, FactMetaData::valueTypeDouble) + , _sensorHeightFact (0, _sensorHeightName, FactMetaData::valueTypeDouble) + , _imageWidthFact (0, _imageWidthName, FactMetaData::valueTypeUint32) + , _imageHeightFact (0, _imageHeightName, FactMetaData::valueTypeUint32) + , _focalLengthFact (0, _focalLengthName, FactMetaData::valueTypeDouble) + , _landscapeFact (0, _landscapeName, FactMetaData::valueTypeBool) + , _fixedOrientationFact (0, _fixedOrientationName, FactMetaData::valueTypeBool) + , _minTriggerIntervalFact (0, _minTriggerIntervalName, FactMetaData::valueTypeDouble) +{ + _init(true); +} + +CameraSpec::CameraSpec(const QString& name, + double sensorWidth, + double sensorHeight, + double imageWidth, + double imageHeight, + double focalLength, + bool landscape, + bool fixedOrientation, + double minTriggerInterval, + QObject* parent) + : QObject (parent) + , _dirty (false) + , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CameraSpec.FactMetaData.json"), this)) + , _nameFact (0, _nameName, FactMetaData::valueTypeString) + , _sensorWidthFact (0, _sensorWidthName, FactMetaData::valueTypeDouble) + , _sensorHeightFact (0, _sensorHeightName, FactMetaData::valueTypeDouble) + , _imageWidthFact (0, _imageWidthName, FactMetaData::valueTypeUint32) + , _imageHeightFact (0, _imageHeightName, FactMetaData::valueTypeUint32) + , _focalLengthFact (0, _focalLengthName, FactMetaData::valueTypeDouble) + , _landscapeFact (0, _landscapeName, FactMetaData::valueTypeBool) + , _fixedOrientationFact (0, _fixedOrientationName, FactMetaData::valueTypeBool) + , _minTriggerIntervalFact (0, _minTriggerIntervalName, FactMetaData::valueTypeDouble) +{ + _init(false); + + _nameFact.setRawValue (name); + _sensorWidthFact.setRawValue (sensorWidth); + _sensorHeightFact.setRawValue (sensorHeight); + _imageWidthFact.setRawValue (imageWidth); + _imageHeightFact.setRawValue (imageHeight); + _focalLengthFact.setRawValue (focalLength); + _landscapeFact.setRawValue (landscape); + _fixedOrientationFact.setRawValue (fixedOrientation); + _minTriggerIntervalFact.setRawValue (minTriggerInterval); +} + +CameraSpec::CameraSpec(const CameraSpec& other, QObject* parent) + : QObject (parent) + , _dirty (false) + , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CameraSpec.FactMetaData.json"), this)) + , _nameFact (0, _nameName, FactMetaData::valueTypeString) + , _sensorWidthFact (0, _sensorWidthName, FactMetaData::valueTypeDouble) + , _sensorHeightFact (0, _sensorHeightName, FactMetaData::valueTypeDouble) + , _imageWidthFact (0, _imageWidthName, FactMetaData::valueTypeUint32) + , _imageHeightFact (0, _imageHeightName, FactMetaData::valueTypeUint32) + , _focalLengthFact (0, _focalLengthName, FactMetaData::valueTypeDouble) + , _landscapeFact (0, _landscapeName, FactMetaData::valueTypeBool) + , _fixedOrientationFact (0, _fixedOrientationName, FactMetaData::valueTypeBool) + , _minTriggerIntervalFact (0, _minTriggerIntervalName, FactMetaData::valueTypeDouble) +{ + _init(false); + + *this = other; +} + +const CameraSpec& CameraSpec::operator=(const CameraSpec& other) +{ + _nameFact.setRawValue (other._nameFact.rawValue()); + _sensorWidthFact.setRawValue (other._sensorWidthFact.rawValue()); + _sensorHeightFact.setRawValue (other._sensorHeightFact.rawValue()); + _imageWidthFact.setRawValue (other._imageWidthFact.rawValue()); + _imageHeightFact.setRawValue (other._imageHeightFact.rawValue()); + _focalLengthFact.setRawValue (other._focalLengthFact.rawValue()); + _landscapeFact.setRawValue (other._landscapeFact.rawValue()); + _fixedOrientationFact.setRawValue (other._fixedOrientationFact.rawValue()); + _minTriggerIntervalFact.setRawValue (other._minTriggerIntervalFact.rawValue()); + + return *this; +} + +void CameraSpec::_init(bool setDefaults) +{ + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + + _nameFact.setMetaData (_metaDataMap[_nameName], setDefaults /* setDefaultFromMetaData */); + _sensorWidthFact.setMetaData (_metaDataMap[_sensorWidthName], setDefaults); + _sensorHeightFact.setMetaData (_metaDataMap[_sensorHeightName], setDefaults); + _imageWidthFact.setMetaData (_metaDataMap[_imageWidthName], setDefaults); + _imageHeightFact.setMetaData (_metaDataMap[_imageHeightName], setDefaults); + _focalLengthFact.setMetaData (_metaDataMap[_focalLengthName], setDefaults); + _landscapeFact.setMetaData (_metaDataMap[_landscapeName], setDefaults); + _fixedOrientationFact.setMetaData (_metaDataMap[_fixedOrientationName], setDefaults); + _minTriggerIntervalFact.setMetaData (_metaDataMap[_minTriggerIntervalName], setDefaults); +} + +void CameraSpec::setDirty(bool dirty) +{ + if (_dirty != dirty) { + _dirty = dirty; + emit dirtyChanged(_dirty); + } +} + +void CameraSpec::save(QJsonObject& json) const +{ +#if 0 + QJsonObject saveObject; + + saveObject[JsonHelper::jsonVersionKey] = 3; + saveObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + saveObject[_jsonManualGridKey] = _manualGridFact.rawValue().toBool(); + saveObject[_jsonFixedValueIsAltitudeKey] = _fixedValueIsAltitudeFact.rawValue().toBool(); + saveObject[_jsonHoverAndCaptureKey] = _hoverAndCaptureFact.rawValue().toBool(); + saveObject[_jsonRefly90DegreesKey] = _refly90Degrees; + saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble(); + saveObject[_jsonCameraTriggerInTurnaroundKey] = _cameraTriggerInTurnaroundFact.rawValue().toBool(); + + QJsonObject gridObject; + gridObject[_jsonGridAltitudeKey] = _gridAltitudeFact.rawValue().toDouble(); + gridObject[_jsonGridAltitudeRelativeKey] = _gridAltitudeRelativeFact.rawValue().toBool(); + gridObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); + gridObject[_jsonGridSpacingKey] = _gridSpacingFact.rawValue().toDouble(); + gridObject[_jsonGridEntryLocationKey] = _gridEntryLocationFact.rawValue().toDouble(); + gridObject[_jsonTurnaroundDistKey] = _turnaroundDistFact.rawValue().toDouble(); + + saveObject[_jsonGridObjectKey] = gridObject; + + if (!_manualGridFact.rawValue().toBool()) { + QJsonObject cameraObject; + cameraObject[_jsonCameraNameKey] = _cameraFact.rawValue().toString(); + cameraObject[_jsonCameraOrientationLandscapeKey] = _cameraOrientationLandscapeFact.rawValue().toBool(); + cameraObject[_jsonCameraSensorWidthKey] = _cameraSensorWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraSensorHeightKey] = _cameraSensorHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionWidthKey] = _cameraResolutionWidthFact.rawValue().toDouble(); + cameraObject[_jsonCameraResolutionHeightKey] = _cameraResolutionHeightFact.rawValue().toDouble(); + cameraObject[_jsonCameraFocalLengthKey] = _cameraFocalLengthFact.rawValue().toDouble(); + cameraObject[_jsonCameraMinTriggerIntervalKey] = _cameraMinTriggerInterval; + cameraObject[_jsonGroundResolutionKey] = _groundResolutionFact.rawValue().toDouble(); + cameraObject[_jsonFrontalOverlapKey] = _frontalOverlapFact.rawValue().toInt(); + cameraObject[_jsonSideOverlapKey] = _sideOverlapFact.rawValue().toInt(); + + saveObject[_jsonCameraObjectKey] = cameraObject; + } + + // Polygon shape + _mapPolygon.saveToJson(saveObject); + + missionItems.append(saveObject); +#endif +} + +bool CameraSpec::load(const QJsonObject& complexObject, QString& errorString) +{ +#if 0 + QJsonObject v2Object = complexObject; + + // We need to pull version first to determine what validation/conversion needs to be performed. + QList versionKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(v2Object, versionKeyInfoList, errorString)) { + return false; + } + + int version = v2Object[JsonHelper::jsonVersionKey].toInt(); + if (version != 2 && version != 3) { + errorString = tr("%1 does not support this version of survey items").arg(qgcApp()->applicationName()); + return false; + } + if (version == 2) { + // Convert to v3 + if (v2Object.contains(VisualMissionItem::jsonTypeKey) && v2Object[VisualMissionItem::jsonTypeKey].toString() == QStringLiteral("survey")) { + v2Object[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + v2Object[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + } + } + + QList mainKeyInfoList = { + { JsonHelper::jsonVersionKey, QJsonValue::Double, true }, + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { QGCMapPolygon::jsonPolygonKey, QJsonValue::Array, true }, + { _jsonGridObjectKey, QJsonValue::Object, true }, + { _jsonCameraObjectKey, QJsonValue::Object, false }, + { _jsonCameraTriggerDistanceKey, QJsonValue::Double, true }, + { _jsonManualGridKey, QJsonValue::Bool, true }, + { _jsonFixedValueIsAltitudeKey, QJsonValue::Bool, true }, + { _jsonHoverAndCaptureKey, QJsonValue::Bool, false }, + { _jsonRefly90DegreesKey, QJsonValue::Bool, false }, + { _jsonCameraTriggerInTurnaroundKey, QJsonValue::Bool, false }, // Should really be required, but it was missing from initial code due to bug + }; + if (!JsonHelper::validateKeys(v2Object, mainKeyInfoList, errorString)) { + return false; + } + + QString itemType = v2Object[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = v2Object[ComplexMissionItem::jsonComplexItemTypeKey].toString(); + if (itemType != VisualMissionItem::jsonTypeComplexItemValue || complexType != jsonComplexItemTypeValue) { + errorString = tr("%1 does not support loading this complex mission item type: %2:%3").arg(qgcApp()->applicationName()).arg(itemType).arg(complexType); + return false; + } + + _ignoreRecalc = true; + + _mapPolygon.clear(); + + setSequenceNumber(sequenceNumber); + + _manualGridFact.setRawValue (v2Object[_jsonManualGridKey].toBool(true)); + _fixedValueIsAltitudeFact.setRawValue (v2Object[_jsonFixedValueIsAltitudeKey].toBool(true)); + _gridAltitudeRelativeFact.setRawValue (v2Object[_jsonGridAltitudeRelativeKey].toBool(true)); + _hoverAndCaptureFact.setRawValue (v2Object[_jsonHoverAndCaptureKey].toBool(false)); + _cameraTriggerInTurnaroundFact.setRawValue (v2Object[_jsonCameraTriggerInTurnaroundKey].toBool(true)); + + _refly90Degrees = v2Object[_jsonRefly90DegreesKey].toBool(false); + + QList gridKeyInfoList = { + { _jsonGridAltitudeKey, QJsonValue::Double, true }, + { _jsonGridAltitudeRelativeKey, QJsonValue::Bool, true }, + { _jsonGridAngleKey, QJsonValue::Double, true }, + { _jsonGridSpacingKey, QJsonValue::Double, true }, + { _jsonGridEntryLocationKey, QJsonValue::Double, false }, + { _jsonTurnaroundDistKey, QJsonValue::Double, true }, + }; + QJsonObject gridObject = v2Object[_jsonGridObjectKey].toObject(); + if (!JsonHelper::validateKeys(gridObject, gridKeyInfoList, errorString)) { + return false; + } + _gridAltitudeFact.setRawValue (gridObject[_jsonGridAltitudeKey].toDouble()); + _gridAngleFact.setRawValue (gridObject[_jsonGridAngleKey].toDouble()); + _gridSpacingFact.setRawValue (gridObject[_jsonGridSpacingKey].toDouble()); + _turnaroundDistFact.setRawValue (gridObject[_jsonTurnaroundDistKey].toDouble()); + _cameraTriggerDistanceFact.setRawValue (v2Object[_jsonCameraTriggerDistanceKey].toDouble()); + if (gridObject.contains(_jsonGridEntryLocationKey)) { + _gridEntryLocationFact.setRawValue(gridObject[_jsonGridEntryLocationKey].toDouble()); + } else { + _gridEntryLocationFact.setRawValue(_gridEntryLocationFact.rawDefaultValue()); + } + + if (!_manualGridFact.rawValue().toBool()) { + if (!v2Object.contains(_jsonCameraObjectKey)) { + errorString = tr("%1 but %2 object is missing").arg("manualGrid = false").arg("camera"); + return false; + } + + QJsonObject cameraObject = v2Object[_jsonCameraObjectKey].toObject(); + + // Older code had typo on "imageSideOverlap" incorrectly being "imageSizeOverlap" + QString incorrectImageSideOverlap = "imageSizeOverlap"; + if (cameraObject.contains(incorrectImageSideOverlap)) { + cameraObject[_jsonSideOverlapKey] = cameraObject[incorrectImageSideOverlap]; + cameraObject.remove(incorrectImageSideOverlap); + } + + QList cameraKeyInfoList = { + { _jsonGroundResolutionKey, QJsonValue::Double, true }, + { _jsonFrontalOverlapKey, QJsonValue::Double, true }, + { _jsonSideOverlapKey, QJsonValue::Double, true }, + { _jsonCameraSensorWidthKey, QJsonValue::Double, true }, + { _jsonCameraSensorHeightKey, QJsonValue::Double, true }, + { _jsonCameraResolutionWidthKey, QJsonValue::Double, true }, + { _jsonCameraResolutionHeightKey, QJsonValue::Double, true }, + { _jsonCameraFocalLengthKey, QJsonValue::Double, true }, + { _jsonCameraNameKey, QJsonValue::String, true }, + { _jsonCameraOrientationLandscapeKey, QJsonValue::Bool, true }, + { _jsonCameraMinTriggerIntervalKey, QJsonValue::Double, false }, + }; + if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, errorString)) { + return false; + } + + _cameraFact.setRawValue(cameraObject[_jsonCameraNameKey].toString()); + _cameraOrientationLandscapeFact.setRawValue(cameraObject[_jsonCameraOrientationLandscapeKey].toBool(true)); + + _groundResolutionFact.setRawValue (cameraObject[_jsonGroundResolutionKey].toDouble()); + _frontalOverlapFact.setRawValue (cameraObject[_jsonFrontalOverlapKey].toInt()); + _sideOverlapFact.setRawValue (cameraObject[_jsonSideOverlapKey].toInt()); + _cameraSensorWidthFact.setRawValue (cameraObject[_jsonCameraSensorWidthKey].toDouble()); + _cameraSensorHeightFact.setRawValue (cameraObject[_jsonCameraSensorHeightKey].toDouble()); + _cameraResolutionWidthFact.setRawValue (cameraObject[_jsonCameraResolutionWidthKey].toDouble()); + _cameraResolutionHeightFact.setRawValue (cameraObject[_jsonCameraResolutionHeightKey].toDouble()); + _cameraFocalLengthFact.setRawValue (cameraObject[_jsonCameraFocalLengthKey].toDouble()); + _cameraMinTriggerInterval = cameraObject[_jsonCameraMinTriggerIntervalKey].toDouble(0); + } + + // Polygon shape + /// Load a polygon from json + /// @param json Json object to load from + /// @param required true: no polygon in object will generate error + /// @param errorString Error string if return is false + /// @return true: success, false: failure (errorString set) + if (!_mapPolygon.loadFromJson(v2Object, true /* required */, errorString)) { + _mapPolygon.clear(); + return false; + } + + _ignoreRecalc = false; + _generateGrid(); + + return true; +#endif + return false; +} + +#if 0 +void CameraSpec::recalcImageOnGround(bool valueIsAltitude, double value) +{ + 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 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 + + if (missionItem.fixedValueIsAltitude.value) { + groundResolution = (altitude * sensorWidth * 100) / (imageWidth * focalLength) + } else { + altitude = (imageWidth * groundResolution * focalLength) / (sensorWidth * 100) + } + + if (missionItem.cameraOrientationLandscape.value) { + imageSizeSideGround = (imageWidth * groundResolution) / 100 + imageSizeFrontGround = (imageHeight * groundResolution) / 100 + } else { + imageSizeSideGround = (imageHeight * groundResolution) / 100 + imageSizeFrontGround = (imageWidth * groundResolution) / 100 + } + + gridSpacing = imageSizeSideGround * ( (100-sideOverlap) / 100 ) + cameraTriggerDistance = imageSizeFrontGround * ( (100-frontalOverlap) / 100 ) + + if (missionItem.fixedValueIsAltitude.value) { + missionItem.groundResolution.rawValue = groundResolution + } else { + missionItem.gridAltitude.rawValue = altitude + } + missionItem.gridSpacing.rawValue = gridSpacing + missionItem.cameraTriggerDistance.rawValue = cameraTriggerDistance +} +#endif diff --git a/src/MissionManager/CameraSpec.h b/src/MissionManager/CameraSpec.h new file mode 100644 index 0000000000000000000000000000000000000000..9b1f87aa59c4e6419eed8dae64369dc49e31c4ef --- /dev/null +++ b/src/MissionManager/CameraSpec.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "Fact.h" + +class CameraSpec : public QObject +{ + Q_OBJECT + +public: + CameraSpec(QObject* parent = NULL); + CameraSpec(const QString& name, + double sensorWidth, + double sensorHeight, + double imageWidth, + double imageHeight, + double focalLength, + bool landscape, + bool fixedOrientation, + double minTriggerInterval, + QObject* parent = NULL); + CameraSpec(const CameraSpec& other, QObject* parent); + + const CameraSpec& operator=(const CameraSpec& other); + + // These properties are persisted to Json + Q_PROPERTY(Fact* name READ name CONSTANT) ///< Camera name + Q_PROPERTY(Fact* sensorWidth READ sensorWidth CONSTANT) ///< Sensor size in millimeters + Q_PROPERTY(Fact* sensorHeight READ sensorHeight CONSTANT) ///< Sensor size in millimeters + Q_PROPERTY(Fact* imageWidth READ imageWidth CONSTANT) ///< Image size in pixels + Q_PROPERTY(Fact* imageHeight READ imageHeight CONSTANT) ///< Image size in pixels + Q_PROPERTY(Fact* focalLength READ focalLength CONSTANT) ///< Focal length in millimeters + Q_PROPERTY(Fact* landscape READ landscape CONSTANT) ///< true: camera is in landscape orientation + Q_PROPERTY(Fact* fixedOrientation READ fixedOrientation CONSTANT) ///< true: camera is in fixed orientation + Q_PROPERTY(Fact* minTriggerInterval READ minTriggerInterval CONSTANT) ///< Minimum time in seconds between each photo taken, 0 for not specified + + Fact* name (void) { return &_nameFact; } + Fact* sensorWidth (void) { return &_sensorWidthFact; } + Fact* sensorHeight (void) { return &_sensorHeightFact; } + Fact* imageWidth (void) { return &_imageWidthFact; } + Fact* imageHeight (void) { return &_imageHeightFact; } + Fact* focalLength (void) { return &_focalLengthFact; } + Fact* landscape (void) { return &_landscapeFact; } + Fact* fixedOrientation (void) { return &_fixedOrientationFact; } + Fact* minTriggerInterval(void) { return &_minTriggerIntervalFact; } + + bool dirty (void) const { return _dirty; } + void setDirty (bool dirty); + + void save(QJsonObject& json) const; + bool load(const QJsonObject& json, QString& errorString); + +signals: + void dirtyChanged(bool dirty); + +private: + void _init(bool setDefaults); + + bool _dirty; + + QMap _metaDataMap; + + Fact _nameFact; + Fact _sensorWidthFact; + Fact _sensorHeightFact; + Fact _imageWidthFact; + Fact _imageHeightFact; + Fact _focalLengthFact; + Fact _landscapeFact; + Fact _fixedOrientationFact; + Fact _minTriggerIntervalFact; + + static const char* _nameName; + static const char* _sensorWidthName; + static const char* _sensorHeightName; + static const char* _imageWidthName; + static const char* _imageHeightName; + static const char* _focalLengthName; + static const char* _landscapeName; + static const char* _fixedOrientationName; + static const char* _minTriggerIntervalName; + + static const char* _jsonNameKey; + static const char* _jsonSensorWidthKey; + static const char* _jsonSensorHeightKey; + static const char* _jsonImageWidthKey; + static const char* _jsonImageHeightKey; + static const char* _jsonFocalLengthKey; + static const char* _jsonLandscapeKey; + static const char* _jsonFixedOrientationKey; + static const char* _jsonMinTriggerIntervalKey; +}; diff --git a/src/MissionManager/StructureScanComplexItem.cc b/src/MissionManager/StructureScanComplexItem.cc index 2407ea35a7c05fceee926ad1d5b7b948c249c08c..d2e4859fedc5e10d1d87dcaafe7d2251537fb229 100644 --- a/src/MissionManager/StructureScanComplexItem.cc +++ b/src/MissionManager/StructureScanComplexItem.cc @@ -25,9 +25,6 @@ const char* StructureScanComplexItem::jsonComplexItemTypeValue = "Stru const char* StructureScanComplexItem::_altitudeFactName = "Altitude"; const char* StructureScanComplexItem::_layersFactName = "Layers"; -const char* StructureScanComplexItem::_layerDistanceFactName = "Layer distance"; -const char* StructureScanComplexItem::_cameraTriggerDistanceFactName = "Trigger distance"; -const char* StructureScanComplexItem::_scanDistanceFactName = "Scan distance"; QMap StructureScanComplexItem::_metaDataMap; @@ -41,11 +38,9 @@ StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, QObject* pa , _scanDistance (0.0) , _cameraShots (0) , _cameraMinTriggerInterval (0) + , _cameraCalc (vehicle) , _altitudeFact (0, _altitudeFactName, FactMetaData::valueTypeDouble) , _layersFact (0, _layersFactName, FactMetaData::valueTypeUint32) - , _layerDistanceFact (0, _layerDistanceFactName, FactMetaData::valueTypeDouble) - , _cameraTriggerDistanceFact(0, _cameraTriggerDistanceFactName, FactMetaData::valueTypeDouble) - , _scanDistanceFact (0, _scanDistanceFactName, FactMetaData::valueTypeDouble) { _editorQml = "qrc:/qml/StructureScanEditor.qml"; @@ -53,26 +48,16 @@ StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, QObject* pa _metaDataMap = FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/StructureScan.SettingsGroup.json"), NULL /* QObject parent */); } - qDebug() << _metaDataMap[_altitudeFactName]; - _altitudeFact.setMetaData (_metaDataMap[_altitudeFactName]); - _layersFact.setMetaData (_metaDataMap[_layersFactName]); - _layerDistanceFact.setMetaData (_metaDataMap[_layerDistanceFactName]); - _cameraTriggerDistanceFact.setMetaData (_metaDataMap[_cameraTriggerDistanceFactName]); - _scanDistanceFact.setMetaData (_metaDataMap[_scanDistanceFactName]); + _altitudeFact.setMetaData (_metaDataMap[_altitudeFactName]); + _layersFact.setMetaData (_metaDataMap[_layersFactName]); - _altitudeFact.setRawValue (_altitudeFact.rawDefaultValue()); - _layersFact.setRawValue (_layersFact.rawDefaultValue()); - _layerDistanceFact.setRawValue (_layerDistanceFact.rawDefaultValue()); - _cameraTriggerDistanceFact.setRawValue (_cameraTriggerDistanceFact.rawDefaultValue()); - _scanDistanceFact.setRawValue (_scanDistanceFact.rawDefaultValue()); + _altitudeFact.setRawValue (_altitudeFact.rawDefaultValue()); + _layersFact.setRawValue (_layersFact.rawDefaultValue()); _altitudeFact.setRawValue(qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue()); - connect(&_altitudeFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); - connect(&_layersFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); - connect(&_layerDistanceFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); - connect(&_cameraTriggerDistanceFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); - connect(&_scanDistanceFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); + connect(&_altitudeFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); + connect(&_layersFact, &Fact::valueChanged, this, &StructureScanComplexItem::_setDirty); connect(this, &StructureScanComplexItem::altitudeRelativeChanged, this, &StructureScanComplexItem::_setDirty); connect(this, &StructureScanComplexItem::altitudeRelativeChanged, this, &StructureScanComplexItem::coordinateHasRelativeAltitudeChanged); @@ -86,7 +71,7 @@ StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, QObject* pa connect(&_flightPolygon, &QGCMapPolygon::pathChanged, this, &StructureScanComplexItem::_flightPathChanged); - connect(&_scanDistanceFact, &Fact::valueChanged, this, &StructureScanComplexItem::_rebuildFlightPolygon); + connect(_cameraCalc.distanceToSurface(), &Fact::valueChanged, this, &StructureScanComplexItem::_rebuildFlightPolygon); } void StructureScanComplexItem::_setScanDistance(double scanDistance) @@ -395,7 +380,7 @@ void StructureScanComplexItem::appendMissionItems(QList& items, QO items.append(item); for (int layer=0; layer<_layersFact.rawValue().toInt(); layer++) { - double layerAltitude = baseAltitude + (layer * _layerDistanceFact.rawValue().toDouble()); + double layerAltitude = baseAltitude + (layer * _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble()); for (int i=0; i<_flightPolygon.count(); i++) { QGeoCoordinate vertexCoord = _flightPolygon.vertexCoordinate(i); @@ -466,9 +451,9 @@ void StructureScanComplexItem::_polygonDirtyChanged(bool dirty) } } -double StructureScanComplexItem::timeBetweenShots(void) const +double StructureScanComplexItem::timeBetweenShots(void) { - return _cruiseSpeed == 0 ? 0 :_cameraTriggerDistanceFact.rawValue().toDouble() / _cruiseSpeed; + return _cruiseSpeed == 0 ? 0 : _cameraCalc.adjustedFootprintSide()->rawValue().toDouble() / _cruiseSpeed; } QGeoCoordinate StructureScanComplexItem::coordinate(void) const @@ -505,5 +490,5 @@ void StructureScanComplexItem::rotateEntryPoint(void) void StructureScanComplexItem::_rebuildFlightPolygon(void) { _flightPolygon = _structurePolygon; - _flightPolygon.offset(_scanDistanceFact.rawValue().toDouble()); + _flightPolygon.offset(_cameraCalc.distanceToSurface()->rawValue().toDouble()); } diff --git a/src/MissionManager/StructureScanComplexItem.h b/src/MissionManager/StructureScanComplexItem.h index 22dd53de67455ddaed5354e9a9b922a08354ef97..95bd06d6aedb44cb6f7155fe0f9a8a42acd2b04b 100644 --- a/src/MissionManager/StructureScanComplexItem.h +++ b/src/MissionManager/StructureScanComplexItem.h @@ -16,6 +16,7 @@ #include "SettingsFact.h" #include "QGCLoggingCategory.h" #include "QGCMapPolygon.h" +#include "CameraCalc.h" Q_DECLARE_LOGGING_CATEGORY(StructureScanComplexItemLog) @@ -26,11 +27,9 @@ class StructureScanComplexItem : public ComplexMissionItem public: StructureScanComplexItem(Vehicle* vehicle, QObject* parent = NULL); + Q_PROPERTY(CameraCalc* cameraCalc READ cameraCalc CONSTANT) Q_PROPERTY(Fact* altitude READ altitude CONSTANT) Q_PROPERTY(Fact* layers READ layers CONSTANT) - Q_PROPERTY(Fact* layerDistance READ layerDistance CONSTANT) - Q_PROPERTY(Fact* cameraTriggerDistance READ cameraTriggerDistance CONSTANT) - Q_PROPERTY(Fact* scanDistance READ scanDistance CONSTANT) Q_PROPERTY(bool altitudeRelative MEMBER _altitudeRelative NOTIFY altitudeRelativeChanged) Q_PROPERTY(int cameraShots READ cameraShots NOTIFY cameraShotsChanged) Q_PROPERTY(double timeBetweenShots READ timeBetweenShots NOTIFY timeBetweenShotsChanged) @@ -38,14 +37,12 @@ public: Q_PROPERTY(QGCMapPolygon* structurePolygon READ structurePolygon CONSTANT) Q_PROPERTY(QGCMapPolygon* flightPolygon READ flightPolygon CONSTANT) - Fact* altitude (void) { return &_altitudeFact; } - Fact* layers (void) { return &_layersFact; } - Fact* layerDistance (void) { return &_layerDistanceFact; } - Fact* cameraTriggerDistance (void) { return &_cameraTriggerDistanceFact; } - Fact* scanDistance (void) { return &_scanDistanceFact; } + CameraCalc* cameraCalc (void) { return &_cameraCalc; } + Fact* altitude (void) { return &_altitudeFact; } + Fact* layers (void) { return &_layersFact; } int cameraShots (void) const; - double timeBetweenShots(void) const; + double timeBetweenShots(void); QGCMapPolygon* structurePolygon(void) { return &_structurePolygon; } QGCMapPolygon* flightPolygon (void) { return &_flightPolygon; } @@ -123,20 +120,15 @@ private: double _timeBetweenShots; double _cameraMinTriggerInterval; double _cruiseSpeed; + CameraCalc _cameraCalc; static QMap _metaDataMap; Fact _altitudeFact; Fact _layersFact; - Fact _layerDistanceFact; - Fact _cameraTriggerDistanceFact; - Fact _scanDistanceFact; static const char* _altitudeFactName; static const char* _layersFactName; - static const char* _layerDistanceFactName; - static const char* _cameraTriggerDistanceFactName; - static const char* _scanDistanceFactName; }; #endif diff --git a/src/PlanView/CameraCalc.qml b/src/PlanView/CameraCalc.qml new file mode 100644 index 0000000000000000000000000000000000000000..7e51f211a519cd81f09692a8cc92ae971dc35004 --- /dev/null +++ b/src/PlanView/CameraCalc.qml @@ -0,0 +1,330 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.2 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 + +// Camera calculator section for mission item editors +Column { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + + property var cameraCalc + property bool vehicleFlightIsFrontal: true + property string distanceToSurfaceLabel + property string frontalDistanceLabel + property string sideDistanceLabel + + property real _margin: ScreenTools.defaultFontPixelWidth / 2 + property int _cameraIndex: 1 + property real _fieldWidth: ScreenTools.defaultFontPixelWidth * 10.5 + property var _cameraList: [ qsTr("Manual (no camera specs)"), qsTr("Custom Camera") ] + property var _vehicle: QGroundControl.multiVehicleManager.activeVehicle ? QGroundControl.multiVehicleManager.activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle + property var _vehicleCameraList: _vehicle ? _vehicle.staticCameraList : [] + + readonly property int _gridTypeManual: 0 + readonly property int _gridTypeCustomCamera: 1 + readonly property int _gridTypeCamera: 2 + + Component.onCompleted: { + for (var i=0; i<_vehicle.staticCameraList.length; i++) { + _cameraList.push(_vehicle.staticCameraList[i].name) + } + gridTypeCombo.model = _cameraList + if (cameraCalc.cameraSpecType === CameraCalc.CameraSpecNone) { + gridTypeCombo.currentIndex = _gridTypeManual + } else { + var index = -1 + for (index=0; index<_cameraList.length; index++) { + if (_cameraList[index] == cameraCalc.knownCameraName) { + break; + } + } + cameraCalc.fixedOrientation.value = false + if (index == _cameraList.length) { + gridTypeCombo.currentIndex = _gridTypeCustomCamera + } else { + gridTypeCombo.currentIndex = index + if (index != 1) { + // Specific camera is selected + var camera = _vehicleCameraList[index - _gridTypeCamera] + cameraCalc.fixedOrientation.value = camera.fixedOrientation + cameraCalc.minTriggerInterval.value = camera.minTriggerInterval + } + } + } + } + + QGCPalette { id: qgcPal; colorGroupEnabled: true } + + ExclusiveGroup { + id: cameraOrientationGroup + } + + ExclusiveGroup { id: fixedValueGroup } + + SectionHeader { + id: cameraHeader + text: qsTr("Camera") + showSpacer: false + } + + Column { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + visible: cameraHeader.checked + + QGCComboBox { + id: gridTypeCombo + anchors.left: parent.left + anchors.right: parent.right + model: _cameraList + currentIndex: -1 + + onActivated: { + if (index == _gridTypeManual) { + cameraCalc.cameraSpecType = CameraCalc.CameraSpecNone + cameraCalc.valueSetIsDistance.value = false + } else if (index == _gridTypeCustomCamera) { + cameraCalc.cameraSpecType = CameraCalc.CameraSpecCustom + cameraCalc.knownCameraName = gridTypeCombo.textAt(index) + cameraCalc.fixedOrientation.value = false + cameraCalc.minTriggerInterval.value = 0 + } else { + cameraCalc.cameraSpecType = CameraCalc.CameraSpecKnown + cameraCalc.knownCameraName = gridTypeCombo.textAt(index) + } + } + } // QGCComboxBox + + // Camera based grid ui + Column { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + visible: cameraCalc.cameraSpecType !== CameraCalc.CameraSpecNone + + Row { + spacing: _margin + anchors.horizontalCenter: parent.horizontalCenter + visible: !cameraCalc.fixedOrientation.value + + QGCRadioButton { + width: _editFieldWidth + text: "Landscape" + checked: !!cameraCalc.landscape.value + exclusiveGroup: cameraOrientationGroup + onClicked: cameraCalc.landscape.value = 1 + } + + QGCRadioButton { + id: cameraOrientationPortrait + text: "Portrait" + checked: !cameraCalc.landscape.value + exclusiveGroup: cameraOrientationGroup + onClicked: cameraCalc.landscape.value = 0 + } + } + + // Custom camera specs + Column { + id: custCameraCol + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + visible: cameraCalc.cameraSpecType === CameraCalc.CameraSpecCustom + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + Item { Layout.fillWidth: true } + QGCLabel { + Layout.preferredWidth: _root._fieldWidth + text: qsTr("Width") + } + QGCLabel { + Layout.preferredWidth: _root._fieldWidth + text: qsTr("Height") + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + QGCLabel { text: qsTr("Sensor"); Layout.fillWidth: true } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.sensorWidth + } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.sensorHeight + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + QGCLabel { text: qsTr("Image"); Layout.fillWidth: true } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.imageWidth + } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.imageHeight + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + QGCLabel { + text: qsTr("Focal length") + Layout.fillWidth: true + } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.focalLength + } + } + + } // Column - custom camera specs + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + Item { Layout.fillWidth: true } + QGCLabel { + Layout.preferredWidth: _root._fieldWidth + text: qsTr("Front Lap") + } + QGCLabel { + Layout.preferredWidth: _root._fieldWidth + text: qsTr("Side Lap") + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + QGCLabel { text: qsTr("Overlap"); Layout.fillWidth: true } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.frontalOverlap + } + FactTextField { + Layout.preferredWidth: _root._fieldWidth + fact: cameraCalc.sideOverlap + } + } + + QGCLabel { + wrapMode: Text.WordWrap + text: qsTr("Select one:") + Layout.preferredWidth: parent.width + Layout.columnSpan: 2 + } + + GridLayout { + anchors.left: parent.left + anchors.right: parent.right + columnSpacing: _margin + rowSpacing: _margin + columns: 2 + + QGCRadioButton { + id: fixedDistanceRadio + text: distanceToSurfaceLabel + checked: !!cameraCalc.valueSetIsDistance.value + exclusiveGroup: fixedValueGroup + onClicked: cameraCalc.valueSetIsDistance.value = 1 + } + + FactTextField { + fact: cameraCalc.distanceToSurface + enabled: fixedDistanceRadio.checked + Layout.fillWidth: true + } + + QGCRadioButton { + id: fixedImageDensityRadio + text: qsTr("Image density") + checked: !cameraCalc.valueSetIsDistance.value + exclusiveGroup: fixedValueGroup + onClicked: cameraCalc.valueSetIsDistance.value = 0 + } + + FactTextField { + fact: cameraCalc.imageDensity + enabled: fixedImageDensityRadio.checked + Layout.fillWidth: true + } + } + + // Calculated values + GridLayout { + anchors.left: parent.left + anchors.right: parent.right + columnSpacing: _margin + rowSpacing: _margin + columns: 2 + + QGCLabel { text: frontalDistanceLabel } + FactTextField { + Layout.fillWidth: true + fact: cameraCalc.adjustedFootprintFrontal + enabled: false + } + + QGCLabel { text: sideDistanceLabel } + FactTextField { + Layout.fillWidth: true + fact: cameraCalc.adjustedFootprintSide + enabled: false + } + } // GridLayout + + } // Column - Camera spec based ui + + // No camera spec ui + GridLayout { + anchors.left: parent.left + anchors.right: parent.right + columnSpacing: _margin + rowSpacing: _margin + columns: 2 + visible: cameraCalc.cameraSpecType === CameraCalc.CameraSpecNone + + QGCLabel { text: distanceToSurfaceLabel } + FactTextField { + fact: cameraCalc.distanceToSurface + Layout.fillWidth: true + } + + QGCLabel { text: frontalDistanceLabel } + FactTextField { + Layout.fillWidth: true + fact: cameraCalc.adjustedFootprintFrontal + } + + QGCLabel { text: sideDistanceLabel } + FactTextField { + Layout.fillWidth: true + fact: cameraCalc.adjustedFootprintSide + } + } // GridLayout + } // Column - Camera Section +} // Column diff --git a/src/PlanView/StructureScanEditor.qml b/src/PlanView/StructureScanEditor.qml index 34301478849f0288c291312ac436d07b5093c3f6..e8f6a4c6ff65e2486ac289f1a73ab0199b77a495 100644 --- a/src/PlanView/StructureScanEditor.qml +++ b/src/PlanView/StructureScanEditor.qml @@ -60,7 +60,7 @@ Rectangle { QGCLabel { anchors.left: parent.left anchors.right: parent.right - text: qsTr("WARNING: WORK IN PROGRESS. USE AT YOUR OWN RISK. MEANT FOR DISCUSSION ONLY. DO NOT REPORT BUGS.") + text: qsTr("WARNING: WORK IN PROGRESS. DO NOT FLY. NO BUG REPORTS.") wrapMode: Text.WordWrap color: qgcPal.warningText } @@ -82,59 +82,61 @@ Rectangle { visible: missionItem.cameraShots > 0 && missionItem.cameraMinTriggerInterval !== 0 && missionItem.cameraMinTriggerInterval > missionItem.timeBetweenShots } - GridLayout { - anchors.left: parent.left - anchors.right: parent.right - columnSpacing: _margin - rowSpacing: _margin - columns: 2 - - QGCLabel { text: qsTr("Altitude") } - FactTextField { - fact: missionItem.altitude - Layout.fillWidth: true - } - - QGCLabel { text: qsTr("Layers") } - FactTextField { - fact: missionItem.layers - Layout.fillWidth: true - } + CameraCalc { + cameraCalc: missionItem.cameraCalc + vehicleFlightIsFrontal: false + distanceToSurfaceLabel: qsTr("Scan Distance") + frontalDistanceLabel: qsTr("Layer Height") + sideDistanceLabel: qsTr("Trigger Distance") + } - QGCLabel { text: qsTr("Layer distance") } - FactTextField { - fact: missionItem.layerDistance - Layout.fillWidth: true - } + SectionHeader { + id: scanHeader + text: qsTr("Scan") + } - QGCLabel { text: qsTr("Scan distance") } - FactTextField { - fact: missionItem.scanDistance - Layout.fillWidth: true + Column { + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + visible: scanHeader.checked + + GridLayout { + anchors.left: parent.left + anchors.right: parent.right + columnSpacing: _margin + rowSpacing: _margin + columns: 2 + + QGCLabel { text: qsTr("Layers") } + FactTextField { + fact: missionItem.layers + Layout.fillWidth: true + } + + QGCLabel { text: qsTr("Altitude") } + FactTextField { + fact: missionItem.altitude + Layout.fillWidth: true + } + + QGCCheckBox { + text: qsTr("Relative altitude") + checked: missionItem.altitudeRelative + Layout.columnSpan: 2 + onClicked: missionItem.altitudeRelative = checked + } } - QGCLabel { text: qsTr("Trigger Distance") } - FactTextField { - fact: missionItem.cameraTriggerDistance - Layout.fillWidth: true - } + QGCLabel { text: qsTr("Point camera to structure using:") } + QGCRadioButton { text: qsTr("Vehicle yaw"); enabled: false } + QGCRadioButton { text: qsTr("Gimbal yaw"); checked: true; enabled: false } - QGCCheckBox { - text: qsTr("Relative altitude") - checked: missionItem.altitudeRelative - Layout.columnSpan: 2 - onClicked: missionItem.altitudeRelative = checked + QGCButton { + text: qsTr("Rotate entry point") + onClicked: missionItem.rotateEntryPoint() } - } - - QGCLabel { text: qsTr("Point camera to structure using:") } - QGCRadioButton { text: qsTr("Vehicle yaw"); enabled: false } - QGCRadioButton { text: qsTr("Gimbal yaw"); checked: true; enabled: false } - - QGCButton { - text: qsTr("Rotate entry point") - onClicked: missionItem.rotateEntryPoint() - } + } // Column - Scan SectionHeader { id: statsHeader @@ -152,6 +154,5 @@ Rectangle { QGCLabel { text: qsTr("Photo interval") } QGCLabel { text: missionItem.timeBetweenShots.toFixed(1) + " " + qsTr("secs") } } - } -} - + } // Column +} // Rectangle diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index 508f21d222a4f8ae0751977f04f7f87252eaed17..5b61c08937c3e56589b33f83e049bcd583a95903 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -81,6 +81,7 @@ #include "SettingsManager.h" #include "QGCCorePlugin.h" #include "QGCCameraManager.h" +#include "CameraCalc.h" #ifndef NO_SERIAL_LINK #include "SerialLink.h" @@ -348,6 +349,7 @@ void QGCApplication::_initCommon(void) qmlRegisterUncreatableType ("QGroundControl", 1, 0, "CoordinateVector", "Reference only"); qmlRegisterUncreatableType ("QGroundControl", 1, 0, "QmlObjectListModel", "Reference only"); qmlRegisterUncreatableType ("QGroundControl", 1, 0, "MissionCommandTree", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl", 1, 0, "CameraCalc", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", "Reference only"); qmlRegisterUncreatableType ("QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", "Reference only"); diff --git a/src/QmlControls/QGroundControl.Controls.qmldir b/src/QmlControls/QGroundControl.Controls.qmldir index ee2a59064d9a97fe243c4fade18a27996c91c2a4..ad49c42db7e48ea25142897cd184e6ab7a540404 100644 --- a/src/QmlControls/QGroundControl.Controls.qmldir +++ b/src/QmlControls/QGroundControl.Controls.qmldir @@ -2,6 +2,7 @@ Module QGroundControl.Controls AnalyzePage 1.0 AnalyzePage.qml AppMessages 1.0 AppMessages.qml +CameraCalc 1.0 CameraCalc.qml CameraSection 1.0 CameraSection.qml ClickableColor 1.0 ClickableColor.qml DropButton 1.0 DropButton.qml