地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
20 KiB

/*===================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#include "ComplexMissionItem.h"
#include "JsonHelper.h"
#include "MissionController.h"
#include "QGCGeo.h"
#include <QPolygonF>
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::_cameraTriggerChanged);
}
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();
setDirty(true);
emit specifiesCoordinateChanged();
emit lastSequenceNumberChanged(lastSequenceNumber());
}
void ComplexMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate)
{
_polygonPath << QVariant::fromValue(coordinate);
emit polygonPathChanged();
int pointCount = _polygonPath.count();
if (pointCount >= 3) {
if (pointCount == 3) {
emit specifiesCoordinateChanged();
}
_generateGrid();
}
setDirty(true);
}
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);
}
}
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<QGeoCoordinate>(), 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<QJsonValue::Type> 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<polygonArray.count(); i++) {
const QJsonValue& pointValue = polygonArray[i];
QGeoCoordinate pointCoord;
if (!JsonHelper::toQGeoCoordinate(pointValue, pointCoord, false /* altitudeRequired */, errorString)) {
_clear();
return false;
}
_polygonPath << QVariant::fromValue(pointCoord);
}
_generateGrid();
return true;
}
void ComplexMissionItem::_setExitCoordinate(const QGeoCoordinate& coordinate)
{
if (_exitCoordinate != coordinate) {
_exitCoordinate = coordinate;
emit exitCoordinateChanged(coordinate);
}
}
bool ComplexMissionItem::specifiesCoordinate(void) const
{
return _polygonPath.count() > 2;
}
void ComplexMissionItem::_clearGrid(void)
{
// Bug workaround
while (_gridPoints.count() > 1) {
_gridPoints.takeLast();
}
emit gridPointsChanged();
_gridPoints.clear();
}
void ComplexMissionItem::_generateGrid(void)
{
if (_polygonPath.count() < 3) {
_clearGrid();
return;
}
_gridPoints.clear();
QList<QPointF> polygonPoints;
QList<QPointF> gridPoints;
// Convert polygon to Qt coordinate system (y positive is down)
qCDebug(ComplexMissionItemLog) << "Convert polygon";
QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
for (int i=0; i<_polygonPath.count(); i++) {
double y, x, down;
convertGeoToNed(_polygonPath[i].value<QGeoCoordinate>(), tangentOrigin, &y, &x, &down);
polygonPoints += QPointF(x, -y);
qCDebug(ComplexMissionItemLog) << _polygonPath[i].value<QGeoCoordinate>() << polygonPoints.last().x() << polygonPoints.last().y();
}
// Generate grid
_gridGenerator(polygonPoints, gridPoints);
// Convert to Geo and set altitude
for (int i=0; i<gridPoints.count(); i++) {
QPointF& point = gridPoints[i];
QGeoCoordinate geoCoord;
convertNedToGeo(-point.y(), point.x(), 0, tangentOrigin, &geoCoord);
_gridPoints += QVariant::fromValue(geoCoord);
}
emit gridPointsChanged();
emit lastSequenceNumberChanged(lastSequenceNumber());
if (_gridPoints.count()) {
setCoordinate(_gridPoints.first().value<QGeoCoordinate>());
_setExitCoordinate(_gridPoints.last().value<QGeoCoordinate>());
}
}
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<QLineF>& lineList, const QRectF& boundRect, QList<QLineF>& 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.count(); i++) {
QPointF intersectPoint;
QLineF intersectLine;
const QLineF& line = lineList[i];
int foundCount = 0;
if (line.intersect(topLine, &intersectPoint) == QLineF::BoundedIntersection) {
intersectLine.setP1(intersectPoint);
foundCount++;
}
if (line.intersect(rightLine, &intersectPoint) == QLineF::BoundedIntersection) {
if (foundCount == 0) {
intersectLine.setP1(intersectPoint);
} else {
if (foundCount != 1) {
qWarning() << "Found more than two intersecting points";
}
intersectLine.setP2(intersectPoint);
}
foundCount++;
}
if (line.intersect(bottomLine, &intersectPoint) == QLineF::BoundedIntersection) {
if (foundCount == 0) {
intersectLine.setP1(intersectPoint);
} else {
if (foundCount != 1) {
qWarning() << "Found more than two intersecting points";
}
intersectLine.setP2(intersectPoint);
}
foundCount++;
}
if (line.intersect(leftLine, &intersectPoint) == QLineF::BoundedIntersection) {
if (foundCount == 0) {
intersectLine.setP1(intersectPoint);
} else {
if (foundCount != 1) {
qWarning() << "Found more than two intersecting points";
}
intersectLine.setP2(intersectPoint);
}
foundCount++;
}
if (foundCount == 2) {
resultLines += intersectLine;
}
}
}
void ComplexMissionItem::_intersectLinesWithPolygon(const QList<QLineF>& lineList, const QPolygonF& polygon, QList<QLineF>& resultLines)
{
for (int i=0; i<lineList.count(); i++) {
int foundCount = 0;
QLineF intersectLine;
const QLineF& line = lineList[i];
for (int j=0; j<polygon.count()-1; j++) {
QPointF intersectPoint;
QLineF polygonLine = QLineF(polygon[j], polygon[j+1]);
if (line.intersect(polygonLine, &intersectPoint) == QLineF::BoundedIntersection) {
if (foundCount == 0) {
foundCount++;
intersectLine.setP1(intersectPoint);
} else {
foundCount++;
intersectLine.setP2(intersectPoint);
break;
}
}
}
if (foundCount == 2) {
resultLines += intersectLine;
}
}
}
void ComplexMissionItem::_gridGenerator(const QList<QPointF>& polygonPoints, QList<QPointF>& gridPoints)
{
double gridAngle = _gridAngleFact.rawValue().toDouble();
gridPoints.clear();
// Convert polygon to bounding rect
qCDebug(ComplexMissionItemLog) << "Polygon";
QPolygonF polygon;
for (int i=0; i<polygonPoints.count(); i++) {
qCDebug(ComplexMissionItemLog) << polygonPoints[i];
polygon << polygonPoints[i];
}
polygon << polygonPoints[0];
QRectF smallBoundRect = polygon.boundingRect();
QPointF center = smallBoundRect.center();
qCDebug(ComplexMissionItemLog) << "Bounding rect" << smallBoundRect.topLeft().x() << smallBoundRect.topLeft().y() << smallBoundRect.bottomRight().x() << smallBoundRect.bottomRight().y();
// Rotate the bounding rect around it's center to generate the larger bounding rect
QPolygonF boundPolygon;
boundPolygon << _rotatePoint(smallBoundRect.topLeft(), center, gridAngle);
boundPolygon << _rotatePoint(smallBoundRect.topRight(), center, gridAngle);
boundPolygon << _rotatePoint(smallBoundRect.bottomRight(), center, gridAngle);
boundPolygon << _rotatePoint(smallBoundRect.bottomLeft(), center, gridAngle);
boundPolygon << boundPolygon[0];
QRectF largeBoundRect = boundPolygon.boundingRect();
qCDebug(ComplexMissionItemLog) << "Rotated bounding rect" << largeBoundRect.topLeft().x() << largeBoundRect.topLeft().y() << largeBoundRect.bottomRight().x() << largeBoundRect.bottomRight().y();
// Create set of rotated parallel lines within the expanded bounding rect. Make the lines larger than the
// bounding box to guarantee intersection.
QList<QLineF> 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<QLineF> resultLines;
//_intersectLinesWithRect(lineList, smallBoundRect, resultLines);
_intersectLinesWithPolygon(lineList, polygon, resultLines);
// Turn into a path
for (int i=0; i<resultLines.count(); i++) {
const QLineF& line = resultLines[i];
if (i & 1) {
gridPoints << line.p2() << line.p1();
} else {
gridPoints << line.p1() << line.p2();
}
}
}
QmlObjectListModel* ComplexMissionItem::getMissionItems(void) const
{
QmlObjectListModel* pMissionItems = new QmlObjectListModel;
int seqNum = _sequenceNumber;
for (int i=0; i<_gridPoints.count(); i++) {
QGeoCoordinate coord = _gridPoints[i].value<QGeoCoordinate>();
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::_cameraTriggerChanged(void)
{
setDirty(true);
if (_gridPoints.count()) {
// If we have grid turn on/off camera trigger will add/remove two camera trigger mission items
emit lastSequenceNumberChanged(lastSequenceNumber());
}
}