From 3c55e2b0f7dfc48cfe7eb086bc3a30d6a6166905 Mon Sep 17 00:00:00 2001 From: DonLakeFlyer Date: Sat, 11 Nov 2017 09:56:35 -0800 Subject: [PATCH] KML support for Polygons --- UnitTest.qrc | 4 + src/MissionManager/QGCMapPolygon.cc | 80 +++++++++++++++++++ src/MissionManager/QGCMapPolygon.h | 4 + src/MissionManager/QGCMapPolygonTest.cc | 17 ++++ src/MissionManager/QGCMapPolygonTest.h | 1 + src/MissionManager/QGCMapPolygonVisuals.qml | 28 ++++++- .../UnitTest/BadCoordinatesNode.kml | 47 +++++++++++ src/MissionManager/UnitTest/BadXml.kml | 49 ++++++++++++ src/MissionManager/UnitTest/GoodPolygon.kml | 48 +++++++++++ .../UnitTest/MissingPolygonNode.kml | 38 +++++++++ src/PlanView/FWLandingPatternMapVisual.qml | 3 +- src/PlanView/MissionItemMapVisual.qml | 5 +- src/PlanView/PlanView.qml | 1 + src/PlanView/SimpleItemMapVisual.qml | 4 +- src/PlanView/StructureScanMapVisual.qml | 5 +- src/PlanView/SurveyMapVisual.qml | 3 +- 16 files changed, 327 insertions(+), 10 deletions(-) create mode 100755 src/MissionManager/UnitTest/BadCoordinatesNode.kml create mode 100755 src/MissionManager/UnitTest/BadXml.kml create mode 100755 src/MissionManager/UnitTest/GoodPolygon.kml create mode 100755 src/MissionManager/UnitTest/MissingPolygonNode.kml diff --git a/UnitTest.qrc b/UnitTest.qrc index 7d46cc269..61510fde3 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 460c7c0a5..aa1e0b389 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 4f366d8c2..d9160c9d5 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 a70e4a9f2..7f36e926f 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 ab6ebc6b4..0c5300f52 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 000000000..e9f6c3f91 --- /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 000000000..49840fd90 --- /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 000000000..712e859f3 --- /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 000000000..20f4109ff --- /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 9685adc5b..b6fc98884 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 c5c6d4c8d..96d07ff67 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 2012d6eeb..45b57ba85 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 2677f6504..49009ccaa 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 0f691ea5f..21f97ce10 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 4110ddcbe..08a52be38 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 -- 2.22.0