/*=================================================================== QGroundControl Open Source Ground Control Station (c) 2009, 2016 QGROUNDCONTROL PROJECT This file is part of the QGROUNDCONTROL project QGROUNDCONTROL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. QGROUNDCONTROL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QGROUNDCONTROL. If not, see . ======================================================================*/ #include "ComplexMissionItem.h" #include "JsonHelper.h" #include "MissionController.h" #include "QGCGeo.h" #include QGC_LOGGING_CATEGORY(ComplexMissionItemLog, "ComplexMissionItemLog") const char* ComplexMissionItem::_jsonVersionKey = "version"; const char* ComplexMissionItem::_jsonTypeKey = "type"; const char* ComplexMissionItem::_jsonPolygonKey = "polygon"; const char* ComplexMissionItem::_jsonIdKey = "id"; const char* ComplexMissionItem::_jsonGridAltitudeKey = "gridAltitude"; const char* ComplexMissionItem::_jsonGridAltitudeRelativeKey = "gridAltitudeRelative"; const char* ComplexMissionItem::_jsonGridAngleKey = "gridAngle"; const char* ComplexMissionItem::_jsonGridSpacingKey = "gridSpacing"; const char* ComplexMissionItem::_jsonCameraTriggerKey = "cameraTrigger"; const char* ComplexMissionItem::_jsonCameraTriggerDistanceKey = "cameraTriggerDistance"; const char* ComplexMissionItem::_complexType = "survey"; ComplexMissionItem::ComplexMissionItem(Vehicle* vehicle, QObject* parent) : VisualMissionItem(vehicle, parent) , _sequenceNumber(0) , _dirty(false) , _cameraTrigger(false) , _gridAltitudeRelative(true) , _gridAltitudeFact (0, "Altitude:", FactMetaData::valueTypeDouble) , _gridAngleFact (0, "Grid angle:", FactMetaData::valueTypeDouble) , _gridSpacingFact (0, "Grid spacing:", FactMetaData::valueTypeDouble) , _cameraTriggerDistanceFact(0, "Camera trigger distance", FactMetaData::valueTypeDouble) { _gridAltitudeFact.setRawValue(25); _gridSpacingFact.setRawValue(10); _cameraTriggerDistanceFact.setRawValue(25); connect(&_gridSpacingFact, &Fact::valueChanged, this, &ComplexMissionItem::_generateGrid); connect(&_gridAngleFact, &Fact::valueChanged, this, &ComplexMissionItem::_generateGrid); connect(this, &ComplexMissionItem::cameraTriggerChanged, this, &ComplexMissionItem::_signalLastSequenceNumberChanged); } void ComplexMissionItem::clearPolygon(void) { // Bug workaround, see below while (_polygonPath.count() > 1) { _polygonPath.takeLast(); } emit polygonPathChanged(); // Although this code should remove the polygon from the map it doesn't. There appears // to be a bug in MapPolygon which causes it to not be redrawn if the list is empty. So // we work around it by using the code above to remove all but the last point which in turn // will cause the polygon to go away. _polygonPath.clear(); _clearGrid(); emit specifiesCoordinateChanged(); } void ComplexMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate) { _polygonPath << QVariant::fromValue(coordinate); emit polygonPathChanged(); int pointCount = _polygonPath.count(); if (pointCount == 1) { setCoordinate(coordinate); } else if (pointCount == 3) { emit specifiesCoordinateChanged(); } _setExitCoordinate(coordinate); _generateGrid(); } int ComplexMissionItem::lastSequenceNumber(void) const { int lastSeq = _sequenceNumber; if (_gridPoints.count()) { lastSeq += _gridPoints.count() - 1; } if (_cameraTrigger) { // Account for two trigger messages lastSeq += 2; } return lastSeq; } void ComplexMissionItem::setCoordinate(const QGeoCoordinate& coordinate) { if (_coordinate != coordinate) { _coordinate = coordinate; emit coordinateChanged(_coordinate); _setExitCoordinate(coordinate); } } void ComplexMissionItem::setDirty(bool dirty) { if (_dirty != dirty) { _dirty = dirty; emit dirtyChanged(_dirty); } } void ComplexMissionItem::save(QJsonObject& saveObject) const { saveObject[_jsonVersionKey] = 1; saveObject[_jsonTypeKey] = _complexType; saveObject[_jsonIdKey] = sequenceNumber(); saveObject[_jsonGridAltitudeKey] = _gridAltitudeFact.rawValue().toDouble(); saveObject[_jsonGridAltitudeRelativeKey] = _gridAltitudeRelative; saveObject[_jsonGridAngleKey] = _gridAngleFact.rawValue().toDouble(); saveObject[_jsonGridSpacingKey] = _gridSpacingFact.rawValue().toDouble(); saveObject[_jsonCameraTriggerKey] = _cameraTrigger; saveObject[_jsonCameraTriggerDistanceKey] = _cameraTriggerDistanceFact.rawValue().toDouble(); // Polygon shape QJsonArray polygonArray; for (int i=0; i<_polygonPath.count(); i++) { const QVariant& polyVar = _polygonPath[i]; QJsonValue jsonValue; JsonHelper::writeQGeoCoordinate(jsonValue, polyVar.value(), false /* writeAltitude */); polygonArray += jsonValue; } saveObject[_jsonPolygonKey] = polygonArray; } void ComplexMissionItem::setSequenceNumber(int sequenceNumber) { if (_sequenceNumber != sequenceNumber) { _sequenceNumber = sequenceNumber; emit sequenceNumberChanged(sequenceNumber); emit lastSequenceNumberChanged(lastSequenceNumber()); } } void ComplexMissionItem::_clear(void) { clearPolygon(); _clearGrid(); } bool ComplexMissionItem::load(const QJsonObject& complexObject, QString& errorString) { _clear(); // Validate requires keys QStringList requiredKeys; requiredKeys << _jsonVersionKey << _jsonTypeKey << _jsonIdKey << _jsonPolygonKey << _jsonGridAltitudeKey << _jsonGridAngleKey << _jsonGridSpacingKey << _jsonCameraTriggerKey << _jsonCameraTriggerDistanceKey << _jsonGridAltitudeRelativeKey; if (!JsonHelper::validateRequiredKeys(complexObject, requiredKeys, errorString)) { _clear(); return false; } // Validate types QStringList keyList; QList typeList; keyList << _jsonVersionKey << _jsonTypeKey << _jsonIdKey << _jsonPolygonKey << _jsonGridAltitudeKey << _jsonGridAngleKey << _jsonGridSpacingKey << _jsonCameraTriggerKey << _jsonCameraTriggerDistanceKey << _jsonGridAltitudeRelativeKey; typeList << QJsonValue::Double << QJsonValue::String << QJsonValue::Double << QJsonValue::Array << QJsonValue::Double << QJsonValue::Double<< QJsonValue::Double << QJsonValue::Bool << QJsonValue::Double << QJsonValue::Bool; if (!JsonHelper::validateKeyTypes(complexObject, keyList, typeList, errorString)) { _clear(); return false; } // Version check if (complexObject[_jsonVersionKey].toInt() != 1) { errorString = tr("QGroundControl does not support this version of survey items"); _clear(); return false; } QString complexType = complexObject[_jsonTypeKey].toString(); if (complexType != _complexType) { errorString = tr("QGroundControl does not support loading this complex mission item type: %1").arg(complexType); _clear(); return false; } setSequenceNumber(complexObject[_jsonIdKey].toInt()); _cameraTrigger = complexObject[_jsonCameraTriggerKey].toBool(); _gridAltitudeRelative = complexObject[_jsonGridAltitudeRelativeKey].toBool(); _gridAltitudeFact.setRawValue (complexObject[_jsonGridAltitudeKey].toDouble()); _gridAngleFact.setRawValue (complexObject[_jsonGridAngleKey].toDouble()); _gridSpacingFact.setRawValue (complexObject[_jsonGridSpacingKey].toDouble()); _cameraTriggerDistanceFact.setRawValue (complexObject[_jsonCameraTriggerDistanceKey].toDouble()); // Polygon shape QJsonArray polygonArray(complexObject[_jsonPolygonKey].toArray()); for (int i=0; i 2; } void ComplexMissionItem::_clearGrid(void) { // Bug workaround while (_gridPoints.count() > 1) { _gridPoints.takeLast(); } emit gridPointsChanged(); _gridPoints.clear(); emit gridPointsChanged(); } void ComplexMissionItem::_generateGrid(void) { if (_polygonPath.count() < 3) { _clearGrid(); return; } _gridPoints.clear(); QList polygonPoints; QList gridPoints; // Convert polygon to Qt coordinate system (y positive is down) qCDebug(ComplexMissionItemLog) << "Convert polygon"; QGeoCoordinate tangentOrigin = _polygonPath[0].value(); for (int i=0; i<_polygonPath.count(); i++) { double y, x, down; convertGeoToNed(_polygonPath[i].value(), tangentOrigin, &y, &x, &down); polygonPoints += QPointF(x, -y); qCDebug(ComplexMissionItemLog) << _polygonPath[i].value() << polygonPoints.last().x() << polygonPoints.last().y(); } // Generate grid _gridGenerator(polygonPoints, gridPoints); // Convert to Geo and set altitude for (int i=0; i()); _setExitCoordinate(_gridPoints.last().value()); } } QPointF ComplexMissionItem::_rotatePoint(const QPointF& point, const QPointF& origin, double angle) { QPointF rotated; double radians = (M_PI / 180.0) * angle; rotated.setX(((point.x() - origin.x()) * cos(radians)) - ((point.y() - origin.y()) * sin(radians)) + origin.x()); rotated.setY(((point.x() - origin.x()) * sin(radians)) + ((point.y() - origin.y()) * cos(radians)) + origin.y()); return rotated; } void ComplexMissionItem::_intersectLinesWithRect(const QList& lineList, const QRectF& boundRect, QList& resultLines) { QLineF topLine (boundRect.topLeft(), boundRect.topRight()); QLineF bottomLine (boundRect.bottomLeft(), boundRect.bottomRight()); QLineF leftLine (boundRect.topLeft(), boundRect.bottomLeft()); QLineF rightLine (boundRect.topRight(), boundRect.bottomRight()); for (int i=0; i& lineList, const QPolygonF& polygon, QList& resultLines) { for (int i=0; i& polygonPoints, QList& gridPoints) { double gridAngle = _gridAngleFact.rawValue().toDouble(); gridPoints.clear(); // Convert polygon to bounding rect qCDebug(ComplexMissionItemLog) << "Polygon"; QPolygonF polygon; for (int i=0; i lineList; float x = largeBoundRect.topLeft().x(); float gridSpacing = _gridSpacingFact.rawValue().toDouble(); while (x < largeBoundRect.bottomRight().x()) { float yTop = largeBoundRect.topLeft().y() - 100.0; float yBottom = largeBoundRect.bottomRight().y() + 100.0; lineList += QLineF(_rotatePoint(QPointF(x, yTop), center, gridAngle), _rotatePoint(QPointF(x, yBottom), center, gridAngle)); qCDebug(ComplexMissionItemLog) << "line" << lineList.last().x1() << lineList.last().y1() << lineList.last().x2() << lineList.last().y2(); x += gridSpacing; } // Now intesect the lines with the smaller bounding rect QList resultLines; //_intersectLinesWithRect(lineList, smallBoundRect, resultLines); _intersectLinesWithPolygon(lineList, polygon, resultLines); // Turn into a path for (int i=0; i(); double altitude = _gridAltitudeFact.rawValue().toDouble(); MissionItem* item = new MissionItem(seqNum++, // sequence number MAV_CMD_NAV_WAYPOINT, // MAV_CMD _gridAltitudeRelative ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL, // MAV_FRAME 0.0, 0.0, 0.0, 0.0, // param 1-4 coord.latitude(), coord.longitude(), altitude, true, // autoContinue false, // isCurrentItem pMissionItems); // parent - allow delete on pMissionItems to delete everthing pMissionItems->append(item); if (_cameraTrigger && i == 0) { MissionItem* item = new MissionItem(seqNum++, // sequence number MAV_CMD_DO_SET_CAM_TRIGG_DIST, // MAV_CMD MAV_FRAME_MISSION, // MAV_FRAME _cameraTriggerDistanceFact.rawValue().toDouble(), // trigger distance 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, // param 2-7 true, // autoContinue false, // isCurrentItem pMissionItems); // parent - allow delete on pMissionItems to delete everthing pMissionItems->append(item); } } if (_cameraTrigger) { MissionItem* item = new MissionItem(seqNum++, // sequence number MAV_CMD_DO_SET_CAM_TRIGG_DIST, // MAV_CMD MAV_FRAME_MISSION, // MAV_FRAME 0.0, // trigger distance 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, // param 2-7 true, // autoContinue false, // isCurrentItem pMissionItems); // parent - allow delete on pMissionItems to delete everthing pMissionItems->append(item); } return pMissionItems; } void ComplexMissionItem::_signalLastSequenceNumberChanged(void) { emit lastSequenceNumberChanged(lastSequenceNumber()); }