diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index ed2714441ff499c8f68b7cdd14bafddf1551a2ba..114ab1c3ad4c13cd4686fb1b85840fd24e217cf0 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -515,6 +515,7 @@ HEADERS += \ src/Joystick/Joystick.h \ src/Joystick/JoystickManager.h \ src/JsonHelper.h \ + src/KMLFileHelper.h \ src/LogCompressor.h \ src/MG.h \ src/MissionManager/CameraCalc.h \ @@ -711,6 +712,7 @@ SOURCES += \ src/Joystick/Joystick.cc \ src/Joystick/JoystickManager.cc \ src/JsonHelper.cc \ + src/KMLFileHelper.cc \ src/LogCompressor.cc \ src/MissionManager/CameraCalc.cc \ src/MissionManager/CameraSection.cc \ diff --git a/src/KMLFileHelper.cc b/src/KMLFileHelper.cc new file mode 100644 index 0000000000000000000000000000000000000000..7415c4d3ae6b4d2add6065fb81f66982ff04a2f2 --- /dev/null +++ b/src/KMLFileHelper.cc @@ -0,0 +1,176 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "KMLFileHelper.h" + +#include + +QDomDocument KMLFileHelper::loadFile(const QString& kmlFile, QString& errorString) +{ + QFile file(kmlFile); + + errorString.clear(); + + if (!file.exists()) { + errorString = tr("File not found: %1").arg(kmlFile); + return QDomDocument(); + } + + if (!file.open(QIODevice::ReadOnly)) { + errorString = tr("Unable to open file: %1 error: $%2").arg(kmlFile).arg(file.errorString()); + return QDomDocument(); + } + + QDomDocument doc; + QString errorMessage; + int errorLine; + if (!doc.setContent(&file, &errorMessage, &errorLine)) { + errorString = tr("Unable to parse KML file: %1 error: %2 line: %3").arg(kmlFile).arg(errorMessage).arg(errorLine); + return QDomDocument(); + } + + return doc; +} + +QVariantList KMLFileHelper::determineFileContents(const QString& kmlFile) +{ + QString errorString; + KMLFileContents fileContents = determineFileContents(kmlFile, errorString); + + QVariantList varList; + varList.append(QVariant::fromValue(fileContents)); + varList.append(QVariant::fromValue(errorString)); + + return varList; +} + +KMLFileHelper::KMLFileContents KMLFileHelper::determineFileContents(const QString& kmlFile, QString& errorString) +{ + QDomDocument domDocument = KMLFileHelper::loadFile(kmlFile, errorString); + if (!errorString.isEmpty()) { + return Error; + } + + QDomNodeList rgNodes = domDocument.elementsByTagName("Polygon"); + if (rgNodes.count()) { + return Polygon; + } + + rgNodes = domDocument.elementsByTagName("LineString"); + if (rgNodes.count()) { + return Polyline; + } + + errorString = tr("No known type found in KML file."); + return Error; +} + +bool KMLFileHelper::loadPolygonFromFile(const QString& kmlFile, QList& vertices, QString& errorString) +{ + errorString.clear(); + vertices.clear(); + + QDomDocument domDocument = KMLFileHelper::loadFile(kmlFile, errorString); + if (!errorString.isEmpty()) { + return false; + } + + QDomNodeList rgNodes = domDocument.elementsByTagName("Polygon"); + if (rgNodes.count() == 0) { + errorString = tr("Unable to find Polygon node in KML"); + return false; + } + + QDomNode coordinatesNode = rgNodes.item(0).namedItem("outerBoundaryIs").namedItem("LinearRing").namedItem("coordinates"); + if (coordinatesNode.isNull()) { + errorString = tr("Internal error: Unable to find coordinates node in KML"); + return false; + } + + QString coordinatesString = coordinatesNode.toElement().text().simplified(); + QStringList rgCoordinateStrings = coordinatesString.split(" "); + + QList rgCoords; + for (int i=0; i rgReversed; + + for (int i=0; i& coords, QString& errorString) +{ + errorString.clear(); + coords.clear(); + + QDomDocument domDocument = KMLFileHelper::loadFile(kmlFile, errorString); + if (!errorString.isEmpty()) { + return false; + } + + QDomNodeList rgNodes = domDocument.elementsByTagName("LineString"); + if (rgNodes.count() == 0) { + errorString = tr("Unable to find LineString node in KML"); + return false; + } + + QDomNode coordinatesNode = rgNodes.item(0).namedItem("coordinates"); + if (coordinatesNode.isNull()) { + errorString = tr("Internal error: Unable to find coordinates node in KML"); + return false; + } + + QString coordinatesString = coordinatesNode.toElement().text().simplified(); + QStringList rgCoordinateStrings = coordinatesString.split(" "); + + QList rgCoords; + for (int i=0; i + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +/// The QGCMapPolygon class provides a polygon which can be displayed on a map using a map visuals control. +/// It maintains a representation of the polygon on QVariantList and QmlObjectListModel format. +class KMLFileHelper : public QObject +{ + Q_OBJECT + +public: + enum KMLFileContents { + Polygon, + Polyline, + Error + }; + Q_ENUM(KMLFileContents) + + Q_INVOKABLE static QVariantList determineFileContents(const QString& kmlFile); + + static KMLFileContents determineFileContents(const QString& kmlFile, QString& errorString); + static QDomDocument loadFile(const QString& kmlFile, QString& errorString); + static bool loadPolygonFromFile(const QString& kmlFile, QList& vertices, QString& errorString); + static bool loadPolylineFromFile(const QString& kmlFile, QList& coords, QString& errorString); +}; diff --git a/src/MissionManager/CorridorScanComplexItem.cc b/src/MissionManager/CorridorScanComplexItem.cc index bef3ce21766dfe4635e882ab3a27ff9d4cc8f112..827ba910ee5d69b253181fe16b62a795c713364e 100644 --- a/src/MissionManager/CorridorScanComplexItem.cc +++ b/src/MissionManager/CorridorScanComplexItem.cc @@ -27,7 +27,7 @@ const char* CorridorScanComplexItem::_jsonEntryPointKey = "EntryPoint"; const char* CorridorScanComplexItem::jsonComplexItemTypeValue = "CorridorScan"; -CorridorScanComplexItem::CorridorScanComplexItem(Vehicle* vehicle, bool flyView, QObject* parent) +CorridorScanComplexItem::CorridorScanComplexItem(Vehicle* vehicle, bool flyView, const QString& kmlFile, QObject* parent) : TransectStyleComplexItem (vehicle, flyView, settingsGroup, parent) , _entryPoint (0) , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CorridorScan.SettingsGroup.json"), this)) @@ -50,6 +50,12 @@ CorridorScanComplexItem::CorridorScanComplexItem(Vehicle* vehicle, bool flyView, connect(&_corridorPolyline, &QGCMapPolyline::pathChanged, this, &CorridorScanComplexItem::_rebuildCorridorPolygon); connect(&_corridorWidthFact, &Fact::valueChanged, this, &CorridorScanComplexItem::_rebuildCorridorPolygon); + + if (!kmlFile.isEmpty()) { + _corridorPolyline.loadKMLFile(kmlFile); + _corridorPolyline.setDirty(false); + } + setDirty(false); } void CorridorScanComplexItem::save(QJsonArray& planItems) diff --git a/src/MissionManager/CorridorScanComplexItem.h b/src/MissionManager/CorridorScanComplexItem.h index 4a1144029be4978b247cf8a3c72c00e4be331e61..a5fe2cab8acee6a353cd838c2862545caa04bc86 100644 --- a/src/MissionManager/CorridorScanComplexItem.h +++ b/src/MissionManager/CorridorScanComplexItem.h @@ -23,7 +23,10 @@ class CorridorScanComplexItem : public TransectStyleComplexItem Q_OBJECT public: - CorridorScanComplexItem(Vehicle* vehicle, bool flyView, QObject* parent); + /// @param vehicle Vehicle which this is being contructed for + /// @param flyView true: Created for use in the Fly View, false: Created for use in the Plan View + /// @param kmlFile Polyline comes from this file, empty for default polyline + CorridorScanComplexItem(Vehicle* vehicle, bool flyView, const QString& kmlFile, QObject* parent); Q_PROPERTY(QGCMapPolyline* corridorPolyline READ corridorPolyline CONSTANT) Q_PROPERTY(Fact* corridorWidth READ corridorWidth CONSTANT) diff --git a/src/MissionManager/CorridorScanComplexItemTest.cc b/src/MissionManager/CorridorScanComplexItemTest.cc index 142e2a0db3b1cd17a8ebc6eac9c7307e981b19c4..3086fb97972f1548b7356670a7cb7ce9c2db7afe 100644 --- a/src/MissionManager/CorridorScanComplexItemTest.cc +++ b/src/MissionManager/CorridorScanComplexItemTest.cc @@ -23,7 +23,7 @@ void CorridorScanComplexItemTest::init(void) UnitTest::init(); _offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this); - _corridorItem = new CorridorScanComplexItem(_offlineVehicle, false /* flyView */, this); + _corridorItem = new CorridorScanComplexItem(_offlineVehicle, false /* flyView */, QString() /* kmlFile */, this /* parent */); // vehicleSpeed need for terrain calcs MissionController::MissionFlightStatus_t missionFlightStatus; diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index 311b2ef537e1ebc725e6f0370019de41758b0a09..572e0ca03840753e1da0fd3cb860530e9e3286d8 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -389,25 +389,48 @@ int MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int i) int MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int i) { ComplexMissionItem* newItem; - bool surveyStyleItem = false; int sequenceNumber = _nextSequenceNumber(); if (itemName == _surveyMissionItemName) { - newItem = new SurveyComplexItem(_controllerVehicle, _flyView, _visualItems); + newItem = new SurveyComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, _visualItems /* parent */); newItem->setCoordinate(mapCenterCoordinate); - surveyStyleItem = true; } else if (itemName == _fwLandingMissionItemName) { - newItem = new FixedWingLandingComplexItem(_controllerVehicle, _flyView, _visualItems); + newItem = new FixedWingLandingComplexItem(_controllerVehicle, _flyView, _visualItems /* parent */); } else if (itemName == _structureScanMissionItemName) { - newItem = new StructureScanComplexItem(_controllerVehicle, _flyView, _visualItems); + newItem = new StructureScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, _visualItems /* parent */); } else if (itemName == _corridorScanMissionItemName) { - newItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, _visualItems); - surveyStyleItem = true; + newItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, _visualItems /* parent */); } else { qWarning() << "Internal error: Unknown complex item:" << itemName; return sequenceNumber; } + return _insertComplexMissionItemWorker(newItem, i); +} + +int MissionController::insertComplexMissionItemFromKML(QString itemName, QString kmlFile, int i) +{ + ComplexMissionItem* newItem; + + if (itemName == _surveyMissionItemName) { + newItem = new SurveyComplexItem(_controllerVehicle, _flyView, kmlFile, _visualItems); + } else if (itemName == _structureScanMissionItemName) { + newItem = new StructureScanComplexItem(_controllerVehicle, _flyView, kmlFile, _visualItems); + } else if (itemName == _corridorScanMissionItemName) { + newItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, kmlFile, _visualItems); + } else { + qWarning() << "Internal error: Unknown complex item:" << itemName; + return _nextSequenceNumber(); + } + + return _insertComplexMissionItemWorker(newItem, i); +} + +int MissionController::_insertComplexMissionItemWorker(ComplexMissionItem* complexItem, int i) +{ + int sequenceNumber = _nextSequenceNumber(); + bool surveyStyleItem = qobject_cast(complexItem) || qobject_cast(complexItem); + if (surveyStyleItem) { bool rollSupported = false; bool pitchSupported = false; @@ -434,14 +457,18 @@ int MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate } } - newItem->setSequenceNumber(sequenceNumber); - _initVisualItem(newItem); + complexItem->setSequenceNumber(sequenceNumber); + _initVisualItem(complexItem); - _visualItems->insert(i, newItem); + if (i == -1) { + _visualItems->append(complexItem); + } else { + _visualItems->insert(i, complexItem); + } _recalcAll(); - return newItem->sequenceNumber(); + return complexItem->sequenceNumber(); } void MissionController::removeMissionItem(int index) @@ -529,7 +556,7 @@ bool MissionController::_loadJsonMissionFileV1(const QJsonObject& json, QmlObjec return false; } - SurveyComplexItem* item = new SurveyComplexItem(_controllerVehicle, _flyView, visualItems); + SurveyComplexItem* item = new SurveyComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems /* parent */); const QJsonObject itemObject = itemValue.toObject(); if (item->load(itemObject, itemObject["id"].toInt(), errorString)) { surveyItems.append(item); @@ -687,7 +714,7 @@ bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjec if (complexItemType == SurveyComplexItem::jsonComplexItemTypeValue) { qCDebug(MissionControllerLog) << "Loading Survey: nextSequenceNumber" << nextSequenceNumber; - SurveyComplexItem* surveyItem = new SurveyComplexItem(_controllerVehicle, _flyView, visualItems); + SurveyComplexItem* surveyItem = new SurveyComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems); if (!surveyItem->load(itemObject, nextSequenceNumber++, errorString)) { return false; } @@ -705,7 +732,7 @@ bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjec visualItems->append(landingItem); } else if (complexItemType == StructureScanComplexItem::jsonComplexItemTypeValue) { qCDebug(MissionControllerLog) << "Loading Structure Scan: nextSequenceNumber" << nextSequenceNumber; - StructureScanComplexItem* structureItem = new StructureScanComplexItem(_controllerVehicle, _flyView, visualItems); + StructureScanComplexItem* structureItem = new StructureScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems); if (!structureItem->load(itemObject, nextSequenceNumber++, errorString)) { return false; } @@ -714,7 +741,7 @@ bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjec visualItems->append(structureItem); } else if (complexItemType == CorridorScanComplexItem::jsonComplexItemTypeValue) { qCDebug(MissionControllerLog) << "Loading Corridor Scan: nextSequenceNumber" << nextSequenceNumber; - CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, visualItems); + CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems); if (!corridorItem->load(itemObject, nextSequenceNumber++, errorString)) { return false; } diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h index 54c4469b4179c62038b6f9c917345370ff58bd2c..5015a95aea93afa5648fbeecb395fbd93458be05 100644 --- a/src/MissionManager/MissionController.h +++ b/src/MissionManager/MissionController.h @@ -25,6 +25,7 @@ class MissionSettingsItem; class AppSettings; class MissionManager; class SimpleMissionItem; +class ComplexMissionItem; class QDomDocument; Q_DECLARE_LOGGING_CATEGORY(MissionControllerLog) @@ -89,6 +90,10 @@ public: Q_PROPERTY(int batteryChangePoint READ batteryChangePoint NOTIFY batteryChangePointChanged) Q_PROPERTY(int batteriesRequired READ batteriesRequired NOTIFY batteriesRequiredChanged) + Q_PROPERTY(QString surveyComplexItemName READ surveyComplexItemName CONSTANT) + Q_PROPERTY(QString corridorScanComplexItemName READ corridorScanComplexItemName CONSTANT) + Q_PROPERTY(QString structureScanComplexItemName READ structureScanComplexItemName CONSTANT) + Q_INVOKABLE void removeMissionItem(int index); /// Add a new simple mission item to the list @@ -108,6 +113,12 @@ public: /// @return Sequence number for new item Q_INVOKABLE int insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int i); + /// Add a new complex mission item to the list + /// @param itemName: Name of complex item to create (from complexMissionItemNames) + /// @param i: index to insert at, -1 for end + /// @return Sequence number for new item + Q_INVOKABLE int insertComplexMissionItemFromKML(QString itemName, QString kmlFile, int i); + Q_INVOKABLE void resumeMission(int resumeIndex); /// Updates the altitudes of the items in the current mission to the new default altitude @@ -148,13 +159,16 @@ public: // Property accessors - QmlObjectListModel* visualItems (void) { return _visualItems; } - QmlObjectListModel* waypointLines (void) { return &_waypointLines; } - QVariantList waypointPath (void) { return _waypointPath; } - QStringList complexMissionItemNames (void) const; - QGeoCoordinate plannedHomePosition (void) const; - VisualMissionItem* currentPlanViewItem (void) const; - double progressPct (void) const { return _progressPct; } + QmlObjectListModel* visualItems (void) { return _visualItems; } + QmlObjectListModel* waypointLines (void) { return &_waypointLines; } + QVariantList waypointPath (void) { return _waypointPath; } + QStringList complexMissionItemNames (void) const; + QGeoCoordinate plannedHomePosition (void) const; + VisualMissionItem* currentPlanViewItem (void) const; + double progressPct (void) const { return _progressPct; } + QString surveyComplexItemName (void) const { return _surveyMissionItemName; } + QString corridorScanComplexItemName (void) const { return _corridorScanMissionItemName; } + QString structureScanComplexItemName(void) const { return _structureScanMissionItemName; } int currentMissionIndex (void) const; int resumeMissionIndex (void) const; @@ -242,6 +256,7 @@ private: void _addWaypointLineSegment(CoordVectHashTable& prevItemPairHashTable, VisualItemPair& pair); void _addCommandTimeDelay(SimpleMissionItem* simpleItem, bool vtolInHover); void _addTimeDistance(bool vtolInHover, double hoverTime, double cruiseTime, double extraTime, double distance, int seqNum); + int _insertComplexMissionItemWorker(ComplexMissionItem* complexItem, int i); private: MissionManager* _missionManager; diff --git a/src/MissionManager/PlanMasterController.cc b/src/MissionManager/PlanMasterController.cc index c0638ccca8d6d606923103646418906dd90c84ff..48b331a79e300727c75db37ca880d54a717cc717 100644 --- a/src/MissionManager/PlanMasterController.cc +++ b/src/MissionManager/PlanMasterController.cc @@ -484,7 +484,7 @@ QStringList PlanMasterController::saveNameFilters(void) const return filters; } -QStringList PlanMasterController::saveKmlFilters(void) const +QStringList PlanMasterController::fileKmlFilters(void) const { QStringList filters; diff --git a/src/MissionManager/PlanMasterController.h b/src/MissionManager/PlanMasterController.h index f688b4e0501f0518b131b6a9ee2599b8c1e6bac8..d6d454fa8509cb32c86d56e1bcdfb857597efdcc 100644 --- a/src/MissionManager/PlanMasterController.h +++ b/src/MissionManager/PlanMasterController.h @@ -43,7 +43,7 @@ public: ///< kml file extension for missions Q_PROPERTY(QStringList loadNameFilters READ loadNameFilters CONSTANT) ///< File filter list loading plan files Q_PROPERTY(QStringList saveNameFilters READ saveNameFilters CONSTANT) ///< File filter list saving plan files - Q_PROPERTY(QStringList saveKmlFilters READ saveKmlFilters CONSTANT) ///< File filter list saving KML files + Q_PROPERTY(QStringList fileKmlFilters READ fileKmlFilters CONSTANT) ///< File filter list for load/save KML files /// Should be called immediately upon Component.onCompleted. Q_INVOKABLE void start(bool flyView); @@ -81,7 +81,7 @@ public: QString kmlFileExtension(void) const; QStringList loadNameFilters (void) const; QStringList saveNameFilters (void) const; - QStringList saveKmlFilters (void) const; + QStringList fileKmlFilters (void) const; QJsonDocument saveToJson (); diff --git a/src/MissionManager/QGCMapPolygon.cc b/src/MissionManager/QGCMapPolygon.cc index 0611d5f066ec3e6d08ef4076a8151af4d42eade5..feec11d0ffb3dfd85d7aef124fc9380b9a8e9454 100644 --- a/src/MissionManager/QGCMapPolygon.cc +++ b/src/MissionManager/QGCMapPolygon.cc @@ -12,6 +12,7 @@ #include "JsonHelper.h" #include "QGCQGeoCoordinate.h" #include "QGCApplication.h" +#include "KMLFileHelper.h" #include #include @@ -453,71 +454,11 @@ void QGCMapPolygon::offset(double distance) bool QGCMapPolygon::loadKMLFile(const QString& kmlFile) { - QFile file(kmlFile); - - if (!file.exists()) { - qgcApp()->showMessage(tr("File not found: %1").arg(kmlFile)); - return false; - } - - if (!file.open(QIODevice::ReadOnly)) { - qgcApp()->showMessage(tr("Unable to open file: %1 error: $%2").arg(kmlFile).arg(file.errorString())); - return false; - } - - QDomDocument doc; - QString errorMessage; - int errorLine; - if (!doc.setContent(&file, &errorMessage, &errorLine)) { - qgcApp()->showMessage(tr("Unable to parse KML file: %1 error: %2 line: %3").arg(kmlFile).arg(errorMessage).arg(errorLine)); - return false; - } - - QDomNodeList rgNodes = doc.elementsByTagName("Polygon"); - if (rgNodes.count() == 0) { - qgcApp()->showMessage(tr("Unable to find Polygon node in KML")); - return false; - } - - QDomNode coordinatesNode = rgNodes.item(0).namedItem("outerBoundaryIs").namedItem("LinearRing").namedItem("coordinates"); - if (coordinatesNode.isNull()) { - qgcApp()->showMessage(tr("Internal error: Unable to find coordinates node in KML")); - return false; - } - - QString coordinatesString = coordinatesNode.toElement().text().simplified(); - QStringList rgCoordinateStrings = coordinatesString.split(" "); - + QString errorString; QList rgCoords; - for (int i=0; i rgReversed; - - for (int i=0; ishowMessage(errorString); + return false; } clear(); diff --git a/src/MissionManager/QGCMapPolyline.cc b/src/MissionManager/QGCMapPolyline.cc index 34372d5066886e779571ee8b40fee4dd7b1cf7bb..9d6eda2b93168b5b5500017106cf6065c8cb836d 100644 --- a/src/MissionManager/QGCMapPolyline.cc +++ b/src/MissionManager/QGCMapPolyline.cc @@ -12,6 +12,7 @@ #include "JsonHelper.h" #include "QGCQGeoCoordinate.h" #include "QGCApplication.h" +#include "KMLFileHelper.h" #include #include @@ -338,52 +339,11 @@ QList QGCMapPolyline::offsetPolyline(double distance) bool QGCMapPolyline::loadKMLFile(const QString& kmlFile) { - QFile file(kmlFile); - - if (!file.exists()) { - qgcApp()->showMessage(tr("File not found: %1").arg(kmlFile)); - return false; - } - - if (!file.open(QIODevice::ReadOnly)) { - qgcApp()->showMessage(tr("Unable to open file: %1 error: $%2").arg(kmlFile).arg(file.errorString())); - return false; - } - - QDomDocument doc; - QString errorMessage; - int errorLine; - if (!doc.setContent(&file, &errorMessage, &errorLine)) { - qgcApp()->showMessage(tr("Unable to parse KML file: %1 error: %2 line: %3").arg(kmlFile).arg(errorMessage).arg(errorLine)); - return false; - } - - QDomNodeList rgNodes = doc.elementsByTagName("LineString"); - if (rgNodes.count() == 0) { - qgcApp()->showMessage(tr("Unable to find LineString node in KML")); - return false; - } - - QDomNode coordinatesNode = rgNodes.item(0).namedItem("coordinates"); - if (coordinatesNode.isNull()) { - qgcApp()->showMessage(tr("Internal error: Unable to find coordinates node in KML")); - return false; - } - - QString coordinatesString = coordinatesNode.toElement().text().simplified(); - QStringList rgCoordinateStrings = coordinatesString.split(" "); - + QString errorString; QList rgCoords; - for (int i=0; ishowMessage(errorString); + return false; } clear(); diff --git a/src/MissionManager/SectionTest.cc b/src/MissionManager/SectionTest.cc index 8a2a9a19deadffc76e6a16674870f901351838ba..4d8ffc3fb3fae70bb4b9b0077e625eb82dfb4343 100644 --- a/src/MissionManager/SectionTest.cc +++ b/src/MissionManager/SectionTest.cc @@ -83,7 +83,7 @@ void SectionTest::_commonScanTest(Section* section) waypointVisualItems.append(&simpleItem); QmlObjectListModel complexVisualItems; - SurveyComplexItem surveyItem(_offlineVehicle, false /* fly View */, this); + SurveyComplexItem surveyItem(_offlineVehicle, false /* fly View */, QString() /* kmlFile */, this /* parent */); complexVisualItems.append(&surveyItem); // This tests the common cases which should not lead to scan succeess diff --git a/src/MissionManager/StructureScanComplexItem.cc b/src/MissionManager/StructureScanComplexItem.cc index 1886303d888e15b9088e81032f14e6543c3b25e1..c9a2fc1a34dff31539659ebbb30506797021fefb 100644 --- a/src/MissionManager/StructureScanComplexItem.cc +++ b/src/MissionManager/StructureScanComplexItem.cc @@ -30,7 +30,7 @@ const char* StructureScanComplexItem::jsonComplexItemTypeValue = "StructureSc const char* StructureScanComplexItem::_jsonCameraCalcKey = "CameraCalc"; const char* StructureScanComplexItem::_jsonAltitudeRelativeKey = "altitudeRelative"; -StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, bool flyView, QObject* parent) +StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, bool flyView, const QString& kmlFile, QObject* parent) : ComplexMissionItem (vehicle, flyView, parent) , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/StructureScan.SettingsGroup.json"), this /* QObject parent */)) , _sequenceNumber (0) @@ -75,6 +75,13 @@ StructureScanComplexItem::StructureScanComplexItem(Vehicle* vehicle, bool flyVie connect(&_layersFact, &Fact::valueChanged, this, &StructureScanComplexItem::_recalcCameraShots); _recalcLayerInfo(); + + if (!kmlFile.isEmpty()) { + _structurePolygon.loadKMLFile(kmlFile); + _structurePolygon.setDirty(false); + } + + setDirty(false); } void StructureScanComplexItem::_setScanDistance(double scanDistance) diff --git a/src/MissionManager/StructureScanComplexItem.h b/src/MissionManager/StructureScanComplexItem.h index 133d0cd7353b57ef8c472680cd3a481b65bbd48b..904f57e4c3c1bc9cacc6f3035705863d949a89c4 100644 --- a/src/MissionManager/StructureScanComplexItem.h +++ b/src/MissionManager/StructureScanComplexItem.h @@ -25,7 +25,10 @@ class StructureScanComplexItem : public ComplexMissionItem Q_OBJECT public: - StructureScanComplexItem(Vehicle* vehicle, bool flyView, QObject* parent); + /// @param vehicle Vehicle which this is being contructed for + /// @param flyView true: Created for use in the Fly View, false: Created for use in the Plan View + /// @param kmlFile Polygon comes from this file, empty for default polygon + StructureScanComplexItem(Vehicle* vehicle, bool flyView, const QString& kmlFile, QObject* parent); Q_PROPERTY(CameraCalc* cameraCalc READ cameraCalc CONSTANT) Q_PROPERTY(Fact* altitude READ altitude CONSTANT) diff --git a/src/MissionManager/StructureScanComplexItemTest.cc b/src/MissionManager/StructureScanComplexItemTest.cc index 5efadfcfcdede80793bc7087fadbd149ddf3928b..13b0173ac1b30057f457bdbbaa88bd036c8c3d7b 100644 --- a/src/MissionManager/StructureScanComplexItemTest.cc +++ b/src/MissionManager/StructureScanComplexItemTest.cc @@ -24,7 +24,7 @@ void StructureScanComplexItemTest::init(void) _rgSignals[dirtyChangedIndex] = SIGNAL(dirtyChanged(bool)); _offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this); - _structureScanItem = new StructureScanComplexItem(_offlineVehicle, false /* flyView */, this); + _structureScanItem = new StructureScanComplexItem(_offlineVehicle, false /* flyView */, QString() /* kmlFile */, this /* parent */); _structureScanItem->setDirty(false); _multiSpy = new MultiSignalSpy(); @@ -121,7 +121,7 @@ void StructureScanComplexItemTest::_testSaveLoad(void) _structureScanItem->save(items); QString errorString; - StructureScanComplexItem* newItem = new StructureScanComplexItem(_offlineVehicle, false /* flyView */, this); + StructureScanComplexItem* newItem = new StructureScanComplexItem(_offlineVehicle, false /* flyView */, QString() /* kmlFile */, this /* parent */); QVERIFY(newItem->load(items[0].toObject(), 10, errorString)); QVERIFY(errorString.isEmpty()); _validateItem(newItem); diff --git a/src/MissionManager/SurveyComplexItem.cc b/src/MissionManager/SurveyComplexItem.cc index bd246983f9cb2d2b74a0f3d99bb2e2258ce39812..ad96827c787b1b211739870ffe6d3585937c3d43 100644 --- a/src/MissionManager/SurveyComplexItem.cc +++ b/src/MissionManager/SurveyComplexItem.cc @@ -59,7 +59,7 @@ const char* SurveyComplexItem::_jsonV3FixedValueIsAltitudeKey = "fixedVa const char* SurveyComplexItem::_jsonV3Refly90DegreesKey = "refly90Degrees"; const char* SurveyComplexItem::_jsonFlyAlternateTransectsKey = "flyAlternateTransects"; -SurveyComplexItem::SurveyComplexItem(Vehicle* vehicle, bool flyView, QObject* parent) +SurveyComplexItem::SurveyComplexItem(Vehicle* vehicle, bool flyView, const QString& kmlFile, QObject* parent) : TransectStyleComplexItem (vehicle, flyView, settingsGroup, parent) , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/Survey.SettingsGroup.json"), this)) , _gridAngleFact (settingsGroup, _metaDataMap[gridAngleName]) @@ -91,6 +91,12 @@ SurveyComplexItem::SurveyComplexItem(Vehicle* vehicle, bool flyView, QObject* pa // FIXME: Shouldn't these be in TransectStyleComplexItem? They are also in CorridorScanComplexItem constructur connect(&_cameraCalc, &CameraCalc::distanceToSurfaceRelativeChanged, this, &SurveyComplexItem::coordinateHasRelativeAltitudeChanged); connect(&_cameraCalc, &CameraCalc::distanceToSurfaceRelativeChanged, this, &SurveyComplexItem::exitCoordinateHasRelativeAltitudeChanged); + + if (!kmlFile.isEmpty()) { + _surveyAreaPolygon.loadKMLFile(kmlFile); + _surveyAreaPolygon.setDirty(false); + } + setDirty(false); } void SurveyComplexItem::save(QJsonArray& planItems) diff --git a/src/MissionManager/SurveyComplexItem.h b/src/MissionManager/SurveyComplexItem.h index f65f11d68cc6eaa9086f027e038b27a62ce100c6..7935b484502f616c156a41ef953a03019d80b9a1 100644 --- a/src/MissionManager/SurveyComplexItem.h +++ b/src/MissionManager/SurveyComplexItem.h @@ -21,7 +21,10 @@ class SurveyComplexItem : public TransectStyleComplexItem Q_OBJECT public: - SurveyComplexItem(Vehicle* vehicle, bool flyView, QObject* parent); + /// @param vehicle Vehicle which this is being contructed for + /// @param flyView true: Created for use in the Fly View, false: Created for use in the Plan View + /// @param kmlFile Polygon comes from this file, empty for default polygon + SurveyComplexItem(Vehicle* vehicle, bool flyView, const QString& kmlFile, QObject* parent); Q_PROPERTY(Fact* gridAngle READ gridAngle CONSTANT) Q_PROPERTY(Fact* flyAlternateTransects READ flyAlternateTransects CONSTANT) diff --git a/src/MissionManager/SurveyComplexItemTest.cc b/src/MissionManager/SurveyComplexItemTest.cc index b7a534c8f1c81e8967d6dc232d6d4faa0fa89f1d..ca86135c39a8fde26b74062d72b3f47c4d14b686 100644 --- a/src/MissionManager/SurveyComplexItemTest.cc +++ b/src/MissionManager/SurveyComplexItemTest.cc @@ -29,7 +29,7 @@ void SurveyComplexItemTest::init(void) _rgSurveySignals[surveyDirtyChangedIndex] = SIGNAL(dirtyChanged(bool)); _offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this); - _surveyItem = new SurveyComplexItem(_offlineVehicle, false /* flyView */, this); + _surveyItem = new SurveyComplexItem(_offlineVehicle, false /* flyView */, QString() /* kmlFile */, this /* parent */); _surveyItem->turnAroundDistance()->setRawValue(0); // Unit test written for no turnaround distance _surveyItem->setDirty(false); _mapPolygon = _surveyItem->surveyAreaPolygon(); diff --git a/src/MissionManager/TransectStyleComplexItem.cc b/src/MissionManager/TransectStyleComplexItem.cc index 33f8847a5669505254c39c89889708985810b88b..e22dd92b2f9195c1fa1dc02cf41e3de1ca503cd0 100644 --- a/src/MissionManager/TransectStyleComplexItem.cc +++ b/src/MissionManager/TransectStyleComplexItem.cc @@ -106,6 +106,8 @@ TransectStyleComplexItem::TransectStyleComplexItem(Vehicle* vehicle, bool flyVie connect(this, &TransectStyleComplexItem::visualTransectPointsChanged, this, &TransectStyleComplexItem::greatestDistanceToChanged); connect(this, &TransectStyleComplexItem::followTerrainChanged, this, &TransectStyleComplexItem::_followTerrainChanged); + + setDirty(false); } void TransectStyleComplexItem::_setCameraShots(int cameraShots) diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index 268d2a406f2348d46a303ae703cf7b6b1febc660..412fcab841e5e7588e27b4b6012ab8499c086ed6 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -24,6 +24,7 @@ import QGroundControl.FactSystem 1.0 import QGroundControl.FactControls 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controllers 1.0 +import QGroundControl.KMLFileHelper 1.0 /// Mission Editor @@ -77,6 +78,11 @@ QGCView { _missionController.setCurrentPlanViewIndex(sequenceNumber, true) } + function insertComplexMissionItemFromKML(complexItemName, kmlFile, index) { + var sequenceNumber = _missionController.insertComplexMissionItemFromKML(complexItemName, kmlFile, index) + _missionController.setCurrentPlanViewIndex(sequenceNumber, true) + } + property bool _firstMissionLoadComplete: false property bool _firstFenceLoadComplete: false property bool _firstRallyLoadComplete: false @@ -192,7 +198,7 @@ QGCView { return } fileDialog.title = qsTr("Save Plan") - fileDialog.plan = true + fileDialog.planFiles = true fileDialog.selectExisting = false fileDialog.nameFilters = masterController.saveNameFilters fileDialog.fileExtension = QGroundControl.settingsManager.appSettings.planFileExtension @@ -204,15 +210,25 @@ QGCView { mapFitFunctions.fitMapViewportToMissionItems() } + function loadKmlFromSelectedFile() { + fileDialog.title = qsTr("Load KML") + fileDialog.planFiles = false + fileDialog.selectExisting = true + fileDialog.nameFilters = masterController.fileKmlFilters + fileDialog.fileExtension = QGroundControl.settingsManager.appSettings.kmlFileExtension + fileDialog.fileExtension2 = "" + fileDialog.openForLoad() + } + function saveKmlToSelectedFile() { if (!readyForSaveSend()) { waitingOnDataMessage() return } fileDialog.title = qsTr("Save KML") - fileDialog.plan = false + fileDialog.planFiles = false fileDialog.selectExisting = false - fileDialog.nameFilters = masterController.saveKmlFilters + fileDialog.nameFilters = masterController.fileKmlFilters fileDialog.fileExtension = QGroundControl.settingsManager.appSettings.kmlFileExtension fileDialog.fileExtension2 = "" fileDialog.openForSave() @@ -259,22 +275,96 @@ QGCView { QGCFileDialog { id: fileDialog qgcView: _qgcView - property bool plan: true folder: QGroundControl.settingsManager.appSettings.missionSavePath + property bool planFiles: true ///< true: working with plan files, false: working with kml file + onAcceptedForSave: { - plan ? masterController.saveToFile(file) : masterController.saveToKml(file) + if (planFiles) { + masterController.saveToFile(file) + } else { + masterController.saveToKml(file) + } close() } onAcceptedForLoad: { - masterController.loadFromFile(file) - masterController.fitViewportToItems() - _missionController.setCurrentPlanViewIndex(0, true) + if (planFiles) { + masterController.loadFromFile(file) + masterController.fitViewportToItems() + _missionController.setCurrentPlanViewIndex(0, true) + } else { + var retList = KMLFileHelper.determineFileContents(file) + if (retList[0] == KMLFileHelper.Error) { + _qgcView.showMessage("Error", retList[1], StandardButton.Ok) + } else if (retList[0] == KMLFileHelper.Polygon) { + kmlPolygonSelectDialogKMLFile = file + _qgcView.showDialog(kmlPolygonSelectDialog, fileDialog.title, _qgcView.showDialogDefaultWidth, StandardButton.Ok | StandardButton.Cancel) + } else if (retList[0] == KMLFileHelper.Polyline) { + insertComplexMissionItemFromKML(_missionController.corridorScanComplexItemName, file, -1) + } + } close() } } + property string kmlPolygonSelectDialogKMLFile + Component { + id: kmlPolygonSelectDialog + + QGCViewDialog { + property var editVehicle: _activeVehicle ? _activeVehicle : QGroundControl.multiVehicleManager.offlineEditingVehicle + + function accept() { + var complexItemName + if (surveyRadio.checked) { + complexItemName = _missionController.surveyComplexItemName + } else { + complexItemName = _missionController.structureScanComplexItemName + } + insertComplexMissionItemFromKML(complexItemName, kmlPolygonSelectDialogKMLFile, -1) + hideDialog() + } + + Component.onCompleted: { + if (editVehicle.fixedWing) { + // Only Survey available + accept() + } + } + + ExclusiveGroup { + id: radioGroup + } + + Column { + anchors.left: parent.left + anchors.right: parent.right + spacing: ScreenTools.defaultFontPixelHeight + + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: qsTr("What would you like to create from the polygon specified by the KML file?") + } + + QGCRadioButton { + id: surveyRadio + text: qsTr("Survey") + checked: true + exclusiveGroup: radioGroup + } + + QGCRadioButton { + text: qsTr("Structure Scan") + exclusiveGroup: radioGroup + visible: !editVehicle.fixedWing + } + } + } + } + Component { id: moveDialog @@ -808,7 +898,7 @@ QGCView { } QGCButton { - text: qsTr("Save To File...") + text: qsTr("Save Plan...") Layout.fillWidth: true enabled: !masterController.syncInProgress onClicked: { @@ -818,7 +908,7 @@ QGCView { } QGCButton { - text: qsTr("Load From File...") + text: qsTr("Load Plan...") Layout.fillWidth: true enabled: !masterController.syncInProgress onClicked: { @@ -832,14 +922,16 @@ QGCView { } QGCButton { - text: qsTr("Remove All") + text: qsTr("Load KML...") Layout.fillWidth: true - onClicked: { + enabled: !masterController.syncInProgress + onClicked: { dropPanel.hide() - _qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No) + masterController.loadKmlFromSelectedFile() } } + QGCButton { text: qsTr("Save KML...") Layout.fillWidth: true @@ -854,6 +946,15 @@ QGCView { masterController.saveKmlToSelectedFile() } } + + QGCButton { + text: qsTr("Remove All") + Layout.fillWidth: true + onClicked: { + dropPanel.hide() + _qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No) + } + } } } } diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index 35dbde0ebd4d61f057de60c6fa182f87612bd8cf..067fe858f0b7c9f1bc9ab0a2a210aa9c2b918d1a 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -84,6 +84,7 @@ #include "VisualMissionItem.h" #include "EditPositionDialogController.h" #include "FactValueSliderListModel.h" +#include "KMLFileHelper.h" #ifndef NO_SERIAL_LINK #include "SerialLink.h" @@ -139,6 +140,11 @@ static QObject* qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine*, QJSEngine*) return qmlGlobal; } +static QObject* kmlFileHelperSingletonFactory(QQmlEngine*, QJSEngine*) +{ + return new KMLFileHelper; +} + QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) #ifdef __mobile__ : QGuiApplication (argc, argv) @@ -394,6 +400,7 @@ void QGCApplication::_initCommon(void) // Register Qml Singletons qmlRegisterSingletonType ("QGroundControl", 1, 0, "QGroundControl", qgroundcontrolQmlGlobalSingletonFactory); qmlRegisterSingletonType ("QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController", screenToolsControllerSingletonFactory); + qmlRegisterSingletonType ("QGroundControl.KMLFileHelper", 1, 0, "KMLFileHelper", kmlFileHelperSingletonFactory); } bool QGCApplication::_initForNormalAppBoot(void)