diff --git a/UnitTest.qrc b/UnitTest.qrc index 7d46cc26966de43e37a3f4f0c5bb4216b4098838..61510fde340a034be48ab0aee7593c72c27ebf2d 100644 --- a/UnitTest.qrc +++ b/UnitTest.qrc @@ -9,5 +9,9 @@ src/MissionManager/UnitTest/MavCmdInfoVTOL.json src/MissionManager/UnitTest/MissionPlanner.waypoints src/MissionManager/UnitTest/OldFileFormat.mission + src/MissionManager/UnitTest/GoodPolygon.kml + src/MissionManager/UnitTest/MissingPolygonNode.kml + src/MissionManager/UnitTest/BadXml.kml + src/MissionManager/UnitTest/BadCoordinatesNode.kml diff --git a/src/MissionManager/QGCMapPolygon.cc b/src/MissionManager/QGCMapPolygon.cc index 460c7c0a56337de9357f29eee099e17d5edf1f91..aa1e0b3898a2f0afd2dc9d81ea6bb26c008fd94a 100644 --- a/src/MissionManager/QGCMapPolygon.cc +++ b/src/MissionManager/QGCMapPolygon.cc @@ -11,11 +11,14 @@ #include "QGCGeo.h" #include "JsonHelper.h" #include "QGCQGeoCoordinate.h" +#include "QGCApplication.h" #include #include #include #include +#include +#include const char* QGCMapPolygon::jsonPolygonKey = "polygon"; @@ -434,3 +437,80 @@ void QGCMapPolygon::offset(double distance) appendVertex(rgNewPolygon[i]); } } + +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(" "); + + QList rgCoords; + for (int i=0; i rgReversed; + + for (int i=0; i coordinateList(void) const; diff --git a/src/MissionManager/QGCMapPolygonTest.cc b/src/MissionManager/QGCMapPolygonTest.cc index 4f366d8c2084f7a8c035d77bacd1dc5a53ecadfe..d9160c9d5e24d6c27b7a077d283778db6814941c 100644 --- a/src/MissionManager/QGCMapPolygonTest.cc +++ b/src/MissionManager/QGCMapPolygonTest.cc @@ -197,3 +197,20 @@ void QGCMapPolygonTest::_testVertexManipulation(void) QCOMPARE(polyList.count(), 0); QCOMPARE(_pathModel->count(), 0); } + +void QGCMapPolygonTest::_testKMLLoad(void) +{ + QVERIFY(_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/GoodPolygon.kml"))); + + setExpectedMessageBox(QMessageBox::Ok); + QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/BadXml.kml"))); + checkExpectedMessageBox(); + + setExpectedMessageBox(QMessageBox::Ok); + QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/MissingPolygonNode.kml"))); + checkExpectedMessageBox(); + + setExpectedMessageBox(QMessageBox::Ok); + QVERIFY(!_mapPolygon->loadKMLFile(QStringLiteral(":/unittest/BadCoordinatesNode.kml"))); + checkExpectedMessageBox(); +} diff --git a/src/MissionManager/QGCMapPolygonTest.h b/src/MissionManager/QGCMapPolygonTest.h index a70e4a9f2aa18420eece4cee2b08abea405c8d28..7f36e926ff372188dbcf5d7bca555fbefb19f880 100644 --- a/src/MissionManager/QGCMapPolygonTest.h +++ b/src/MissionManager/QGCMapPolygonTest.h @@ -29,6 +29,7 @@ protected: private slots: void _testDirty(void); void _testVertexManipulation(void); + void _testKMLLoad(void); private: enum { diff --git a/src/MissionManager/QGCMapPolygonVisuals.qml b/src/MissionManager/QGCMapPolygonVisuals.qml index ab6ebc6b44c4c60d38ae1656532483dfa7affc3f..0c5300f523d4371ecfce839e154e1e6e2975724a 100644 --- a/src/MissionManager/QGCMapPolygonVisuals.qml +++ b/src/MissionManager/QGCMapPolygonVisuals.qml @@ -22,8 +22,9 @@ import QGroundControl.FlightMap 1.0 Item { id: _root - property var mapControl ///< Map control to place item in - property var mapPolygon ///< QGCMapPolygon object + property var qgcView ///< QGCView for popping dialogs + property var mapControl ///< Map control to place item in + property var mapPolygon ///< QGCMapPolygon object property bool interactive: mapPolygon.interactive property color interiorColor: "transparent" property real interiorOpacity: 1 @@ -146,6 +147,10 @@ Item { setCircleRadius(center, radius) } + function loadKMLFile() { + mapPolygon.loadKMLFile("/Users/Don/Downloads/polygon.kml") + } + onInteractiveChanged: { if (interactive) { addHandles() @@ -168,6 +173,21 @@ Item { QGCPalette { id: qgcPal } + QGCFileDialog { + id: kmlLoadDialog + qgcView: _root.qgcView + folder: QGroundControl.settingsManager.appSettings.missionSavePath + title: qsTr("Select KML File") + selectExisting: true + nameFilters: [ qsTr("KML files (*.kml)") ] + + + onAcceptedForLoad: { + mapPolygon.loadKMLFile(file) + close() + } + } + Component { id: polygonComponent @@ -393,8 +413,8 @@ Item { } MenuItem { - text: qsTr("Load KML...") - enabled: false + text: qsTr("Load KML...") + onTriggered: kmlLoadDialog.openForLoad() } } diff --git a/src/MissionManager/UnitTest/BadCoordinatesNode.kml b/src/MissionManager/UnitTest/BadCoordinatesNode.kml new file mode 100755 index 0000000000000000000000000000000000000000..e9f6c3f91bfb6676e9ecc03181bd754d5feaacc7 --- /dev/null +++ b/src/MissionManager/UnitTest/BadCoordinatesNode.kml @@ -0,0 +1,47 @@ +Incorrectly formed XML test file + + + + Polygon.kmz + + + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl + + + + Untitled Polygon + #m_ylw-pushpin + + 1 + + + -122.1059149362712,47.65965281788451,0 -122.1044593196253,47.66002598220988,0 -122.1047336695092,47.66034166158975,0 -122.1061470943783,47.6599810708829,0 -122.1059149362712,47.65965281788451,0 + + + + + + diff --git a/src/MissionManager/UnitTest/BadXml.kml b/src/MissionManager/UnitTest/BadXml.kml new file mode 100755 index 0000000000000000000000000000000000000000..49840fd905490db68e56d1567693914e922e7def --- /dev/null +++ b/src/MissionManager/UnitTest/BadXml.kml @@ -0,0 +1,49 @@ +Incorrectly formed XML test file + + + + Polygon.kmz + + + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl + + + + Untitled Polygon + #m_ylw-pushpin + + 1 + + + + -122.1059149362712,47.65965281788451,0 -122.1044593196253,47.66002598220988,0 -122.1047336695092,47.66034166158975,0 -122.1061470943783,47.6599810708829,0 -122.1059149362712,47.65965281788451,0 + + + + + + + diff --git a/src/MissionManager/UnitTest/GoodPolygon.kml b/src/MissionManager/UnitTest/GoodPolygon.kml new file mode 100755 index 0000000000000000000000000000000000000000..712e859f3aa35a44910612b05fd173b2c349e01b --- /dev/null +++ b/src/MissionManager/UnitTest/GoodPolygon.kml @@ -0,0 +1,48 @@ + + + + Polygon.kmz + + + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl + + + + Untitled Polygon + #m_ylw-pushpin + + 1 + + + + -122.1059149362712,47.65965281788451,0 -122.1044593196253,47.66002598220988,0 -122.1047336695092,47.66034166158975,0 -122.1061470943783,47.6599810708829,0 -122.1059149362712,47.65965281788451,0 + + + + + + + diff --git a/src/MissionManager/UnitTest/MissingPolygonNode.kml b/src/MissionManager/UnitTest/MissingPolygonNode.kml new file mode 100755 index 0000000000000000000000000000000000000000..20f4109ff33adfe1477c05f65948eef53215f702 --- /dev/null +++ b/src/MissionManager/UnitTest/MissingPolygonNode.kml @@ -0,0 +1,38 @@ + + + + Polygon.kmz + + + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl + + + + Untitled Polygon + #m_ylw-pushpin + + + diff --git a/src/PlanView/FWLandingPatternMapVisual.qml b/src/PlanView/FWLandingPatternMapVisual.qml index 9685adc5b7550a3f39efa945a9e3082e6fb75c75..b6fc98884562e196a6efabe84b8c4016dbeb816c 100644 --- a/src/PlanView/FWLandingPatternMapVisual.qml +++ b/src/PlanView/FWLandingPatternMapVisual.qml @@ -22,7 +22,8 @@ import QGroundControl.FlightMap 1.0 Item { id: _root - property var map ///< Map control to place item in + property var map ///< Map control to place item in + property var qgcView ///< QGCView to use for popping dialogs signal clicked(int sequenceNumber) diff --git a/src/PlanView/MissionItemMapVisual.qml b/src/PlanView/MissionItemMapVisual.qml index c5c6d4c8dc0520303a5197e7082273e1d2dd916b..96d07ff67e432bbb20d331ed5c32b23c03dcc1c1 100644 --- a/src/PlanView/MissionItemMapVisual.qml +++ b/src/PlanView/MissionItemMapVisual.qml @@ -21,7 +21,8 @@ import QGroundControl.Controls 1.0 Item { id: _root - property var map ///< Map control to place item in + property var map ///< Map control to place item in + property var qgcView ///< QGCView to use for popping dialogs signal clicked(int sequenceNumber) @@ -33,7 +34,7 @@ Item { if (component.status === Component.Error) { console.log("Error loading Qml: ", object.mapVisualQML, component.errorString()) } - _visualItem = component.createObject(map, { "map": _root.map }) + _visualItem = component.createObject(map, { "map": _root.map, "qgcView": _root.qgcView }) _visualItem.clicked.connect(_root.clicked) } } diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index 2012d6eebb240eaed3f95d2576c36f60de9ca655..45b57ba852ce51a646fe9abaaabe8ba115ac1223 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -346,6 +346,7 @@ QGCView { delegate: MissionItemMapVisual { map: editorMap + qgcView: _qgcView onClicked: _missionController.setCurrentPlanViewIndex(sequenceNumber, false) visible: _editingLayer == _layerMission } diff --git a/src/PlanView/SimpleItemMapVisual.qml b/src/PlanView/SimpleItemMapVisual.qml index 2677f6504bf828e1a8b9b90bf1aee9414c0ea218..49009ccaae7f120a330e89fe196c0c492239e2a1 100644 --- a/src/PlanView/SimpleItemMapVisual.qml +++ b/src/PlanView/SimpleItemMapVisual.qml @@ -21,7 +21,9 @@ import QGroundControl.FlightMap 1.0 /// Simple Mission Item visuals Item { id: _root - property var map ///< Map control to place item in + + property var map ///< Map control to place item in + property var qgcView ///< QGCView to use for popping dialogs property var _missionItem: object property var _itemVisual diff --git a/src/PlanView/StructureScanMapVisual.qml b/src/PlanView/StructureScanMapVisual.qml index 0f691ea5f255fddef379f0e727a6528866f7467d..21f97ce10e81f111048f975f2f9c11a959a5ebc2 100644 --- a/src/PlanView/StructureScanMapVisual.qml +++ b/src/PlanView/StructureScanMapVisual.qml @@ -22,7 +22,8 @@ import QGroundControl.FlightMap 1.0 Item { id: _root - property var map ///< Map control to place item in + property var map ///< Map control to place item in + property var qgcView ///< QGCView to use for popping dialogs property var _missionItem: object property var _structurePolygon: object.structurePolygon @@ -85,6 +86,7 @@ Item { } QGCMapPolygonVisuals { + qgcView: _root.qgcView mapControl: map mapPolygon: _structurePolygon interactive: _missionItem.isCurrentItem @@ -95,6 +97,7 @@ Item { } QGCMapPolygonVisuals { + qgcView: _root.qgcView mapControl: map mapPolygon: _flightPolygon interactive: false diff --git a/src/PlanView/SurveyMapVisual.qml b/src/PlanView/SurveyMapVisual.qml index 4110ddcbe9e0c675ab50c70c9ac8175c7582400f..08a52be380aa7cfa768bb91fd59eed983fe0a5c1 100644 --- a/src/PlanView/SurveyMapVisual.qml +++ b/src/PlanView/SurveyMapVisual.qml @@ -22,7 +22,8 @@ import QGroundControl.FlightMap 1.0 Item { id: _root - property var map ///< Map control to place item in + property var map ///< Map control to place item in + property var qgcView ///< QGCView to use for popping dialogs property var _missionItem: object property var _mapPolygon: object.mapPolygon