From e5b570c5cce85a81894cbfe3d05b526f6ccc6b59 Mon Sep 17 00:00:00 2001 From: Valentin Platzgummer Date: Mon, 7 Dec 2020 12:04:17 +0100 Subject: [PATCH] temp, QVariant compile error --- src/FactSystem/FactGroup.cc | 2 +- src/FactSystem/FactGroup.h | 2 +- src/FactSystem/FactMetaData.cc | 8 +- src/FactSystem/FactMetaData.h | 2 +- src/JsonHelper.cc | 893 +++--- src/JsonHelper.h | 345 ++- src/MeasurementComplexItem/AreaData.cc | 40 + src/MeasurementComplexItem/AreaData.h | 5 + .../CircularGenerator.cpp | 54 +- .../CircularGenerator.h | 15 +- src/MeasurementComplexItem/GeneratorBase.h | 5 + .../LinearGenerator.cpp | 46 + src/MeasurementComplexItem/LinearGenerator.h | 11 + .../MeasurementComplexItem.cc | 59 +- .../MeasurementComplexItem.h | 3 +- src/MissionManager/CorridorScanComplexItem.cc | 2 +- src/MissionManager/CorridorScanComplexItem.h | 2 +- src/MissionManager/FWLandingPatternTest.cc | 2 +- .../FixedWingLandingComplexItem.cc | 2 +- .../FixedWingLandingComplexItem.h | 2 +- src/MissionManager/GeoFenceController.cc | 8 +- src/MissionManager/LandingComplexItemTest.h | 2 +- src/MissionManager/MissionCommandList.cc | 2 +- src/MissionManager/MissionController.cc | 8 +- src/MissionManager/MissionItem.cc | 8 +- src/MissionManager/MissionItemTest.cc | 24 +- src/MissionManager/MissionSettingsItem.cc | 2 +- src/MissionManager/MissionSettingsItem.h | 2 +- src/MissionManager/RallyPointController.cc | 2 +- src/MissionManager/SimpleMissionItem.cc | 2 +- src/MissionManager/SimpleMissionItem.h | 2 +- .../StructureScanComplexItem.cc | 2 +- src/MissionManager/StructureScanComplexItem.h | 2 +- .../StructureScanComplexItemTest.cc | 2 +- src/MissionManager/SurveyComplexItem.cc | 2733 +++++++++-------- src/MissionManager/SurveyComplexItem.h | 309 +- .../TransectStyleComplexItem.cc | 4 +- src/MissionManager/TransectStyleComplexItem.h | 2 +- .../TransectStyleComplexItemTest.h | 2 +- src/MissionManager/VTOLLandingComplexItem.cc | 2 +- src/MissionManager/VTOLLandingComplexItem.h | 2 +- src/MissionManager/VisualMissionItem.h | 2 +- src/QtLocationPlugin/QGeoCodeReplyQGC.cpp | 8 +- src/Terrain/TerrainQuery.cc | 10 +- src/TerrainTile.cc | 8 +- src/Vehicle/CompInfoParam.cc | 2 +- src/Vehicle/CompInfoVersion.cc | 2 +- src/VehicleSetup/FirmwareUpgradeController.cc | 8 +- src/comm/QGCSerialPortInfo.cc | 4 +- 49 files changed, 2587 insertions(+), 2079 deletions(-) diff --git a/src/FactSystem/FactGroup.cc b/src/FactSystem/FactGroup.cc index 436f9d0e0..517e948f4 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 QJsonArray jsonArray) +void FactGroup::_loadFromJsonArray(const QJsonObject jsonArray) { QMap defineMap; _nameToFactMetaDataMap = FactMetaData::createMapFromJsonArray(jsonArray, defineMap, this); diff --git a/src/FactSystem/FactGroup.h b/src/FactSystem/FactGroup.h index 114fb44f4..f3b3981c6 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 QJsonArray jsonArray); + void _loadFromJsonArray (const QJsonObject 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 7e1c95a8a..0ac10c22e 100644 --- a/src/FactSystem/FactMetaData.cc +++ b/src/FactSystem/FactMetaData.cc @@ -1448,7 +1448,7 @@ QMap FactMetaData::createMapFromJsonFile(const QString& return metaDataMap; } - QJsonArray factArray; + QJsonObject factArray; QMap defineMap; QList keyInfoList = { @@ -1466,7 +1466,7 @@ QMap FactMetaData::createMapFromJsonFile(const QString& return createMapFromJsonArray(factArray, defineMap, metaDataParent); } -QMap FactMetaData::createMapFromJsonArray(const QJsonArray jsonArray, QMap& defineMap, QObject* metaDataParent) +QMap FactMetaData::createMapFromJsonArray(const QJsonObject 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 QJsonArray jsonArray, DefineMap_t& defineMap, QObject* metaDataParent); + static QMap createMapFromJsonArray(const QJsonObject jsonArray, DefineMap_t& defineMap, QObject* metaDataParent); static FactMetaData* createFromJsonObject(const QJsonObject& json, QMap& defineMap, QObject* metaDataParent); diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc index 82579c829..d701185b6 100644 --- a/src/JsonHelper.cc +++ b/src/JsonHelper.cc @@ -8,518 +8,551 @@ ****************************************************************************/ #include "JsonHelper.h" -#include "QGCQGeoCoordinate.h" -#include "QmlObjectListModel.h" -#include "MissionCommandList.h" #include "FactMetaData.h" +#include "MissionCommandList.h" #include "QGCApplication.h" +#include "QGCQGeoCoordinate.h" +#include "QmlObjectListModel.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; - } - - 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; +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; } -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); +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); } -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, + 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; + } } + } - return true; + return true; } -bool JsonHelper::isJsonFile(const QByteArray& bytes, QJsonDocument& jsonDoc, QString& errorString) -{ - QJsonParseError parseError; +bool JsonHelper::isJsonFile(const QByteArray &bytes, QJsonDocument &jsonDoc, + QString &errorString) { + QJsonParseError parseError; - jsonDoc = QJsonDocument::fromJson(bytes, &parseError); + jsonDoc = QJsonDocument::fromJson(bytes, &parseError); - if (parseError.error == QJsonParseError::NoError) { - return true; - } else { - int startPos = qMax(0, parseError.offset - 100); - int length = qMin(bytes.count() - startPos, 200); - qDebug() << QStringLiteral("Json read error '%1'").arg(bytes.mid(startPos, length).constData()); - errorString = parseError.errorString(); - return false; - } + if (parseError.error == QJsonParseError::NoError) { + return true; + } else { + int startPos = qMax(0, parseError.offset - 100); + int length = qMin(bytes.count() - startPos, 200); + qDebug() << QStringLiteral("Json read error '%1'") + .arg(bytes.mid(startPos, length).constData()); + errorString = parseError.errorString(); + return false; + } } -bool JsonHelper::isJsonFile(const QString& fileName, QJsonDocument& jsonDoc, QString& errorString) -{ - QFile jsonFile(fileName); - if (!jsonFile.open(QFile::ReadOnly)) { - errorString = tr("File open failed: file:error %1 %2").arg(jsonFile.fileName()).arg(jsonFile.errorString()); - return false; - } - QByteArray jsonBytes = jsonFile.readAll(); - jsonFile.close(); - - return isJsonFile(jsonBytes, jsonDoc, errorString); +bool JsonHelper::isJsonFile(const QString &fileName, QJsonDocument &jsonDoc, + QString &errorString) { + QFile jsonFile(fileName); + if (!jsonFile.open(QFile::ReadOnly)) { + errorString = tr("File open failed: file:error %1 %2") + .arg(jsonFile.fileName()) + .arg(jsonFile.errorString()); + return false; + } + QByteArray jsonBytes = jsonFile.readAll(); + jsonFile.close(); + + return isJsonFile(jsonBytes, jsonDoc, 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; +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; } -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::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); } -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(","); +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(","); } -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); +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); + } } - } - return jsonObject; + 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()) { + 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; } -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()); - } + rgPoints.clear(); + for (int i = 0; i < rgVarPoints.count(); i++) { + rgPoints.append(rgVarPoints[i].value()); + } - return true; + return true; } -void JsonHelper::saveGeoCoordinateArray(const QVariantList& rgVarPoints, - bool writeAltitude, - QJsonValue& jsonValue) -{ - QJsonArray rgJsonPoints; +void JsonHelper::saveGeoCoordinateArray(const QVariantList &rgVarPoints, + bool writeAltitude, + QJsonValue &jsonValue) { + QJsonObject rgJsonPoints; - // 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& keyInfo, QString& errorString) -{ - QStringList keyList; - QList typeList; - - 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); } -QString JsonHelper::_jsonValueTypeToString(QJsonValue::Type type) -{ - const struct { - QJsonValue::Type type; - const char* string; - } rgTypeToString[] = { - { QJsonValue::Null, "NULL" }, - { QJsonValue::Bool, "Bool" }, - { QJsonValue::Double, "Double" }, - { QJsonValue::String, "String" }, - { QJsonValue::Array, "Array" }, - { QJsonValue::Object, "Object" }, - { QJsonValue::Undefined, "Undefined" }, -}; - - for (size_t i=0; i(i)->coordinate(); +void JsonHelper::savePolygon(QmlObjectListModel &list, + QJsonObject &polygonArray) { + for (int i = 0; i < list.count(); i++) { + QGeoCoordinate vertex = list.value(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 74a9634ea..f953ebe68 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,164 +21,193 @@ 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 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; + /// 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; 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 QJsonArray _translateArray(QJsonArray& 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 QJsonObject _translateArray(QJsonObject &jsonArray, + const QString &translateContext, + const QStringList &translateKeys); + + static const char *_translateKeysKey; + static const char *_arrayIDKeysKey; }; diff --git a/src/MeasurementComplexItem/AreaData.cc b/src/MeasurementComplexItem/AreaData.cc index 65d3ba1ec..a50eafc92 100644 --- a/src/MeasurementComplexItem/AreaData.cc +++ b/src/MeasurementComplexItem/AreaData.cc @@ -4,12 +4,17 @@ #include "geometry/SafeArea.h" #include "geometry/snake.h" +#include "JsonHelper.h" #include "QGCApplication.h" #include "QGCLoggingCategory.h" #include "QGCQGeoCoordinate.h" QGC_LOGGING_CATEGORY(AreaDataLog, "AreaDataLog") +const char *originJsonKey = "Origin"; +const char *areaListJsonKey = "AreaList"; +const char *initializedJsonKey = "Initialized"; + AreaData::AreaData(QObject *parent) : QObject(parent) {} AreaData::~AreaData() {} @@ -270,6 +275,41 @@ bool AreaData::operator!=(const AreaData &other) const { return !(*this == other); } +bool AreaData::load(const QJsonObject &obj, QString &guiErrorString) { + qWarning() << "AreaData::load impl. missing"; + return true; +} + +bool AreaData::save(QJsonObject &obj) const { + // QJsonObject temp; + + // QJsonValue jsonOrigin; + // JsonHelper::saveGeoCoordinate(_origin, true, jsonOrigin); + // temp[originJsonKey] = jsonOrigin; + // temp[initializedJsonKey] = _initialized; + + // QJsonObject jsonAreaList; + // for (int i = 0; i < _areaList.count(); ++i) { + // auto const &qobj = _areaList[i]; + // auto const &area = qobject_cast(qobj); + // QJsonObject jsonArea; + // if (area->saveToJson(jsonArea)) { + // QJsonObject jsonObj; + // jsonObj[area->objectName()] = jsonArea; + // jsonAreaList.append(jsonObj); + // } else { + // qDebug(AreaListLog) << "save(): not able to save area: " + // << area->objectName(); + // _processError(tr("Not able to save area: ") + area->objectName()); + // return false; + // } + // } + // temp[areaListJsonKey] = jsonAreaList; + + // obj = std::move(temp); + return true; +} + void AreaData::_setOrigin(const QGeoCoordinate &origin) { if (this->_origin != origin) { this->_origin = origin; diff --git a/src/MeasurementComplexItem/AreaData.h b/src/MeasurementComplexItem/AreaData.h index 41df7011d..195eef2cd 100644 --- a/src/MeasurementComplexItem/AreaData.h +++ b/src/MeasurementComplexItem/AreaData.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include "QmlObjectListModel.h" @@ -72,6 +74,9 @@ public: bool operator==(const AreaData &other) const; bool operator!=(const AreaData &other) const; + bool load(const QJsonObject &obj, QString &guiErrorString); + bool save(QJsonObject &obj) const; + QString errorString() const; // Contains a message about the last error. bool showErrorMessages() const; diff --git a/src/MeasurementComplexItem/CircularGenerator.cpp b/src/MeasurementComplexItem/CircularGenerator.cpp index 65e77ef24..73b48a78f 100644 --- a/src/MeasurementComplexItem/CircularGenerator.cpp +++ b/src/MeasurementComplexItem/CircularGenerator.cpp @@ -1,5 +1,6 @@ #include "CircularGenerator.h" +#include "JsonHelper.h" #include "QGCLoggingCategory.h" #include "SettingsFact.h" @@ -29,9 +30,7 @@ const char *CircularGenerator::settingsGroup = "CircularGenerator"; const char *CircularGenerator::distanceName = "TransectDistance"; const char *CircularGenerator::deltaAlphaName = "DeltaAlpha"; const char *CircularGenerator::minLengthName = "MinLength"; -const char *CircularGenerator::refPointLatitudeName = "ReferencePointLat"; -const char *CircularGenerator::refPointLongitudeName = "ReferencePointLong"; -const char *CircularGenerator::refPointAltitudeName = "ReferencePointAlt"; +const char *CircularGenerator::refPointName = "ReferencePoint"; CircularGenerator::CircularGenerator(QObject *parent) : CircularGenerator(nullptr, parent) {} @@ -184,6 +183,55 @@ void CircularGenerator::setReference(const QGeoCoordinate &reference) { } } +bool CircularGenerator::save(QJsonObject &obj) const { + // QJsonObject temp; + + // bool ok = false; + // auto variant = _distance.rawValue(); + // auto val = variant.toDouble(ok); + // if (!ok) { + // qCDebug(CircularGeneratorLog) + // << "save(): not able to save distance. Not a double: " + // << variant.typeName(); + // return false; + // } else { + // temp[distanceName] = val; + // } + + // variant = _deltaAlpha.rawValue(); + // val = variant.toDouble(ok); + // if (!ok) { + // qCDebug(CircularGeneratorLog) + // << "save(): not able to save deltaAlpha. Not a double: " + // << variant.typeName(); + // return false; + // } else { + // temp[deltaAlphaName] = val; + // } + + // variant = _minLength.rawValue(); + // val = variant.toDouble(ok); + // if (!ok) { + // qCDebug(CircularGeneratorLog) + // << "save(): not able to save minLength. Not a double: " + // << variant.typeName(); + // return false; + // } else { + // temp[minLengthName] = val; + // } + + // QJsonObject jsonReference; + // JsonHelper::saveGeoCoordinate(_reference, true, jsonReference); + // temp[refPointName] = jsonReference; + + // obj = std::move(temp); + return true; +} + +bool CircularGenerator::load(const QJsonObject &obj, QString &guiErrorMessage) { + qWarning() << "CircularGenerator::load() impl. missing"; + return true; +} void CircularGenerator::resetReferenceIfInvalid() { if (!this->_reference.isValid()) { resetReference(); diff --git a/src/MeasurementComplexItem/CircularGenerator.h b/src/MeasurementComplexItem/CircularGenerator.h index 98fca1967..c42710cef 100644 --- a/src/MeasurementComplexItem/CircularGenerator.h +++ b/src/MeasurementComplexItem/CircularGenerator.h @@ -35,13 +35,22 @@ public: void setReference(const QGeoCoordinate &reference); + //! + //! \brief save Saves the generator. + //! \param obj Json object for saveing. + //! \return Returns true on success, false either. + //! + //! Saves distance, alpha and minLength. + //! \note This methode does not save the data. + //! + virtual bool save(QJsonObject &obj) const override; + virtual bool load(const QJsonObject &obj, QString &guiErrorMessage) override; + static const char *settingsGroup; static const char *distanceName; static const char *deltaAlphaName; static const char *minLengthName; - static const char *refPointLongitudeName; - static const char *refPointLatitudeName; - static const char *refPointAltitudeName; + static const char *refPointName; signals: void referenceChanged(); diff --git a/src/MeasurementComplexItem/GeneratorBase.h b/src/MeasurementComplexItem/GeneratorBase.h index 8fb375b36..1f0bb22a5 100644 --- a/src/MeasurementComplexItem/GeneratorBase.h +++ b/src/MeasurementComplexItem/GeneratorBase.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -27,6 +29,9 @@ public: virtual QString editorQml() = 0; virtual QString mapVisualQml() = 0; + virtual bool save(QJsonObject &obj) const = 0; + virtual bool load(const QJsonObject &obj, QString &guiErrorMessage) = 0; + virtual QString name() = 0; virtual QString abbreviation() = 0; diff --git a/src/MeasurementComplexItem/LinearGenerator.cpp b/src/MeasurementComplexItem/LinearGenerator.cpp index 118e307b9..a3a18bc0f 100644 --- a/src/MeasurementComplexItem/LinearGenerator.cpp +++ b/src/MeasurementComplexItem/LinearGenerator.cpp @@ -149,6 +149,52 @@ bool LinearGenerator::get(Generator &generator) { } } +bool LinearGenerator::save(QJsonObject &obj) const { + // QJsonObject temp; + + // bool ok = false; + // auto variant = _distance.rawValue(); + // auto val = variant.toDouble(ok); + // if (!ok) { + // qCDebug(LinearGeneratorLog) + // << "save(): not able to save distance. Not a double: " + // << variant.typeName(); + // return false; + // } else { + // temp[distanceName] = val; + // } + + // variant = _alpha.rawValue(); + // val = variant.toDouble(ok); + // if (!ok) { + // qCDebug(LinearGeneratorLog) + // << "save(): not able to save alpha. Not a double: " + // << variant.typeName(); + // return false; + // } else { + // temp[alphaName] = val; + // } + + // variant = _minLength.rawValue(); + // val = variant.toDouble(ok); + // if (!ok) { + // qCDebug(LinearGeneratorLog) + // << "save(): not able to save minLength. Not a double: " + // << variant.typeName(); + // return false; + // } else { + // temp[minLengthName] = val; + // } + + // obj = std::move(temp); + return true; +} + +bool LinearGenerator::load(const QJsonObject &obj, QString &guiErrorString) { + qWarning() << "LinearGenerator::load() impl. missing"; + return true; +} + Fact *LinearGenerator::distance() { return &_distance; } Fact *LinearGenerator::alpha() { return &_alpha; } diff --git a/src/MeasurementComplexItem/LinearGenerator.h b/src/MeasurementComplexItem/LinearGenerator.h index 0e6cc531a..fd4f0e3dd 100644 --- a/src/MeasurementComplexItem/LinearGenerator.h +++ b/src/MeasurementComplexItem/LinearGenerator.h @@ -27,6 +27,17 @@ public: virtual bool get(Generator &generator) override; + //! + //! \brief save Saves the generator. + //! \param obj Json object for saveing. + //! \return Returns true on success, false either. + //! + //! Saves distance, alpha and minLength. + //! \note This methode does not save the data. + //! + virtual bool save(QJsonObject &obj) const override; + virtual bool load(const QJsonObject &obj, QString &guiErrorString) override; + Fact *distance(); Fact *alpha(); Fact *minLength(); diff --git a/src/MeasurementComplexItem/MeasurementComplexItem.cc b/src/MeasurementComplexItem/MeasurementComplexItem.cc index 41262996d..6b01d642a 100644 --- a/src/MeasurementComplexItem/MeasurementComplexItem.cc +++ b/src/MeasurementComplexItem/MeasurementComplexItem.cc @@ -33,6 +33,10 @@ const char *MeasurementComplexItem::jsonComplexItemTypeValue = const char *MeasurementComplexItem::variantName = "Variant"; const char *MeasurementComplexItem::altitudeName = "Altitude"; const QString MeasurementComplexItem::name(tr("Measurement")); +const char *areaDataName = "AreaDataName"; +const char *variantNamesName = "VariantNames"; +const char *generatorsName = "Generators"; +const char *variantsName = "Variants"; MeasurementComplexItem::MeasurementComplexItem( PlanMasterController *masterController, bool flyView, @@ -208,10 +212,7 @@ QString MeasurementComplexItem::mapVisualQML() const { return QStringLiteral("MeasurementItemMapVisual.qml"); } -void MeasurementComplexItem::save(QJsonArray &planItems) { - qWarning() << "MeasurementComplexItem::save(): area data save missing."; - qWarning() << "MeasurementComplexItem::save(): mission item save missing."; - +void MeasurementComplexItem::save(QJsonObject &planItems) { if (idle()) { QJsonObject saveObject; @@ -221,10 +222,60 @@ void MeasurementComplexItem::save(QJsonArray &planItems) { saveObject[ComplexMissionItem::jsonComplexItemTypeKey] = jsonComplexItemTypeValue; + // Variant and altitude. saveObject[variantName] = double(_variant.rawValue().toUInt()); saveObject[altitudeName] = double(_altitude.rawValue().toUInt()); + // Variant names. + QJsonObject jsonVariantNames; + for (auto const &name : _variantNames) { + jsonVariantNames.append(name); + } + saveObject[variantNamesName] = jsonVariantNames; + + // AreaData. + // QJsonObject jsonAreaData; + // if (!_pAreaData->save(jsonAreaData)) { + // qCDebug(MeasurementComplexItemLog) + // << "save(): not able to save area data"; + // return; + // } + // saveObject[areaDataName] = jsonAreaData; + + // Generators. + // QJsonArray generatorArray; + // for (int i = 0; i < _generatorList.size(); ++i) { + // auto const gen = _generatorList[i]; + // auto const &name = _generatorNameList[i]; + // QJsonObject innerObj; + // if (!gen->save(innerObj)) { + // qCDebug(MeasurementComplexItemLog) + // << "save(): not able to save generator: " << name; + // return; + // } else { + // QJsonObject outerObj; + // outerObj[name] = innerObj; + // generatorArray.append(outerObj); + // } + // } + // saveObject[generatorsName] = generatorArray; + + // Route Variants + QJsonObject variantsArray; + for (auto const &route : _variantVector) { + QJsonValue variant; + if (!(route.size() == 0)) { + JsonHelper::saveGeoCoordinateArray(route, true, variant); + } else { + JsonHelper::saveGeoCoordinateArray(_route, true, variant); + } + variantsArray.append(variant); + } + saveObject[variantsName] = variantsArray; + planItems.append(saveObject); + } else { + qCDebug(MeasurementComplexItemLog) << "save(): called while not idle."; } } diff --git a/src/MeasurementComplexItem/MeasurementComplexItem.h b/src/MeasurementComplexItem/MeasurementComplexItem.h index adba43f7e..b95466c2a 100644 --- a/src/MeasurementComplexItem/MeasurementComplexItem.h +++ b/src/MeasurementComplexItem/MeasurementComplexItem.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -84,7 +85,7 @@ public: virtual void setDirty(bool dirty) override final; virtual void setCoordinate(const QGeoCoordinate &coordinate) override final; virtual void setSequenceNumber(int sequenceNumber) override final; - virtual void save(QJsonArray &missionItems) override final; + virtual void save(QJsonObject &missionItems) override final; virtual double amslEntryAlt(void) const override final; virtual double amslExitAlt(void) const override final; virtual double minAMSLAltitude(void) const override final; diff --git a/src/MissionManager/CorridorScanComplexItem.cc b/src/MissionManager/CorridorScanComplexItem.cc index 73836271e..704922bba 100644 --- a/src/MissionManager/CorridorScanComplexItem.cc +++ b/src/MissionManager/CorridorScanComplexItem.cc @@ -61,7 +61,7 @@ CorridorScanComplexItem::CorridorScanComplexItem(PlanMasterController* masterCon setDirty(false); } -void CorridorScanComplexItem::save(QJsonArray& planItems) +void CorridorScanComplexItem::save(QJsonObject& planItems) { QJsonObject saveObject; diff --git a/src/MissionManager/CorridorScanComplexItem.h b/src/MissionManager/CorridorScanComplexItem.h index 6eefc413e..f4537251c 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 (QJsonArray& planItems) final; + void save (QJsonObject& planItems) final; bool specifiesCoordinate (void) const final; double timeBetweenShots (void) final; diff --git a/src/MissionManager/FWLandingPatternTest.cc b/src/MissionManager/FWLandingPatternTest.cc index 7e662dbf8..f86bebd05 100644 --- a/src/MissionManager/FWLandingPatternTest.cc +++ b/src/MissionManager/FWLandingPatternTest.cc @@ -81,7 +81,7 @@ void FWLandingPatternTest::_testDirty(void) void FWLandingPatternTest::_testSaveLoad(void) { - QJsonArray items; + QJsonObject items; _fwItem->save(items); diff --git a/src/MissionManager/FixedWingLandingComplexItem.cc b/src/MissionManager/FixedWingLandingComplexItem.cc index 486647360..2da97f594 100644 --- a/src/MissionManager/FixedWingLandingComplexItem.cc +++ b/src/MissionManager/FixedWingLandingComplexItem.cc @@ -60,7 +60,7 @@ FixedWingLandingComplexItem::FixedWingLandingComplexItem(PlanMasterController* m setDirty(false); } -void FixedWingLandingComplexItem::save(QJsonArray& missionItems) +void FixedWingLandingComplexItem::save(QJsonObject& missionItems) { QJsonObject saveObject = _save(); diff --git a/src/MissionManager/FixedWingLandingComplexItem.h b/src/MissionManager/FixedWingLandingComplexItem.h index 7316fa88e..812f62706 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 (QJsonArray& missionItems) final; + void save (QJsonObject& missionItems) final; static const QString name; diff --git a/src/MissionManager/GeoFenceController.cc b/src/MissionManager/GeoFenceController.cc index 4e3042d19..db25fdc65 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; } - QJsonArray jsonPolygonArray = json[_jsonPolygonsKey].toArray(); + QJsonObject 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); } - QJsonArray jsonCircleArray = json[_jsonCirclesKey].toArray(); + QJsonObject 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; - QJsonArray jsonPolygonArray; + QJsonObject 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; - QJsonArray jsonCircleArray; + QJsonObject 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 cf32f8230..347f2bef4 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 (QJsonArray& /*missionItems*/) { }; + void save (QJsonObject& /*missionItems*/) { }; static const QString name; diff --git a/src/MissionManager/MissionCommandList.cc b/src/MissionManager/MissionCommandList.cc index 5ca37136c..0bc4b203f 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 - QJsonArray jsonArray = jsonValue.toArray(); + QJsonObject 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 e69d87b7d..c5651b741 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; - QJsonArray complexArray(json[_jsonComplexItemsKey].toArray()); + QJsonObject 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 - QJsonArray itemArray(json[_jsonItemsKey].toArray()); + QJsonObject 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 QJsonArray rgMissionItems(json[_jsonItemsKey].toArray()); + const QJsonObject 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 - QJsonArray rgJsonMissionItems; + QJsonObject 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 3fb185546..f9bad1552 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; - QJsonArray rgParams = {param1(), param2(), param3(), param4(), + QJsonObject rgParams = {param1(), param2(), param3(), param4(), param5(), param6(), param7()}; json[_jsonParamsKey] = rgParams; } @@ -206,7 +206,7 @@ bool MissionItem::_convertJsonV1ToV2(const QJsonObject &json, VisualMissionItem::jsonTypeSimpleItemValue; } - QJsonArray rgParams = { + QJsonObject 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; } - QJsonArray rgParam = json[_jsonParamsKey].toArray(); + QJsonObject 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; } - QJsonArray rgParams = convertedJson[_jsonParamsKey].toArray(); + QJsonObject 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 e2df716a2..45546e550 100644 --- a/src/MissionManager/MissionItemTest.cc +++ b/src/MissionManager/MissionItemTest.cc @@ -342,7 +342,7 @@ void MissionItemTest::_testLoadFromJsonV2(void) QVERIFY(!errorString.isEmpty()); qDebug() << errorString; - QJsonArray badCoordinateArray; + QJsonObject badCoordinateArray; badCoordinateArray << -10.0 << -20.0 ; badObject = jsonObject; badObject.remove("coordinate"); @@ -351,7 +351,7 @@ void MissionItemTest::_testLoadFromJsonV2(void) QVERIFY(!errorString.isEmpty()); qDebug() << errorString; - QJsonArray badCoordinateArray_second; + QJsonObject 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; - QJsonArray badCoordinateArray2; + QJsonObject badCoordinateArray2; badCoordinateArray2 << 1 << 2; - QJsonArray badCoordinateArray_third; + QJsonObject 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; - QJsonArray rgParam = badObject[MissionItem::_jsonParamsKey].toArray(); + QJsonObject 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; - QJsonArray coordinateArray; + QJsonObject 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); - QJsonArray rgParams = { 10, 20, 30, 40 }; + QJsonObject 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; - QJsonArray coordinateArray; + QJsonObject 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; - QJsonArray coordinateArray; + QJsonObject 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); - QJsonArray rgParams = { 10, 20, 30, 40 }; + QJsonObject 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) { - QJsonArray rgParams = { NAN, NAN, NAN, NAN, NAN, NAN, NAN }; + QJsonObject rgParams = { NAN, NAN, NAN, NAN, NAN, NAN, NAN }; jsonObject.insert(MissionItem::_jsonParamsKey, rgParams); } else { - QJsonArray rgParams = { 10, 20, 30, 40, -10, -20, -30 }; + QJsonObject 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 fceba9d1d..326eb86c3 100644 --- a/src/MissionManager/MissionSettingsItem.cc +++ b/src/MissionManager/MissionSettingsItem.cc @@ -88,7 +88,7 @@ void MissionSettingsItem::setDirty(bool dirty) } } -void MissionSettingsItem::save(QJsonArray& missionItems) +void MissionSettingsItem::save(QJsonObject& missionItems) { QList items; diff --git a/src/MissionManager/MissionSettingsItem.h b/src/MissionManager/MissionSettingsItem.h index 921225c08..824dea40d 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 (QJsonArray& missionItems) final; + void save (QJsonObject& 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 77be14448..8faa00d0e 100644 --- a/src/MissionManager/RallyPointController.cc +++ b/src/MissionManager/RallyPointController.cc @@ -128,7 +128,7 @@ void RallyPointController::save(QJsonObject& json) { json[JsonHelper::jsonVersionKey] = _jsonCurrentVersion; - QJsonArray rgPoints; + QJsonObject 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 a1b4c70e8..14f1eff3b 100644 --- a/src/MissionManager/SimpleMissionItem.cc +++ b/src/MissionManager/SimpleMissionItem.cc @@ -260,7 +260,7 @@ SimpleMissionItem::~SimpleMissionItem() { } -void SimpleMissionItem::save(QJsonArray& missionItems) +void SimpleMissionItem::save(QJsonObject& missionItems) { QList items; diff --git a/src/MissionManager/SimpleMissionItem.h b/src/MissionManager/SimpleMissionItem.h index fd33ad4c3..6fd358bfe 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 (QJsonArray& missionItems) final; + void save (QJsonObject& missionItems) final; signals: void commandChanged (int command); diff --git a/src/MissionManager/StructureScanComplexItem.cc b/src/MissionManager/StructureScanComplexItem.cc index db9d933c0..580f1c288 100644 --- a/src/MissionManager/StructureScanComplexItem.cc +++ b/src/MissionManager/StructureScanComplexItem.cc @@ -177,7 +177,7 @@ void StructureScanComplexItem::setDirty(bool dirty) } } -void StructureScanComplexItem::save(QJsonArray& missionItems) +void StructureScanComplexItem::save(QJsonObject& missionItems) { QJsonObject saveObject; diff --git a/src/MissionManager/StructureScanComplexItem.h b/src/MissionManager/StructureScanComplexItem.h index d99f4d9f7..93916b8d1 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 (QJsonArray& missionItems) final; + void save (QJsonObject& 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 db3c06e9a..5fd8e5153 100644 --- a/src/MissionManager/StructureScanComplexItemTest.cc +++ b/src/MissionManager/StructureScanComplexItemTest.cc @@ -110,7 +110,7 @@ void StructureScanComplexItemTest::_testSaveLoad(void) { _initItem(); - QJsonArray items; + QJsonObject items; _structureScanItem->save(items); QString errorString; diff --git a/src/MissionManager/SurveyComplexItem.cc b/src/MissionManager/SurveyComplexItem.cc index 3096dab60..c5e7621d2 100644 --- a/src/MissionManager/SurveyComplexItem.cc +++ b/src/MissionManager/SurveyComplexItem.cc @@ -7,16 +7,15 @@ * ****************************************************************************/ - #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 @@ -24,1417 +23,1625 @@ 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); + } + + 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); } -void SurveyComplexItem::save(QJsonArray& planItems) -{ - QJsonObject saveObject; +void SurveyComplexItem::save(QJsonObject &planItems) { + QJsonObject saveObject; - _saveWorker(saveObject); - planItems.append(saveObject); + _saveWorker(saveObject); + planItems.append(saveObject); } -void SurveyComplexItem::savePreset(const QString& name) -{ - QJsonObject saveObject; +void SurveyComplexItem::savePreset(const QString &name) { + QJsonObject saveObject; - _saveWorker(saveObject); - _savePresetJson(name, saveObject); + _saveWorker(saveObject); + _savePresetJson(name, 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::_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::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::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(); - } +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); - - if (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, errorString)) { - _surveyAreaPolygon.clear(); - return false; - } - } - - if (!TransectStyleComplexItem::_load(complexObject, forPresets, errorString)) { - _ignoreRecalc = false; - return false; - } - - _gridAngleFact.setRawValue (complexObject[_jsonGridAngleKey].toDouble()); - _flyAlternateTransectsFact.setRawValue (complexObject[_jsonFlyAlternateTransectsKey].toBool(false)); +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); - if (version == 5) { - _splitConcavePolygonsFact.setRawValue (complexObject[_jsonSplitConcavePolygonsKey].toBool(true)); + if (!_surveyAreaPolygon.loadFromJson(complexObject, true /* required */, + errorString)) { + _surveyAreaPolygon.clear(); + return false; } + } - _entryPoint = complexObject[_jsonEntryPointKey].toInt(); - + if (!TransectStyleComplexItem::_load(complexObject, forPresets, + errorString)) { _ignoreRecalc = false; + return false; + } - return true; -} + _gridAngleFact.setRawValue(complexObject[_jsonGridAngleKey].toDouble()); + _flyAlternateTransectsFact.setRawValue( + complexObject[_jsonFlyAlternateTransectsKey].toBool(false)); -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; - } + if (version == 5) { + _splitConcavePolygonsFact.setRawValue( + complexObject[_jsonSplitConcavePolygonsKey].toBool(true)); + } - _ignoreRecalc = true; + _entryPoint = complexObject[_jsonEntryPointKey].toInt(); - setSequenceNumber(sequenceNumber); + _ignoreRecalc = false; - _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); + return 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 }, +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}, }; - 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 }, - }; - 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(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 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 < 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; + } } -/// 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, + 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) { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - return line.intersect(linePosition, &intersectPoint) == QLineF::BoundedIntersection; + return line.intersect(linePosition, &intersectPoint) == + QLineF::BoundedIntersection; #else - return line.intersects(linePosition, &intersectPoint) == QLineF::BoundedIntersection; + return line.intersects(linePosition, &intersectPoint) == + QLineF::BoundedIntersection; #endif }; - int foundCount = 0; - if (isLineBoundedIntersect(topLine)) { - intersectLine.setP1(intersectPoint); - foundCount++; + int foundCount = 0; + if (isLineBoundedIntersect(topLine)) { + intersectLine.setP1(intersectPoint); + foundCount++; + } + if (isLineBoundedIntersect(rightLine)) { + if (foundCount == 0) { + intersectLine.setP1(intersectPoint); + } else { + if (foundCount != 1) { + qWarning() << "Found more than two intersecting points"; } - if (isLineBoundedIntersect(rightLine)) { - if (foundCount == 0) { - intersectLine.setP1(intersectPoint); - } else { - if (foundCount != 1) { - qWarning() << "Found more than two intersecting points"; - } - intersectLine.setP2(intersectPoint); - } - foundCount++; + intersectLine.setP2(intersectPoint); + } + foundCount++; + } + if (isLineBoundedIntersect(bottomLine)) { + if (foundCount == 0) { + intersectLine.setP1(intersectPoint); + } else { + if (foundCount != 1) { + qWarning() << "Found more than two intersecting points"; } - if (isLineBoundedIntersect(bottomLine)) { - if (foundCount == 0) { - intersectLine.setP1(intersectPoint); - } else { - if (foundCount != 1) { - qWarning() << "Found more than two intersecting points"; - } - intersectLine.setP2(intersectPoint); - } - foundCount++; - } - if (isLineBoundedIntersect(leftLine)) { - if (foundCount == 0) { - intersectLine.setP1(intersectPoint); - } else { - if (foundCount != 1) { - qWarning() << "Found more than two intersecting points"; - } - intersectLine.setP2(intersectPoint); - } - foundCount++; + intersectLine.setP2(intersectPoint); + } + foundCount++; + } + if (isLineBoundedIntersect(leftLine)) { + if (foundCount == 0) { + intersectLine.setP1(intersectPoint); + } else { + if (foundCount != 1) { + qWarning() << "Found more than two intersecting points"; } + intersectLine.setP2(intersectPoint); + } + foundCount++; + } - if (foundCount == 2) { - resultLines += intersectLine; - } + if (foundCount == 2) { + resultLines += intersectLine; } + } } -void SurveyComplexItem::_intersectLinesWithPolygon(const QList& 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 intersections; + for (int i = 0; i < lineList.count(); i++) { + const QLineF &line = lineList[i]; + QList intersections; - // 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 currentMaxDistance) { - firstPoint = intersections[i]; - secondPoint = intersections[j]; - currentMaxDistance = newMaxDistance; - } - } - } - - 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 < 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; + } } + } + + 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; iP2 +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; - if (qAbs(line.angle() - firstAngle) > 1.0) { - adjustedLine.setP1(line.p2()); - adjustedLine.setP2(line.p1()); - } else { - adjustedLine = line; - } + if (i == 0) { + firstAngle = line.angle(); + } - resultLines += adjustedLine; + if (qAbs(line.angle() - firstAngle) > 1.0) { + adjustedLine.setP1(line.p2()); + adjustedLine.setP2(line.p1()); + } else { + adjustedLine = line; } + + 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 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 < 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; #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; 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; i 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); } - - _transects.append(coordInfoTransect); - } -} - - -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; - } + // Extend the transect ends for turnaround + if (_hasTurnaround()) { + QGeoCoordinate turnaroundCoord; + double turnAroundDistance = _turnAroundDistanceFact.rawValue().toDouble(); - // 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(); - } + 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 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; - } - } - - } - - - // 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); - } + _transects.append(coordInfoTransect); + } } -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; - } +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; } - + } } - // assemble output - if (decomposedPolygonsMin.size() > 0) { - decomposedPolygons << decomposedPolygonsMin; - } else { - decomposedPolygons << polygon; - } + // 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); + } +} +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{}; +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; 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; + // 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); } - 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; + if (triggerDistance == 0) { + _cameraShots = 0; + } else { + if (_cameraTriggerInTurnAroundFact.rawValue().toBool()) { + _cameraShots = qCeil(_complexDistance / triggerDistance); } else { - if (_cameraTriggerInTurnAroundFact.rawValue().toBool()) { - _cameraShots = qCeil(_complexDistance / triggerDistance); + _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 { - _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); - } + 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); + } + } } + } - 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 78c103a1a..3ead3027f 100644 --- a/src/MissionManager/SurveyComplexItem.h +++ b/src/MissionManager/SurveyComplexItem.h @@ -9,163 +9,186 @@ #pragma once -#include "TransectStyleComplexItem.h" #include "MissionItem.h" -#include "SettingsFact.h" #include "QGCLoggingCategory.h" +#include "SettingsFact.h" +#include "TransectStyleComplexItem.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 (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; + /// @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; 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 e85f94970..504f28d2a 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 - QJsonArray missionItemsJsonArray; + QJsonObject 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); - QJsonArray missionItemsJsonArray = innerObject[_jsonItemsKey].toArray(); + QJsonObject 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 949c3bf3c..d44e4ffc8 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 (QJsonArray& planItems) override = 0; + void save (QJsonObject& 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 d430615f7..054b78b75 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 (QJsonArray& missionItems) final { Q_UNUSED(missionItems); } + void save (QJsonObject& 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 808ccec96..5ff8d8502 100644 --- a/src/MissionManager/VTOLLandingComplexItem.cc +++ b/src/MissionManager/VTOLLandingComplexItem.cc @@ -57,7 +57,7 @@ VTOLLandingComplexItem::VTOLLandingComplexItem(PlanMasterController* masterContr setDirty(false); } -void VTOLLandingComplexItem::save(QJsonArray& missionItems) +void VTOLLandingComplexItem::save(QJsonObject& missionItems) { QJsonObject saveObject = _save(); diff --git a/src/MissionManager/VTOLLandingComplexItem.h b/src/MissionManager/VTOLLandingComplexItem.h index f5a8afc8f..a604524bc 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 (QJsonArray& missionItems) final; + void save (QJsonObject& missionItems) final; static const QString name; diff --git a/src/MissionManager/VisualMissionItem.h b/src/MissionManager/VisualMissionItem.h index df0bac217..887a0410d 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(QJsonArray& missionItems) = 0; + virtual void save(QJsonObject& 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 1bf8cd3ed..b81ea9cd0 100644 --- a/src/QtLocationPlugin/QGeoCodeReplyQGC.cpp +++ b/src/QtLocationPlugin/QGeoCodeReplyQGC.cpp @@ -97,7 +97,7 @@ enum QGCGeoCodeType { class JasonMonger { public: JasonMonger(); - QSet json2QGCGeoCodeType(const QJsonArray &types); + QSet json2QGCGeoCodeType(const QJsonObject &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 QJsonArray &types) { +QSet JasonMonger::json2QGCGeoCodeType(const QJsonObject &types) { QSet result; for (int i=0; i locations; - QJsonArray results = object.value(QStringLiteral("results")).toArray(); + QJsonObject results = object.value(QStringLiteral("results")).toArray(); for (int i=0; i heights; - const QJsonArray& dataArray = coordinateJson.toArray(); + const QJsonObject& 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(); - QJsonArray stepArray = jsonObject["step"].toArray(); - QJsonArray profileArray = jsonObject["profile"].toArray(); + QJsonObject stepArray = jsonObject["step"].toArray(); + QJsonObject 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) { - QJsonArray carpetArray = jsonObject["carpet"].toArray(); + QJsonObject 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 f71243e41..06300facf 100644 --- a/src/Vehicle/CompInfoVersion.cc +++ b/src/Vehicle/CompInfoVersion.cc @@ -58,7 +58,7 @@ void CompInfoVersion::setJson(const QString& metadataJsonFileName, const QString return; } - QJsonArray rgSupportedTypes = jsonObj[_jsonSupportedCompMetadataTypesKey].toArray(); + QJsonObject 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 5874560a6..c628e4e80 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; } - QJsonArray releases = doc.array(); + QJsonObject 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(); - QJsonArray rgFirmware = json[_manifestFirmwareJsonKey].toArray(); + QJsonObject rgFirmware = json[_manifestFirmwareJsonKey].toArray(); for (int i=0; i