diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 170838fd8e5ff7d5c2c1ffbff276ae3fd418d40e..47e517ca3eb482397d2665d582ac9f9caabff5ec 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -501,6 +501,7 @@ HEADERS += \ src/MissionManager/FixedWingLandingComplexItem.h \ src/MissionManager/GeoFenceController.h \ src/MissionManager/GeoFenceManager.h \ + src/MissionManager/KML.h \ src/MissionManager/MissionCommandList.h \ src/MissionManager/MissionCommandTree.h \ src/MissionManager/MissionCommandUIInfo.h \ @@ -685,6 +686,7 @@ SOURCES += \ src/MissionManager/FixedWingLandingComplexItem.cc \ src/MissionManager/GeoFenceController.cc \ src/MissionManager/GeoFenceManager.cc \ + src/MissionManager/KML.cc \ src/MissionManager/MissionCommandList.cc \ src/MissionManager/MissionCommandTree.cc \ src/MissionManager/MissionCommandUIInfo.cc \ diff --git a/src/MissionManager/KML.cc b/src/MissionManager/KML.cc new file mode 100644 index 0000000000000000000000000000000000000000..164cce9c3cf42dda0d7c4d736a6319207ed884d0 --- /dev/null +++ b/src/MissionManager/KML.cc @@ -0,0 +1,128 @@ +/**************************************************************************** + * + * (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 "KML.h" + +#include +#include + +const QString Kml::_version("version=\"1.0\""); +const QString Kml::_encoding("encoding=\"UTF-8\""); +const QString Kml::_opengis("http://www.opengis.net/kml/2.2"); +const QString Kml::_qgckml("QGC KML"); + +Kml::Kml() +{ + //create header + createHeader(); + //name + createTextElement(_docEle, "name", _qgckml); + //open + createTextElement(_docEle, "open", "1"); + //create style + createStyles(); +} + +void Kml::points(const QStringList& points) +{ + //create placemark + QDomElement placemark = _domDocument.createElement("Placemark"); + _docEle.appendChild(placemark); + createTextElement(placemark, "styleUrl", "yellowLineGreenPoly"); + createTextElement(placemark, "name", "Absolute"); + createTextElement(placemark, "visibility", "0"); + createTextElement(placemark, "description", "Transparent purple line"); + + QStringList latLonAlt = points[0].split(","); + QStringList lookAtList({latLonAlt[0], latLonAlt[1], "0" \ + , "-100", "45", "2500"}); + createLookAt(placemark, lookAtList); + + //Add linestring + QDomElement lineString = _domDocument.createElement("LineString"); + placemark.appendChild(lineString); + + //extruder + createTextElement(lineString, "extruder", "1"); + createTextElement(lineString, "tessellate", "1"); + createTextElement(lineString, "altitudeMode", "absolute"); + QString coordinates; + for(const auto& point : points) { + coordinates += point + "\n"; + } + createTextElement(lineString, "coordinates", coordinates); +} + +void Kml::save(QDomDocument& document) +{ + document = _domDocument; +} + +void Kml::createHeader() +{ + QDomProcessingInstruction header = _domDocument.createProcessingInstruction("xml", _version + " " + _encoding); + _domDocument.appendChild(header); + QDomElement kml = _domDocument.createElement("kml"); + kml.setAttribute("xmlns", _opengis); + _docEle = _domDocument.createElement("Document"); + kml.appendChild(_docEle); + _domDocument.appendChild(kml); +} + +void Kml::createStyles() +{ + QDomElement style = _domDocument.createElement("Style"); + style.setAttribute("id", "yellowLineGreenPoly"); + createStyleLine(style, "7f00ffff", "4", "7f00ff00"); + _docEle.appendChild(style); +} + +void Kml::createLookAt(QDomElement& placemark, const QStringList &lookAtList) +{ + QDomElement lookAt = _domDocument.createElement("LookAt"); + placemark.appendChild(lookAt); + createTextElement(lookAt, "longitude", lookAtList[0]); + createTextElement(lookAt, "latitude", lookAtList[1]); + createTextElement(lookAt, "altitude", lookAtList[2]); + createTextElement(lookAt, "heading", lookAtList[3]); + createTextElement(lookAt, "tilt", lookAtList[4]); + createTextElement(lookAt, "range", lookAtList[5]); +} + +void Kml::createTextElement(QDomElement& domEle, const QString& elementName, const QString& textElement) +{ + // textElement + auto element = _domDocument.createElement(elementName); + element.appendChild(_domDocument.createTextNode(textElement)); + domEle.appendChild(element); +} + +void Kml::createStyleLine(QDomElement& domEle, const QString& lineColor, const QString& lineWidth, const QString& polyColor) +{ + /* + + 7f00ffff + 4 + + + 7f00ff00 + + */ + auto lineStyle = _domDocument.createElement("LineStyle"); + auto polyStyle = _domDocument.createElement("PolyStyle"); + domEle.appendChild(lineStyle); + domEle.appendChild(polyStyle); + createTextElement(lineStyle, "color", lineColor); + createTextElement(lineStyle, "width", lineWidth); + createTextElement(polyStyle, "color", polyColor); +} + +Kml::~Kml() +{ +} diff --git a/src/MissionManager/KML.h b/src/MissionManager/KML.h new file mode 100644 index 0000000000000000000000000000000000000000..15d62455471d2ec55ba6c4d9c912075d53f27972 --- /dev/null +++ b/src/MissionManager/KML.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * + * (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. + * + ****************************************************************************/ + +#ifndef KML_H +#define KML_H + +#include +#include + +class Kml +{ + +public: + Kml(); + ~Kml(); + + void points(const QStringList& points); + void polygon(const QStringList& points); + void save(QDomDocument& document); + +private: + void createHeader(); + void createLookAt(QDomElement& placemark, const QStringList &lookAtList); + void createStyles(); + void createStyleLine(QDomElement& domEle, const QString& lineColor, const QString& lineWidth, const QString& polyColor); + void createTextElement(QDomElement& domEle, const QString& elementName, const QString& textElement); + + QDomDocument _domDocument; + QDomElement _docEle; + static const QString _encoding; + static const QString _opengis; + static const QString _qgckml; + static const QString _version; +}; + +#endif diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index 14abc40d7f478bcc6c1a8d143790d7fd92c4c75a..bd1acc5f0df1cace5873d4189f2c8c049d188ca6 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -8,6 +8,7 @@ ****************************************************************************/ +#include "MissionCommandUIInfo.h" #include "MissionController.h" #include "MultiVehicleManager.h" #include "MissionManager.h" @@ -25,6 +26,7 @@ #include "MissionSettingsItem.h" #include "QGCQGeoCoordinate.h" #include "PlanMasterController.h" +#include "KML.h" #ifndef __mobile__ #include "MainWindow.h" @@ -256,6 +258,44 @@ bool MissionController::_convertToMissionItems(QmlObjectListModel* visualMission return endActionSet; } +void MissionController::convertToKMLDocument(QDomDocument& document) +{ + QJsonObject missionJson; + QmlObjectListModel* visualItems = new QmlObjectListModel(); + QList missionItens; + QString error; + save(missionJson); + _loadItemsFromJson(missionJson, visualItems, error); + _convertToMissionItems(visualItems, missionItens, this); + + float altitude = missionJson[_jsonPlannedHomePositionKey].toArray()[2].toDouble(); + + QString coord; + QStringList coords; + // Drop home position + bool dropPoint = true; + for(const auto& item : missionItens) { + if(dropPoint) { + dropPoint = false; + continue; + } + const MissionCommandUIInfo* uiInfo = \ + qgcApp()->toolbox()->missionCommandTree()->getUIInfo(_controllerVehicle, item->command()); + + if (uiInfo && uiInfo->specifiesCoordinate() && !uiInfo->isStandaloneCoordinate()) { + coord = QString::number(item->param6()) \ + + "," \ + + QString::number(item->param5()) \ + + "," \ + + QString::number(item->param7() + altitude); + coords.append(coord); + } + } + Kml kml; + kml.points(coords); + kml.save(document); +} + void MissionController::sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems) { if (vehicle) { diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h index 54362623dd4bb502edcb157456f98d03292d8876..abc553c3781869a44ec18e64c164ce84ce9aefa2 100644 --- a/src/MissionManager/MissionController.h +++ b/src/MissionManager/MissionController.h @@ -26,6 +26,7 @@ class MissionSettingsItem; class AppSettings; class MissionManager; class SimpleMissionItem; +class QDomDocument; Q_DECLARE_LOGGING_CATEGORY(MissionControllerLog) @@ -125,6 +126,9 @@ public: void managerVehicleChanged (Vehicle* managerVehicle) final; bool showPlanFromManagerVehicle (void) final; + // Create KML file + void convertToKMLDocument(QDomDocument& document); + // Property accessors QmlObjectListModel* visualItems (void) { return _visualItems; } diff --git a/src/MissionManager/PlanMasterController.cc b/src/MissionManager/PlanMasterController.cc index 6b9e453f6f7ddf619733f5d895b2e51e79b4a6ee..ca5bfe040d9a68579edc295b83d0a48a527978b4 100644 --- a/src/MissionManager/PlanMasterController.cc +++ b/src/MissionManager/PlanMasterController.cc @@ -14,7 +14,9 @@ #include "AppSettings.h" #include "JsonHelper.h" #include "MissionManager.h" +#include "KML.h" +#include #include #include @@ -372,6 +374,30 @@ void PlanMasterController::saveToFile(const QString& filename) } } +void PlanMasterController::saveToKml(const QString& filename) +{ + if (filename.isEmpty()) { + return; + } + + QString kmlFilename = filename; + if (!QFileInfo(filename).fileName().contains(".")) { + kmlFilename += QString(".%1").arg(kmlFileExtension()); + } + + QFile file(kmlFilename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qgcApp()->showMessage(tr("KML save error %1 : %2").arg(filename).arg(file.errorString())); + } else { + QDomDocument domDocument; + _missionController.convertToKMLDocument(domDocument); + QTextStream stream(&file); + stream << domDocument.toString(); + file.close(); + } +} + void PlanMasterController::removeAll(void) { _missionController.removeAll(); @@ -417,6 +443,11 @@ QString PlanMasterController::fileExtension(void) const return AppSettings::planFileExtension; } +QString PlanMasterController::kmlFileExtension(void) const +{ + return AppSettings::kmlFileExtension; +} + QStringList PlanMasterController::loadNameFilters(void) const { QStringList filters; @@ -435,6 +466,14 @@ QStringList PlanMasterController::saveNameFilters(void) const return filters; } +QStringList PlanMasterController::saveKmlFilters(void) const +{ + QStringList filters; + + filters << tr("KML Files (*.%1)").arg(kmlFileExtension()) << tr("All Files (*.*)"); + return filters; +} + void PlanMasterController::sendPlanToVehicle(Vehicle* vehicle, const QString& filename) { // Use a transient PlanMasterController to accomplish this diff --git a/src/MissionManager/PlanMasterController.h b/src/MissionManager/PlanMasterController.h index 82c6e3ded452c8dd14a785608ec8b45217f2a8fc..03ba7b6e3f3c16ab8612dfb965abe63619e5bcc6 100644 --- a/src/MissionManager/PlanMasterController.h +++ b/src/MissionManager/PlanMasterController.h @@ -39,8 +39,11 @@ public: Q_PROPERTY(bool syncInProgress READ syncInProgress NOTIFY syncInProgressChanged) ///< true: Information is currently being saved/sent, false: no active save/send in progress Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) ///< true: Unsaved/sent changes are present, false: no changes since last save/send Q_PROPERTY(QString fileExtension READ fileExtension CONSTANT) ///< File extension for missions + Q_PROPERTY(QString kmlFileExtension READ kmlFileExtension CONSTANT) + ///< 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 /// Should be called immediately upon Component.onCompleted. /// @param editMode true: controller being used in Plan view, false: controller being used in Fly view @@ -58,6 +61,7 @@ public: Q_INVOKABLE void sendToVehicle(void); Q_INVOKABLE void loadFromFile(const QString& filename); Q_INVOKABLE void saveToFile(const QString& filename); + Q_INVOKABLE void saveToKml(const QString& filename); Q_INVOKABLE void removeAll(void); ///< Removes all from controller only, synce required to remove from vehicle Q_INVOKABLE void removeAllFromVehicle(void); ///< Removes all from vehicle and controller @@ -71,8 +75,10 @@ public: bool dirty (void) const; void setDirty (bool dirty); QString fileExtension (void) const; + QString kmlFileExtension(void) const; QStringList loadNameFilters (void) const; QStringList saveNameFilters (void) const; + QStringList saveKmlFilters (void) const; Vehicle* controllerVehicle(void) { return _controllerVehicle; } Vehicle* managerVehicle(void) { return _managerVehicle; } diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index c2d515617763b3f1078bd79bf226563909d886cb..30618bf04afc82a3ff098db8b88d5b855d806a4a 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -169,6 +169,7 @@ QGCView { function saveToSelectedFile() { fileDialog.title = qsTr("Save Plan") + fileDialog.plan = true fileDialog.selectExisting = false fileDialog.nameFilters = masterController.saveNameFilters fileDialog.openForSave() @@ -177,6 +178,14 @@ QGCView { function fitViewportToItems() { mapFitFunctions.fitMapViewportToMissionItems() } + + function saveKmlToSelectedFile() { + fileDialog.title = qsTr("Save KML") + fileDialog.plan = false + fileDialog.selectExisting = false + fileDialog.nameFilters = masterController.saveKmlFilters + fileDialog.openForSave() + } } Connections { @@ -228,12 +237,13 @@ QGCView { QGCFileDialog { id: fileDialog qgcView: _qgcView + property var plan: true folder: QGroundControl.settingsManager.appSettings.missionSavePath fileExtension: QGroundControl.settingsManager.appSettings.planFileExtension fileExtension2: QGroundControl.settingsManager.appSettings.missionFileExtension onAcceptedForSave: { - masterController.saveToFile(file) + plan ? masterController.saveToFile(file) : masterController.saveToKml(file) close() } @@ -792,6 +802,16 @@ QGCView { _qgcView.showDialog(removeAllPromptDialog, qsTr("Remove all"), _qgcView.showDialogDefaultWidth, StandardButton.Yes | StandardButton.No) } } + + QGCButton { + text: qsTr("Save KML...") + Layout.fillWidth: true + enabled: !masterController.syncInProgress + onClicked: { + dropPanel.hide() + masterController.saveKmlToSelectedFile() + } + } } } } diff --git a/src/Settings/AppSettings.cc b/src/Settings/AppSettings.cc index f8166d5e8f37ec2833597e4d7ea5213602174c92..65068ef39ef40bd38d44838f2f86cffec647a62b 100644 --- a/src/Settings/AppSettings.cc +++ b/src/Settings/AppSettings.cc @@ -44,6 +44,7 @@ const char* AppSettings::waypointsFileExtension = "waypoints"; const char* AppSettings::fenceFileExtension = "fence"; const char* AppSettings::rallyPointFileExtension = "rally"; const char* AppSettings::telemetryFileExtension = "tlog"; +const char* AppSettings::kmlFileExtension = "kml"; const char* AppSettings::logFileExtension = "ulg"; const char* AppSettings::parameterDirectory = "Parameters"; diff --git a/src/Settings/AppSettings.h b/src/Settings/AppSettings.h index 8df20b4d35e2eb7623c66e484d8d230e1f671e19..5b29ac37096b61e46ef5ca16e5b1b593edbf89e5 100644 --- a/src/Settings/AppSettings.h +++ b/src/Settings/AppSettings.h @@ -52,6 +52,7 @@ public: Q_PROPERTY(QString waypointsFileExtension MEMBER waypointsFileExtension CONSTANT) Q_PROPERTY(QString parameterFileExtension MEMBER parameterFileExtension CONSTANT) Q_PROPERTY(QString telemetryFileExtension MEMBER telemetryFileExtension CONSTANT) + Q_PROPERTY(QString kmlFileExtension MEMBER kmlFileExtension CONSTANT) Q_PROPERTY(QString logFileExtension MEMBER logFileExtension CONSTANT) Fact* offlineEditingFirmwareType (void); @@ -115,6 +116,7 @@ public: static const char* fenceFileExtension; static const char* rallyPointFileExtension; static const char* telemetryFileExtension; + static const char* kmlFileExtension; static const char* logFileExtension; // Child directories of savePath for specific file types