Commit 03d3d538 authored by DonLakeFlyer's avatar DonLakeFlyer

Support for Sync - Load KML

parent 4b3b4769
......@@ -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 \
......
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "KMLFileHelper.h"
#include <QFile>
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<QGeoCoordinate>& 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<QGeoCoordinate> rgCoords;
for (int i=0; i<rgCoordinateStrings.count()-1; i++) {
QString coordinateString = rgCoordinateStrings[i];
QStringList rgValueStrings = coordinateString.split(",");
QGeoCoordinate coord;
coord.setLongitude(rgValueStrings[0].toDouble());
coord.setLatitude(rgValueStrings[1].toDouble());
rgCoords.append(coord);
}
// Determine winding, reverse if needed
double sum = 0;
for (int i=0; i<rgCoords.count(); i++) {
QGeoCoordinate coord1 = rgCoords[i];
QGeoCoordinate coord2 = (i == rgCoords.count() - 1) ? rgCoords[0] : rgCoords[i+1];
sum += (coord2.longitude() - coord1.longitude()) * (coord2.latitude() + coord1.latitude());
}
bool reverse = sum < 0.0;
if (reverse) {
QList<QGeoCoordinate> rgReversed;
for (int i=0; i<rgCoords.count(); i++) {
rgReversed.prepend(rgCoords[i]);
}
rgCoords = rgReversed;
}
vertices = rgCoords;
return true;
}
bool KMLFileHelper::loadPolylineFromFile(const QString& kmlFile, QList<QGeoCoordinate>& 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<QGeoCoordinate> rgCoords;
for (int i=0; i<rgCoordinateStrings.count()-1; i++) {
QString coordinateString = rgCoordinateStrings[i];
QStringList rgValueStrings = coordinateString.split(",");
QGeoCoordinate coord;
coord.setLongitude(rgValueStrings[0].toDouble());
coord.setLatitude(rgValueStrings[1].toDouble());
rgCoords.append(coord);
}
coords = rgCoords;
return true;
}
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#pragma once
#include <QObject>
#include <QDomDocument>
#include <QList>
#include <QGeoCoordinate>
/// 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<QGeoCoordinate>& vertices, QString& errorString);
static bool loadPolylineFromFile(const QString& kmlFile, QList<QGeoCoordinate>& coords, QString& errorString);
};
......@@ -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)
......
......@@ -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)
......
......@@ -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;
......
......@@ -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<SurveyComplexItem*>(complexItem) || qobject_cast<CorridorScanComplexItem*>(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;
}
......
......@@ -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;
......
......@@ -484,7 +484,7 @@ QStringList PlanMasterController::saveNameFilters(void) const
return filters;
}
QStringList PlanMasterController::saveKmlFilters(void) const
QStringList PlanMasterController::fileKmlFilters(void) const
{
QStringList filters;
......
......@@ -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 ();
......
......@@ -12,6 +12,7 @@
#include "JsonHelper.h"
#include "QGCQGeoCoordinate.h"
#include "QGCApplication.h"
#include "KMLFileHelper.h"
#include <QGeoRectangle>
#include <QDebug>
......@@ -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<QGeoCoordinate> rgCoords;
for (int i=0; i<rgCoordinateStrings.count()-1; i++) {
QString coordinateString = rgCoordinateStrings[i];
QStringList rgValueStrings = coordinateString.split(",");
QGeoCoordinate coord;
coord.setLongitude(rgValueStrings[0].toDouble());
coord.setLatitude(rgValueStrings[1].toDouble());
rgCoords.append(coord);
}
// Determine winding, reverse if needed
double sum = 0;
for (int i=0; i<rgCoords.count(); i++) {
QGeoCoordinate coord1 = rgCoords[i];
QGeoCoordinate coord2 = (i == rgCoords.count() - 1) ? rgCoords[0] : rgCoords[i+1];
sum += (coord2.longitude() - coord1.longitude()) * (coord2.latitude() + coord1.latitude());
}
bool reverse = sum < 0.0;
if (reverse) {
QList<QGeoCoordinate> rgReversed;
for (int i=0; i<rgCoords.count(); i++) {
rgReversed.prepend(rgCoords[i]);
}
rgCoords = rgReversed;
if (!KMLFileHelper::loadPolygonFromFile(kmlFile, rgCoords, errorString)) {
qgcApp()->showMessage(errorString);
return false;
}
clear();
......
......@@ -12,6 +12,7 @@
#include "JsonHelper.h"
#include "QGCQGeoCoordinate.h"
#include "QGCApplication.h"
#include "KMLFileHelper.h"
#include <QGeoRectangle>
#include <QDebug>
......@@ -338,52 +339,11 @@ QList<QGeoCoordinate> 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<QGeoCoordinate> rgCoords;
for (int i=0; i<rgCoordinateStrings.count()-1; i++) {
QString coordinateString = rgCoordinateStrings[i];
QStringList rgValueStrings = coordinateString.split(",");
QGeoCoordinate coord;
coord.setLongitude(rgValueStrings[0].toDouble());
coord.setLatitude(rgValueStrings[1].toDouble());
rgCoords.append(coord);
if (!KMLFileHelper::loadPolylineFromFile(kmlFile, rgCoords, errorString)) {
qgcApp()->showMessage(errorString);
return false;
}
clear();
......
......@@ -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
......
......@@ -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)
......
......@@ -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)
......
......@@ -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);
......
......@@ -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)
......
......@@ -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)
......
......@@ -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();
......
......@@ -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)
......
......@@ -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)
}
}
}
}
}
......
......@@ -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<QGroundControlQmlGlobal> ("QGroundControl", 1, 0, "QGroundControl", qgroundcontrolQmlGlobalSingletonFactory);
qmlRegisterSingletonType<ScreenToolsController> ("QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController", screenToolsControllerSingletonFactory);
qmlRegisterSingletonType<KMLFileHelper> ("QGroundControl.KMLFileHelper", 1, 0, "KMLFileHelper", kmlFileHelperSingletonFactory);
}
bool QGCApplication::_initForNormalAppBoot(void)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment