diff --git a/src/FactSystem/FactGroup.cc b/src/FactSystem/FactGroup.cc index 517e948f4878ec8eb84efc76144d106b4a74d33c..436f9d0e0253aec84bc7711f7dfbfffddb2b0a7f 100644 --- a/src/FactSystem/FactGroup.cc +++ b/src/FactSystem/FactGroup.cc @@ -37,7 +37,7 @@ FactGroup::FactGroup(int updateRateMsecs, QObject* parent, bool ignoreCamelCase) QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); } -void FactGroup::_loadFromJsonArray(const QJsonObject jsonArray) +void FactGroup::_loadFromJsonArray(const QJsonArray jsonArray) { QMap defineMap; _nameToFactMetaDataMap = FactMetaData::createMapFromJsonArray(jsonArray, defineMap, this); diff --git a/src/FactSystem/FactGroup.h b/src/FactSystem/FactGroup.h index f3b3981c6ea10f38ec3fb12c3689b3d7e23b775a..114fb44f4e0dfd82322082438f0528052a17411f 100644 --- a/src/FactSystem/FactGroup.h +++ b/src/FactSystem/FactGroup.h @@ -64,7 +64,7 @@ protected slots: protected: void _addFact (Fact* fact, const QString& name); void _addFactGroup (FactGroup* factGroup, const QString& name); - void _loadFromJsonArray (const QJsonObject jsonArray); + void _loadFromJsonArray (const QJsonArray jsonArray); void _setTelemetryAvailable (bool telemetryAvailable); int _updateRateMSecs; ///< Update rate for Fact::valueChanged signals, 0: immediate update diff --git a/src/FactSystem/FactMetaData.cc b/src/FactSystem/FactMetaData.cc index 0ac10c22edd477bb37e6de7124db84155ca09b42..7e1c95a8ac5fe54e58428c2dd3b4ccb65f0ab3e6 100644 --- a/src/FactSystem/FactMetaData.cc +++ b/src/FactSystem/FactMetaData.cc @@ -1448,7 +1448,7 @@ QMap FactMetaData::createMapFromJsonFile(const QString& return metaDataMap; } - QJsonObject factArray; + QJsonArray factArray; QMap defineMap; QList keyInfoList = { @@ -1466,7 +1466,7 @@ QMap FactMetaData::createMapFromJsonFile(const QString& return createMapFromJsonArray(factArray, defineMap, metaDataParent); } -QMap FactMetaData::createMapFromJsonArray(const QJsonObject jsonArray, QMap& defineMap, QObject* metaDataParent) +QMap FactMetaData::createMapFromJsonArray(const QJsonArray jsonArray, QMap& defineMap, QObject* metaDataParent) { QMap metaDataMap; for (int i=0; i DefineMap_t; static QMap createMapFromJsonFile(const QString& jsonFilename, QObject* metaDataParent); - static QMap createMapFromJsonArray(const QJsonObject jsonArray, DefineMap_t& defineMap, QObject* metaDataParent); + static QMap createMapFromJsonArray(const QJsonArray jsonArray, DefineMap_t& defineMap, QObject* metaDataParent); static FactMetaData* createFromJsonObject(const QJsonObject& json, QMap& defineMap, QObject* metaDataParent); diff --git a/src/FactSystemFactGroup.cc b/src/FactSystemFactGroup.cc new file mode 100644 index 0000000000000000000000000000000000000000..436f9d0e0253aec84bc7711f7dfbfffddb2b0a7f --- /dev/null +++ b/src/FactSystemFactGroup.cc @@ -0,0 +1,194 @@ +/**************************************************************************** + * + * (c) 2009-2020 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + + +#include "FactGroup.h" +#include "JsonHelper.h" + +#include +#include +#include +#include +#include +#include + +FactGroup::FactGroup(int updateRateMsecs, const QString& metaDataFile, QObject* parent, bool ignoreCamelCase) + : QObject(parent) + , _updateRateMSecs(updateRateMsecs) + , _ignoreCamelCase(ignoreCamelCase) +{ + _setupTimer(); + _nameToFactMetaDataMap = FactMetaData::createMapFromJsonFile(metaDataFile, this); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); +} + +FactGroup::FactGroup(int updateRateMsecs, QObject* parent, bool ignoreCamelCase) + : QObject(parent) + , _updateRateMSecs(updateRateMsecs) + , _ignoreCamelCase(ignoreCamelCase) +{ + _setupTimer(); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); +} + +void FactGroup::_loadFromJsonArray(const QJsonArray jsonArray) +{ + QMap defineMap; + _nameToFactMetaDataMap = FactMetaData::createMapFromJsonArray(jsonArray, defineMap, this); +} + +void FactGroup::_setupTimer() +{ + if (_updateRateMSecs > 0) { + connect(&_updateTimer, &QTimer::timeout, this, &FactGroup::_updateAllValues); + _updateTimer.setSingleShot(false); + _updateTimer.setInterval(_updateRateMSecs); + _updateTimer.start(); + } +} + +bool FactGroup::factExists(const QString& name) +{ + if (name.contains(".")) { + QStringList parts = name.split("."); + if (parts.count() != 2) { + qWarning() << "Only single level of hierarchy supported"; + return false; + } + + FactGroup * factGroup = getFactGroup(parts[0]); + if (!factGroup) { + qWarning() << "Unknown FactGroup" << parts[0]; + return false; + } + + return factGroup->factExists(parts[1]); + } + + QString camelCaseName = _ignoreCamelCase ? name : _camelCase(name); + + return _nameToFactMap.contains(camelCaseName); +} + +Fact* FactGroup::getFact(const QString& name) +{ + if (name.contains(".")) { + QStringList parts = name.split("."); + if (parts.count() != 2) { + qWarning() << "Only single level of hierarchy supported"; + return nullptr; + } + + FactGroup * factGroup = getFactGroup(parts[0]); + if (!factGroup) { + qWarning() << "Unknown FactGroup" << parts[0]; + return nullptr; + } + + return factGroup->getFact(parts[1]); + } + + Fact* fact = nullptr; + QString camelCaseName = _ignoreCamelCase ? name : _camelCase(name); + + if (_nameToFactMap.contains(camelCaseName)) { + fact = _nameToFactMap[camelCaseName]; + QQmlEngine::setObjectOwnership(fact, QQmlEngine::CppOwnership); + } else { + qWarning() << "Unknown Fact" << camelCaseName; + } + + return fact; +} + +FactGroup* FactGroup::getFactGroup(const QString& name) +{ + FactGroup* factGroup = nullptr; + QString camelCaseName = _ignoreCamelCase ? name : _camelCase(name); + + if (_nameToFactGroupMap.contains(camelCaseName)) { + factGroup = _nameToFactGroupMap[camelCaseName]; + QQmlEngine::setObjectOwnership(factGroup, QQmlEngine::CppOwnership); + } else { + qWarning() << "Unknown FactGroup" << camelCaseName; + } + + return factGroup; +} + +void FactGroup::_addFact(Fact* fact, const QString& name) +{ + if (_nameToFactMap.contains(name)) { + qWarning() << "Duplicate Fact" << name; + return; + } + + fact->setSendValueChangedSignals(_updateRateMSecs == 0); + if (_nameToFactMetaDataMap.contains(name)) { + fact->setMetaData(_nameToFactMetaDataMap[name], true /* setDefaultFromMetaData */); + } + _nameToFactMap[name] = fact; + _factNames.append(name); + + emit factNamesChanged(); +} + +void FactGroup::_addFactGroup(FactGroup* factGroup, const QString& name) +{ + if (_nameToFactGroupMap.contains(name)) { + qWarning() << "Duplicate FactGroup" << name; + return; + } + + _nameToFactGroupMap[name] = factGroup; + + emit factGroupNamesChanged(); +} + +void FactGroup::_updateAllValues(void) +{ + for(Fact* fact: _nameToFactMap) { + fact->sendDeferredValueChangedSignal(); + } +} + +void FactGroup::setLiveUpdates(bool liveUpdates) +{ + if (_updateTimer.interval() == 0) { + return; + } + + if (liveUpdates) { + _updateTimer.stop(); + } else { + _updateTimer.start(); + } + for(Fact* fact: _nameToFactMap) { + fact->setSendValueChangedSignals(liveUpdates); + } +} + + +QString FactGroup::_camelCase(const QString& text) +{ + return text[0].toLower() + text.right(text.length() - 1); +} + +void FactGroup::handleMessage(Vehicle* /* vehicle */, mavlink_message_t& /* message */) +{ + // Default implementation does nothing +} + +void FactGroup::_setTelemetryAvailable (bool telemetryAvailable) +{ + if (telemetryAvailable != _telemetryAvailable) { + _telemetryAvailable = telemetryAvailable; + emit telemetryAvailableChanged(_telemetryAvailable); + } +} diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc index d701185b66524fcd5b29e5e169b65272e40038d3..82579c829b7197a8d29e5bc6bfe48b6f18eb2f92 100644 --- a/src/JsonHelper.cc +++ b/src/JsonHelper.cc @@ -8,551 +8,518 @@ ****************************************************************************/ #include "JsonHelper.h" -#include "FactMetaData.h" -#include "MissionCommandList.h" -#include "QGCApplication.h" #include "QGCQGeoCoordinate.h" #include "QmlObjectListModel.h" +#include "MissionCommandList.h" +#include "FactMetaData.h" +#include "QGCApplication.h" -#include #include #include #include #include #include +#include #include -const char *JsonHelper::jsonVersionKey = "version"; -const char *JsonHelper::jsonGroundStationKey = "groundStation"; -const char *JsonHelper::jsonGroundStationValue = "QGroundControl"; -const char *JsonHelper::jsonFileTypeKey = "fileType"; -const char *JsonHelper::_translateKeysKey = "translateKeys"; -const char *JsonHelper::_arrayIDKeysKey = "_arrayIDKeys"; - -bool JsonHelper::validateRequiredKeys(const QJsonObject &jsonObject, - const QStringList &keys, - QString &errorString) { - QString missingKeys; - - foreach (const QString &key, keys) { - if (!jsonObject.contains(key)) { - if (!missingKeys.isEmpty()) { - missingKeys += QStringLiteral(", "); - } - missingKeys += key; - } - } - - if (missingKeys.count() != 0) { - errorString = QObject::tr("The following required keys are missing: %1") - .arg(missingKeys); - return false; - } - - return true; +const char* JsonHelper::jsonVersionKey = "version"; +const char* JsonHelper::jsonGroundStationKey = "groundStation"; +const char* JsonHelper::jsonGroundStationValue = "QGroundControl"; +const char* JsonHelper::jsonFileTypeKey = "fileType"; +const char* JsonHelper::_translateKeysKey = "translateKeys"; +const char* JsonHelper::_arrayIDKeysKey = "_arrayIDKeys"; + +bool JsonHelper::validateRequiredKeys(const QJsonObject& jsonObject, const QStringList& keys, QString& errorString) +{ + QString missingKeys; + + foreach(const QString& key, keys) { + if (!jsonObject.contains(key)) { + if (!missingKeys.isEmpty()) { + missingKeys += QStringLiteral(", "); + } + missingKeys += key; + } + } + + if (missingKeys.count() != 0) { + errorString = QObject::tr("The following required keys are missing: %1").arg(missingKeys); + return false; + } + + return true; } -bool JsonHelper::_loadGeoCoordinate(const QJsonValue &jsonValue, - bool altitudeRequired, - QGeoCoordinate &coordinate, - QString &errorString, bool geoJsonFormat) { - if (!jsonValue.isArray()) { - errorString = QObject::tr("value for coordinate is not array"); - return false; - } - - QJsonObject coordinateArray = jsonValue.toArray(); - int requiredCount = altitudeRequired ? 3 : 2; - if (coordinateArray.count() != requiredCount) { - errorString = QObject::tr("Coordinate array must contain %1 values") - .arg(requiredCount); - return false; - } - - foreach (const QJsonValue &jsonValue, coordinateArray) { - if (jsonValue.type() != QJsonValue::Double && - jsonValue.type() != QJsonValue::Null) { - errorString = - QObject::tr( - "Coordinate array may only contain double values, found: %1") - .arg(jsonValue.type()); - return false; - } - } - - if (geoJsonFormat) { - coordinate = QGeoCoordinate(coordinateArray[1].toDouble(), - coordinateArray[0].toDouble()); - } else { - coordinate = QGeoCoordinate(possibleNaNJsonValue(coordinateArray[0]), - possibleNaNJsonValue(coordinateArray[1])); - } - if (altitudeRequired) { - coordinate.setAltitude(possibleNaNJsonValue(coordinateArray[2])); - } - - return true; +bool JsonHelper::_loadGeoCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString, + bool geoJsonFormat) +{ + if (!jsonValue.isArray()) { + errorString = QObject::tr("value for coordinate is not array"); + return false; + } + + QJsonArray coordinateArray = jsonValue.toArray(); + int requiredCount = altitudeRequired ? 3 : 2; + if (coordinateArray.count() != requiredCount) { + errorString = QObject::tr("Coordinate array must contain %1 values").arg(requiredCount); + return false; + } + + foreach(const QJsonValue& jsonValue, coordinateArray) { + if (jsonValue.type() != QJsonValue::Double && jsonValue.type() != QJsonValue::Null) { + errorString = QObject::tr("Coordinate array may only contain double values, found: %1").arg(jsonValue.type()); + return false; + } + } + + if (geoJsonFormat) { + coordinate = QGeoCoordinate(coordinateArray[1].toDouble(), coordinateArray[0].toDouble()); + } else { + coordinate = QGeoCoordinate(possibleNaNJsonValue(coordinateArray[0]), possibleNaNJsonValue(coordinateArray[1])); + } + if (altitudeRequired) { + coordinate.setAltitude(possibleNaNJsonValue(coordinateArray[2])); + } + + return true; } -void JsonHelper::_saveGeoCoordinate(const QGeoCoordinate &coordinate, - bool writeAltitude, QJsonValue &jsonValue, - bool geoJsonFormat) { - QJsonObject coordinateArray; - - if (geoJsonFormat) { - coordinateArray << coordinate.longitude() << coordinate.latitude(); - } else { - coordinateArray << coordinate.latitude() << coordinate.longitude(); - } - if (writeAltitude) { - coordinateArray << coordinate.altitude(); - } - - jsonValue = QJsonValue(coordinateArray); +void JsonHelper::_saveGeoCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue, + bool geoJsonFormat) +{ + QJsonArray coordinateArray; + + if (geoJsonFormat) { + coordinateArray << coordinate.longitude() << coordinate.latitude(); + } else { + coordinateArray << coordinate.latitude() << coordinate.longitude(); + } + if (writeAltitude) { + coordinateArray << coordinate.altitude(); + } + + jsonValue = QJsonValue(coordinateArray); } -bool JsonHelper::loadGeoCoordinate(const QJsonValue &jsonValue, - bool altitudeRequired, - QGeoCoordinate &coordinate, - QString &errorString, bool geoJsonFormat) { - return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, - errorString, geoJsonFormat); +bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString, + bool geoJsonFormat) +{ + return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, errorString, geoJsonFormat); } -void JsonHelper::saveGeoCoordinate(const QGeoCoordinate &coordinate, - bool writeAltitude, QJsonValue &jsonValue) { - _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, - false /* geoJsonFormat */); +void JsonHelper::saveGeoCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue) +{ + _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, false /* geoJsonFormat */); } -bool JsonHelper::loadGeoJsonCoordinate(const QJsonValue &jsonValue, - bool altitudeRequired, - QGeoCoordinate &coordinate, - QString &errorString) { - return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, - errorString, true /* geoJsonFormat */); +bool JsonHelper::loadGeoJsonCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString) +{ + return _loadGeoCoordinate(jsonValue, altitudeRequired, coordinate, errorString, true /* geoJsonFormat */); } -void JsonHelper::saveGeoJsonCoordinate(const QGeoCoordinate &coordinate, - bool writeAltitude, - QJsonValue &jsonValue) { - _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, - true /* geoJsonFormat */); +void JsonHelper::saveGeoJsonCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue) +{ + _saveGeoCoordinate(coordinate, writeAltitude, jsonValue, true /* geoJsonFormat */); } -bool JsonHelper::validateKeyTypes(const QJsonObject &jsonObject, - const QStringList &keys, - const QList &types, - QString &errorString) { - for (int i = 0; i < types.count(); i++) { - QString valueKey = keys[i]; - if (jsonObject.contains(valueKey)) { - const QJsonValue &jsonValue = jsonObject[valueKey]; - if (jsonValue.type() == QJsonValue::Null && - types[i] == QJsonValue::Double) { - // Null type signals a NaN on a double value - continue; - } - if (jsonValue.type() != types[i]) { - errorString = - QObject::tr("Incorrect value type - key:type:expected %1:%2:%3") - .arg(valueKey) - .arg(_jsonValueTypeToString(jsonValue.type())) - .arg(_jsonValueTypeToString(types[i])); - return false; - } +bool JsonHelper::validateKeyTypes(const QJsonObject& jsonObject, const QStringList& keys, const QList& types, QString& errorString) +{ + for (int i=0; i requiredKeys = { - {jsonFileTypeKey, QJsonValue::String, true}, - {jsonVersionKey, QJsonValue::Double, true}, - }; - if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) { - return false; - } - - // Make sure file type is correct - QString fileTypeValue = jsonObject[jsonFileTypeKey].toString(); - if (fileTypeValue != expectedFileType) { - errorString = QObject::tr("Incorrect file type key expected:%1 actual:%2") - .arg(expectedFileType) - .arg(fileTypeValue); - return false; - } - - // Version check - version = jsonObject[jsonVersionKey].toInt(); - if (version < minSupportedVersion) { - errorString = - QObject::tr("File version %1 is no longer supported").arg(version); - return false; - } - if (version > maxSupportedVersion) { - errorString = - QObject::tr( - "File version %1 is newer than current supported version %2") - .arg(version) - .arg(maxSupportedVersion); - return false; - } - - return true; + return isJsonFile(jsonBytes, jsonDoc, errorString); } -bool JsonHelper::validateExternalQGCJsonFile(const QJsonObject &jsonObject, - const QString &expectedFileType, - int minSupportedVersion, - int maxSupportedVersion, - int &version, - QString &errorString) { - // Validate required keys - QList requiredKeys = { - {jsonGroundStationKey, QJsonValue::String, true}, - }; - if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) { - return false; - } - - return validateInternalQGCJsonFile(jsonObject, expectedFileType, - minSupportedVersion, maxSupportedVersion, - version, errorString); +bool JsonHelper::validateInternalQGCJsonFile(const QJsonObject& jsonObject, + const QString& expectedFileType, + int minSupportedVersion, + int maxSupportedVersion, + int& version, + QString& errorString) +{ + // Validate required keys + QList requiredKeys = { + { jsonFileTypeKey, QJsonValue::String, true }, + { jsonVersionKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) { + return false; + } + + // Make sure file type is correct + QString fileTypeValue = jsonObject[jsonFileTypeKey].toString(); + if (fileTypeValue != expectedFileType) { + errorString = QObject::tr("Incorrect file type key expected:%1 actual:%2").arg(expectedFileType).arg(fileTypeValue); + return false; + } + + // Version check + version = jsonObject[jsonVersionKey].toInt(); + if (version < minSupportedVersion) { + errorString = QObject::tr("File version %1 is no longer supported").arg(version); + return false; + } + if (version > maxSupportedVersion) { + errorString = QObject::tr("File version %1 is newer than current supported version %2").arg(version).arg(maxSupportedVersion); + return false; + } + + return true; } -QStringList JsonHelper::_addDefaultLocKeys(QJsonObject &jsonObject) { - QString translateKeys; - QString fileType = jsonObject[jsonFileTypeKey].toString(); - if (!fileType.isEmpty()) { - if (fileType == MissionCommandList::qgcFileType) { - if (jsonObject.contains(_translateKeysKey)) { - translateKeys = jsonObject[_translateKeysKey].toString(); - } else { - translateKeys = "label,enumStrings,friendlyName,description,category"; - jsonObject[_translateKeysKey] = translateKeys; - } - if (!jsonObject.contains(_arrayIDKeysKey)) { - jsonObject[_arrayIDKeysKey] = "rawName,comment"; - } - } else if (fileType == FactMetaData::qgcFileType) { - if (jsonObject.contains(_translateKeysKey)) { - translateKeys = jsonObject[_translateKeysKey].toString(); - } else { - translateKeys = "shortDescription,longDescription,enumStrings"; - jsonObject[_translateKeysKey] = - "shortDescription,longDescription,enumStrings"; - } - if (!jsonObject.contains(_arrayIDKeysKey)) { - jsonObject[_arrayIDKeysKey] = "name"; - } - } - } - return translateKeys.split(","); +bool JsonHelper::validateExternalQGCJsonFile(const QJsonObject& jsonObject, + const QString& expectedFileType, + int minSupportedVersion, + int maxSupportedVersion, + int& version, + QString& errorString) +{ + // Validate required keys + QList requiredKeys = { + { jsonGroundStationKey, QJsonValue::String, true }, + }; + if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) { + return false; + } + + return validateInternalQGCJsonFile(jsonObject, expectedFileType, minSupportedVersion, maxSupportedVersion, version, errorString); } -QJsonObject JsonHelper::_translateObject(QJsonObject &jsonObject, - const QString &translateContext, - const QStringList &translateKeys) { - for (const QString &key : jsonObject.keys()) { - if (jsonObject[key].isString()) { - QString locString = jsonObject[key].toString(); - if (translateKeys.contains(key)) { - QString disambiguation; - QString disambiguationPrefix("#loc.disambiguation#"); - - if (locString.startsWith(disambiguationPrefix)) { - locString = locString.right(locString.length() - - disambiguationPrefix.length()); - int commentEndIndex = locString.indexOf("#"); - if (commentEndIndex != -1) { - disambiguation = locString.left(commentEndIndex); - locString = locString.right(locString.length() - - disambiguation.length() - 1); - } +QStringList JsonHelper::_addDefaultLocKeys(QJsonObject& jsonObject) +{ + QString translateKeys; + QString fileType = jsonObject[jsonFileTypeKey].toString(); + if (!fileType.isEmpty()) { + if (fileType == MissionCommandList::qgcFileType) { + if (jsonObject.contains(_translateKeysKey)) { + translateKeys = jsonObject[_translateKeysKey].toString(); + } else { + translateKeys = "label,enumStrings,friendlyName,description,category"; + jsonObject[_translateKeysKey] = translateKeys; + } + if (!jsonObject.contains(_arrayIDKeysKey)) { + jsonObject[_arrayIDKeysKey] = "rawName,comment"; + } + } else if (fileType == FactMetaData::qgcFileType) { + if (jsonObject.contains(_translateKeysKey)) { + translateKeys = jsonObject[_translateKeysKey].toString(); + } else { + translateKeys = "shortDescription,longDescription,enumStrings"; + jsonObject[_translateKeysKey] = "shortDescription,longDescription,enumStrings"; + } + if (!jsonObject.contains(_arrayIDKeysKey)) { + jsonObject[_arrayIDKeysKey] = "name"; + } } + } + return translateKeys.split(","); +} - QString xlatString = qgcApp()->qgcJSONTranslator().translate( - translateContext.toUtf8().constData(), - locString.toUtf8().constData(), - disambiguation.toUtf8().constData()); - if (!xlatString.isNull()) { - jsonObject[key] = xlatString; +QJsonObject JsonHelper::_translateObject(QJsonObject& jsonObject, const QString& translateContext, const QStringList& translateKeys) +{ + for (const QString& key: jsonObject.keys()) { + if (jsonObject[key].isString()) { + QString locString = jsonObject[key].toString(); + if (translateKeys.contains(key)) { + QString disambiguation; + QString disambiguationPrefix("#loc.disambiguation#"); + + if (locString.startsWith(disambiguationPrefix)) { + locString = locString.right(locString.length() - disambiguationPrefix.length()); + int commentEndIndex = locString.indexOf("#"); + if (commentEndIndex != -1) { + disambiguation = locString.left(commentEndIndex); + locString = locString.right(locString.length() - disambiguation.length() - 1); + } + } + + QString xlatString = qgcApp()->qgcJSONTranslator().translate(translateContext.toUtf8().constData(), locString.toUtf8().constData(), disambiguation.toUtf8().constData()); + if (!xlatString.isNull()) { + jsonObject[key] = xlatString; + } + } + } else if (jsonObject[key].isArray()) { + QJsonArray childJsonArray = jsonObject[key].toArray(); + jsonObject[key] = _translateArray(childJsonArray, translateContext, translateKeys); + } else if (jsonObject[key].isObject()) { + QJsonObject childJsonObject = jsonObject[key].toObject(); + jsonObject[key] = _translateObject(childJsonObject, translateContext, translateKeys); } - } - } else if (jsonObject[key].isArray()) { - QJsonObject childJsonArray = jsonObject[key].toArray(); - jsonObject[key] = - _translateArray(childJsonArray, translateContext, translateKeys); - } else if (jsonObject[key].isObject()) { - QJsonObject childJsonObject = jsonObject[key].toObject(); - jsonObject[key] = - _translateObject(childJsonObject, translateContext, translateKeys); - } - } - - return jsonObject; + } + + return jsonObject; } -QJsonObject JsonHelper::_translateArray(QJsonObject &jsonArray, - const QString &translateContext, - const QStringList &translateKeys) { - for (int i = 0; i < jsonArray.count(); i++) { - QJsonObject childJsonObject = jsonArray[i].toObject(); - jsonArray[i] = - _translateObject(childJsonObject, translateContext, translateKeys); - } +QJsonArray JsonHelper::_translateArray(QJsonArray& jsonArray, const QString& translateContext, const QStringList& translateKeys) +{ + for (int i=0; i &rgPoints, - QString &errorString) { - QVariantList rgVarPoints; +bool JsonHelper::loadGeoCoordinateArray(const QJsonValue& jsonValue, + bool altitudeRequired, + QList& rgPoints, + QString& errorString) +{ + QVariantList rgVarPoints; - if (!loadGeoCoordinateArray(jsonValue, altitudeRequired, rgVarPoints, - errorString)) { - return false; - } + if (!loadGeoCoordinateArray(jsonValue, altitudeRequired, rgVarPoints, errorString)) { + return false; + } - rgPoints.clear(); - for (int i = 0; i < rgVarPoints.count(); i++) { - rgPoints.append(rgVarPoints[i].value()); - } + rgPoints.clear(); + for (int i=0; i()); + } - return true; + return true; } -void JsonHelper::saveGeoCoordinateArray(const QVariantList &rgVarPoints, - bool writeAltitude, - QJsonValue &jsonValue) { - QJsonObject rgJsonPoints; +void JsonHelper::saveGeoCoordinateArray(const QVariantList& rgVarPoints, + bool writeAltitude, + QJsonValue& jsonValue) +{ + QJsonArray rgJsonPoints; - // Add all points to the array - for (int i = 0; i < rgVarPoints.count(); i++) { - QJsonValue jsonPoint; + // Add all points to the array + for (int i=0; i(), - writeAltitude, jsonPoint); - rgJsonPoints.append(jsonPoint); - } + JsonHelper::saveGeoCoordinate(rgVarPoints[i].value(), writeAltitude, jsonPoint); + rgJsonPoints.append(jsonPoint); + } - jsonValue = rgJsonPoints; + jsonValue = rgJsonPoints; } -void JsonHelper::saveGeoCoordinateArray(const QList &rgPoints, - bool writeAltitude, - QJsonValue &jsonValue) { - QVariantList rgVarPoints; +void JsonHelper::saveGeoCoordinateArray(const QList& rgPoints, + bool writeAltitude, + QJsonValue& jsonValue) +{ + QVariantList rgVarPoints; - for (int i = 0; i < rgPoints.count(); i++) { - rgVarPoints.append(QVariant::fromValue(rgPoints[i])); - } - return saveGeoCoordinateArray(rgVarPoints, writeAltitude, jsonValue); + for (int i=0; i &keyInfo, - QString &errorString) { - QStringList keyList; - QList typeList; - - for (int i = 0; i < keyInfo.count(); i++) { - if (keyInfo[i].required) { - keyList.append(keyInfo[i].key); - } - } - if (!validateRequiredKeys(jsonObject, keyList, errorString)) { - return false; - } - - keyList.clear(); - for (int i = 0; i < keyInfo.count(); i++) { - keyList.append(keyInfo[i].key); - typeList.append(keyInfo[i].type); - } - return validateKeyTypes(jsonObject, keyList, typeList, errorString); +bool JsonHelper::validateKeys(const QJsonObject& jsonObject, const QList& keyInfo, QString& errorString) +{ + QStringList keyList; + QList typeList; + + for (int i=0; i(i)->coordinate(); +void JsonHelper::savePolygon(QmlObjectListModel& list, QJsonArray& polygonArray) +{ + for (int i=0; i(i)->coordinate(); - QJsonValue jsonValue; - JsonHelper::saveGeoCoordinate(vertex, false /* writeAltitude */, jsonValue); - polygonArray.append(jsonValue); - } + QJsonValue jsonValue; + JsonHelper::saveGeoCoordinate(vertex, false /* writeAltitude */, jsonValue); + polygonArray.append(jsonValue); + } } -double JsonHelper::possibleNaNJsonValue(const QJsonValue &value) { - if (value.type() == QJsonValue::Null) { - return std::numeric_limits::quiet_NaN(); - } else { - return value.toDouble(); - } +double JsonHelper::possibleNaNJsonValue(const QJsonValue& value) +{ + if (value.type() == QJsonValue::Null) { + return std::numeric_limits::quiet_NaN(); + } else { + return value.toDouble(); + } } diff --git a/src/JsonHelper.h b/src/JsonHelper.h index f953ebe68d9918f0350984778a94bcc16cd434a3..74a9634ea9e5046dfcb8e926603d5013080451c5 100644 --- a/src/JsonHelper.h +++ b/src/JsonHelper.h @@ -9,10 +9,10 @@ #pragma once -#include -#include #include #include +#include +#include /// @file /// @author Don Gagne @@ -21,193 +21,164 @@ class QmlObjectListModel; /// @brief Json manipulation helper class. /// Primarily used for parsing and processing Fact metadata. -class JsonHelper { - Q_DECLARE_TR_FUNCTIONS(JsonHelper) +class JsonHelper +{ + Q_DECLARE_TR_FUNCTIONS(JsonHelper) public: - /// Determines is the specified file is a json file - /// @return true: file is json, false: file is not json - static bool isJsonFile(const QString &fileName, ///< filename - QJsonDocument &jsonDoc, ///< returned json document - QString &errorString); ///< error on parse failure - - /// Determines is the specified data is a json file - /// @return true: file is json, false: file is not json - static bool isJsonFile(const QByteArray &bytes, ///< json bytes - QJsonDocument &jsonDoc, ///< returned json document - QString &errorString); ///< error on parse failure - - /// Saves the standard file header the json object - static void - saveQGCJsonFileHeader(QJsonObject &jsonObject, ///< root json object - const QString &fileType, ///< file type for file - int version); ///< version number for file - - /// Validates the standard parts of an external QGC json file (Plan file, - /// ...): - /// jsonFileTypeKey - Required and checked to be equal to expectedFileType - /// jsonVersionKey - Required and checked to be below - /// supportedMajorVersion, supportedMinorVersion jsonGroundStationKey - - /// Required and checked to be string type - /// @return false: validation failed, errorString set - static bool validateExternalQGCJsonFile( - const QJsonObject &jsonObject, ///< json object to validate - const QString &expectedFileType, ///< correct file type for file - int minSupportedVersion, ///< minimum supported version - int maxSupportedVersion, ///< maximum supported major version - int &version, ///< returned file version - QString &errorString); ///< returned error string if validation fails - - /// Validates the standard parts of a internal QGC json file (FactMetaData, - /// ...): - /// jsonFileTypeKey - Required and checked to be equal to expectedFileType - /// jsonVersionKey - Required and checked to be below - /// supportedMajorVersion, supportedMinorVersion jsonGroundStationKey - - /// Required and checked to be string type - /// @return false: validation failed, errorString set - static bool validateInternalQGCJsonFile( - const QJsonObject &jsonObject, ///< json object to validate - const QString &expectedFileType, ///< correct file type for file - int minSupportedVersion, ///< minimum supported version - int maxSupportedVersion, ///< maximum supported major version - int &version, ///< returned file version - QString &errorString); ///< returned error string if validation fails - - // Opens, validates and translates an internal QGC json file. - // @return Json root object for file. Empty QJsonObject if error. - static QJsonObject openInternalQGCJsonFile( - const QString &jsonFilename, ///< Json file to open - const QString &expectedFileType, ///< correct file type for file - int minSupportedVersion, ///< minimum supported version - int maxSupportedVersion, ///< maximum supported major version - int &version, ///< returned file version - QString &errorString); ///< returned error string if validation fails - - /// Validates that the specified keys are in the object - /// @return false: validation failed, errorString set - static bool validateRequiredKeys( - const QJsonObject &jsonObject, ///< json object to validate - const QStringList &keys, ///< keys which are required to be present - QString &errorString); ///< returned error string if validation fails - - /// Validates the types of specified keys are in the object - /// @return false: validation failed, errorString set - static bool validateKeyTypes( - const QJsonObject &jsonObject, ///< json object to validate - const QStringList &keys, ///< keys to validate - const QList - &types, ///< required type for each key, QJsonValue::Null specifies - ///< double with possible NaN - QString &errorString); ///< returned error string if validation fails - - typedef struct { - const char *key; ///< json key name - QJsonValue::Type type; ///< required type for key, QJsonValue::Null - ///< specifies double with possible NaN - bool required; ///< true: key must be present - } KeyValidateInfo; - - static bool validateKeys(const QJsonObject &jsonObject, - const QList &keyInfo, - QString &errorString); - - /// Loads a QGeoCoordinate - /// Stored as array [ lat, lon, alt ] - /// @return false: validation failed - static bool loadGeoCoordinate( - const QJsonValue &jsonValue, ///< json value to load from - bool altitudeRequired, ///< true: altitude must be specified - QGeoCoordinate &coordinate, ///< returned QGeoCordinate - QString &errorString, ///< returned error string if load failure - bool geoJsonFormat = - false); ///< if true, use [lon, lat], [lat, lon] otherwise - - /// Saves a QGeoCoordinate - /// Stored as array [ lat, lon, alt ] - static void saveGeoCoordinate( - const QGeoCoordinate &coordinate, ///< QGeoCoordinate to save - bool writeAltitude, ///< true: write altitude to json - QJsonValue &jsonValue); ///< json value to save to - - /// Loads a QGeoCoordinate - /// Stored as array [ lon, lat, alt ] - /// @return false: validation failed - static bool loadGeoJsonCoordinate( - const QJsonValue &jsonValue, ///< json value to load from - bool altitudeRequired, ///< true: altitude must be specified - QGeoCoordinate &coordinate, ///< returned QGeoCordinate - QString &errorString); ///< returned error string if load failure - - /// Saves a QGeoCoordinate - /// Stored as array [ lon, lat, alt ] - static void saveGeoJsonCoordinate( - const QGeoCoordinate &coordinate, ///< QGeoCoordinate to save - bool writeAltitude, ///< true: write altitude to json - QJsonValue &jsonValue); ///< json value to save to - - /// Loads a polygon from an array - static bool loadPolygon( - const QJsonObject &polygonArray, ///< Array of coordinates - QmlObjectListModel &list, ///< Empty list to add vertices to - QObject *parent, ///< parent for newly allocated QGCQGeoCoordinates - QString &errorString); ///< returned error string if load failure - - /// Loads a list of QGeoCoordinates from a json array - /// @return false: validation failed - static bool loadGeoCoordinateArray( - const QJsonValue &jsonValue, ///< json value which contains points - bool altitudeRequired, ///< true: altitude field must be specified - QVariantList &rgVarPoints, ///< returned points - QString &errorString); ///< returned error string if load failure - static bool loadGeoCoordinateArray( - const QJsonValue &jsonValue, ///< json value which contains points - bool altitudeRequired, ///< true: altitude field must be specified - QList &rgPoints, ///< returned points - QString &errorString); ///< returned error string if load failure - - /// Saves a list of QGeoCoordinates to a json array - static void - saveGeoCoordinateArray(const QVariantList &rgVarPoints, ///< points to save - bool writeAltitude, ///< true: write altitide value - QJsonValue &jsonValue); ///< json value to save to - static void saveGeoCoordinateArray( - const QList &rgPoints, ///< points to save - bool writeAltitude, ///< true: write altitide value - QJsonValue &jsonValue); ///< json value to save to - - /// Saves a polygon to a json array - static void - savePolygon(QmlObjectListModel &list, ///< List which contains vertices - QJsonObject &polygonArray); ///< Array to save into - - /// Returns NaN if the value is null, or if not, the double value - static double possibleNaNJsonValue(const QJsonValue &value); - - static const char *jsonVersionKey; - static const char *jsonGroundStationKey; - static const char *jsonGroundStationValue; - static const char *jsonFileTypeKey; + /// Determines is the specified file is a json file + /// @return true: file is json, false: file is not json + static bool isJsonFile(const QString& fileName, ///< filename + QJsonDocument& jsonDoc, ///< returned json document + QString& errorString); ///< error on parse failure + + /// Determines is the specified data is a json file + /// @return true: file is json, false: file is not json + static bool isJsonFile(const QByteArray& bytes, ///< json bytes + QJsonDocument& jsonDoc, ///< returned json document + QString& errorString); ///< error on parse failure + + /// Saves the standard file header the json object + static void saveQGCJsonFileHeader(QJsonObject& jsonObject, ///< root json object + const QString& fileType, ///< file type for file + int version); ///< version number for file + + /// Validates the standard parts of an external QGC json file (Plan file, ...): + /// jsonFileTypeKey - Required and checked to be equal to expectedFileType + /// jsonVersionKey - Required and checked to be below supportedMajorVersion, supportedMinorVersion + /// jsonGroundStationKey - Required and checked to be string type + /// @return false: validation failed, errorString set + static bool validateExternalQGCJsonFile(const QJsonObject& jsonObject, ///< json object to validate + const QString& expectedFileType, ///< correct file type for file + int minSupportedVersion, ///< minimum supported version + int maxSupportedVersion, ///< maximum supported major version + int &version, ///< returned file version + QString& errorString); ///< returned error string if validation fails + + /// Validates the standard parts of a internal QGC json file (FactMetaData, ...): + /// jsonFileTypeKey - Required and checked to be equal to expectedFileType + /// jsonVersionKey - Required and checked to be below supportedMajorVersion, supportedMinorVersion + /// jsonGroundStationKey - Required and checked to be string type + /// @return false: validation failed, errorString set + static bool validateInternalQGCJsonFile(const QJsonObject& jsonObject, ///< json object to validate + const QString& expectedFileType, ///< correct file type for file + int minSupportedVersion, ///< minimum supported version + int maxSupportedVersion, ///< maximum supported major version + int &version, ///< returned file version + QString& errorString); ///< returned error string if validation fails + + // Opens, validates and translates an internal QGC json file. + // @return Json root object for file. Empty QJsonObject if error. + static QJsonObject openInternalQGCJsonFile(const QString& jsonFilename, ///< Json file to open + const QString& expectedFileType, ///< correct file type for file + int minSupportedVersion, ///< minimum supported version + int maxSupportedVersion, ///< maximum supported major version + int &version, ///< returned file version + QString& errorString); ///< returned error string if validation fails + + /// Validates that the specified keys are in the object + /// @return false: validation failed, errorString set + static bool validateRequiredKeys(const QJsonObject& jsonObject, ///< json object to validate + const QStringList& keys, ///< keys which are required to be present + QString& errorString); ///< returned error string if validation fails + + /// Validates the types of specified keys are in the object + /// @return false: validation failed, errorString set + static bool validateKeyTypes(const QJsonObject& jsonObject, ///< json object to validate + const QStringList& keys, ///< keys to validate + const QList& types, ///< required type for each key, QJsonValue::Null specifies double with possible NaN + QString& errorString); ///< returned error string if validation fails + + typedef struct { + const char* key; ///< json key name + QJsonValue::Type type; ///< required type for key, QJsonValue::Null specifies double with possible NaN + bool required; ///< true: key must be present + } KeyValidateInfo; + + static bool validateKeys(const QJsonObject& jsonObject, const QList& keyInfo, QString& errorString); + + /// Loads a QGeoCoordinate + /// Stored as array [ lat, lon, alt ] + /// @return false: validation failed + static bool loadGeoCoordinate(const QJsonValue& jsonValue, ///< json value to load from + bool altitudeRequired, ///< true: altitude must be specified + QGeoCoordinate& coordinate, ///< returned QGeoCordinate + QString& errorString, ///< returned error string if load failure + bool geoJsonFormat = false); ///< if true, use [lon, lat], [lat, lon] otherwise + + /// Saves a QGeoCoordinate + /// Stored as array [ lat, lon, alt ] + static void saveGeoCoordinate(const QGeoCoordinate& coordinate, ///< QGeoCoordinate to save + bool writeAltitude, ///< true: write altitude to json + QJsonValue& jsonValue); ///< json value to save to + + /// Loads a QGeoCoordinate + /// Stored as array [ lon, lat, alt ] + /// @return false: validation failed + static bool loadGeoJsonCoordinate(const QJsonValue& jsonValue, ///< json value to load from + bool altitudeRequired, ///< true: altitude must be specified + QGeoCoordinate& coordinate, ///< returned QGeoCordinate + QString& errorString); ///< returned error string if load failure + + /// Saves a QGeoCoordinate + /// Stored as array [ lon, lat, alt ] + static void saveGeoJsonCoordinate(const QGeoCoordinate& coordinate, ///< QGeoCoordinate to save + bool writeAltitude, ///< true: write altitude to json + QJsonValue& jsonValue); ///< json value to save to + + /// Loads a polygon from an array + static bool loadPolygon(const QJsonArray& polygonArray, ///< Array of coordinates + QmlObjectListModel& list, ///< Empty list to add vertices to + QObject* parent, ///< parent for newly allocated QGCQGeoCoordinates + QString& errorString); ///< returned error string if load failure + + /// Loads a list of QGeoCoordinates from a json array + /// @return false: validation failed + static bool loadGeoCoordinateArray(const QJsonValue& jsonValue, ///< json value which contains points + bool altitudeRequired, ///< true: altitude field must be specified + QVariantList& rgVarPoints, ///< returned points + QString& errorString); ///< returned error string if load failure + static bool loadGeoCoordinateArray(const QJsonValue& jsonValue, ///< json value which contains points + bool altitudeRequired, ///< true: altitude field must be specified + QList& rgPoints, ///< returned points + QString& errorString); ///< returned error string if load failure + + /// Saves a list of QGeoCoordinates to a json array + static void saveGeoCoordinateArray(const QVariantList& rgVarPoints, ///< points to save + bool writeAltitude, ///< true: write altitide value + QJsonValue& jsonValue); ///< json value to save to + static void saveGeoCoordinateArray(const QList& rgPoints, ///< points to save + bool writeAltitude, ///< true: write altitide value + QJsonValue& jsonValue); ///< json value to save to + + /// Saves a polygon to a json array + static void savePolygon(QmlObjectListModel& list, ///< List which contains vertices + QJsonArray& polygonArray); ///< Array to save into + + /// Returns NaN if the value is null, or if not, the double value + static double possibleNaNJsonValue(const QJsonValue& value); + + static const char* jsonVersionKey; + static const char* jsonGroundStationKey; + static const char* jsonGroundStationValue; + static const char* jsonFileTypeKey; private: - static QString _jsonValueTypeToString(QJsonValue::Type type); - static bool _loadGeoCoordinate(const QJsonValue &jsonValue, - bool altitudeRequired, - QGeoCoordinate &coordinate, - QString &errorString, bool geoJsonFormat); - static void _saveGeoCoordinate(const QGeoCoordinate &coordinate, - bool writeAltitude, QJsonValue &jsonValue, - bool geoJsonFormat); - static QStringList _addDefaultLocKeys(QJsonObject &jsonObject); - static QJsonObject _translateRoot(QJsonObject &jsonObject, - const QString &translateContext, - const QStringList &translateKeys); - static QJsonObject _translateObject(QJsonObject &jsonObject, - const QString &translateContext, - const QStringList &translateKeys); - static QJsonObject _translateArray(QJsonObject &jsonArray, - const QString &translateContext, - const QStringList &translateKeys); - - static const char *_translateKeysKey; - static const char *_arrayIDKeysKey; + static QString _jsonValueTypeToString(QJsonValue::Type type); + static bool _loadGeoCoordinate(const QJsonValue& jsonValue, + bool altitudeRequired, + QGeoCoordinate& coordinate, + QString& errorString, + bool geoJsonFormat); + static void _saveGeoCoordinate(const QGeoCoordinate& coordinate, + bool writeAltitude, + QJsonValue& jsonValue, + bool geoJsonFormat); + static QStringList _addDefaultLocKeys(QJsonObject& jsonObject); + static QJsonObject _translateRoot(QJsonObject& jsonObject, const QString& translateContext, const QStringList& translateKeys); + static QJsonObject _translateObject(QJsonObject& jsonObject, const QString& translateContext, const QStringList& translateKeys); + static QJsonArray _translateArray(QJsonArray& jsonArray, const QString& translateContext, const QStringList& translateKeys); + + static const char* _translateKeysKey; + static const char* _arrayIDKeysKey; }; diff --git a/src/MissionManager/CorridorScanComplexItem.cc b/src/MissionManager/CorridorScanComplexItem.cc index 704922bbae1be8a90b9d74d82eab2bffae515cae..73836271ebe33788ca2cff3e898e263f74138e58 100644 --- a/src/MissionManager/CorridorScanComplexItem.cc +++ b/src/MissionManager/CorridorScanComplexItem.cc @@ -61,7 +61,7 @@ CorridorScanComplexItem::CorridorScanComplexItem(PlanMasterController* masterCon setDirty(false); } -void CorridorScanComplexItem::save(QJsonObject& planItems) +void CorridorScanComplexItem::save(QJsonArray& planItems) { QJsonObject saveObject; diff --git a/src/MissionManager/CorridorScanComplexItem.h b/src/MissionManager/CorridorScanComplexItem.h index f4537251cc26575b1dff9a7c886924092abcfbba..6eefc413e65fb685aa0ec20103f93ec6520a5360 100644 --- a/src/MissionManager/CorridorScanComplexItem.h +++ b/src/MissionManager/CorridorScanComplexItem.h @@ -37,7 +37,7 @@ public: // Overrides from TransectStyleComplexItem QString patternName (void) const final { return name; } - void save (QJsonObject& planItems) final; + void save (QJsonArray& planItems) final; bool specifiesCoordinate (void) const final; double timeBetweenShots (void) final; diff --git a/src/MissionManager/FWLandingPatternTest.cc b/src/MissionManager/FWLandingPatternTest.cc index f86bebd05b65a2785b192003ae1a63dce21109b9..7e662dbf8fb1754aa88b80593ccec59987eca297 100644 --- a/src/MissionManager/FWLandingPatternTest.cc +++ b/src/MissionManager/FWLandingPatternTest.cc @@ -81,7 +81,7 @@ void FWLandingPatternTest::_testDirty(void) void FWLandingPatternTest::_testSaveLoad(void) { - QJsonObject items; + QJsonArray items; _fwItem->save(items); diff --git a/src/MissionManager/FixedWingLandingComplexItem.cc b/src/MissionManager/FixedWingLandingComplexItem.cc index 2da97f59452f1eef0d5da1cac959da919d8d6e69..486647360f11e88f5f501d78cc83b6fa2aec7093 100644 --- a/src/MissionManager/FixedWingLandingComplexItem.cc +++ b/src/MissionManager/FixedWingLandingComplexItem.cc @@ -60,7 +60,7 @@ FixedWingLandingComplexItem::FixedWingLandingComplexItem(PlanMasterController* m setDirty(false); } -void FixedWingLandingComplexItem::save(QJsonObject& missionItems) +void FixedWingLandingComplexItem::save(QJsonArray& missionItems) { QJsonObject saveObject = _save(); diff --git a/src/MissionManager/FixedWingLandingComplexItem.h b/src/MissionManager/FixedWingLandingComplexItem.h index 812f62706bc2cc68d47e86b1263c7472bd6d0b3e..7316fa88e784a91b094963a21dec43caca9295bb 100644 --- a/src/MissionManager/FixedWingLandingComplexItem.h +++ b/src/MissionManager/FixedWingLandingComplexItem.h @@ -44,7 +44,7 @@ public: QString mapVisualQML (void) const final { return QStringLiteral("FWLandingPatternMapVisual.qml"); } // Overrides from VisualMissionItem - void save (QJsonObject& missionItems) final; + void save (QJsonArray& missionItems) final; static const QString name; diff --git a/src/MissionManager/GeoFenceController.cc b/src/MissionManager/GeoFenceController.cc index db25fdc651e1db4b1f5e9d488158e88cacc3762d..4e3042d192e71e442a4add731916b7cdaa654d64 100644 --- a/src/MissionManager/GeoFenceController.cc +++ b/src/MissionManager/GeoFenceController.cc @@ -152,7 +152,7 @@ bool GeoFenceController::load(const QJsonObject& json, QString& errorString) return false; } - QJsonObject jsonPolygonArray = json[_jsonPolygonsKey].toArray(); + QJsonArray jsonPolygonArray = json[_jsonPolygonsKey].toArray(); for (const QJsonValue jsonPolygonValue: jsonPolygonArray) { if (jsonPolygonValue.type() != QJsonValue::Object) { errorString = tr("GeoFence polygon not stored as object"); @@ -166,7 +166,7 @@ bool GeoFenceController::load(const QJsonObject& json, QString& errorString) _polygons.append(fencePolygon); } - QJsonObject jsonCircleArray = json[_jsonCirclesKey].toArray(); + QJsonArray jsonCircleArray = json[_jsonCirclesKey].toArray(); for (const QJsonValue jsonCircleValue: jsonCircleArray) { if (jsonCircleValue.type() != QJsonValue::Object) { errorString = tr("GeoFence circle not stored as object"); @@ -200,7 +200,7 @@ void GeoFenceController::save(QJsonObject& json) { json[JsonHelper::jsonVersionKey] = _jsonCurrentVersion; - QJsonObject jsonPolygonArray; + QJsonArray jsonPolygonArray; for (int i=0; i<_polygons.count(); i++) { QJsonObject jsonPolygon; QGCFencePolygon* fencePolygon = _polygons.value(i); @@ -209,7 +209,7 @@ void GeoFenceController::save(QJsonObject& json) } json[_jsonPolygonsKey] = jsonPolygonArray; - QJsonObject jsonCircleArray; + QJsonArray jsonCircleArray; for (int i=0; i<_circles.count(); i++) { QJsonObject jsonCircle; QGCFenceCircle* fenceCircle = _circles.value(i); diff --git a/src/MissionManager/LandingComplexItemTest.h b/src/MissionManager/LandingComplexItemTest.h index 347f2bef4223a9f2e84589bebaf3e5b67061e48d..cf32f823049bd9f0b4eaf33ac6bc6b9d23c76df5 100644 --- a/src/MissionManager/LandingComplexItemTest.h +++ b/src/MissionManager/LandingComplexItemTest.h @@ -82,7 +82,7 @@ public: QString mapVisualQML(void) const final { return QStringLiteral("FWLandingPatternMapVisual.qml"); } // Overrides from VisualMissionItem - void save (QJsonObject& /*missionItems*/) { }; + void save (QJsonArray& /*missionItems*/) { }; static const QString name; diff --git a/src/MissionManager/MissionCommandList.cc b/src/MissionManager/MissionCommandList.cc index 0bc4b203f3b4afb5faaf95f45701b935e1fd7881..5ca37136c841ec346b05a9e8642153ff1dcf4fbb 100644 --- a/src/MissionManager/MissionCommandList.cc +++ b/src/MissionManager/MissionCommandList.cc @@ -55,7 +55,7 @@ void MissionCommandList::_loadMavCmdInfoJson(const QString& jsonFilename, bool b } // Iterate over MissionCommandUIInfo objects - QJsonObject jsonArray = jsonValue.toArray(); + QJsonArray jsonArray = jsonValue.toArray(); for(QJsonValue info: jsonArray) { if (!info.isObject()) { qWarning() << jsonFilename << "mavCmdArray should contain objects"; diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index c5651b741ffec2644351b960af91068ba220a074..e69d87b7daa9fdba8bc740f663663f3579cbeaee 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -744,7 +744,7 @@ bool MissionController::_loadJsonMissionFileV1(const QJsonObject &json, // Read complex items QList surveyItems; - QJsonObject complexArray(json[_jsonComplexItemsKey].toArray()); + QJsonArray complexArray(json[_jsonComplexItemsKey].toArray()); qCDebug(MissionControllerLog) << "Json load: complex item count" << complexArray.count(); for (int i = 0; i < complexArray.count(); i++) { @@ -771,7 +771,7 @@ bool MissionController::_loadJsonMissionFileV1(const QJsonObject &json, int nextSimpleItemIndex = 0; int nextComplexItemIndex = 0; int nextSequenceNumber = 1; // Start with 1 since home is in 0 - QJsonObject itemArray(json[_jsonItemsKey].toArray()); + QJsonArray itemArray(json[_jsonItemsKey].toArray()); MissionSettingsItem *settingsItem = _addMissionSettings(visualItems); if (json.contains(_jsonPlannedHomePositionKey)) { @@ -932,7 +932,7 @@ bool MissionController::_loadJsonMissionFileV2(const QJsonObject &json, // Read mission items int nextSequenceNumber = 1; // Start with 1 since home is in 0 - const QJsonObject rgMissionItems(json[_jsonItemsKey].toArray()); + const QJsonArray rgMissionItems(json[_jsonItemsKey].toArray()); for (int i = 0; i < rgMissionItems.count(); i++) { // Convert to QJsonObject const QJsonValue &itemValue = rgMissionItems[i]; @@ -1335,7 +1335,7 @@ void MissionController::save(QJsonObject &json) { // Save the visual items - QJsonObject rgJsonMissionItems; + QJsonArray rgJsonMissionItems; for (int i = 0; i < _visualItems->count(); i++) { VisualMissionItem *visualItem = qobject_cast(_visualItems->get(i)); diff --git a/src/MissionManager/MissionItem.cc b/src/MissionManager/MissionItem.cc index f9bad15527af2ed831a8b8ae397fe847348e23b9..3fb185546a789fc3082e1ae09a802199e854b1f8 100644 --- a/src/MissionManager/MissionItem.cc +++ b/src/MissionManager/MissionItem.cc @@ -147,7 +147,7 @@ void MissionItem::save(QJsonObject &json) const { json[_jsonAutoContinueKey] = autoContinue(); json[_jsonDoJumpIdKey] = _sequenceNumber; - QJsonObject rgParams = {param1(), param2(), param3(), param4(), + QJsonArray rgParams = {param1(), param2(), param3(), param4(), param5(), param6(), param7()}; json[_jsonParamsKey] = rgParams; } @@ -206,7 +206,7 @@ bool MissionItem::_convertJsonV1ToV2(const QJsonObject &json, VisualMissionItem::jsonTypeSimpleItemValue; } - QJsonObject rgParams = { + QJsonArray rgParams = { json[_jsonParam1Key].toDouble(), json[_jsonParam2Key].toDouble(), json[_jsonParam3Key].toDouble(), json[_jsonParam4Key].toDouble()}; v2Json[_jsonParamsKey] = rgParams; @@ -241,7 +241,7 @@ bool MissionItem::_convertJsonV2ToV3(QJsonObject &json, QString &errorString) { return false; } - QJsonObject rgParam = json[_jsonParamsKey].toArray(); + QJsonArray rgParam = json[_jsonParamsKey].toArray(); rgParam.append(coordinate.latitude()); rgParam.append(coordinate.longitude()); rgParam.append(coordinate.altitude()); @@ -283,7 +283,7 @@ bool MissionItem::load(const QJsonObject &json, int sequenceNumber, return false; } - QJsonObject rgParams = convertedJson[_jsonParamsKey].toArray(); + QJsonArray rgParams = convertedJson[_jsonParamsKey].toArray(); if (rgParams.count() != 7) { errorString = tr("%1 key must contains 7 values").arg(_jsonParamsKey); return false; diff --git a/src/MissionManager/MissionItemTest.cc b/src/MissionManager/MissionItemTest.cc index 45546e5507690bbb85858f1dee1a069d80d1a2fb..e2df716a21666becdf2a51b15481984dd8d41e91 100644 --- a/src/MissionManager/MissionItemTest.cc +++ b/src/MissionManager/MissionItemTest.cc @@ -342,7 +342,7 @@ void MissionItemTest::_testLoadFromJsonV2(void) QVERIFY(!errorString.isEmpty()); qDebug() << errorString; - QJsonObject badCoordinateArray; + QJsonArray badCoordinateArray; badCoordinateArray << -10.0 << -20.0 ; badObject = jsonObject; badObject.remove("coordinate"); @@ -351,7 +351,7 @@ void MissionItemTest::_testLoadFromJsonV2(void) QVERIFY(!errorString.isEmpty()); qDebug() << errorString; - QJsonObject badCoordinateArray_second; + QJsonArray badCoordinateArray_second; badCoordinateArray_second << -10.0 << -20.0 << true; badObject = jsonObject; badObject.remove("coordinate"); @@ -360,9 +360,9 @@ void MissionItemTest::_testLoadFromJsonV2(void) QVERIFY(!errorString.isEmpty()); qDebug() << errorString; - QJsonObject badCoordinateArray2; + QJsonArray badCoordinateArray2; badCoordinateArray2 << 1 << 2; - QJsonObject badCoordinateArray_third; + QJsonArray badCoordinateArray_third; badCoordinateArray_third << -10.0 << -20.0 << badCoordinateArray2; badObject = jsonObject; badObject.remove("coordinate"); @@ -412,7 +412,7 @@ void MissionItemTest::_testLoadFromJsonV3(void) // Incorrect param count badObject = jsonObject; - QJsonObject rgParam = badObject[MissionItem::_jsonParamsKey].toArray(); + QJsonArray rgParam = badObject[MissionItem::_jsonParamsKey].toArray(); rgParam.removeFirst(); badObject[MissionItem::_jsonParamsKey] = rgParam; QCOMPARE(missionItem.load(badObject, _seq, errorString), false); @@ -450,7 +450,7 @@ void MissionItemTest::_testSimpleLoadFromJson(void) SimpleMissionItem simpleMissionItem(_masterController, false /* flyView */, false /* forLoad */, nullptr); QString errorString; - QJsonObject coordinateArray; + QJsonArray coordinateArray; QJsonObject jsonObject; coordinateArray << -10.0 << -20.0 <<-30.0; @@ -460,7 +460,7 @@ void MissionItemTest::_testSimpleLoadFromJson(void) jsonObject.insert(VisualMissionItem::jsonTypeKey, VisualMissionItem::jsonTypeSimpleItemValue); jsonObject.insert(MissionItem::_jsonCoordinateKey, coordinateArray); - QJsonObject rgParams = { 10, 20, 30, 40 }; + QJsonArray rgParams = { 10, 20, 30, 40 }; jsonObject.insert(MissionItem::_jsonParamsKey, rgParams); QVERIFY(simpleMissionItem.load(jsonObject, _seq, errorString)); @@ -489,7 +489,7 @@ void MissionItemTest::_testSaveToJson(void) QJsonObject MissionItemTest::_createV1Json(void) { QJsonObject jsonObject; - QJsonObject coordinateArray; + QJsonArray coordinateArray; coordinateArray << -10.0 << -20.0 <<-30.0; jsonObject.insert(MissionItem::_jsonAutoContinueKey, true); @@ -508,7 +508,7 @@ QJsonObject MissionItemTest::_createV1Json(void) QJsonObject MissionItemTest::_createV2Json(void) { QJsonObject jsonObject; - QJsonObject coordinateArray; + QJsonArray coordinateArray; coordinateArray << -10.0 << -20.0 <<-30.0; jsonObject.insert(MissionItem::_jsonAutoContinueKey, true); @@ -517,7 +517,7 @@ QJsonObject MissionItemTest::_createV2Json(void) jsonObject.insert(VisualMissionItem::jsonTypeKey, VisualMissionItem::jsonTypeSimpleItemValue); jsonObject.insert(MissionItem::_jsonCoordinateKey, coordinateArray); - QJsonObject rgParams = { 10, 20, 30, 40 }; + QJsonArray rgParams = { 10, 20, 30, 40 }; jsonObject.insert(MissionItem::_jsonParamsKey, rgParams); return jsonObject; @@ -533,10 +533,10 @@ QJsonObject MissionItemTest::_createV3Json(bool allNaNs) jsonObject.insert(VisualMissionItem::jsonTypeKey, VisualMissionItem::jsonTypeSimpleItemValue); if (allNaNs) { - QJsonObject rgParams = { NAN, NAN, NAN, NAN, NAN, NAN, NAN }; + QJsonArray rgParams = { NAN, NAN, NAN, NAN, NAN, NAN, NAN }; jsonObject.insert(MissionItem::_jsonParamsKey, rgParams); } else { - QJsonObject rgParams = { 10, 20, 30, 40, -10, -20, -30 }; + QJsonArray rgParams = { 10, 20, 30, 40, -10, -20, -30 }; jsonObject.insert(MissionItem::_jsonParamsKey, rgParams); } diff --git a/src/MissionManager/MissionSettingsItem.cc b/src/MissionManager/MissionSettingsItem.cc index 326eb86c3193dac48bddd0a874825e91464fbb7f..fceba9d1d7c34b75a0e4ed6bd455d087d30c7e1d 100644 --- a/src/MissionManager/MissionSettingsItem.cc +++ b/src/MissionManager/MissionSettingsItem.cc @@ -88,7 +88,7 @@ void MissionSettingsItem::setDirty(bool dirty) } } -void MissionSettingsItem::save(QJsonObject& missionItems) +void MissionSettingsItem::save(QJsonArray& missionItems) { QList items; diff --git a/src/MissionManager/MissionSettingsItem.h b/src/MissionManager/MissionSettingsItem.h index 824dea40d4051553b51a613d072ef139d6a91617..921225c088c4d23b9745cec290db9032f7d27c07 100644 --- a/src/MissionManager/MissionSettingsItem.h +++ b/src/MissionManager/MissionSettingsItem.h @@ -87,7 +87,7 @@ public: void setDirty (bool dirty) final; void setCoordinate (const QGeoCoordinate& coordinate) final; // Should only be called if the end user is moving void setSequenceNumber (int sequenceNumber) final; - void save (QJsonObject& missionItems) final; + void save (QJsonArray& missionItems) final; double amslEntryAlt (void) const final { return _plannedHomePositionCoordinate.altitude(); } double amslExitAlt (void) const final { return amslEntryAlt(); } double minAMSLAltitude (void) const final { return amslEntryAlt(); } diff --git a/src/MissionManager/RallyPointController.cc b/src/MissionManager/RallyPointController.cc index 8faa00d0e0a9c3a983a907ce8329e9d128a38c39..77be144488159873bc98485c35761ee0fd28f067 100644 --- a/src/MissionManager/RallyPointController.cc +++ b/src/MissionManager/RallyPointController.cc @@ -128,7 +128,7 @@ void RallyPointController::save(QJsonObject& json) { json[JsonHelper::jsonVersionKey] = _jsonCurrentVersion; - QJsonObject rgPoints; + QJsonArray rgPoints; QJsonValue jsonPoint; for (int i=0; i<_points.count(); i++) { JsonHelper::saveGeoCoordinate(qobject_cast(_points[i])->coordinate(), true /* writeAltitude */, jsonPoint); diff --git a/src/MissionManager/SimpleMissionItem.cc b/src/MissionManager/SimpleMissionItem.cc index 14f1eff3b2abe73c68faba13b1cb79ede4552725..a1b4c70e8af9efabeffd875f30691885bc03d8e4 100644 --- a/src/MissionManager/SimpleMissionItem.cc +++ b/src/MissionManager/SimpleMissionItem.cc @@ -260,7 +260,7 @@ SimpleMissionItem::~SimpleMissionItem() { } -void SimpleMissionItem::save(QJsonObject& missionItems) +void SimpleMissionItem::save(QJsonArray& missionItems) { QList items; diff --git a/src/MissionManager/SimpleMissionItem.h b/src/MissionManager/SimpleMissionItem.h index 6fd358bfecf82f4a60defa74ed717f797ae7b00e..fd33ad4c341f66166dba827f99bc46882c87e6bc 100644 --- a/src/MissionManager/SimpleMissionItem.h +++ b/src/MissionManager/SimpleMissionItem.h @@ -127,7 +127,7 @@ public: void setCoordinate (const QGeoCoordinate& coordinate) override; void setSequenceNumber (int sequenceNumber) final; int lastSequenceNumber (void) const final; - void save (QJsonObject& missionItems) final; + void save (QJsonArray& missionItems) final; signals: void commandChanged (int command); diff --git a/src/MissionManager/StructureScanComplexItem.cc b/src/MissionManager/StructureScanComplexItem.cc index 580f1c28829a480531cbec72b98d01d6f414c5ad..db9d933c0c2d28f05f539abe9138d96b00f5d71b 100644 --- a/src/MissionManager/StructureScanComplexItem.cc +++ b/src/MissionManager/StructureScanComplexItem.cc @@ -177,7 +177,7 @@ void StructureScanComplexItem::setDirty(bool dirty) } } -void StructureScanComplexItem::save(QJsonObject& missionItems) +void StructureScanComplexItem::save(QJsonArray& missionItems) { QJsonObject saveObject; diff --git a/src/MissionManager/StructureScanComplexItem.h b/src/MissionManager/StructureScanComplexItem.h index 93916b8d17bf47e501e5ade32859da53cb169fbd..d99f4d9f7165efac1a08cc4788893d0fac22a26e 100644 --- a/src/MissionManager/StructureScanComplexItem.h +++ b/src/MissionManager/StructureScanComplexItem.h @@ -94,7 +94,7 @@ public: void setDirty (bool dirty) final; void setCoordinate (const QGeoCoordinate& coordinate) final { Q_UNUSED(coordinate); } void setSequenceNumber (int sequenceNumber) final; - void save (QJsonObject& missionItems) final; + void save (QJsonArray& missionItems) final; double amslEntryAlt (void) const final; double amslExitAlt (void) const final { return amslEntryAlt(); }; double minAMSLAltitude (void) const final; diff --git a/src/MissionManager/StructureScanComplexItemTest.cc b/src/MissionManager/StructureScanComplexItemTest.cc index 5fd8e51535d3b524f5ecdfc85bc51fa3e1134e57..db3c06e9a50d399f031b429f6d1e8e93053f3d4f 100644 --- a/src/MissionManager/StructureScanComplexItemTest.cc +++ b/src/MissionManager/StructureScanComplexItemTest.cc @@ -110,7 +110,7 @@ void StructureScanComplexItemTest::_testSaveLoad(void) { _initItem(); - QJsonObject items; + QJsonArray items; _structureScanItem->save(items); QString errorString; diff --git a/src/MissionManager/SurveyComplexItem.cc b/src/MissionManager/SurveyComplexItem.cc index c5e7621d24d3d84b957fa7c0978434b1f3156fd0..3096dab60bb3f865fe3d0baca48b10699d4c4ef5 100644 --- a/src/MissionManager/SurveyComplexItem.cc +++ b/src/MissionManager/SurveyComplexItem.cc @@ -7,15 +7,16 @@ * ****************************************************************************/ + #include "SurveyComplexItem.h" -#include "AppSettings.h" #include "JsonHelper.h" #include "MissionController.h" -#include "PlanMasterController.h" -#include "QGCApplication.h" #include "QGCGeo.h" #include "QGCQGeoCoordinate.h" #include "SettingsManager.h" +#include "AppSettings.h" +#include "PlanMasterController.h" +#include "QGCApplication.h" #include @@ -23,1625 +24,1417 @@ QGC_LOGGING_CATEGORY(SurveyComplexItemLog, "SurveyComplexItemLog") const QString SurveyComplexItem::name(tr("Survey")); -const char *SurveyComplexItem::jsonComplexItemTypeValue = "survey"; -const char *SurveyComplexItem::jsonV3ComplexItemTypeValue = "survey"; - -const char *SurveyComplexItem::settingsGroup = "Survey"; -const char *SurveyComplexItem::gridAngleName = "GridAngle"; -const char *SurveyComplexItem::gridEntryLocationName = "GridEntryLocation"; -const char *SurveyComplexItem::flyAlternateTransectsName = - "FlyAlternateTransects"; -const char *SurveyComplexItem::splitConcavePolygonsName = - "SplitConcavePolygons"; - -const char *SurveyComplexItem::_jsonGridAngleKey = "angle"; -const char *SurveyComplexItem::_jsonEntryPointKey = "entryLocation"; - -const char *SurveyComplexItem::_jsonV3GridObjectKey = "grid"; -const char *SurveyComplexItem::_jsonV3GridAltitudeKey = "altitude"; -const char *SurveyComplexItem::_jsonV3GridAltitudeRelativeKey = - "relativeAltitude"; -const char *SurveyComplexItem::_jsonV3GridAngleKey = "angle"; -const char *SurveyComplexItem::_jsonV3GridSpacingKey = "spacing"; -const char *SurveyComplexItem::_jsonV3EntryPointKey = "entryLocation"; -const char *SurveyComplexItem::_jsonV3TurnaroundDistKey = "turnAroundDistance"; -const char *SurveyComplexItem::_jsonV3CameraTriggerDistanceKey = - "cameraTriggerDistance"; -const char *SurveyComplexItem::_jsonV3CameraTriggerInTurnaroundKey = - "cameraTriggerInTurnaround"; -const char *SurveyComplexItem::_jsonV3HoverAndCaptureKey = "hoverAndCapture"; -const char *SurveyComplexItem::_jsonV3GroundResolutionKey = "groundResolution"; -const char *SurveyComplexItem::_jsonV3FrontalOverlapKey = "imageFrontalOverlap"; -const char *SurveyComplexItem::_jsonV3SideOverlapKey = "imageSideOverlap"; -const char *SurveyComplexItem::_jsonV3CameraSensorWidthKey = "sensorWidth"; -const char *SurveyComplexItem::_jsonV3CameraSensorHeightKey = "sensorHeight"; -const char *SurveyComplexItem::_jsonV3CameraResolutionWidthKey = - "resolutionWidth"; -const char *SurveyComplexItem::_jsonV3CameraResolutionHeightKey = - "resolutionHeight"; -const char *SurveyComplexItem::_jsonV3CameraFocalLengthKey = "focalLength"; -const char *SurveyComplexItem::_jsonV3CameraMinTriggerIntervalKey = - "minTriggerInterval"; -const char *SurveyComplexItem::_jsonV3CameraObjectKey = "camera"; -const char *SurveyComplexItem::_jsonV3CameraNameKey = "name"; -const char *SurveyComplexItem::_jsonV3ManualGridKey = "manualGrid"; -const char *SurveyComplexItem::_jsonV3CameraOrientationLandscapeKey = - "orientationLandscape"; -const char *SurveyComplexItem::_jsonV3FixedValueIsAltitudeKey = - "fixedValueIsAltitude"; -const char *SurveyComplexItem::_jsonV3Refly90DegreesKey = "refly90Degrees"; -const char *SurveyComplexItem::_jsonFlyAlternateTransectsKey = - "flyAlternateTransects"; -const char *SurveyComplexItem::_jsonSplitConcavePolygonsKey = - "splitConcavePolygons"; - -SurveyComplexItem::SurveyComplexItem(PlanMasterController *masterController, - bool flyView, const QString &kmlOrShpFile, - QObject *parent) - : TransectStyleComplexItem(masterController, flyView, settingsGroup, - parent), - _metaDataMap(FactMetaData::createMapFromJsonFile( - QStringLiteral(":/json/Survey.SettingsGroup.json"), this)), - _gridAngleFact(settingsGroup, _metaDataMap[gridAngleName]), - _flyAlternateTransectsFact(settingsGroup, - _metaDataMap[flyAlternateTransectsName]), - _splitConcavePolygonsFact(settingsGroup, - _metaDataMap[splitConcavePolygonsName]), - _entryPoint(EntryLocationTopLeft) { - _editorQml = "qrc:/qml/SurveyItemEditor.qml"; - - // If the user hasn't changed turnaround from the default (which is a fixed - // wing default) and we are multi-rotor set the multi-rotor default. NULL - // check since object creation during unit testing passes NULL for vehicle - if (_controllerVehicle && _controllerVehicle->multiRotor() && - _turnAroundDistanceFact.rawValue().toDouble() == - _turnAroundDistanceFact.rawDefaultValue().toDouble()) { - // Note this is set to 10 meters to work around a problem with PX4 Pro - // turnaround behavior. Don't change unless firmware gets better as well. - _turnAroundDistanceFact.setRawValue(10); - } - - if (_controllerVehicle && - !(_controllerVehicle->fixedWing() || _controllerVehicle->vtol())) { - // Only fixed wing flight paths support alternate transects - _flyAlternateTransectsFact.setRawValue(false); - } - - // We override the altitude to the mission default - if (_cameraCalc.isManualCamera() || - !_cameraCalc.valueSetIsDistance()->rawValue().toBool()) { - _cameraCalc.distanceToSurface()->setRawValue( - qgcApp() - ->toolbox() - ->settingsManager() - ->appSettings() - ->defaultMissionItemAltitude() - ->rawValue()); - } - - connect(&_gridAngleFact, &Fact::valueChanged, this, - &SurveyComplexItem::_setDirty); - connect(&_flyAlternateTransectsFact, &Fact::valueChanged, this, - &SurveyComplexItem::_setDirty); - connect(&_splitConcavePolygonsFact, &Fact::valueChanged, this, - &SurveyComplexItem::_setDirty); - connect(this, &SurveyComplexItem::refly90DegreesChanged, this, - &SurveyComplexItem::_setDirty); - - connect(&_gridAngleFact, &Fact::valueChanged, this, - &SurveyComplexItem::_rebuildTransects); - connect(&_flyAlternateTransectsFact, &Fact::valueChanged, this, - &SurveyComplexItem::_rebuildTransects); - connect(&_splitConcavePolygonsFact, &Fact::valueChanged, this, - &SurveyComplexItem::_rebuildTransects); - connect(this, &SurveyComplexItem::refly90DegreesChanged, this, - &SurveyComplexItem::_rebuildTransects); - - connect(&_surveyAreaPolygon, &QGCMapPolygon::isValidChanged, this, - &SurveyComplexItem::_updateWizardMode); - connect(&_surveyAreaPolygon, &QGCMapPolygon::traceModeChanged, this, - &SurveyComplexItem::_updateWizardMode); - - if (!kmlOrShpFile.isEmpty()) { - _surveyAreaPolygon.loadKMLOrSHPFile(kmlOrShpFile); - _surveyAreaPolygon.setDirty(false); - } - setDirty(false); -} +const char* SurveyComplexItem::jsonComplexItemTypeValue = "survey"; +const char* SurveyComplexItem::jsonV3ComplexItemTypeValue = "survey"; + +const char* SurveyComplexItem::settingsGroup = "Survey"; +const char* SurveyComplexItem::gridAngleName = "GridAngle"; +const char* SurveyComplexItem::gridEntryLocationName = "GridEntryLocation"; +const char* SurveyComplexItem::flyAlternateTransectsName = "FlyAlternateTransects"; +const char* SurveyComplexItem::splitConcavePolygonsName = "SplitConcavePolygons"; + +const char* SurveyComplexItem::_jsonGridAngleKey = "angle"; +const char* SurveyComplexItem::_jsonEntryPointKey = "entryLocation"; + +const char* SurveyComplexItem::_jsonV3GridObjectKey = "grid"; +const char* SurveyComplexItem::_jsonV3GridAltitudeKey = "altitude"; +const char* SurveyComplexItem::_jsonV3GridAltitudeRelativeKey = "relativeAltitude"; +const char* SurveyComplexItem::_jsonV3GridAngleKey = "angle"; +const char* SurveyComplexItem::_jsonV3GridSpacingKey = "spacing"; +const char* SurveyComplexItem::_jsonV3EntryPointKey = "entryLocation"; +const char* SurveyComplexItem::_jsonV3TurnaroundDistKey = "turnAroundDistance"; +const char* SurveyComplexItem::_jsonV3CameraTriggerDistanceKey = "cameraTriggerDistance"; +const char* SurveyComplexItem::_jsonV3CameraTriggerInTurnaroundKey = "cameraTriggerInTurnaround"; +const char* SurveyComplexItem::_jsonV3HoverAndCaptureKey = "hoverAndCapture"; +const char* SurveyComplexItem::_jsonV3GroundResolutionKey = "groundResolution"; +const char* SurveyComplexItem::_jsonV3FrontalOverlapKey = "imageFrontalOverlap"; +const char* SurveyComplexItem::_jsonV3SideOverlapKey = "imageSideOverlap"; +const char* SurveyComplexItem::_jsonV3CameraSensorWidthKey = "sensorWidth"; +const char* SurveyComplexItem::_jsonV3CameraSensorHeightKey = "sensorHeight"; +const char* SurveyComplexItem::_jsonV3CameraResolutionWidthKey = "resolutionWidth"; +const char* SurveyComplexItem::_jsonV3CameraResolutionHeightKey = "resolutionHeight"; +const char* SurveyComplexItem::_jsonV3CameraFocalLengthKey = "focalLength"; +const char* SurveyComplexItem::_jsonV3CameraMinTriggerIntervalKey = "minTriggerInterval"; +const char* SurveyComplexItem::_jsonV3CameraObjectKey = "camera"; +const char* SurveyComplexItem::_jsonV3CameraNameKey = "name"; +const char* SurveyComplexItem::_jsonV3ManualGridKey = "manualGrid"; +const char* SurveyComplexItem::_jsonV3CameraOrientationLandscapeKey = "orientationLandscape"; +const char* SurveyComplexItem::_jsonV3FixedValueIsAltitudeKey = "fixedValueIsAltitude"; +const char* SurveyComplexItem::_jsonV3Refly90DegreesKey = "refly90Degrees"; +const char* SurveyComplexItem::_jsonFlyAlternateTransectsKey = "flyAlternateTransects"; +const char* SurveyComplexItem::_jsonSplitConcavePolygonsKey = "splitConcavePolygons"; + +SurveyComplexItem::SurveyComplexItem(PlanMasterController* masterController, bool flyView, const QString& kmlOrShpFile, QObject* parent) + : TransectStyleComplexItem (masterController, flyView, settingsGroup, parent) + , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/Survey.SettingsGroup.json"), this)) + , _gridAngleFact (settingsGroup, _metaDataMap[gridAngleName]) + , _flyAlternateTransectsFact(settingsGroup, _metaDataMap[flyAlternateTransectsName]) + , _splitConcavePolygonsFact (settingsGroup, _metaDataMap[splitConcavePolygonsName]) + , _entryPoint (EntryLocationTopLeft) +{ + _editorQml = "qrc:/qml/SurveyItemEditor.qml"; + + // If the user hasn't changed turnaround from the default (which is a fixed wing default) and we are multi-rotor set the multi-rotor default. + // NULL check since object creation during unit testing passes NULL for vehicle + if (_controllerVehicle && _controllerVehicle->multiRotor() && _turnAroundDistanceFact.rawValue().toDouble() == _turnAroundDistanceFact.rawDefaultValue().toDouble()) { + // Note this is set to 10 meters to work around a problem with PX4 Pro turnaround behavior. Don't change unless firmware gets better as well. + _turnAroundDistanceFact.setRawValue(10); + } -void SurveyComplexItem::save(QJsonObject &planItems) { - QJsonObject saveObject; + if (_controllerVehicle && !(_controllerVehicle->fixedWing() || _controllerVehicle->vtol())) { + // Only fixed wing flight paths support alternate transects + _flyAlternateTransectsFact.setRawValue(false); + } + + // We override the altitude to the mission default + if (_cameraCalc.isManualCamera() || !_cameraCalc.valueSetIsDistance()->rawValue().toBool()) { + _cameraCalc.distanceToSurface()->setRawValue(qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue()); + } + + connect(&_gridAngleFact, &Fact::valueChanged, this, &SurveyComplexItem::_setDirty); + connect(&_flyAlternateTransectsFact,&Fact::valueChanged, this, &SurveyComplexItem::_setDirty); + connect(&_splitConcavePolygonsFact, &Fact::valueChanged, this, &SurveyComplexItem::_setDirty); + connect(this, &SurveyComplexItem::refly90DegreesChanged, this, &SurveyComplexItem::_setDirty); - _saveWorker(saveObject); - planItems.append(saveObject); + connect(&_gridAngleFact, &Fact::valueChanged, this, &SurveyComplexItem::_rebuildTransects); + connect(&_flyAlternateTransectsFact,&Fact::valueChanged, this, &SurveyComplexItem::_rebuildTransects); + connect(&_splitConcavePolygonsFact, &Fact::valueChanged, this, &SurveyComplexItem::_rebuildTransects); + connect(this, &SurveyComplexItem::refly90DegreesChanged, this, &SurveyComplexItem::_rebuildTransects); + + connect(&_surveyAreaPolygon, &QGCMapPolygon::isValidChanged, this, &SurveyComplexItem::_updateWizardMode); + connect(&_surveyAreaPolygon, &QGCMapPolygon::traceModeChanged, this, &SurveyComplexItem::_updateWizardMode); + + if (!kmlOrShpFile.isEmpty()) { + _surveyAreaPolygon.loadKMLOrSHPFile(kmlOrShpFile); + _surveyAreaPolygon.setDirty(false); + } + setDirty(false); } -void SurveyComplexItem::savePreset(const QString &name) { - QJsonObject saveObject; +void SurveyComplexItem::save(QJsonArray& planItems) +{ + QJsonObject saveObject; - _saveWorker(saveObject); - _savePresetJson(name, saveObject); + _saveWorker(saveObject); + planItems.append(saveObject); } -void SurveyComplexItem::_saveWorker(QJsonObject &saveObject) { - TransectStyleComplexItem::_save(saveObject); - - saveObject[JsonHelper::jsonVersionKey] = 5; - saveObject[VisualMissionItem::jsonTypeKey] = - VisualMissionItem::jsonTypeComplexItemValue; - saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = - jsonComplexItemTypeValue; - saveObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); - saveObject[_jsonFlyAlternateTransectsKey] = - _flyAlternateTransectsFact.rawValue().toBool(); - saveObject[_jsonSplitConcavePolygonsKey] = - _splitConcavePolygonsFact.rawValue().toBool(); - saveObject[_jsonEntryPointKey] = _entryPoint; - - // Polygon shape - _surveyAreaPolygon.saveToJson(saveObject); +void SurveyComplexItem::savePreset(const QString& name) +{ + QJsonObject saveObject; + + _saveWorker(saveObject); + _savePresetJson(name, saveObject); } -void SurveyComplexItem::loadPreset(const QString &name) { - QString errorString; - - QJsonObject presetObject = _loadPresetJson(name); - if (!_loadV4V5(presetObject, 0, errorString, 5, true /* forPresets */)) { - qgcApp()->showAppMessage( - QStringLiteral("Internal Error: Preset load failed. Name: %1 Error: %2") - .arg(name) - .arg(errorString)); - } - _rebuildTransects(); +void SurveyComplexItem::_saveWorker(QJsonObject& saveObject) +{ + TransectStyleComplexItem::_save(saveObject); + + saveObject[JsonHelper::jsonVersionKey] = 5; + saveObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + saveObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); + saveObject[_jsonFlyAlternateTransectsKey] = _flyAlternateTransectsFact.rawValue().toBool(); + saveObject[_jsonSplitConcavePolygonsKey] = _splitConcavePolygonsFact.rawValue().toBool(); + saveObject[_jsonEntryPointKey] = _entryPoint; + + // Polygon shape + _surveyAreaPolygon.saveToJson(saveObject); } -bool SurveyComplexItem::load(const QJsonObject &complexObject, - int sequenceNumber, QString &errorString) { - // 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(complexObject, versionKeyInfoList, - errorString)) { - return false; - } - - int version = complexObject[JsonHelper::jsonVersionKey].toInt(); - if (version < 2 || version > 5) { - errorString = tr("Survey items do not support version %1").arg(version); - return false; - } - - if (version == 4 || version == 5) { - if (!_loadV4V5(complexObject, sequenceNumber, errorString, version, - false /* forPresets */)) { - return false; - } - - _recalcComplexDistance(); - if (_cameraShots == 0) { - // Shot count was possibly not available from plan file - _recalcCameraShots(); - } - } else { - // Must be v2 or v3 - QJsonObject v3ComplexObject = complexObject; - if (version == 2) { - // Convert to v3 - if (v3ComplexObject.contains(VisualMissionItem::jsonTypeKey) && - v3ComplexObject[VisualMissionItem::jsonTypeKey].toString() == - QStringLiteral("survey")) { - v3ComplexObject[VisualMissionItem::jsonTypeKey] = - VisualMissionItem::jsonTypeComplexItemValue; - v3ComplexObject[ComplexMissionItem::jsonComplexItemTypeKey] = - jsonComplexItemTypeValue; - } - } - if (!_loadV3(complexObject, sequenceNumber, errorString)) { - return false; - } - - // V2/3 doesn't include individual items so we need to rebuild manually +void SurveyComplexItem::loadPreset(const QString& name) +{ + QString errorString; + + QJsonObject presetObject = _loadPresetJson(name); + if (!_loadV4V5(presetObject, 0, errorString, 5, true /* forPresets */)) { + qgcApp()->showAppMessage(QStringLiteral("Internal Error: Preset load failed. Name: %1 Error: %2").arg(name).arg(errorString)); + } _rebuildTransects(); - } +} + +bool SurveyComplexItem::load(const QJsonObject& complexObject, int sequenceNumber, QString& errorString) +{ + // 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(complexObject, versionKeyInfoList, errorString)) { + return false; + } + + int version = complexObject[JsonHelper::jsonVersionKey].toInt(); + if (version < 2 || version > 5) { + errorString = tr("Survey items do not support version %1").arg(version); + return false; + } + + if (version == 4 || version == 5) { + if (!_loadV4V5(complexObject, sequenceNumber, errorString, version, false /* forPresets */)) { + return false; + } + + _recalcComplexDistance(); + if (_cameraShots == 0) { + // Shot count was possibly not available from plan file + _recalcCameraShots(); + } + } else { + // Must be v2 or v3 + QJsonObject v3ComplexObject = complexObject; + if (version == 2) { + // Convert to v3 + if (v3ComplexObject.contains(VisualMissionItem::jsonTypeKey) && v3ComplexObject[VisualMissionItem::jsonTypeKey].toString() == QStringLiteral("survey")) { + v3ComplexObject[VisualMissionItem::jsonTypeKey] = VisualMissionItem::jsonTypeComplexItemValue; + v3ComplexObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + } + } + if (!_loadV3(complexObject, sequenceNumber, errorString)) { + return false; + } + + // V2/3 doesn't include individual items so we need to rebuild manually + _rebuildTransects(); + } - return true; + return true; } -bool SurveyComplexItem::_loadV4V5(const QJsonObject &complexObject, - int sequenceNumber, QString &errorString, - int version, bool forPresets) { - QList keyInfoList = { - {VisualMissionItem::jsonTypeKey, QJsonValue::String, true}, - {ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true}, - {_jsonEntryPointKey, QJsonValue::Double, true}, - {_jsonGridAngleKey, QJsonValue::Double, true}, - {_jsonFlyAlternateTransectsKey, QJsonValue::Bool, false}, - }; - - if (version == 5) { - JsonHelper::KeyValidateInfo jSplitPolygon = {_jsonSplitConcavePolygonsKey, - QJsonValue::Bool, true}; - keyInfoList.append(jSplitPolygon); - } - - if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) { - return false; - } - - QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString(); - QString complexType = - complexObject[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 = !forPresets; - - if (!forPresets) { - setSequenceNumber(sequenceNumber); +bool SurveyComplexItem::_loadV4V5(const QJsonObject& complexObject, int sequenceNumber, QString& errorString, int version, bool forPresets) +{ + QList keyInfoList = { + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { _jsonEntryPointKey, QJsonValue::Double, true }, + { _jsonGridAngleKey, QJsonValue::Double, true }, + { _jsonFlyAlternateTransectsKey, QJsonValue::Bool, false }, + }; - if (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, - errorString)) { - _surveyAreaPolygon.clear(); - return false; + if(version == 5) { + JsonHelper::KeyValidateInfo jSplitPolygon = { _jsonSplitConcavePolygonsKey, QJsonValue::Bool, true }; + keyInfoList.append(jSplitPolygon); } - } - if (!TransectStyleComplexItem::_load(complexObject, forPresets, - errorString)) { - _ignoreRecalc = false; - return false; - } + if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) { + return false; + } - _gridAngleFact.setRawValue(complexObject[_jsonGridAngleKey].toDouble()); - _flyAlternateTransectsFact.setRawValue( - complexObject[_jsonFlyAlternateTransectsKey].toBool(false)); + QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = complexObject[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; + } - if (version == 5) { - _splitConcavePolygonsFact.setRawValue( - complexObject[_jsonSplitConcavePolygonsKey].toBool(true)); - } + _ignoreRecalc = !forPresets; - _entryPoint = complexObject[_jsonEntryPointKey].toInt(); + if (!forPresets) { + setSequenceNumber(sequenceNumber); - _ignoreRecalc = false; + if (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, errorString)) { + _surveyAreaPolygon.clear(); + return false; + } + } - return true; -} + if (!TransectStyleComplexItem::_load(complexObject, forPresets, errorString)) { + _ignoreRecalc = false; + return false; + } + + _gridAngleFact.setRawValue (complexObject[_jsonGridAngleKey].toDouble()); + _flyAlternateTransectsFact.setRawValue (complexObject[_jsonFlyAlternateTransectsKey].toBool(false)); + + if (version == 5) { + _splitConcavePolygonsFact.setRawValue (complexObject[_jsonSplitConcavePolygonsKey].toBool(true)); + } + + _entryPoint = complexObject[_jsonEntryPointKey].toInt(); -bool SurveyComplexItem::_loadV3(const QJsonObject &complexObject, - int sequenceNumber, QString &errorString) { - QList mainKeyInfoList = { - {VisualMissionItem::jsonTypeKey, QJsonValue::String, true}, - {ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true}, - {QGCMapPolygon::jsonPolygonKey, QJsonValue::Array, true}, - {_jsonV3GridObjectKey, QJsonValue::Object, true}, - {_jsonV3CameraObjectKey, QJsonValue::Object, false}, - {_jsonV3CameraTriggerDistanceKey, QJsonValue::Double, true}, - {_jsonV3ManualGridKey, QJsonValue::Bool, true}, - {_jsonV3FixedValueIsAltitudeKey, QJsonValue::Bool, true}, - {_jsonV3HoverAndCaptureKey, QJsonValue::Bool, false}, - {_jsonV3Refly90DegreesKey, QJsonValue::Bool, false}, - {_jsonV3CameraTriggerInTurnaroundKey, QJsonValue::Bool, - false}, // Should really be required, but it was missing from initial - // code due to bug - }; - if (!JsonHelper::validateKeys(complexObject, mainKeyInfoList, errorString)) { - return false; - } - - QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString(); - QString complexType = - complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString(); - if (itemType != VisualMissionItem::jsonTypeComplexItemValue || - complexType != jsonV3ComplexItemTypeValue) { - 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; - - setSequenceNumber(sequenceNumber); - - _hoverAndCaptureFact.setRawValue( - complexObject[_jsonV3HoverAndCaptureKey].toBool(false)); - _refly90DegreesFact.setRawValue( - complexObject[_jsonV3Refly90DegreesKey].toBool(false)); - _cameraTriggerInTurnAroundFact.setRawValue( - complexObject[_jsonV3CameraTriggerInTurnaroundKey].toBool(true)); - - _cameraCalc.valueSetIsDistance()->setRawValue( - complexObject[_jsonV3FixedValueIsAltitudeKey].toBool(true)); - _cameraCalc.setDistanceToSurfaceRelative( - complexObject[_jsonV3GridAltitudeRelativeKey].toBool(true)); - - bool manualGrid = complexObject[_jsonV3ManualGridKey].toBool(true); - - QList gridKeyInfoList = { - {_jsonV3GridAltitudeKey, QJsonValue::Double, true}, - {_jsonV3GridAltitudeRelativeKey, QJsonValue::Bool, true}, - {_jsonV3GridAngleKey, QJsonValue::Double, true}, - {_jsonV3GridSpacingKey, QJsonValue::Double, true}, - {_jsonEntryPointKey, QJsonValue::Double, false}, - {_jsonV3TurnaroundDistKey, QJsonValue::Double, true}, - }; - QJsonObject gridObject = complexObject[_jsonV3GridObjectKey].toObject(); - if (!JsonHelper::validateKeys(gridObject, gridKeyInfoList, errorString)) { _ignoreRecalc = false; - return false; - } - - _gridAngleFact.setRawValue(gridObject[_jsonV3GridAngleKey].toDouble()); - _turnAroundDistanceFact.setRawValue( - gridObject[_jsonV3TurnaroundDistKey].toDouble()); - - if (gridObject.contains(_jsonEntryPointKey)) { - _entryPoint = gridObject[_jsonEntryPointKey].toInt(); - } else { - _entryPoint = EntryLocationTopRight; - } - - _cameraCalc.distanceToSurface()->setRawValue( - gridObject[_jsonV3GridAltitudeKey].toDouble()); - _cameraCalc.adjustedFootprintSide()->setRawValue( - gridObject[_jsonV3GridSpacingKey].toDouble()); - _cameraCalc.adjustedFootprintFrontal()->setRawValue( - complexObject[_jsonV3CameraTriggerDistanceKey].toDouble()); - - if (manualGrid) { - _cameraCalc.setCameraBrand(CameraCalc::canonicalManualCameraName()); - } else { - if (!complexObject.contains(_jsonV3CameraObjectKey)) { - errorString = tr("%1 but %2 object is missing") - .arg("manualGrid = false") - .arg("camera"); - _ignoreRecalc = false; - return false; - } - - QJsonObject cameraObject = complexObject[_jsonV3CameraObjectKey].toObject(); - - // Older code had typo on "imageSideOverlap" incorrectly being - // "imageSizeOverlap" - QString incorrectImageSideOverlap = "imageSizeOverlap"; - if (cameraObject.contains(incorrectImageSideOverlap)) { - cameraObject[_jsonV3SideOverlapKey] = - cameraObject[incorrectImageSideOverlap]; - cameraObject.remove(incorrectImageSideOverlap); - } - - QList cameraKeyInfoList = { - {_jsonV3GroundResolutionKey, QJsonValue::Double, true}, - {_jsonV3FrontalOverlapKey, QJsonValue::Double, true}, - {_jsonV3SideOverlapKey, QJsonValue::Double, true}, - {_jsonV3CameraSensorWidthKey, QJsonValue::Double, true}, - {_jsonV3CameraSensorHeightKey, QJsonValue::Double, true}, - {_jsonV3CameraResolutionWidthKey, QJsonValue::Double, true}, - {_jsonV3CameraResolutionHeightKey, QJsonValue::Double, true}, - {_jsonV3CameraFocalLengthKey, QJsonValue::Double, true}, - {_jsonV3CameraNameKey, QJsonValue::String, true}, - {_jsonV3CameraOrientationLandscapeKey, QJsonValue::Bool, true}, - {_jsonV3CameraMinTriggerIntervalKey, QJsonValue::Double, false}, + + return true; +} + +bool SurveyComplexItem::_loadV3(const QJsonObject& complexObject, int sequenceNumber, QString& errorString) +{ + QList mainKeyInfoList = { + { VisualMissionItem::jsonTypeKey, QJsonValue::String, true }, + { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true }, + { QGCMapPolygon::jsonPolygonKey, QJsonValue::Array, true }, + { _jsonV3GridObjectKey, QJsonValue::Object, true }, + { _jsonV3CameraObjectKey, QJsonValue::Object, false }, + { _jsonV3CameraTriggerDistanceKey, QJsonValue::Double, true }, + { _jsonV3ManualGridKey, QJsonValue::Bool, true }, + { _jsonV3FixedValueIsAltitudeKey, QJsonValue::Bool, true }, + { _jsonV3HoverAndCaptureKey, QJsonValue::Bool, false }, + { _jsonV3Refly90DegreesKey, QJsonValue::Bool, false }, + { _jsonV3CameraTriggerInTurnaroundKey, QJsonValue::Bool, false }, // Should really be required, but it was missing from initial code due to bug }; - if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, - errorString)) { - _ignoreRecalc = false; - return false; - } - - _cameraCalc.landscape()->setRawValue( - cameraObject[_jsonV3CameraOrientationLandscapeKey].toBool(true)); - _cameraCalc.frontalOverlap()->setRawValue( - cameraObject[_jsonV3FrontalOverlapKey].toInt()); - _cameraCalc.sideOverlap()->setRawValue( - cameraObject[_jsonV3SideOverlapKey].toInt()); - _cameraCalc.sensorWidth()->setRawValue( - cameraObject[_jsonV3CameraSensorWidthKey].toDouble()); - _cameraCalc.sensorHeight()->setRawValue( - cameraObject[_jsonV3CameraSensorHeightKey].toDouble()); - _cameraCalc.focalLength()->setRawValue( - cameraObject[_jsonV3CameraFocalLengthKey].toDouble()); - _cameraCalc.imageWidth()->setRawValue( - cameraObject[_jsonV3CameraResolutionWidthKey].toInt()); - _cameraCalc.imageHeight()->setRawValue( - cameraObject[_jsonV3CameraResolutionHeightKey].toInt()); - _cameraCalc.minTriggerInterval()->setRawValue( - cameraObject[_jsonV3CameraMinTriggerIntervalKey].toDouble(0)); - _cameraCalc.imageDensity()->setRawValue( - cameraObject[_jsonV3GroundResolutionKey].toDouble()); - _cameraCalc.fixedOrientation()->setRawValue(false); - _cameraCalc._setCameraNameFromV3TransectLoad( - cameraObject[_jsonV3CameraNameKey].toString()); - } - - // 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 (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, - errorString)) { - _surveyAreaPolygon.clear(); - _ignoreRecalc = false; - return false; - } + if (!JsonHelper::validateKeys(complexObject, mainKeyInfoList, errorString)) { + return false; + } + + QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString(); + QString complexType = complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString(); + if (itemType != VisualMissionItem::jsonTypeComplexItemValue || complexType != jsonV3ComplexItemTypeValue) { + 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; + + setSequenceNumber(sequenceNumber); + + _hoverAndCaptureFact.setRawValue (complexObject[_jsonV3HoverAndCaptureKey].toBool(false)); + _refly90DegreesFact.setRawValue (complexObject[_jsonV3Refly90DegreesKey].toBool(false)); + _cameraTriggerInTurnAroundFact.setRawValue (complexObject[_jsonV3CameraTriggerInTurnaroundKey].toBool(true)); + + _cameraCalc.valueSetIsDistance()->setRawValue (complexObject[_jsonV3FixedValueIsAltitudeKey].toBool(true)); + _cameraCalc.setDistanceToSurfaceRelative (complexObject[_jsonV3GridAltitudeRelativeKey].toBool(true)); + + bool manualGrid = complexObject[_jsonV3ManualGridKey].toBool(true); + + QList gridKeyInfoList = { + { _jsonV3GridAltitudeKey, QJsonValue::Double, true }, + { _jsonV3GridAltitudeRelativeKey, QJsonValue::Bool, true }, + { _jsonV3GridAngleKey, QJsonValue::Double, true }, + { _jsonV3GridSpacingKey, QJsonValue::Double, true }, + { _jsonEntryPointKey, QJsonValue::Double, false }, + { _jsonV3TurnaroundDistKey, QJsonValue::Double, true }, + }; + QJsonObject gridObject = complexObject[_jsonV3GridObjectKey].toObject(); + if (!JsonHelper::validateKeys(gridObject, gridKeyInfoList, errorString)) { + _ignoreRecalc = false; + return false; + } + + _gridAngleFact.setRawValue (gridObject[_jsonV3GridAngleKey].toDouble()); + _turnAroundDistanceFact.setRawValue (gridObject[_jsonV3TurnaroundDistKey].toDouble()); + + if (gridObject.contains(_jsonEntryPointKey)) { + _entryPoint = gridObject[_jsonEntryPointKey].toInt(); + } else { + _entryPoint = EntryLocationTopRight; + } + + _cameraCalc.distanceToSurface()->setRawValue (gridObject[_jsonV3GridAltitudeKey].toDouble()); + _cameraCalc.adjustedFootprintSide()->setRawValue (gridObject[_jsonV3GridSpacingKey].toDouble()); + _cameraCalc.adjustedFootprintFrontal()->setRawValue (complexObject[_jsonV3CameraTriggerDistanceKey].toDouble()); + + if (manualGrid) { + _cameraCalc.setCameraBrand(CameraCalc::canonicalManualCameraName()); + } else { + if (!complexObject.contains(_jsonV3CameraObjectKey)) { + errorString = tr("%1 but %2 object is missing").arg("manualGrid = false").arg("camera"); + _ignoreRecalc = false; + return false; + } + + QJsonObject cameraObject = complexObject[_jsonV3CameraObjectKey].toObject(); + + // Older code had typo on "imageSideOverlap" incorrectly being "imageSizeOverlap" + QString incorrectImageSideOverlap = "imageSizeOverlap"; + if (cameraObject.contains(incorrectImageSideOverlap)) { + cameraObject[_jsonV3SideOverlapKey] = cameraObject[incorrectImageSideOverlap]; + cameraObject.remove(incorrectImageSideOverlap); + } - _ignoreRecalc = false; + QList cameraKeyInfoList = { + { _jsonV3GroundResolutionKey, QJsonValue::Double, true }, + { _jsonV3FrontalOverlapKey, QJsonValue::Double, true }, + { _jsonV3SideOverlapKey, QJsonValue::Double, true }, + { _jsonV3CameraSensorWidthKey, QJsonValue::Double, true }, + { _jsonV3CameraSensorHeightKey, QJsonValue::Double, true }, + { _jsonV3CameraResolutionWidthKey, QJsonValue::Double, true }, + { _jsonV3CameraResolutionHeightKey, QJsonValue::Double, true }, + { _jsonV3CameraFocalLengthKey, QJsonValue::Double, true }, + { _jsonV3CameraNameKey, QJsonValue::String, true }, + { _jsonV3CameraOrientationLandscapeKey, QJsonValue::Bool, true }, + { _jsonV3CameraMinTriggerIntervalKey, QJsonValue::Double, false }, + }; + if (!JsonHelper::validateKeys(cameraObject, cameraKeyInfoList, errorString)) { + _ignoreRecalc = false; + return false; + } + + _cameraCalc.landscape()->setRawValue (cameraObject[_jsonV3CameraOrientationLandscapeKey].toBool(true)); + _cameraCalc.frontalOverlap()->setRawValue (cameraObject[_jsonV3FrontalOverlapKey].toInt()); + _cameraCalc.sideOverlap()->setRawValue (cameraObject[_jsonV3SideOverlapKey].toInt()); + _cameraCalc.sensorWidth()->setRawValue (cameraObject[_jsonV3CameraSensorWidthKey].toDouble()); + _cameraCalc.sensorHeight()->setRawValue (cameraObject[_jsonV3CameraSensorHeightKey].toDouble()); + _cameraCalc.focalLength()->setRawValue (cameraObject[_jsonV3CameraFocalLengthKey].toDouble()); + _cameraCalc.imageWidth()->setRawValue (cameraObject[_jsonV3CameraResolutionWidthKey].toInt()); + _cameraCalc.imageHeight()->setRawValue (cameraObject[_jsonV3CameraResolutionHeightKey].toInt()); + _cameraCalc.minTriggerInterval()->setRawValue (cameraObject[_jsonV3CameraMinTriggerIntervalKey].toDouble(0)); + _cameraCalc.imageDensity()->setRawValue (cameraObject[_jsonV3GroundResolutionKey].toDouble()); + _cameraCalc.fixedOrientation()->setRawValue (false); + _cameraCalc._setCameraNameFromV3TransectLoad (cameraObject[_jsonV3CameraNameKey].toString()); + } + + // 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 (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, errorString)) { + _surveyAreaPolygon.clear(); + _ignoreRecalc = false; + return false; + } + + _ignoreRecalc = false; - return true; + return true; } -/// Reverse the order of the transects. First transect becomes last and so -/// forth. -void SurveyComplexItem::_reverseTransectOrder( - QList> &transects) { - QList> rgReversedTransects; - for (int i = transects.count() - 1; i >= 0; i--) { - rgReversedTransects.append(transects[i]); - } - transects = rgReversedTransects; +/// Reverse the order of the transects. First transect becomes last and so forth. +void SurveyComplexItem::_reverseTransectOrder(QList>& transects) +{ + QList> rgReversedTransects; + for (int i=transects.count() - 1; i>=0; i--) { + rgReversedTransects.append(transects[i]); + } + transects = rgReversedTransects; } -/// Reverse the order of all points withing each transect, First point becomes -/// last and so forth. -void SurveyComplexItem::_reverseInternalTransectPoints( - QList> &transects) { - for (int i = 0; i < transects.count(); i++) { - QList rgReversedCoords; - QList &rgOriginalCoords = transects[i]; - for (int j = rgOriginalCoords.count() - 1; j >= 0; j--) { - rgReversedCoords.append(rgOriginalCoords[j]); - } - transects[i] = rgReversedCoords; - } +/// Reverse the order of all points withing each transect, First point becomes last and so forth. +void SurveyComplexItem::_reverseInternalTransectPoints(QList>& transects) +{ + for (int i=0; i rgReversedCoords; + QList& rgOriginalCoords = transects[i]; + for (int j=rgOriginalCoords.count()-1; j>=0; j--) { + rgReversedCoords.append(rgOriginalCoords[j]); + } + transects[i] = rgReversedCoords; + } } -/// Reorders the transects such that the first transect is the shortest distance -/// to the specified coordinate and the first point within that transect is the -/// shortest distance to the specified coordinate. +/// Reorders the transects such that the first transect is the shortest distance to the specified coordinate +/// and the first point within that transect is the shortest distance to the specified coordinate. /// @param distanceCoord Coordinate to measure distance against /// @param transects Transects to test and reorder -void SurveyComplexItem::_optimizeTransectsForShortestDistance( - const QGeoCoordinate &distanceCoord, - QList> &transects) { - double rgTransectDistance[4]; - rgTransectDistance[0] = transects.first().first().distanceTo(distanceCoord); - rgTransectDistance[1] = transects.first().last().distanceTo(distanceCoord); - rgTransectDistance[2] = transects.last().first().distanceTo(distanceCoord); - rgTransectDistance[3] = transects.last().last().distanceTo(distanceCoord); - - int shortestIndex = 0; - double shortestDistance = rgTransectDistance[0]; - for (int i = 1; i < 3; i++) { - if (rgTransectDistance[i] < shortestDistance) { - shortestIndex = i; - shortestDistance = rgTransectDistance[i]; - } - } - - if (shortestIndex > 1) { - // We need to reverse the order of segments - _reverseTransectOrder(transects); - } - if (shortestIndex & 1) { - // We need to reverse the points within each segment - _reverseInternalTransectPoints(transects); - } +void SurveyComplexItem::_optimizeTransectsForShortestDistance(const QGeoCoordinate& distanceCoord, QList>& transects) +{ + double rgTransectDistance[4]; + rgTransectDistance[0] = transects.first().first().distanceTo(distanceCoord); + rgTransectDistance[1] = transects.first().last().distanceTo(distanceCoord); + rgTransectDistance[2] = transects.last().first().distanceTo(distanceCoord); + rgTransectDistance[3] = transects.last().last().distanceTo(distanceCoord); + + int shortestIndex = 0; + double shortestDistance = rgTransectDistance[0]; + for (int i=1; i<3; i++) { + if (rgTransectDistance[i] < shortestDistance) { + shortestIndex = i; + shortestDistance = rgTransectDistance[i]; + } + } + + if (shortestIndex > 1) { + // We need to reverse the order of segments + _reverseTransectOrder(transects); + } + if (shortestIndex & 1) { + // We need to reverse the points within each segment + _reverseInternalTransectPoints(transects); + } } -qreal SurveyComplexItem::_ccw(QPointF pt1, QPointF pt2, QPointF pt3) { - return (pt2.x() - pt1.x()) * (pt3.y() - pt1.y()) - - (pt2.y() - pt1.y()) * (pt3.x() - pt1.x()); +qreal SurveyComplexItem::_ccw(QPointF pt1, QPointF pt2, QPointF pt3) +{ + return (pt2.x()-pt1.x())*(pt3.y()-pt1.y()) - (pt2.y()-pt1.y())*(pt3.x()-pt1.x()); } -qreal SurveyComplexItem::_dp(QPointF pt1, QPointF pt2) { - return (pt2.x() - pt1.x()) / qSqrt((pt2.x() - pt1.x()) * (pt2.x() - pt1.x()) + - (pt2.y() - pt1.y()) * (pt2.y() - pt1.y())); +qreal SurveyComplexItem::_dp(QPointF pt1, QPointF pt2) +{ + return (pt2.x()-pt1.x())/qSqrt((pt2.x()-pt1.x())*(pt2.x()-pt1.x()) + (pt2.y()-pt1.y())*(pt2.y()-pt1.y())); } -void SurveyComplexItem::_swapPoints(QList &points, int index1, - int index2) { - QPointF temp = points[index1]; - points[index1] = points[index2]; - points[index2] = temp; +void SurveyComplexItem::_swapPoints(QList& points, int index1, int index2) +{ + QPointF temp = points[index1]; + points[index1] = points[index2]; + points[index2] = temp; } -/// Returns true if the current grid angle generates north/south oriented -/// transects -bool SurveyComplexItem::_gridAngleIsNorthSouthTransects() { - // Grid angle ranges from -360<->360 - double gridAngle = qAbs(_gridAngleFact.rawValue().toDouble()); - return gridAngle < 45.0 || (gridAngle > 360.0 - 45.0) || - (gridAngle > 90.0 + 45.0 && gridAngle < 270.0 - 45.0); +/// Returns true if the current grid angle generates north/south oriented transects +bool SurveyComplexItem::_gridAngleIsNorthSouthTransects() +{ + // Grid angle ranges from -360<->360 + double gridAngle = qAbs(_gridAngleFact.rawValue().toDouble()); + return gridAngle < 45.0 || (gridAngle > 360.0 - 45.0) || (gridAngle > 90.0 + 45.0 && gridAngle < 270.0 - 45.0); } -void SurveyComplexItem::_adjustTransectsToEntryPointLocation( - QList> &transects) { - if (transects.count() == 0) { - return; - } - - bool reversePoints = false; - bool reverseTransects = false; - - if (_entryPoint == EntryLocationBottomLeft || - _entryPoint == EntryLocationBottomRight) { - reversePoints = true; - } - if (_entryPoint == EntryLocationTopRight || - _entryPoint == EntryLocationBottomRight) { - reverseTransects = true; - } - - if (reversePoints) { - qCDebug(SurveyComplexItemLog) - << "_adjustTransectsToEntryPointLocation Reverse Points"; - _reverseInternalTransectPoints(transects); - } - if (reverseTransects) { - qCDebug(SurveyComplexItemLog) - << "_adjustTransectsToEntryPointLocation Reverse Transects"; - _reverseTransectOrder(transects); - } - - qCDebug(SurveyComplexItemLog) << "_adjustTransectsToEntryPointLocation " - "Modified entry point:entryLocation" - << transects.first().first() << _entryPoint; +void SurveyComplexItem::_adjustTransectsToEntryPointLocation(QList>& transects) +{ + if (transects.count() == 0) { + return; + } + + bool reversePoints = false; + bool reverseTransects = false; + + if (_entryPoint == EntryLocationBottomLeft || _entryPoint == EntryLocationBottomRight) { + reversePoints = true; + } + if (_entryPoint == EntryLocationTopRight || _entryPoint == EntryLocationBottomRight) { + reverseTransects = true; + } + + if (reversePoints) { + qCDebug(SurveyComplexItemLog) << "_adjustTransectsToEntryPointLocation Reverse Points"; + _reverseInternalTransectPoints(transects); + } + if (reverseTransects) { + qCDebug(SurveyComplexItemLog) << "_adjustTransectsToEntryPointLocation Reverse Transects"; + _reverseTransectOrder(transects); + } + + qCDebug(SurveyComplexItemLog) << "_adjustTransectsToEntryPointLocation Modified entry point:entryLocation" << transects.first().first() << _entryPoint; } -QPointF SurveyComplexItem::_rotatePoint(const QPointF &point, - const QPointF &origin, double angle) { - QPointF rotated; - double radians = (M_PI / 180.0) * -angle; +QPointF SurveyComplexItem::_rotatePoint(const QPointF& point, const QPointF& origin, double angle) +{ + QPointF rotated; + double radians = (M_PI / 180.0) * -angle; - rotated.setX(((point.x() - origin.x()) * cos(radians)) - - ((point.y() - origin.y()) * sin(radians)) + origin.x()); - rotated.setY(((point.x() - origin.x()) * sin(radians)) + - ((point.y() - origin.y()) * cos(radians)) + origin.y()); + rotated.setX(((point.x() - origin.x()) * cos(radians)) - ((point.y() - origin.y()) * sin(radians)) + origin.x()); + rotated.setY(((point.x() - origin.x()) * sin(radians)) + ((point.y() - origin.y()) * cos(radians)) + origin.y()); - return rotated; + return rotated; } -void SurveyComplexItem::_intersectLinesWithRect(const QList &lineList, - const QRectF &boundRect, - QList &resultLines) { - QLineF topLine(boundRect.topLeft(), boundRect.topRight()); - QLineF bottomLine(boundRect.bottomLeft(), boundRect.bottomRight()); - QLineF leftLine(boundRect.topLeft(), boundRect.bottomLeft()); - QLineF rightLine(boundRect.topRight(), boundRect.bottomRight()); - - for (int i = 0; i < lineList.count(); i++) { - QPointF intersectPoint; - QLineF intersectLine; - const QLineF &line = lineList[i]; - - auto isLineBoundedIntersect = - [&line, &intersectPoint](const QLineF &linePosition) { +void SurveyComplexItem::_intersectLinesWithRect(const QList& lineList, const QRectF& boundRect, QList& resultLines) +{ + QLineF topLine (boundRect.topLeft(), boundRect.topRight()); + QLineF bottomLine (boundRect.bottomLeft(), boundRect.bottomRight()); + QLineF leftLine (boundRect.topLeft(), boundRect.bottomLeft()); + QLineF rightLine (boundRect.topRight(), boundRect.bottomRight()); + + for (int i=0; i &lineList, const QPolygonF &polygon, - QList &resultLines) { - resultLines.clear(); +void SurveyComplexItem::_intersectLinesWithPolygon(const QList& lineList, const QPolygonF& polygon, QList& resultLines) +{ + resultLines.clear(); - for (int i = 0; i < lineList.count(); i++) { - const QLineF &line = lineList[i]; - QList intersections; + for (int i=0; i intersections; - // Intersect the line with all the polygon edges - for (int j = 0; j < polygon.count() - 1; j++) { - QPointF intersectPoint; - QLineF polygonLine = QLineF(polygon[j], polygon[j + 1]); + // Intersect the line with all the polygon edges + for (int j=0; j 1) { - QPointF firstPoint; - QPointF secondPoint; - double currentMaxDistance = 0; - - for (int i = 0; i < intersections.count(); i++) { - for (int j = 0; j < intersections.count(); j++) { - QLineF lineTest(intersections[i], intersections[j]); - - double newMaxDistance = lineTest.length(); - if (newMaxDistance > currentMaxDistance) { - firstPoint = intersections[i]; - secondPoint = intersections[j]; - currentMaxDistance = newMaxDistance; - } + if (intersect == QLineF::BoundedIntersection) { + if (!intersections.contains(intersectPoint)) { + intersections.append(intersectPoint); + } + } } - } - resultLines += QLineF(firstPoint, secondPoint); + // We now have one or more intersection points all along the same line. Find the two + // which are furthest away from each other to form the transect. + if (intersections.count() > 1) { + QPointF firstPoint; + QPointF secondPoint; + double currentMaxDistance = 0; + + for (int i=0; i currentMaxDistance) { + firstPoint = intersections[i]; + secondPoint = intersections[j]; + currentMaxDistance = newMaxDistance; + } + } + } + + resultLines += QLineF(firstPoint, secondPoint); + } } - } } -/// Adjust the line segments such that they are all going the same direction -/// with respect to going from P1->P2 -void SurveyComplexItem::_adjustLineDirection(const QList &lineList, - QList &resultLines) { - qreal firstAngle = 0; - for (int i = 0; i < lineList.count(); i++) { - const QLineF &line = lineList[i]; - QLineF adjustedLine; +/// Adjust the line segments such that they are all going the same direction with respect to going from P1->P2 +void SurveyComplexItem::_adjustLineDirection(const QList& lineList, QList& resultLines) +{ + qreal firstAngle = 0; + for (int i=0; i 1.0) { - adjustedLine.setP1(line.p2()); - adjustedLine.setP2(line.p1()); - } else { - adjustedLine = line; - } + if (qAbs(line.angle() - firstAngle) > 1.0) { + adjustedLine.setP1(line.p2()); + adjustedLine.setP2(line.p1()); + } else { + adjustedLine = line; + } - resultLines += adjustedLine; - } + resultLines += adjustedLine; + } } -double SurveyComplexItem::_clampGridAngle90(double gridAngle) { - // Clamp grid angle to -90<->90. This prevents transects from being rotated to - // a reversed order. - if (gridAngle > 90.0) { - gridAngle -= 180.0; - } else if (gridAngle < -90.0) { - gridAngle += 180; - } - return gridAngle; +double SurveyComplexItem::_clampGridAngle90(double gridAngle) +{ + // Clamp grid angle to -90<->90. This prevents transects from being rotated to a reversed order. + if (gridAngle > 90.0) { + gridAngle -= 180.0; + } else if (gridAngle < -90.0) { + gridAngle += 180; + } + return gridAngle; } -bool SurveyComplexItem::_nextTransectCoord( - const QList &transectPoints, int pointIndex, - QGeoCoordinate &coord) { - if (pointIndex > transectPoints.count()) { - qWarning() << "Bad grid generation"; - return false; - } +bool SurveyComplexItem::_nextTransectCoord(const QList& transectPoints, int pointIndex, QGeoCoordinate& coord) +{ + if (pointIndex > transectPoints.count()) { + qWarning() << "Bad grid generation"; + return false; + } - coord = transectPoints[pointIndex]; - return true; + coord = transectPoints[pointIndex]; + return true; } -bool SurveyComplexItem::_hasTurnaround(void) const { - return _turnAroundDistance() > 0; +bool SurveyComplexItem::_hasTurnaround(void) const +{ + return _turnAroundDistance() > 0; } -double SurveyComplexItem::_turnaroundDistance(void) const { - return _turnAroundDistanceFact.rawValue().toDouble(); +double SurveyComplexItem::_turnaroundDistance(void) const +{ + return _turnAroundDistanceFact.rawValue().toDouble(); } -void SurveyComplexItem::_rebuildTransectsPhase1(void) { - bool split = splitConcavePolygons()->rawValue().toBool(); - if (split) { - _rebuildTransectsPhase1WorkerSplitPolygons(false /* refly */); - } else { - _rebuildTransectsPhase1WorkerSinglePolygon(false /* refly */); - } - if (_refly90DegreesFact.rawValue().toBool()) { - if (split) { - _rebuildTransectsPhase1WorkerSplitPolygons(true /* refly */); - } else { - _rebuildTransectsPhase1WorkerSinglePolygon(true /* refly */); +void SurveyComplexItem::_rebuildTransectsPhase1(void) +{ + bool split = splitConcavePolygons()->rawValue().toBool(); + if (split) { + _rebuildTransectsPhase1WorkerSplitPolygons(false /* refly */); + } else { + _rebuildTransectsPhase1WorkerSinglePolygon(false /* refly */); + } + if (_refly90DegreesFact.rawValue().toBool()) { + if (split) { + _rebuildTransectsPhase1WorkerSplitPolygons(true /* refly */); + } else { + _rebuildTransectsPhase1WorkerSinglePolygon(true /* refly */); + } } - } } -void SurveyComplexItem::_rebuildTransectsPhase1WorkerSinglePolygon(bool refly) { - if (_ignoreRecalc) { - return; - } - - // If the transects are getting rebuilt then any previously loaded mission - // items are now invalid - if (_loadedMissionItemsParent) { - _loadedMissionItems.clear(); - _loadedMissionItemsParent->deleteLater(); - _loadedMissionItemsParent = nullptr; - } - - // First pass will clear old transect data, refly will append to existing data - if (!refly) { - _transects.clear(); - _transectsPathHeightInfo.clear(); - } - - if (_surveyAreaPolygon.count() < 3) { - return; - } - - // Convert polygon to NED - - QList polygonPoints; - QGeoCoordinate tangentOrigin = _surveyAreaPolygon.pathModel() - .value(0) - ->coordinate(); - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 Convert polygon to NED - " - "_surveyAreaPolygon.count():tangentOrigin" - << _surveyAreaPolygon.count() << tangentOrigin; - for (int i = 0; i < _surveyAreaPolygon.count(); i++) { - double y, x, down; - QGeoCoordinate vertex = _surveyAreaPolygon.pathModel() - .value(i) - ->coordinate(); - if (i == 0) { - // This avoids a nan calculation that comes out of convertGeoToNed - x = y = 0; - } else { - convertGeoToNed(vertex, tangentOrigin, &y, &x, &down); - } - polygonPoints += QPointF(x, y); - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 vertex:x:y" << vertex - << polygonPoints.last().x() << polygonPoints.last().y(); - } - - // Generate transects - - double gridAngle = _gridAngleFact.rawValue().toDouble(); - double gridSpacing = - _cameraCalc.adjustedFootprintSide()->rawValue().toDouble(); - if (gridSpacing < 0.5) { - // We can't let gridSpacing get too small otherwise we will end up with too - // many transects. So we limit to 0.5 meter spacing as min and set to huge - // value which will cause a single transect to be added. - gridSpacing = 100000; - } - - gridAngle = _clampGridAngle90(gridAngle); - gridAngle += refly ? 90 : 0; - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 Clamped grid angle" << gridAngle; - - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 gridSpacing:gridAngle:refly" << gridSpacing - << gridAngle << refly; - - // Convert polygon to bounding rect - - qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Polygon"; - QPolygonF polygon; - for (int i = 0; i < polygonPoints.count(); i++) { - qCDebug(SurveyComplexItemLog) << "Vertex" << polygonPoints[i]; - polygon << polygonPoints[i]; - } - polygon << polygonPoints[0]; - QRectF boundingRect = polygon.boundingRect(); - QPointF boundingCenter = boundingRect.center(); - qCDebug(SurveyComplexItemLog) - << "Bounding rect" << boundingRect.topLeft().x() - << boundingRect.topLeft().y() << boundingRect.bottomRight().x() - << boundingRect.bottomRight().y(); - - // Create set of rotated parallel lines within the expanded bounding rect. - // Make the lines larger than the bounding box to guarantee intersection. - - QList lineList; - - // Transects are generated to be as long as the largest width/height of the - // bounding rect plus some fudge factor. This way they will always be - // guaranteed to intersect with a polygon edge no matter what angle they are - // rotated to. They are initially generated with the transects flowing from - // west to east and then points within the transect north to south. - double maxWidth = qMax(boundingRect.width(), boundingRect.height()) + 2000.0; - double halfWidth = maxWidth / 2.0; - double transectX = boundingCenter.x() - halfWidth; - double transectXMax = transectX + maxWidth; - while (transectX < transectXMax) { - double transectYTop = boundingCenter.y() - halfWidth; - double transectYBottom = boundingCenter.y() + halfWidth; - - lineList += QLineF(_rotatePoint(QPointF(transectX, transectYTop), - boundingCenter, gridAngle), - _rotatePoint(QPointF(transectX, transectYBottom), - boundingCenter, gridAngle)); - transectX += gridSpacing; - } - - // Now intersect the lines with the polygon - QList intersectLines; +void SurveyComplexItem::_rebuildTransectsPhase1WorkerSinglePolygon(bool refly) +{ + if (_ignoreRecalc) { + return; + } + + // If the transects are getting rebuilt then any previously loaded mission items are now invalid + if (_loadedMissionItemsParent) { + _loadedMissionItems.clear(); + _loadedMissionItemsParent->deleteLater(); + _loadedMissionItemsParent = nullptr; + } + + // First pass will clear old transect data, refly will append to existing data + if (!refly) { + _transects.clear(); + _transectsPathHeightInfo.clear(); + } + + if (_surveyAreaPolygon.count() < 3) { + return; + } + + // Convert polygon to NED + + QList polygonPoints; + QGeoCoordinate tangentOrigin = _surveyAreaPolygon.pathModel().value(0)->coordinate(); + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Convert polygon to NED - _surveyAreaPolygon.count():tangentOrigin" << _surveyAreaPolygon.count() << tangentOrigin; + for (int i=0; i<_surveyAreaPolygon.count(); i++) { + double y, x, down; + QGeoCoordinate vertex = _surveyAreaPolygon.pathModel().value(i)->coordinate(); + if (i == 0) { + // This avoids a nan calculation that comes out of convertGeoToNed + x = y = 0; + } else { + convertGeoToNed(vertex, tangentOrigin, &y, &x, &down); + } + polygonPoints += QPointF(x, y); + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 vertex:x:y" << vertex << polygonPoints.last().x() << polygonPoints.last().y(); + } + + // Generate transects + + double gridAngle = _gridAngleFact.rawValue().toDouble(); + double gridSpacing = _cameraCalc.adjustedFootprintSide()->rawValue().toDouble(); + if (gridSpacing < 0.5) { + // We can't let gridSpacing get too small otherwise we will end up with too many transects. + // So we limit to 0.5 meter spacing as min and set to huge value which will cause a single + // transect to be added. + gridSpacing = 100000; + } + + gridAngle = _clampGridAngle90(gridAngle); + gridAngle += refly ? 90 : 0; + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Clamped grid angle" << gridAngle; + + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 gridSpacing:gridAngle:refly" << gridSpacing << gridAngle << refly; + + // Convert polygon to bounding rect + + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Polygon"; + QPolygonF polygon; + for (int i=0; i lineList; + + // Transects are generated to be as long as the largest width/height of the bounding rect plus some fudge factor. + // This way they will always be guaranteed to intersect with a polygon edge no matter what angle they are rotated to. + // They are initially generated with the transects flowing from west to east and then points within the transect north to south. + double maxWidth = qMax(boundingRect.width(), boundingRect.height()) + 2000.0; + double halfWidth = maxWidth / 2.0; + double transectX = boundingCenter.x() - halfWidth; + double transectXMax = transectX + maxWidth; + while (transectX < transectXMax) { + double transectYTop = boundingCenter.y() - halfWidth; + double transectYBottom = boundingCenter.y() + halfWidth; + + lineList += QLineF(_rotatePoint(QPointF(transectX, transectYTop), boundingCenter, gridAngle), _rotatePoint(QPointF(transectX, transectYBottom), boundingCenter, gridAngle)); + transectX += gridSpacing; + } + + // Now intersect the lines with the polygon + QList intersectLines; #if 1 - _intersectLinesWithPolygon(lineList, polygon, intersectLines); + _intersectLinesWithPolygon(lineList, polygon, intersectLines); #else - // This is handy for debugging grid problems, not for release - intersectLines = lineList; + // This is handy for debugging grid problems, not for release + intersectLines = lineList; #endif - // Less than two transects intersected with the polygon: - // Create a single transect which goes through the center of the polygon - // Intersect it with the polygon - if (intersectLines.count() < 2) { - _surveyAreaPolygon.center(); - QLineF firstLine = lineList.first(); - QPointF lineCenter = firstLine.pointAt(0.5); - QPointF centerOffset = boundingCenter - lineCenter; - firstLine.translate(centerOffset); - lineList.clear(); - lineList.append(firstLine); - intersectLines = lineList; - _intersectLinesWithPolygon(lineList, polygon, intersectLines); - } - - // Make sure all lines are going the same direction. Polygon intersection - // leads to lines which can be in varied directions depending on the order of - // the intesecting sides. - QList resultLines; - _adjustLineDirection(intersectLines, resultLines); - - // Convert from NED to Geo - QList> transects; - for (const QLineF &line : resultLines) { - QGeoCoordinate coord; - QList transect; - - convertNedToGeo(line.p1().y(), line.p1().x(), 0, tangentOrigin, &coord); - transect.append(coord); - convertNedToGeo(line.p2().y(), line.p2().x(), 0, tangentOrigin, &coord); - transect.append(coord); - - transects.append(transect); - } - - _adjustTransectsToEntryPointLocation(transects); - - if (refly) { - _optimizeTransectsForShortestDistance(_transects.last().last().coord, - transects); - } - - if (_flyAlternateTransectsFact.rawValue().toBool()) { - QList> alternatingTransects; - for (int i = 0; i < transects.count(); i++) { - if (!(i & 1)) { - alternatingTransects.append(transects[i]); - } - } - for (int i = transects.count() - 1; i > 0; i--) { - if (i & 1) { - alternatingTransects.append(transects[i]); - } - } - transects = alternatingTransects; - } - - // Adjust to lawnmower pattern - bool reverseVertices = false; - for (int i = 0; i < transects.count(); i++) { - // We must reverse the vertices for every other transect in order to make a - // lawnmower pattern - QList transectVertices = transects[i]; - if (reverseVertices) { - reverseVertices = false; - QList reversedVertices; - for (int j = transectVertices.count() - 1; j >= 0; j--) { - reversedVertices.append(transectVertices[j]); - } - transectVertices = reversedVertices; - } else { - reverseVertices = true; - } - transects[i] = transectVertices; - } - - // Convert to CoordInfo transects and append to _transects - for (const QList &transect : transects) { - QGeoCoordinate coord; - QList coordInfoTransect; - TransectStyleComplexItem::CoordInfo_t coordInfo; - - coordInfo = {transect[0], CoordTypeSurveyEntry}; - coordInfoTransect.append(coordInfo); - coordInfo = {transect[1], CoordTypeSurveyExit}; - coordInfoTransect.append(coordInfo); - - // For hover and capture we need points for each camera location within the - // transect - if (triggerCamera() && hoverAndCaptureEnabled()) { - double transectLength = transect[0].distanceTo(transect[1]); - double transectAzimuth = transect[0].azimuthTo(transect[1]); - if (triggerDistance() < transectLength) { - int cInnerHoverPoints = - static_cast(floor(transectLength / triggerDistance())); - qCDebug(SurveyComplexItemLog) - << "cInnerHoverPoints" << cInnerHoverPoints; - for (int i = 0; i < cInnerHoverPoints; i++) { - QGeoCoordinate hoverCoord = transect[0].atDistanceAndAzimuth( - triggerDistance() * (i + 1), transectAzimuth); - TransectStyleComplexItem::CoordInfo_t coordInfo = { - hoverCoord, CoordTypeInteriorHoverTrigger}; - coordInfoTransect.insert(1 + i, coordInfo); - } - } + // Less than two transects intersected with the polygon: + // Create a single transect which goes through the center of the polygon + // Intersect it with the polygon + if (intersectLines.count() < 2) { + _surveyAreaPolygon.center(); + QLineF firstLine = lineList.first(); + QPointF lineCenter = firstLine.pointAt(0.5); + QPointF centerOffset = boundingCenter - lineCenter; + firstLine.translate(centerOffset); + lineList.clear(); + lineList.append(firstLine); + intersectLines = lineList; + _intersectLinesWithPolygon(lineList, polygon, intersectLines); } - // Extend the transect ends for turnaround - if (_hasTurnaround()) { - QGeoCoordinate turnaroundCoord; - double turnAroundDistance = _turnAroundDistanceFact.rawValue().toDouble(); + // Make sure all lines are going the same direction. Polygon intersection leads to lines which + // can be in varied directions depending on the order of the intesecting sides. + QList resultLines; + _adjustLineDirection(intersectLines, resultLines); - double azimuth = transect[0].azimuthTo(transect[1]); - turnaroundCoord = - transect[0].atDistanceAndAzimuth(-turnAroundDistance, azimuth); - turnaroundCoord.setAltitude(qQNaN()); - TransectStyleComplexItem::CoordInfo_t coordInfo = {turnaroundCoord, - CoordTypeTurnaround}; - coordInfoTransect.prepend(coordInfo); + // Convert from NED to Geo + QList> transects; + for (const QLineF& line : resultLines) { + QGeoCoordinate coord; + QList transect; - azimuth = transect.last().azimuthTo(transect[transect.count() - 2]); - turnaroundCoord = - transect.last().atDistanceAndAzimuth(-turnAroundDistance, azimuth); - turnaroundCoord.setAltitude(qQNaN()); - coordInfo = {turnaroundCoord, CoordTypeTurnaround}; - coordInfoTransect.append(coordInfo); + convertNedToGeo(line.p1().y(), line.p1().x(), 0, tangentOrigin, &coord); + transect.append(coord); + convertNedToGeo(line.p2().y(), line.p2().x(), 0, tangentOrigin, &coord); + transect.append(coord); + + transects.append(transect); } - _transects.append(coordInfoTransect); - } -} + _adjustTransectsToEntryPointLocation(transects); -void SurveyComplexItem::_rebuildTransectsPhase1WorkerSplitPolygons(bool refly) { - if (_ignoreRecalc) { - return; - } - - // If the transects are getting rebuilt then any previously loaded mission - // items are now invalid - if (_loadedMissionItemsParent) { - _loadedMissionItems.clear(); - _loadedMissionItemsParent->deleteLater(); - _loadedMissionItemsParent = nullptr; - } - - // First pass will clear old transect data, refly will append to existing data - if (!refly) { - _transects.clear(); - _transectsPathHeightInfo.clear(); - } - - if (_surveyAreaPolygon.count() < 3) { - return; - } - - // Convert polygon to NED - - QList polygonPoints; - QGeoCoordinate tangentOrigin = _surveyAreaPolygon.pathModel() - .value(0) - ->coordinate(); - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 Convert polygon to NED - " - "_surveyAreaPolygon.count():tangentOrigin" - << _surveyAreaPolygon.count() << tangentOrigin; - for (int i = 0; i < _surveyAreaPolygon.count(); i++) { - double y, x, down; - QGeoCoordinate vertex = _surveyAreaPolygon.pathModel() - .value(i) - ->coordinate(); - if (i == 0) { - // This avoids a nan calculation that comes out of convertGeoToNed - x = y = 0; - } else { - convertGeoToNed(vertex, tangentOrigin, &y, &x, &down); - } - polygonPoints += QPointF(x, y); - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 vertex:x:y" << vertex - << polygonPoints.last().x() << polygonPoints.last().y(); - } - - // convert into QPolygonF - QPolygonF polygon; - for (int i = 0; i < polygonPoints.count(); i++) { - qCDebug(SurveyComplexItemLog) << "Vertex" << polygonPoints[i]; - polygon << polygonPoints[i]; - } - - // Create list of separate polygons - QList polygons{}; - _PolygonDecomposeConvex(polygon, polygons); - - // iterate over polygons - for (auto p = polygons.begin(); p != polygons.end(); ++p) { - QPointF *vMatch = nullptr; - // find matching vertex in previous polygon - if (p != polygons.begin()) { - auto pLast = p - 1; - for (auto &i : *p) { - for (auto &j : *pLast) { - if (i == j) { - vMatch = &i; - break; - } - if (vMatch) - break; + if (refly) { + _optimizeTransectsForShortestDistance(_transects.last().last().coord, transects); + } + + if (_flyAlternateTransectsFact.rawValue().toBool()) { + QList> alternatingTransects; + for (int i=0; i0; i--) { + if (i & 1) { + alternatingTransects.append(transects[i]); + } + } + transects = alternatingTransects; + } + + // Adjust to lawnmower pattern + bool reverseVertices = false; + for (int i=0; i transectVertices = transects[i]; + if (reverseVertices) { + reverseVertices = false; + QList reversedVertices; + for (int j=transectVertices.count()-1; j>=0; j--) { + reversedVertices.append(transectVertices[j]); + } + transectVertices = reversedVertices; + } else { + reverseVertices = true; } - } + transects[i] = transectVertices; } - // close polygon - *p << p->front(); - // build transects for this polygon - // TODO figure out tangent origin - // TODO improve selection of entry points - // qCDebug(SurveyComplexItemLog) << "Transects from polynom p " << p; - _rebuildTransectsFromPolygon(refly, *p, tangentOrigin, vMatch); - } + // Convert to CoordInfo transects and append to _transects + for (const QList& transect : transects) { + QGeoCoordinate coord; + QList coordInfoTransect; + TransectStyleComplexItem::CoordInfo_t coordInfo; + + coordInfo = { transect[0], CoordTypeSurveyEntry }; + coordInfoTransect.append(coordInfo); + coordInfo = { transect[1], CoordTypeSurveyExit }; + coordInfoTransect.append(coordInfo); + + // For hover and capture we need points for each camera location within the transect + if (triggerCamera() && hoverAndCaptureEnabled()) { + double transectLength = transect[0].distanceTo(transect[1]); + double transectAzimuth = transect[0].azimuthTo(transect[1]); + if (triggerDistance() < transectLength) { + int cInnerHoverPoints = static_cast(floor(transectLength / triggerDistance())); + qCDebug(SurveyComplexItemLog) << "cInnerHoverPoints" << cInnerHoverPoints; + for (int i=0; i &decomposedPolygons) { - // this follows "Mark Keil's Algorithm" https://mpen.ca/406/keil - int decompSize = std::numeric_limits::max(); - if (polygon.size() < 3) - return; - if (polygon.size() == 3) { - decomposedPolygons << polygon; - return; - } - - QList decomposedPolygonsMin{}; - - for (auto vertex = polygon.begin(); vertex != polygon.end(); ++vertex) { - // is vertex reflex? - bool vertexIsReflex = _VertexIsReflex(polygon, vertex); - - if (!vertexIsReflex) - continue; - - for (auto vertexOther = polygon.begin(); vertexOther != polygon.end(); - ++vertexOther) { - auto vertexBefore = - vertex == polygon.begin() ? polygon.end() - 1 : vertex - 1; - auto vertexAfter = - vertex == polygon.end() - 1 ? polygon.begin() : vertex + 1; - if (vertexOther == vertex) - continue; - if (vertexAfter == vertexOther) - continue; - if (vertexBefore == vertexOther) - continue; - bool canSee = _VertexCanSeeOther(polygon, vertex, vertexOther); - if (!canSee) - continue; - - QPolygonF polyLeft; - auto v = vertex; - auto polyLeftContainsReflex = false; - while (v != vertexOther) { - if (v != vertex && _VertexIsReflex(polygon, v)) { - polyLeftContainsReflex = true; + +void SurveyComplexItem::_rebuildTransectsPhase1WorkerSplitPolygons(bool refly) +{ + if (_ignoreRecalc) { + return; + } + + // If the transects are getting rebuilt then any previously loaded mission items are now invalid + if (_loadedMissionItemsParent) { + _loadedMissionItems.clear(); + _loadedMissionItemsParent->deleteLater(); + _loadedMissionItemsParent = nullptr; + } + + // First pass will clear old transect data, refly will append to existing data + if (!refly) { + _transects.clear(); + _transectsPathHeightInfo.clear(); + } + + if (_surveyAreaPolygon.count() < 3) { + return; + } + + // Convert polygon to NED + + QList polygonPoints; + QGeoCoordinate tangentOrigin = _surveyAreaPolygon.pathModel().value(0)->coordinate(); + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Convert polygon to NED - _surveyAreaPolygon.count():tangentOrigin" << _surveyAreaPolygon.count() << tangentOrigin; + for (int i=0; i<_surveyAreaPolygon.count(); i++) { + double y, x, down; + QGeoCoordinate vertex = _surveyAreaPolygon.pathModel().value(i)->coordinate(); + if (i == 0) { + // This avoids a nan calculation that comes out of convertGeoToNed + x = y = 0; + } else { + convertGeoToNed(vertex, tangentOrigin, &y, &x, &down); } - polyLeft << *v; - ++v; - if (v == polygon.end()) - v = polygon.begin(); - } - polyLeft << *vertexOther; - auto polyLeftValid = !(polyLeftContainsReflex && polyLeft.size() == 3); - - QPolygonF polyRight; - v = vertexOther; - auto polyRightContainsReflex = false; - while (v != vertex) { - if (v != vertex && _VertexIsReflex(polygon, v)) { - polyRightContainsReflex = true; + polygonPoints += QPointF(x, y); + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 vertex:x:y" << vertex << polygonPoints.last().x() << polygonPoints.last().y(); + } + + // convert into QPolygonF + QPolygonF polygon; + for (int i=0; i polygons{}; + _PolygonDecomposeConvex(polygon, polygons); + + // iterate over polygons + for (auto p = polygons.begin(); p != polygons.end(); ++p) { + QPointF* vMatch = nullptr; + // find matching vertex in previous polygon + if (p != polygons.begin()) { + auto pLast = p - 1; + for (auto& i : *p) { + for (auto& j : *pLast) { + if (i == j) { + vMatch = &i; + break; + } + if (vMatch) break; + } + } + } - polyRight << *v; - ++v; - if (v == polygon.end()) - v = polygon.begin(); - } - polyRight << *vertex; - auto polyRightValid = !(polyRightContainsReflex && polyRight.size() == 3); - - if (!polyLeftValid || !polyRightValid) { - // decompSize = std::numeric_limits::max(); - continue; - } - - // recursion - QList polyLeftDecomposed{}; - _PolygonDecomposeConvex(polyLeft, polyLeftDecomposed); - - QList polyRightDecomposed{}; - _PolygonDecomposeConvex(polyRight, polyRightDecomposed); - - // compositon - auto subSize = polyLeftDecomposed.size() + polyRightDecomposed.size(); - if ((polyLeftContainsReflex && polyLeftDecomposed.size() == 1) || - (polyRightContainsReflex && polyRightDecomposed.size() == 1)) { - // don't accept polygons that contian reflex vertices and were not split - subSize = std::numeric_limits::max(); - } - if (subSize < decompSize) { - decompSize = subSize; - decomposedPolygonsMin = polyLeftDecomposed + polyRightDecomposed; - } - } - } - - // assemble output - if (decomposedPolygonsMin.size() > 0) { - decomposedPolygons << decomposedPolygonsMin; - } else { - decomposedPolygons << polygon; - } - - return; + + + // close polygon + *p << p->front(); + // build transects for this polygon + // TODO figure out tangent origin + // TODO improve selection of entry points +// qCDebug(SurveyComplexItemLog) << "Transects from polynom p " << p; + _rebuildTransectsFromPolygon(refly, *p, tangentOrigin, vMatch); + } } -bool SurveyComplexItem::_VertexCanSeeOther(const QPolygonF &polygon, - const QPointF *vertexA, - const QPointF *vertexB) { - if (vertexA == vertexB) - return false; - auto vertexAAfter = - vertexA + 1 == polygon.end() ? polygon.begin() : vertexA + 1; - auto vertexABefore = - vertexA == polygon.begin() ? polygon.end() - 1 : vertexA - 1; - if (vertexAAfter == vertexB) - return false; - if (vertexABefore == vertexB) - return false; - // qCDebug(SurveyComplexItemLog) << "_VertexCanSeeOther false after first - // checks "; - - bool visible = true; - // auto diff = *vertexA - *vertexB; - QLineF lineAB{*vertexA, *vertexB}; - auto distanceAB = - lineAB.length(); // sqrtf(diff.x() * diff.x() + diff.y()*diff.y()); - - // qCDebug(SurveyComplexItemLog) << "_VertexCanSeeOther distanceAB " << - // distanceAB; - for (auto vertexC = polygon.begin(); vertexC != polygon.end(); ++vertexC) { - if (vertexC == vertexA) - continue; - if (vertexC == vertexB) - continue; - auto vertexD = vertexC + 1 == polygon.end() ? polygon.begin() : vertexC + 1; - if (vertexD == vertexA) - continue; - if (vertexD == vertexB) - continue; - QLineF lineCD(*vertexC, *vertexD); - QPointF intersection{}; +void SurveyComplexItem::_PolygonDecomposeConvex(const QPolygonF& polygon, QList& decomposedPolygons) +{ + // this follows "Mark Keil's Algorithm" https://mpen.ca/406/keil + int decompSize = std::numeric_limits::max(); + if (polygon.size() < 3) return; + if (polygon.size() == 3) { + decomposedPolygons << polygon; + return; + } + + QList decomposedPolygonsMin{}; + + for (auto vertex = polygon.begin(); vertex != polygon.end(); ++vertex) + { + // is vertex reflex? + bool vertexIsReflex = _VertexIsReflex(polygon, vertex); + + if (!vertexIsReflex) continue; + + for (auto vertexOther = polygon.begin(); vertexOther != polygon.end(); ++vertexOther) + { + auto vertexBefore = vertex == polygon.begin() ? polygon.end() - 1 : vertex - 1; + auto vertexAfter = vertex == polygon.end() - 1 ? polygon.begin() : vertex + 1; + if (vertexOther == vertex) continue; + if (vertexAfter == vertexOther) continue; + if (vertexBefore == vertexOther) continue; + bool canSee = _VertexCanSeeOther(polygon, vertex, vertexOther); + if (!canSee) continue; + + QPolygonF polyLeft; + auto v = vertex; + auto polyLeftContainsReflex = false; + while ( v != vertexOther) { + if (v != vertex && _VertexIsReflex(polygon, v)) { + polyLeftContainsReflex = true; + } + polyLeft << *v; + ++v; + if (v == polygon.end()) v = polygon.begin(); + } + polyLeft << *vertexOther; + auto polyLeftValid = !(polyLeftContainsReflex && polyLeft.size() == 3); + + QPolygonF polyRight; + v = vertexOther; + auto polyRightContainsReflex = false; + while ( v != vertex) { + if (v != vertex && _VertexIsReflex(polygon, v)) { + polyRightContainsReflex = true; + } + polyRight << *v; + ++v; + if (v == polygon.end()) v = polygon.begin(); + } + polyRight << *vertex; + auto polyRightValid = !(polyRightContainsReflex && polyRight.size() == 3); + + if (!polyLeftValid || ! polyRightValid) { +// decompSize = std::numeric_limits::max(); + continue; + } + + // recursion + QList polyLeftDecomposed{}; + _PolygonDecomposeConvex(polyLeft, polyLeftDecomposed); + + QList polyRightDecomposed{}; + _PolygonDecomposeConvex(polyRight, polyRightDecomposed); + + // compositon + auto subSize = polyLeftDecomposed.size() + polyRightDecomposed.size(); + if ((polyLeftContainsReflex && polyLeftDecomposed.size() == 1) + || (polyRightContainsReflex && polyRightDecomposed.size() == 1)) + { + // don't accept polygons that contian reflex vertices and were not split + subSize = std::numeric_limits::max(); + } + if (subSize < decompSize) { + decompSize = subSize; + decomposedPolygonsMin = polyLeftDecomposed + polyRightDecomposed; + } + } + + } + + // assemble output + if (decomposedPolygonsMin.size() > 0) { + decomposedPolygons << decomposedPolygonsMin; + } else { + decomposedPolygons << polygon; + } + + return; +} + +bool SurveyComplexItem::_VertexCanSeeOther(const QPolygonF& polygon, const QPointF* vertexA, const QPointF* vertexB) { + if (vertexA == vertexB) return false; + auto vertexAAfter = vertexA + 1 == polygon.end() ? polygon.begin() : vertexA + 1; + auto vertexABefore = vertexA == polygon.begin() ? polygon.end() - 1 : vertexA - 1; + if (vertexAAfter == vertexB) return false; + if (vertexABefore == vertexB) return false; +// qCDebug(SurveyComplexItemLog) << "_VertexCanSeeOther false after first checks "; + + bool visible = true; +// auto diff = *vertexA - *vertexB; + QLineF lineAB{*vertexA, *vertexB}; + auto distanceAB = lineAB.length();//sqrtf(diff.x() * diff.x() + diff.y()*diff.y()); + +// qCDebug(SurveyComplexItemLog) << "_VertexCanSeeOther distanceAB " << distanceAB; + for (auto vertexC = polygon.begin(); vertexC != polygon.end(); ++vertexC) + { + if (vertexC == vertexA) continue; + if (vertexC == vertexB) continue; + auto vertexD = vertexC + 1 == polygon.end() ? polygon.begin() : vertexC + 1; + if (vertexD == vertexA) continue; + if (vertexD == vertexB) continue; + QLineF lineCD(*vertexC, *vertexD); + QPointF intersection{}; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - auto intersects = lineAB.intersect(lineCD, &intersection); + auto intersects = lineAB.intersect(lineCD, &intersection); #else - auto intersects = lineAB.intersects(lineCD, &intersection); + auto intersects = lineAB.intersects(lineCD, &intersection); #endif - if (intersects == QLineF::IntersectType::BoundedIntersection) { - // auto diffIntersection = *vertexA - intersection; - // auto distanceIntersection = sqrtf(diffIntersection.x() * - // diffIntersection.x() + - // diffIntersection.y()*diffIntersection.y()); - // qCDebug(SurveyComplexItemLog) << "*vertexA " << *vertexA << - // "*vertexB " << *vertexB << " intersection " << - // intersection; - - QLineF lineIntersection{*vertexA, intersection}; - auto distanceIntersection = - lineIntersection - .length(); // sqrtf(diff.x() * diff.x() + diff.y()*diff.y()); - qCDebug(SurveyComplexItemLog) - << "_VertexCanSeeOther distanceIntersection " << distanceIntersection; - if (distanceIntersection < distanceAB) { - visible = false; - break; - } - } - } - - return visible; + if (intersects == QLineF::IntersectType::BoundedIntersection) { +// auto diffIntersection = *vertexA - intersection; +// auto distanceIntersection = sqrtf(diffIntersection.x() * diffIntersection.x() + diffIntersection.y()*diffIntersection.y()); +// qCDebug(SurveyComplexItemLog) << "*vertexA " << *vertexA << "*vertexB " << *vertexB << " intersection " << intersection; + + QLineF lineIntersection{*vertexA, intersection}; + auto distanceIntersection = lineIntersection.length();//sqrtf(diff.x() * diff.x() + diff.y()*diff.y()); + qCDebug(SurveyComplexItemLog) << "_VertexCanSeeOther distanceIntersection " << distanceIntersection; + if (distanceIntersection < distanceAB) { + visible = false; + break; + } + } + + } + + return visible; } -bool SurveyComplexItem::_VertexIsReflex(const QPolygonF &polygon, - const QPointF *vertex) { - auto vertexBefore = - vertex == polygon.begin() ? polygon.end() - 1 : vertex - 1; - auto vertexAfter = vertex == polygon.end() - 1 ? polygon.begin() : vertex + 1; - auto area = (((vertex->x() - vertexBefore->x()) * - (vertexAfter->y() - vertexBefore->y())) - - ((vertexAfter->x() - vertexBefore->x()) * - (vertex->y() - vertexBefore->y()))); - return area > 0; +bool SurveyComplexItem::_VertexIsReflex(const QPolygonF& polygon, const QPointF* vertex) { + auto vertexBefore = vertex == polygon.begin() ? polygon.end() - 1 : vertex - 1; + auto vertexAfter = vertex == polygon.end() - 1 ? polygon.begin() : vertex + 1; + auto area = (((vertex->x() - vertexBefore->x())*(vertexAfter->y() - vertexBefore->y()))-((vertexAfter->x() - vertexBefore->x())*(vertex->y() - vertexBefore->y()))); + return area > 0; + } -void SurveyComplexItem::_rebuildTransectsFromPolygon( - bool refly, const QPolygonF &polygon, const QGeoCoordinate &tangentOrigin, - const QPointF *const transitionPoint) { - // Generate transects - - double gridAngle = _gridAngleFact.rawValue().toDouble(); - double gridSpacing = - _cameraCalc.adjustedFootprintSide()->rawValue().toDouble(); - - gridAngle = _clampGridAngle90(gridAngle); - gridAngle += refly ? 90 : 0; - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 Clamped grid angle" << gridAngle; - - qCDebug(SurveyComplexItemLog) - << "_rebuildTransectsPhase1 gridSpacing:gridAngle:refly" << gridSpacing - << gridAngle << refly; - - // Convert polygon to bounding rect - - qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Polygon"; - QRectF boundingRect = polygon.boundingRect(); - QPointF boundingCenter = boundingRect.center(); - qCDebug(SurveyComplexItemLog) - << "Bounding rect" << boundingRect.topLeft().x() - << boundingRect.topLeft().y() << boundingRect.bottomRight().x() - << boundingRect.bottomRight().y(); - - // Create set of rotated parallel lines within the expanded bounding rect. - // Make the lines larger than the bounding box to guarantee intersection. - - QList lineList; - - // Transects are generated to be as long as the largest width/height of the - // bounding rect plus some fudge factor. This way they will always be - // guaranteed to intersect with a polygon edge no matter what angle they are - // rotated to. They are initially generated with the transects flowing from - // west to east and then points within the transect north to south. - double maxWidth = qMax(boundingRect.width(), boundingRect.height()) + 2000.0; - double halfWidth = maxWidth / 2.0; - double transectX = boundingCenter.x() - halfWidth; - double transectXMax = transectX + maxWidth; - while (transectX < transectXMax) { - double transectYTop = boundingCenter.y() - halfWidth; - double transectYBottom = boundingCenter.y() + halfWidth; - - lineList += QLineF(_rotatePoint(QPointF(transectX, transectYTop), - boundingCenter, gridAngle), - _rotatePoint(QPointF(transectX, transectYBottom), - boundingCenter, gridAngle)); - transectX += gridSpacing; - } - - // Now intersect the lines with the polygon - QList intersectLines; + +void SurveyComplexItem::_rebuildTransectsFromPolygon(bool refly, const QPolygonF& polygon, const QGeoCoordinate& tangentOrigin, const QPointF* const transitionPoint) +{ + // Generate transects + + double gridAngle = _gridAngleFact.rawValue().toDouble(); + double gridSpacing = _cameraCalc.adjustedFootprintSide()->rawValue().toDouble(); + + gridAngle = _clampGridAngle90(gridAngle); + gridAngle += refly ? 90 : 0; + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Clamped grid angle" << gridAngle; + + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 gridSpacing:gridAngle:refly" << gridSpacing << gridAngle << refly; + + // Convert polygon to bounding rect + + qCDebug(SurveyComplexItemLog) << "_rebuildTransectsPhase1 Polygon"; + QRectF boundingRect = polygon.boundingRect(); + QPointF boundingCenter = boundingRect.center(); + qCDebug(SurveyComplexItemLog) << "Bounding rect" << boundingRect.topLeft().x() << boundingRect.topLeft().y() << boundingRect.bottomRight().x() << boundingRect.bottomRight().y(); + + // Create set of rotated parallel lines within the expanded bounding rect. Make the lines larger than the + // bounding box to guarantee intersection. + + QList lineList; + + // Transects are generated to be as long as the largest width/height of the bounding rect plus some fudge factor. + // This way they will always be guaranteed to intersect with a polygon edge no matter what angle they are rotated to. + // They are initially generated with the transects flowing from west to east and then points within the transect north to south. + double maxWidth = qMax(boundingRect.width(), boundingRect.height()) + 2000.0; + double halfWidth = maxWidth / 2.0; + double transectX = boundingCenter.x() - halfWidth; + double transectXMax = transectX + maxWidth; + while (transectX < transectXMax) { + double transectYTop = boundingCenter.y() - halfWidth; + double transectYBottom = boundingCenter.y() + halfWidth; + + lineList += QLineF(_rotatePoint(QPointF(transectX, transectYTop), boundingCenter, gridAngle), _rotatePoint(QPointF(transectX, transectYBottom), boundingCenter, gridAngle)); + transectX += gridSpacing; + } + + // Now intersect the lines with the polygon + QList intersectLines; #if 1 - _intersectLinesWithPolygon(lineList, polygon, intersectLines); + _intersectLinesWithPolygon(lineList, polygon, intersectLines); #else - // This is handy for debugging grid problems, not for release - intersectLines = lineList; + // This is handy for debugging grid problems, not for release + intersectLines = lineList; #endif - // Less than two transects intersected with the polygon: - // Create a single transect which goes through the center of the polygon - // Intersect it with the polygon - if (intersectLines.count() < 2) { - _surveyAreaPolygon.center(); - QLineF firstLine = lineList.first(); - QPointF lineCenter = firstLine.pointAt(0.5); - QPointF centerOffset = boundingCenter - lineCenter; - firstLine.translate(centerOffset); - lineList.clear(); - lineList.append(firstLine); - intersectLines = lineList; - _intersectLinesWithPolygon(lineList, polygon, intersectLines); - } - - // Make sure all lines are going the same direction. Polygon intersection - // leads to lines which can be in varied directions depending on the order of - // the intesecting sides. - QList resultLines; - _adjustLineDirection(intersectLines, resultLines); - - // Convert from NED to Geo - QList> transects; - - if (transitionPoint != nullptr) { - QList transect; - QGeoCoordinate coord; - convertNedToGeo(transitionPoint->y(), transitionPoint->x(), 0, - tangentOrigin, &coord); - transect.append(coord); - transect.append(coord); // TODO - transects.append(transect); - } - - for (const QLineF &line : resultLines) { - QList transect; - QGeoCoordinate coord; - - convertNedToGeo(line.p1().y(), line.p1().x(), 0, tangentOrigin, &coord); - transect.append(coord); - convertNedToGeo(line.p2().y(), line.p2().x(), 0, tangentOrigin, &coord); - transect.append(coord); - - transects.append(transect); - } - - _adjustTransectsToEntryPointLocation(transects); - - if (refly) { - _optimizeTransectsForShortestDistance(_transects.last().last().coord, - transects); - } - - if (_flyAlternateTransectsFact.rawValue().toBool()) { - QList> alternatingTransects; - for (int i = 0; i < transects.count(); i++) { - if (!(i & 1)) { - alternatingTransects.append(transects[i]); - } - } - for (int i = transects.count() - 1; i > 0; i--) { - if (i & 1) { - alternatingTransects.append(transects[i]); - } - } - transects = alternatingTransects; - } - - // Adjust to lawnmower pattern - bool reverseVertices = false; - for (int i = 0; i < transects.count(); i++) { - // We must reverse the vertices for every other transect in order to make a - // lawnmower pattern - QList transectVertices = transects[i]; - if (reverseVertices) { - reverseVertices = false; - QList reversedVertices; - for (int j = transectVertices.count() - 1; j >= 0; j--) { - reversedVertices.append(transectVertices[j]); - } - transectVertices = reversedVertices; - } else { - reverseVertices = true; - } - transects[i] = transectVertices; - } - - // Convert to CoordInfo transects and append to _transects - for (const QList &transect : transects) { - QGeoCoordinate coord; - QList coordInfoTransect; - TransectStyleComplexItem::CoordInfo_t coordInfo; - - coordInfo = {transect[0], CoordTypeSurveyEntry}; - coordInfoTransect.append(coordInfo); - coordInfo = {transect[1], CoordTypeSurveyExit}; - coordInfoTransect.append(coordInfo); - - // For hover and capture we need points for each camera location within the - // transect - if (triggerCamera() && hoverAndCaptureEnabled()) { - double transectLength = transect[0].distanceTo(transect[1]); - double transectAzimuth = transect[0].azimuthTo(transect[1]); - if (triggerDistance() < transectLength) { - int cInnerHoverPoints = - static_cast(floor(transectLength / triggerDistance())); - qCDebug(SurveyComplexItemLog) - << "cInnerHoverPoints" << cInnerHoverPoints; - for (int i = 0; i < cInnerHoverPoints; i++) { - QGeoCoordinate hoverCoord = transect[0].atDistanceAndAzimuth( - triggerDistance() * (i + 1), transectAzimuth); - TransectStyleComplexItem::CoordInfo_t coordInfo = { - hoverCoord, CoordTypeInteriorHoverTrigger}; - coordInfoTransect.insert(1 + i, coordInfo); - } - } + // Less than two transects intersected with the polygon: + // Create a single transect which goes through the center of the polygon + // Intersect it with the polygon + if (intersectLines.count() < 2) { + _surveyAreaPolygon.center(); + QLineF firstLine = lineList.first(); + QPointF lineCenter = firstLine.pointAt(0.5); + QPointF centerOffset = boundingCenter - lineCenter; + firstLine.translate(centerOffset); + lineList.clear(); + lineList.append(firstLine); + intersectLines = lineList; + _intersectLinesWithPolygon(lineList, polygon, intersectLines); } - // Extend the transect ends for turnaround - if (_hasTurnaround()) { - QGeoCoordinate turnaroundCoord; - double turnAroundDistance = _turnAroundDistanceFact.rawValue().toDouble(); + // Make sure all lines are going the same direction. Polygon intersection leads to lines which + // can be in varied directions depending on the order of the intesecting sides. + QList resultLines; + _adjustLineDirection(intersectLines, resultLines); + + // Convert from NED to Geo + QList> transects; + + if (transitionPoint != nullptr) { + QList transect; + QGeoCoordinate coord; + convertNedToGeo(transitionPoint->y(), transitionPoint->x(), 0, tangentOrigin, &coord); + transect.append(coord); + transect.append(coord); //TODO + transects.append(transect); + } + + for (const QLineF& line: resultLines) { + QList transect; + QGeoCoordinate coord; - double azimuth = transect[0].azimuthTo(transect[1]); - turnaroundCoord = - transect[0].atDistanceAndAzimuth(-turnAroundDistance, azimuth); - turnaroundCoord.setAltitude(qQNaN()); - TransectStyleComplexItem::CoordInfo_t coordInfo = {turnaroundCoord, - CoordTypeTurnaround}; - coordInfoTransect.prepend(coordInfo); + convertNedToGeo(line.p1().y(), line.p1().x(), 0, tangentOrigin, &coord); + transect.append(coord); + convertNedToGeo(line.p2().y(), line.p2().x(), 0, tangentOrigin, &coord); + transect.append(coord); - azimuth = transect.last().azimuthTo(transect[transect.count() - 2]); - turnaroundCoord = - transect.last().atDistanceAndAzimuth(-turnAroundDistance, azimuth); - turnaroundCoord.setAltitude(qQNaN()); - coordInfo = {turnaroundCoord, CoordTypeTurnaround}; - coordInfoTransect.append(coordInfo); + transects.append(transect); } - _transects.append(coordInfoTransect); - } - qCDebug(SurveyComplexItemLog) << "_transects.size() " << _transects.size(); + _adjustTransectsToEntryPointLocation(transects); + + if (refly) { + _optimizeTransectsForShortestDistance(_transects.last().last().coord, transects); + } + + if (_flyAlternateTransectsFact.rawValue().toBool()) { + QList> alternatingTransects; + for (int i=0; i0; i--) { + if (i & 1) { + alternatingTransects.append(transects[i]); + } + } + transects = alternatingTransects; + } + + // Adjust to lawnmower pattern + bool reverseVertices = false; + for (int i=0; i transectVertices = transects[i]; + if (reverseVertices) { + reverseVertices = false; + QList reversedVertices; + for (int j=transectVertices.count()-1; j>=0; j--) { + reversedVertices.append(transectVertices[j]); + } + transectVertices = reversedVertices; + } else { + reverseVertices = true; + } + transects[i] = transectVertices; + } + + // Convert to CoordInfo transects and append to _transects + for (const QList& transect: transects) { + QGeoCoordinate coord; + QList coordInfoTransect; + TransectStyleComplexItem::CoordInfo_t coordInfo; + + coordInfo = { transect[0], CoordTypeSurveyEntry }; + coordInfoTransect.append(coordInfo); + coordInfo = { transect[1], CoordTypeSurveyExit }; + coordInfoTransect.append(coordInfo); + + // For hover and capture we need points for each camera location within the transect + if (triggerCamera() && hoverAndCaptureEnabled()) { + double transectLength = transect[0].distanceTo(transect[1]); + double transectAzimuth = transect[0].azimuthTo(transect[1]); + if (triggerDistance() < transectLength) { + int cInnerHoverPoints = static_cast(floor(transectLength / triggerDistance())); + qCDebug(SurveyComplexItemLog) << "cInnerHoverPoints" << cInnerHoverPoints; + for (int i=0; itriggerDistance(); +void SurveyComplexItem::_recalcCameraShots(void) +{ + double triggerDistance = this->triggerDistance(); - if (triggerDistance == 0) { - _cameraShots = 0; - } else { - if (_cameraTriggerInTurnAroundFact.rawValue().toBool()) { - _cameraShots = qCeil(_complexDistance / triggerDistance); + if (triggerDistance == 0) { + _cameraShots = 0; } else { - _cameraShots = 0; - - if (_loadedMissionItemsParent) { - // We have to do it the hard way based on the mission items themselves - if (hoverAndCaptureEnabled()) { - // Count the number of camera triggers in the mission items - for (const MissionItem *missionItem : _loadedMissionItems) { - _cameraShots += - missionItem->command() == MAV_CMD_IMAGE_START_CAPTURE ? 1 : 0; - } + if (_cameraTriggerInTurnAroundFact.rawValue().toBool()) { + _cameraShots = qCeil(_complexDistance / triggerDistance); } else { - bool waitingForTriggerStop = false; - QGeoCoordinate distanceStartCoord; - QGeoCoordinate distanceEndCoord; - for (const MissionItem *missionItem : _loadedMissionItems) { - if (missionItem->command() == MAV_CMD_NAV_WAYPOINT) { - if (waitingForTriggerStop) { - distanceEndCoord = QGeoCoordinate(missionItem->param5(), - missionItem->param6()); - } else { - distanceStartCoord = QGeoCoordinate(missionItem->param5(), - missionItem->param6()); - } - } else if (missionItem->command() == - MAV_CMD_DO_SET_CAM_TRIGG_DIST) { - if (missionItem->param1() > 0) { - // Trigger start - waitingForTriggerStop = true; - } else { - // Trigger stop - waitingForTriggerStop = false; - _cameraShots += - qCeil(distanceEndCoord.distanceTo(distanceStartCoord) / - triggerDistance); - distanceStartCoord = QGeoCoordinate(); - distanceEndCoord = QGeoCoordinate(); - } + _cameraShots = 0; + + if (_loadedMissionItemsParent) { + // We have to do it the hard way based on the mission items themselves + if (hoverAndCaptureEnabled()) { + // Count the number of camera triggers in the mission items + for (const MissionItem* missionItem: _loadedMissionItems) { + _cameraShots += missionItem->command() == MAV_CMD_IMAGE_START_CAPTURE ? 1 : 0; + } + } else { + bool waitingForTriggerStop = false; + QGeoCoordinate distanceStartCoord; + QGeoCoordinate distanceEndCoord; + for (const MissionItem* missionItem: _loadedMissionItems) { + if (missionItem->command() == MAV_CMD_NAV_WAYPOINT) { + if (waitingForTriggerStop) { + distanceEndCoord = QGeoCoordinate(missionItem->param5(), missionItem->param6()); + } else { + distanceStartCoord = QGeoCoordinate(missionItem->param5(), missionItem->param6()); + } + } else if (missionItem->command() == MAV_CMD_DO_SET_CAM_TRIGG_DIST) { + if (missionItem->param1() > 0) { + // Trigger start + waitingForTriggerStop = true; + } else { + // Trigger stop + waitingForTriggerStop = false; + _cameraShots += qCeil(distanceEndCoord.distanceTo(distanceStartCoord) / triggerDistance); + distanceStartCoord = QGeoCoordinate(); + distanceEndCoord = QGeoCoordinate(); + } + } + } + + } + } else { + // We have transects available, calc from those + for (const QList& transect: _transects) { + QGeoCoordinate firstCameraCoord, lastCameraCoord; + if (_hasTurnaround() && !hoverAndCaptureEnabled()) { + firstCameraCoord = transect[1].coord; + lastCameraCoord = transect[transect.count() - 2].coord; + } else { + firstCameraCoord = transect.first().coord; + lastCameraCoord = transect.last().coord; + } + _cameraShots += qCeil(firstCameraCoord.distanceTo(lastCameraCoord) / triggerDistance); + } } - } } - } else { - // We have transects available, calc from those - for (const QList &transect : - _transects) { - QGeoCoordinate firstCameraCoord, lastCameraCoord; - if (_hasTurnaround() && !hoverAndCaptureEnabled()) { - firstCameraCoord = transect[1].coord; - lastCameraCoord = transect[transect.count() - 2].coord; - } else { - firstCameraCoord = transect.first().coord; - lastCameraCoord = transect.last().coord; - } - _cameraShots += qCeil(firstCameraCoord.distanceTo(lastCameraCoord) / - triggerDistance); - } - } } - } - emit cameraShotsChanged(); + emit cameraShotsChanged(); } -SurveyComplexItem::ReadyForSaveState -SurveyComplexItem::readyForSaveState(void) const { - return TransectStyleComplexItem::readyForSaveState(); +SurveyComplexItem::ReadyForSaveState SurveyComplexItem::readyForSaveState(void) const +{ + return TransectStyleComplexItem::readyForSaveState(); } -void SurveyComplexItem::rotateEntryPoint(void) { - if (_entryPoint == EntryLocationLast) { - _entryPoint = EntryLocationFirst; - } else { - _entryPoint++; - } +void SurveyComplexItem::rotateEntryPoint(void) +{ + if (_entryPoint == EntryLocationLast) { + _entryPoint = EntryLocationFirst; + } else { + _entryPoint++; + } - _rebuildTransects(); + _rebuildTransects(); - setDirty(true); + setDirty(true); } -double SurveyComplexItem::timeBetweenShots(void) { - return _vehicleSpeed == 0 ? 0 : triggerDistance() / _vehicleSpeed; +double SurveyComplexItem::timeBetweenShots(void) +{ + return _vehicleSpeed == 0 ? 0 : triggerDistance() / _vehicleSpeed; } -double SurveyComplexItem::additionalTimeDelay(void) const { - double hoverTime = 0; +double SurveyComplexItem::additionalTimeDelay (void) const +{ + double hoverTime = 0; - if (hoverAndCaptureEnabled()) { - for (const QList &transect : - _transects) { - hoverTime += _hoverAndCaptureDelaySeconds * transect.count(); + if (hoverAndCaptureEnabled()) { + for (const QList& transect: _transects) { + hoverTime += _hoverAndCaptureDelaySeconds * transect.count(); + } } - } - return hoverTime; + return hoverTime; } -void SurveyComplexItem::_updateWizardMode(void) { - if (_surveyAreaPolygon.isValid() && !_surveyAreaPolygon.traceMode()) { - setWizardMode(false); - } +void SurveyComplexItem::_updateWizardMode(void) +{ + if (_surveyAreaPolygon.isValid() && !_surveyAreaPolygon.traceMode()) { + setWizardMode(false); + } } diff --git a/src/MissionManager/SurveyComplexItem.h b/src/MissionManager/SurveyComplexItem.h index 3ead3027f527e6c80a91291bb95a899e5d17e24b..78c103a1ab39a7b84a69e5d4ae63bf39014180a5 100644 --- a/src/MissionManager/SurveyComplexItem.h +++ b/src/MissionManager/SurveyComplexItem.h @@ -9,186 +9,163 @@ #pragma once +#include "TransectStyleComplexItem.h" #include "MissionItem.h" -#include "QGCLoggingCategory.h" #include "SettingsFact.h" -#include "TransectStyleComplexItem.h" +#include "QGCLoggingCategory.h" Q_DECLARE_LOGGING_CATEGORY(SurveyComplexItemLog) class PlanMasterController; -class SurveyComplexItem : public TransectStyleComplexItem { - Q_OBJECT +class SurveyComplexItem : public TransectStyleComplexItem +{ + Q_OBJECT public: - /// @param flyView true: Created for use in the Fly View, false: Created for - /// use in the Plan View - /// @param kmlOrShpFile Polygon comes from this file, empty for default - /// polygon - SurveyComplexItem(PlanMasterController *masterController, bool flyView, - const QString &kmlOrShpFile, QObject *parent); - - Q_PROPERTY(Fact *gridAngle READ gridAngle CONSTANT) - Q_PROPERTY(Fact *flyAlternateTransects READ flyAlternateTransects CONSTANT) - Q_PROPERTY(Fact *splitConcavePolygons READ splitConcavePolygons CONSTANT) - - Fact *gridAngle(void) { return &_gridAngleFact; } - Fact *flyAlternateTransects(void) { return &_flyAlternateTransectsFact; } - Fact *splitConcavePolygons(void) { return &_splitConcavePolygonsFact; } - - Q_INVOKABLE void rotateEntryPoint(void); - - // Overrides from ComplexMissionItem - QString patternName(void) const final { return name; } - bool load(const QJsonObject &complexObject, int sequenceNumber, - QString &errorString) final; - QString mapVisualQML(void) const final { - return QStringLiteral("SurveyMapVisual.qml"); - } - QString presetsSettingsGroup(void) { return settingsGroup; } - void savePreset(const QString &name); - void loadPreset(const QString &name); - - // Overrides from TransectStyleComplexItem - void save(QJsonObject &planItems) final; - bool specifiesCoordinate(void) const final { return true; } - double timeBetweenShots(void) final; - - // Overrides from VisualMissionionItem - QString commandDescription(void) const final { return tr("Survey"); } - QString commandName(void) const final { return tr("Survey"); } - QString abbreviation(void) const final { return tr("S"); } - ReadyForSaveState readyForSaveState(void) const final; - double additionalTimeDelay(void) const final; - - // Must match json spec for GridEntryLocation - enum EntryLocation { - EntryLocationFirst, - EntryLocationTopLeft = EntryLocationFirst, - EntryLocationTopRight, - EntryLocationBottomLeft, - EntryLocationBottomRight, - EntryLocationLast = EntryLocationBottomRight - }; - - static const QString name; - - static const char *jsonComplexItemTypeValue; - static const char *settingsGroup; - static const char *gridAngleName; - static const char *gridEntryLocationName; - static const char *flyAlternateTransectsName; - static const char *splitConcavePolygonsName; - - static const char *jsonV3ComplexItemTypeValue; + /// @param flyView true: Created for use in the Fly View, false: Created for use in the Plan View + /// @param kmlOrShpFile Polygon comes from this file, empty for default polygon + SurveyComplexItem(PlanMasterController* masterController, bool flyView, const QString& kmlOrShpFile, QObject* parent); + + Q_PROPERTY(Fact* gridAngle READ gridAngle CONSTANT) + Q_PROPERTY(Fact* flyAlternateTransects READ flyAlternateTransects CONSTANT) + Q_PROPERTY(Fact* splitConcavePolygons READ splitConcavePolygons CONSTANT) + + Fact* gridAngle (void) { return &_gridAngleFact; } + Fact* flyAlternateTransects (void) { return &_flyAlternateTransectsFact; } + Fact* splitConcavePolygons (void) { return &_splitConcavePolygonsFact; } + + Q_INVOKABLE void rotateEntryPoint(void); + + // Overrides from ComplexMissionItem + QString patternName (void) const final { return name; } + bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final; + QString mapVisualQML (void) const final { return QStringLiteral("SurveyMapVisual.qml"); } + QString presetsSettingsGroup(void) { return settingsGroup; } + void savePreset (const QString& name); + void loadPreset (const QString& name); + + // Overrides from TransectStyleComplexItem + void save (QJsonArray& planItems) final; + bool specifiesCoordinate (void) const final { return true; } + double timeBetweenShots (void) final; + + // Overrides from VisualMissionionItem + QString commandDescription (void) const final { return tr("Survey"); } + QString commandName (void) const final { return tr("Survey"); } + QString abbreviation (void) const final { return tr("S"); } + ReadyForSaveState readyForSaveState (void) const final; + double additionalTimeDelay (void) const final; + + // Must match json spec for GridEntryLocation + enum EntryLocation { + EntryLocationFirst, + EntryLocationTopLeft = EntryLocationFirst, + EntryLocationTopRight, + EntryLocationBottomLeft, + EntryLocationBottomRight, + EntryLocationLast = EntryLocationBottomRight + }; + + static const QString name; + + static const char* jsonComplexItemTypeValue; + static const char* settingsGroup; + static const char* gridAngleName; + static const char* gridEntryLocationName; + static const char* flyAlternateTransectsName; + static const char* splitConcavePolygonsName; + + static const char* jsonV3ComplexItemTypeValue; signals: - void refly90DegreesChanged(bool refly90Degrees); + void refly90DegreesChanged(bool refly90Degrees); private slots: - void _updateWizardMode(void); + void _updateWizardMode (void); - // Overrides from TransectStyleComplexItem - void _rebuildTransectsPhase1(void) final; - void _recalcCameraShots(void) final; + // Overrides from TransectStyleComplexItem + void _rebuildTransectsPhase1 (void) final; + void _recalcCameraShots (void) final; private: - enum CameraTriggerCode { - CameraTriggerNone, - CameraTriggerOn, - CameraTriggerOff, - CameraTriggerHoverAndCapture - }; - - QPointF _rotatePoint(const QPointF &point, const QPointF &origin, - double angle); - void _intersectLinesWithRect(const QList &lineList, - const QRectF &boundRect, - QList &resultLines); - void _intersectLinesWithPolygon(const QList &lineList, - const QPolygonF &polygon, - QList &resultLines); - void _adjustLineDirection(const QList &lineList, - QList &resultLines); - bool _nextTransectCoord(const QList &transectPoints, - int pointIndex, QGeoCoordinate &coord); - bool _appendMissionItemsWorker(QList &items, - QObject *missionItemParent, int &seqNum, - bool hasRefly, bool buildRefly); - void _optimizeTransectsForShortestDistance( - const QGeoCoordinate &distanceCoord, - QList> &transects); - qreal _ccw(QPointF pt1, QPointF pt2, QPointF pt3); - qreal _dp(QPointF pt1, QPointF pt2); - void _swapPoints(QList &points, int index1, int index2); - void _reverseTransectOrder(QList> &transects); - void _reverseInternalTransectPoints(QList> &transects); - void - _adjustTransectsToEntryPointLocation(QList> &transects); - bool _gridAngleIsNorthSouthTransects(); - double _clampGridAngle90(double gridAngle); - bool _imagesEverywhere(void) const; - bool _triggerCamera(void) const; - bool _hasTurnaround(void) const; - double _turnaroundDistance(void) const; - - bool _hoverAndCaptureEnabled(void) const; - bool _loadV3(const QJsonObject &complexObject, int sequenceNumber, - QString &errorString); - bool _loadV4V5(const QJsonObject &complexObject, int sequenceNumber, - QString &errorString, int version, bool forPresets); - void _saveWorker(QJsonObject &complexObject); - void _rebuildTransectsPhase1Worker(bool refly); - void _rebuildTransectsPhase1WorkerSinglePolygon(bool refly); - void _rebuildTransectsPhase1WorkerSplitPolygons(bool refly); - /// Adds to the _transects array from one polygon - void _rebuildTransectsFromPolygon(bool refly, const QPolygonF &polygon, - const QGeoCoordinate &tangentOrigin, - const QPointF *const transitionPoint); - // Decompose polygon into list of convex sub polygons - void _PolygonDecomposeConvex(const QPolygonF &polygon, - QList &decomposedPolygons); - // return true if vertex a can see vertex b - bool _VertexCanSeeOther(const QPolygonF &polygon, const QPointF *vertexA, - const QPointF *vertexB); - bool _VertexIsReflex(const QPolygonF &polygon, const QPointF *vertex); - - QMap _metaDataMap; - - SettingsFact _gridAngleFact; - SettingsFact _flyAlternateTransectsFact; - SettingsFact _splitConcavePolygonsFact; - int _entryPoint; - - static const char *_jsonGridAngleKey; - static const char *_jsonEntryPointKey; - static const char *_jsonFlyAlternateTransectsKey; - static const char *_jsonSplitConcavePolygonsKey; - - static const char *_jsonV3GridObjectKey; - static const char *_jsonV3GridAltitudeKey; - static const char *_jsonV3GridAltitudeRelativeKey; - static const char *_jsonV3GridAngleKey; - static const char *_jsonV3GridSpacingKey; - static const char *_jsonV3EntryPointKey; - static const char *_jsonV3TurnaroundDistKey; - static const char *_jsonV3CameraTriggerDistanceKey; - static const char *_jsonV3CameraTriggerInTurnaroundKey; - static const char *_jsonV3HoverAndCaptureKey; - static const char *_jsonV3GroundResolutionKey; - static const char *_jsonV3FrontalOverlapKey; - static const char *_jsonV3SideOverlapKey; - static const char *_jsonV3CameraSensorWidthKey; - static const char *_jsonV3CameraSensorHeightKey; - static const char *_jsonV3CameraResolutionWidthKey; - static const char *_jsonV3CameraResolutionHeightKey; - static const char *_jsonV3CameraFocalLengthKey; - static const char *_jsonV3CameraMinTriggerIntervalKey; - static const char *_jsonV3ManualGridKey; - static const char *_jsonV3CameraObjectKey; - static const char *_jsonV3CameraNameKey; - static const char *_jsonV3CameraOrientationLandscapeKey; - static const char *_jsonV3FixedValueIsAltitudeKey; - static const char *_jsonV3Refly90DegreesKey; + enum CameraTriggerCode { + CameraTriggerNone, + CameraTriggerOn, + CameraTriggerOff, + CameraTriggerHoverAndCapture + }; + + QPointF _rotatePoint(const QPointF& point, const QPointF& origin, double angle); + void _intersectLinesWithRect(const QList& lineList, const QRectF& boundRect, QList& resultLines); + void _intersectLinesWithPolygon(const QList& lineList, const QPolygonF& polygon, QList& resultLines); + void _adjustLineDirection(const QList& lineList, QList& resultLines); + bool _nextTransectCoord(const QList& transectPoints, int pointIndex, QGeoCoordinate& coord); + bool _appendMissionItemsWorker(QList& items, QObject* missionItemParent, int& seqNum, bool hasRefly, bool buildRefly); + void _optimizeTransectsForShortestDistance(const QGeoCoordinate& distanceCoord, QList>& transects); + qreal _ccw(QPointF pt1, QPointF pt2, QPointF pt3); + qreal _dp(QPointF pt1, QPointF pt2); + void _swapPoints(QList& points, int index1, int index2); + void _reverseTransectOrder(QList>& transects); + void _reverseInternalTransectPoints(QList>& transects); + void _adjustTransectsToEntryPointLocation(QList>& transects); + bool _gridAngleIsNorthSouthTransects(); + double _clampGridAngle90(double gridAngle); + bool _imagesEverywhere(void) const; + bool _triggerCamera(void) const; + bool _hasTurnaround(void) const; + double _turnaroundDistance(void) const; + + bool _hoverAndCaptureEnabled(void) const; + bool _loadV3(const QJsonObject& complexObject, int sequenceNumber, QString& errorString); + bool _loadV4V5(const QJsonObject& complexObject, int sequenceNumber, QString& errorString, int version, bool forPresets); + void _saveWorker(QJsonObject& complexObject); + void _rebuildTransectsPhase1Worker(bool refly); + void _rebuildTransectsPhase1WorkerSinglePolygon(bool refly); + void _rebuildTransectsPhase1WorkerSplitPolygons(bool refly); + /// Adds to the _transects array from one polygon + void _rebuildTransectsFromPolygon(bool refly, const QPolygonF& polygon, const QGeoCoordinate& tangentOrigin, const QPointF* const transitionPoint); + // Decompose polygon into list of convex sub polygons + void _PolygonDecomposeConvex(const QPolygonF& polygon, QList& decomposedPolygons); + // return true if vertex a can see vertex b + bool _VertexCanSeeOther(const QPolygonF& polygon, const QPointF* vertexA, const QPointF* vertexB); + bool _VertexIsReflex(const QPolygonF& polygon, const QPointF* vertex); + + QMap _metaDataMap; + + SettingsFact _gridAngleFact; + SettingsFact _flyAlternateTransectsFact; + SettingsFact _splitConcavePolygonsFact; + int _entryPoint; + + static const char* _jsonGridAngleKey; + static const char* _jsonEntryPointKey; + static const char* _jsonFlyAlternateTransectsKey; + static const char* _jsonSplitConcavePolygonsKey; + + static const char* _jsonV3GridObjectKey; + static const char* _jsonV3GridAltitudeKey; + static const char* _jsonV3GridAltitudeRelativeKey; + static const char* _jsonV3GridAngleKey; + static const char* _jsonV3GridSpacingKey; + static const char* _jsonV3EntryPointKey; + static const char* _jsonV3TurnaroundDistKey; + static const char* _jsonV3CameraTriggerDistanceKey; + static const char* _jsonV3CameraTriggerInTurnaroundKey; + static const char* _jsonV3HoverAndCaptureKey; + static const char* _jsonV3GroundResolutionKey; + static const char* _jsonV3FrontalOverlapKey; + static const char* _jsonV3SideOverlapKey; + static const char* _jsonV3CameraSensorWidthKey; + static const char* _jsonV3CameraSensorHeightKey; + static const char* _jsonV3CameraResolutionWidthKey; + static const char* _jsonV3CameraResolutionHeightKey; + static const char* _jsonV3CameraFocalLengthKey; + static const char* _jsonV3CameraMinTriggerIntervalKey; + static const char* _jsonV3ManualGridKey; + static const char* _jsonV3CameraObjectKey; + static const char* _jsonV3CameraNameKey; + static const char* _jsonV3CameraOrientationLandscapeKey; + static const char* _jsonV3FixedValueIsAltitudeKey; + static const char* _jsonV3Refly90DegreesKey; }; diff --git a/src/MissionManager/TransectStyleComplexItem.cc b/src/MissionManager/TransectStyleComplexItem.cc index 504f28d2a4c8b766be7e882f452d26a93ce0b9a9..e85f9497038da3ab7b4d4689fef18a641dc5c1eb 100644 --- a/src/MissionManager/TransectStyleComplexItem.cc +++ b/src/MissionManager/TransectStyleComplexItem.cc @@ -171,7 +171,7 @@ void TransectStyleComplexItem::_save(QJsonObject& complexObject) innerObject[_jsonVisualTransectPointsKey] = transectPointsJson; // Save the interal mission items - QJsonObject missionItemsJsonArray; + QJsonArray missionItemsJsonArray; QObject* missionItemParent = new QObject(); QList missionItems; appendMissionItems(missionItems, missionItemParent); @@ -242,7 +242,7 @@ bool TransectStyleComplexItem::_load(const QJsonObject& complexObject, bool forP // Load generated mission items _loadedMissionItemsParent = new QObject(this); - QJsonObject missionItemsJsonArray = innerObject[_jsonItemsKey].toArray(); + QJsonArray missionItemsJsonArray = innerObject[_jsonItemsKey].toArray(); for (const QJsonValue missionItemJson: missionItemsJsonArray) { MissionItem* missionItem = new MissionItem(_loadedMissionItemsParent); if (!missionItem->load(missionItemJson.toObject(), 0 /* sequenceNumber */, errorString)) { diff --git a/src/MissionManager/TransectStyleComplexItem.h b/src/MissionManager/TransectStyleComplexItem.h index d44e4ffc8d39ce2a3d76f7beeead22d379e6d1ec..949c3bf3c4156471ef1e15a67123bb53c4e44e0c 100644 --- a/src/MissionManager/TransectStyleComplexItem.h +++ b/src/MissionManager/TransectStyleComplexItem.h @@ -86,7 +86,7 @@ public: double greatestDistanceTo (const QGeoCoordinate &other) const final; // Overrides from VisualMissionItem - void save (QJsonObject& planItems) override = 0; + void save (QJsonArray& planItems) override = 0; bool specifiesCoordinate (void) const override = 0; virtual void appendMissionItems (QList& items, QObject* missionItemParent) final; virtual void applyNewAltitude (double newAltitude) final; diff --git a/src/MissionManager/TransectStyleComplexItemTest.h b/src/MissionManager/TransectStyleComplexItemTest.h index 054b78b755c894f0308bcb937777bacbc8bdbbc4..d430615f7de7c2fe0b2893fd7f9f3d8d27fd9bad 100644 --- a/src/MissionManager/TransectStyleComplexItemTest.h +++ b/src/MissionManager/TransectStyleComplexItemTest.h @@ -88,7 +88,7 @@ public: bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final { Q_UNUSED(complexObject); Q_UNUSED(sequenceNumber); Q_UNUSED(errorString); return false; } // Overrides from VisualMissionItem - void save (QJsonObject& missionItems) final { Q_UNUSED(missionItems); } + void save (QJsonArray& missionItems) final { Q_UNUSED(missionItems); } bool specifiesCoordinate (void) const final { return true; } double additionalTimeDelay (void) const final { return 0; } diff --git a/src/MissionManager/VTOLLandingComplexItem.cc b/src/MissionManager/VTOLLandingComplexItem.cc index 5ff8d850228fc237a7d37069663f42d744ab3871..808ccec96e67b46c87c943af9ca3c7e9651f944a 100644 --- a/src/MissionManager/VTOLLandingComplexItem.cc +++ b/src/MissionManager/VTOLLandingComplexItem.cc @@ -57,7 +57,7 @@ VTOLLandingComplexItem::VTOLLandingComplexItem(PlanMasterController* masterContr setDirty(false); } -void VTOLLandingComplexItem::save(QJsonObject& missionItems) +void VTOLLandingComplexItem::save(QJsonArray& missionItems) { QJsonObject saveObject = _save(); diff --git a/src/MissionManager/VTOLLandingComplexItem.h b/src/MissionManager/VTOLLandingComplexItem.h index a604524bccc50e659a18e9ebce98e96d3b023341..f5a8afc8f3d4a6ed9ba78d72b29e4d557afd6373 100644 --- a/src/MissionManager/VTOLLandingComplexItem.h +++ b/src/MissionManager/VTOLLandingComplexItem.h @@ -35,7 +35,7 @@ public: QString mapVisualQML (void) const final { return QStringLiteral("VTOLLandingPatternMapVisual.qml"); } // Overrides from VisualMissionItem - void save (QJsonObject& missionItems) final; + void save (QJsonArray& missionItems) final; static const QString name; diff --git a/src/MissionManager/VisualMissionItem.h b/src/MissionManager/VisualMissionItem.h index 887a0410d554760efa71a43b3fb2317b1a1ecd5e..df0bac2172ebc25d90341d120aa109dee2b971de 100644 --- a/src/MissionManager/VisualMissionItem.h +++ b/src/MissionManager/VisualMissionItem.h @@ -177,7 +177,7 @@ public: /// Save the item(s) in Json format /// @param missionItems Current set of mission items, new items should be appended to the end - virtual void save(QJsonObject& missionItems) = 0; + virtual void save(QJsonArray& missionItems) = 0; /// @return The QML resource file which contains the control which visualizes the item on the map. virtual QString mapVisualQML(void) const = 0; diff --git a/src/QtLocationPlugin/QGeoCodeReplyQGC.cpp b/src/QtLocationPlugin/QGeoCodeReplyQGC.cpp index b81ea9cd09d6d77c73618c3faf94a2d75b0a5cfe..1bf8cd3ed9b2dd728f85596a8019b167d26d2654 100644 --- a/src/QtLocationPlugin/QGeoCodeReplyQGC.cpp +++ b/src/QtLocationPlugin/QGeoCodeReplyQGC.cpp @@ -97,7 +97,7 @@ enum QGCGeoCodeType { class JasonMonger { public: JasonMonger(); - QSet json2QGCGeoCodeType(const QJsonObject &types); + QSet json2QGCGeoCodeType(const QJsonArray &types); private: int _getCode(const QString &key); QMap _m; @@ -145,7 +145,7 @@ int JasonMonger::_getCode(const QString &key) { return _m.value(key, GeoCodeTypeUnknown); } -QSet JasonMonger::json2QGCGeoCodeType(const QJsonObject &types) { +QSet JasonMonger::json2QGCGeoCodeType(const QJsonArray &types) { QSet result; for (int i=0; i locations; - QJsonObject results = object.value(QStringLiteral("results")).toArray(); + QJsonArray results = object.value(QStringLiteral("results")).toArray(); for (int i=0; i heights; - const QJsonObject& dataArray = coordinateJson.toArray(); + const QJsonArray& dataArray = coordinateJson.toArray(); for (int i = 0; i < dataArray.count(); i++) { heights.append(dataArray[i].toDouble()); } @@ -234,8 +234,8 @@ void TerrainAirMapQuery::_parseCoordinateData(const QJsonValue& coordinateJson) void TerrainAirMapQuery::_parsePathData(const QJsonValue& pathJson) { QJsonObject jsonObject = pathJson.toArray()[0].toObject(); - QJsonObject stepArray = jsonObject["step"].toArray(); - QJsonObject profileArray = jsonObject["profile"].toArray(); + QJsonArray stepArray = jsonObject["step"].toArray(); + QJsonArray profileArray = jsonObject["profile"].toArray(); double latStep = stepArray[0].toDouble(); double lonStep = stepArray[1].toDouble(); @@ -258,10 +258,10 @@ void TerrainAirMapQuery::_parseCarpetData(const QJsonValue& carpetJson) QList> carpet; if (!_carpetStatsOnly) { - QJsonObject carpetArray = jsonObject["carpet"].toArray(); + QJsonArray carpetArray = jsonObject["carpet"].toArray(); for (int i=0; i()); for (int j=0; j emptyDefineMap; diff --git a/src/Vehicle/CompInfoVersion.cc b/src/Vehicle/CompInfoVersion.cc index 06300facf2dfde211c92ccb2f1b1b6a4b9fa8361..f71243e415b95b97026d930a6c16ea646e2a0610 100644 --- a/src/Vehicle/CompInfoVersion.cc +++ b/src/Vehicle/CompInfoVersion.cc @@ -58,7 +58,7 @@ void CompInfoVersion::setJson(const QString& metadataJsonFileName, const QString return; } - QJsonObject rgSupportedTypes = jsonObj[_jsonSupportedCompMetadataTypesKey].toArray(); + QJsonArray rgSupportedTypes = jsonObj[_jsonSupportedCompMetadataTypesKey].toArray(); for (QJsonValue typeValue: rgSupportedTypes) { _supportedTypes.append(static_cast(typeValue.toInt())); } diff --git a/src/VehicleSetup/FirmwareUpgradeController.cc b/src/VehicleSetup/FirmwareUpgradeController.cc index c628e4e8058620b1f85a459253be7c00c57cc5fb..5874560a661fd902369951369dfd373a48377845 100644 --- a/src/VehicleSetup/FirmwareUpgradeController.cc +++ b/src/VehicleSetup/FirmwareUpgradeController.cc @@ -877,7 +877,7 @@ void FirmwareUpgradeController::_px4ReleasesGithubDownloadComplete(QString /*rem qCWarning(FirmwareUpgradeLog) << "px4 releases json document is not an array" << localFile; return; } - QJsonObject releases = doc.array(); + QJsonArray releases = doc.array(); // The first release marked prerelease=false is stable // The first release marked prerelease=true is beta @@ -941,7 +941,7 @@ void FirmwareUpgradeController::_ardupilotManifestDownloadComplete(QString remot } QJsonObject json = doc.object(); - QJsonObject rgFirmware = json[_manifestFirmwareJsonKey].toArray(); + QJsonArray rgFirmware = json[_manifestFirmwareJsonKey].toArray(); for (int i=0; i