地面站终端 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.

544 lines
16 KiB

/****************************************************************************
*
* (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 "QGCMapPolygon.h"
#include "QGCGeo.h"
#include "JsonHelper.h"
#include "QGCQGeoCoordinate.h"
#include "QGCApplication.h"
#include <QGeoRectangle>
#include <QDebug>
#include <QJsonArray>
#include <QLineF>
#include <QFile>
#include <QDomDocument>
const char* QGCMapPolygon::jsonPolygonKey = "polygon";
QGCMapPolygon::QGCMapPolygon(QObject* parent)
: QObject (parent)
, _dirty (false)
, _centerDrag (false)
, _ignoreCenterUpdates (false)
, _interactive (false)
{
_init();
}
QGCMapPolygon::QGCMapPolygon(const QGCMapPolygon& other, QObject* parent)
: QObject (parent)
, _dirty (false)
, _centerDrag (false)
, _ignoreCenterUpdates (false)
, _interactive (false)
{
*this = other;
_init();
}
void QGCMapPolygon::_init(void)
{
connect(&_polygonModel, &QmlObjectListModel::dirtyChanged, this, &QGCMapPolygon::_polygonModelDirtyChanged);
connect(&_polygonModel, &QmlObjectListModel::countChanged, this, &QGCMapPolygon::_polygonModelCountChanged);
connect(this, &QGCMapPolygon::pathChanged, this, &QGCMapPolygon::_updateCenter);
}
const QGCMapPolygon& QGCMapPolygon::operator=(const QGCMapPolygon& other)
{
clear();
QVariantList vertices = other.path();
for (int i=0; i<vertices.count(); i++) {
appendVertex(vertices[i].value<QGeoCoordinate>());
}
setDirty(true);
return *this;
}
void QGCMapPolygon::clear(void)
{
// Bug workaround, see below
while (_polygonPath.count() > 1) {
_polygonPath.takeLast();
}
emit pathChanged();
// Although this code should remove the polygon from the map it doesn't. There appears
// to be a bug in QGCMapPolygon 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();
_polygonModel.clearAndDeleteContents();
emit cleared();
setDirty(true);
}
void QGCMapPolygon::adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
{
_polygonPath[vertexIndex] = QVariant::fromValue(coordinate);
if (!_centerDrag) {
// When dragging center we don't signal path changed until add vertices are updated
emit pathChanged();
}
_polygonModel.value<QGCQGeoCoordinate*>(vertexIndex)->setCoordinate(coordinate);
setDirty(true);
}
void QGCMapPolygon::setDirty(bool dirty)
{
if (_dirty != dirty) {
_dirty = dirty;
if (!dirty) {
_polygonModel.setDirty(false);
}
emit dirtyChanged(dirty);
}
}
QGeoCoordinate QGCMapPolygon::_coordFromPointF(const QPointF& point) const
{
QGeoCoordinate coord;
if (_polygonPath.count() > 0) {
QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
convertNedToGeo(-point.y(), point.x(), 0, tangentOrigin, &coord);
}
return coord;
}
QPointF QGCMapPolygon::_pointFFromCoord(const QGeoCoordinate& coordinate) const
{
if (_polygonPath.count() > 0) {
double y, x, down;
QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
convertGeoToNed(coordinate, tangentOrigin, &y, &x, &down);
return QPointF(x, -y);
}
return QPointF();
}
QPolygonF QGCMapPolygon::_toPolygonF(void) const
{
QPolygonF polygon;
if (_polygonPath.count() > 2) {
for (int i=0; i<_polygonPath.count(); i++) {
polygon.append(_pointFFromCoord(_polygonPath[i].value<QGeoCoordinate>()));
}
}
return polygon;
}
bool QGCMapPolygon::containsCoordinate(const QGeoCoordinate& coordinate) const
{
if (_polygonPath.count() > 2) {
return _toPolygonF().containsPoint(_pointFFromCoord(coordinate), Qt::OddEvenFill);
} else {
return false;
}
}
void QGCMapPolygon::setPath(const QList<QGeoCoordinate>& path)
{
_polygonPath.clear();
_polygonModel.clearAndDeleteContents();
foreach(const QGeoCoordinate& coord, path) {
_polygonPath.append(QVariant::fromValue(coord));
_polygonModel.append(new QGCQGeoCoordinate(coord, this));
}
setDirty(true);
emit pathChanged();
}
void QGCMapPolygon::setPath(const QVariantList& path)
{
_polygonPath = path;
_polygonModel.clearAndDeleteContents();
for (int i=0; i<_polygonPath.count(); i++) {
_polygonModel.append(new QGCQGeoCoordinate(_polygonPath[i].value<QGeoCoordinate>(), this));
}
setDirty(true);
emit pathChanged();
}
void QGCMapPolygon::saveToJson(QJsonObject& json)
{
QJsonValue jsonValue;
JsonHelper::saveGeoCoordinateArray(_polygonPath, false /* writeAltitude*/, jsonValue);
json.insert(jsonPolygonKey, jsonValue);
setDirty(false);
}
bool QGCMapPolygon::loadFromJson(const QJsonObject& json, bool required, QString& errorString)
{
errorString.clear();
clear();
if (required) {
if (!JsonHelper::validateRequiredKeys(json, QStringList(jsonPolygonKey), errorString)) {
return false;
}
} else if (!json.contains(jsonPolygonKey)) {
return true;
}
if (!JsonHelper::loadGeoCoordinateArray(json[jsonPolygonKey], false /* altitudeRequired */, _polygonPath, errorString)) {
return false;
}
for (int i=0; i<_polygonPath.count(); i++) {
_polygonModel.append(new QGCQGeoCoordinate(_polygonPath[i].value<QGeoCoordinate>(), this));
}
setDirty(false);
emit pathChanged();
return true;
}
QList<QGeoCoordinate> QGCMapPolygon::coordinateList(void) const
{
QList<QGeoCoordinate> coords;
for (int i=0; i<_polygonPath.count(); i++) {
coords.append(_polygonPath[i].value<QGeoCoordinate>());
}
return coords;
}
void QGCMapPolygon::splitPolygonSegment(int vertexIndex)
{
int nextIndex = vertexIndex + 1;
if (nextIndex > _polygonPath.length() - 1) {
nextIndex = 0;
}
QGeoCoordinate firstVertex = _polygonPath[vertexIndex].value<QGeoCoordinate>();
QGeoCoordinate nextVertex = _polygonPath[nextIndex].value<QGeoCoordinate>();
double distance = firstVertex.distanceTo(nextVertex);
double azimuth = firstVertex.azimuthTo(nextVertex);
QGeoCoordinate newVertex = firstVertex.atDistanceAndAzimuth(distance / 2, azimuth);
if (nextIndex == 0) {
appendVertex(newVertex);
} else {
_polygonModel.insert(nextIndex, new QGCQGeoCoordinate(newVertex, this));
_polygonPath.insert(nextIndex, QVariant::fromValue(newVertex));
emit pathChanged();
}
}
void QGCMapPolygon::appendVertex(const QGeoCoordinate& coordinate)
{
_polygonPath.append(QVariant::fromValue(coordinate));
_polygonModel.append(new QGCQGeoCoordinate(coordinate, this));
emit pathChanged();
}
void QGCMapPolygon::appendVertices(const QList<QGeoCoordinate>& coordinates)
{
QList<QObject*> objects;
foreach (const QGeoCoordinate& coordinate, coordinates) {
objects.append(new QGCQGeoCoordinate(coordinate, this));
_polygonPath.append(QVariant::fromValue(coordinate));
}
_polygonModel.append(objects);
emit pathChanged();
}
void QGCMapPolygon::_polygonModelDirtyChanged(bool dirty)
{
if (dirty) {
setDirty(true);
}
}
void QGCMapPolygon::removeVertex(int vertexIndex)
{
if (vertexIndex < 0 && vertexIndex > _polygonPath.length() - 1) {
qWarning() << "Call to removePolygonCoordinate with bad vertexIndex:count" << vertexIndex << _polygonPath.length();
return;
}
if (_polygonPath.length() <= 3) {
// Don't allow the user to trash the polygon
return;
}
QObject* coordObj = _polygonModel.removeAt(vertexIndex);
coordObj->deleteLater();
_polygonPath.removeAt(vertexIndex);
emit pathChanged();
}
void QGCMapPolygon::_polygonModelCountChanged(int count)
{
emit countChanged(count);
}
void QGCMapPolygon::_updateCenter(void)
{
if (!_ignoreCenterUpdates) {
QGeoCoordinate center;
if (_polygonPath.count() > 2) {
QPointF centroid(0, 0);
QPolygonF polygonF = _toPolygonF();
for (int i=0; i<polygonF.count(); i++) {
centroid += polygonF[i];
}
center = _coordFromPointF(QPointF(centroid.x() / polygonF.count(), centroid.y() / polygonF.count()));
}
if (_center != center) {
_center = center;
emit centerChanged(center);
}
}
}
void QGCMapPolygon::setCenter(QGeoCoordinate newCenter)
{
if (newCenter != _center) {
_ignoreCenterUpdates = true;
// Adjust polygon vertices to new center
double distance = _center.distanceTo(newCenter);
double azimuth = _center.azimuthTo(newCenter);
for (int i=0; i<count(); i++) {
QGeoCoordinate oldVertex = _polygonPath[i].value<QGeoCoordinate>();
QGeoCoordinate newVertex = oldVertex.atDistanceAndAzimuth(distance, azimuth);
adjustVertex(i, newVertex);
}
if (_centerDrag) {
// When center dragging signals are delayed until all vertices are updated
emit pathChanged();
}
_ignoreCenterUpdates = false;
_center = newCenter;
emit centerChanged(newCenter);
}
}
void QGCMapPolygon::setCenterDrag(bool centerDrag)
{
if (centerDrag != _centerDrag) {
_centerDrag = centerDrag;
emit centerDragChanged(centerDrag);
}
}
void QGCMapPolygon::setInteractive(bool interactive)
{
if (_interactive != interactive) {
_interactive = interactive;
emit interactiveChanged(interactive);
}
}
QGeoCoordinate QGCMapPolygon::vertexCoordinate(int vertex) const
{
if (vertex >= 0 && vertex < _polygonPath.count()) {
return _polygonPath[vertex].value<QGeoCoordinate>();
} else {
qWarning() << "QGCMapPolygon::vertexCoordinate bad vertex requested";
return QGeoCoordinate();
}
}
QList<QPointF> QGCMapPolygon::nedPolygon(void) const
{
QList<QPointF> nedPolygon;
if (count() > 0) {
QGeoCoordinate tangentOrigin = vertexCoordinate(0);
for (int i=0; i<_polygonModel.count(); i++) {
double y, x, down;
QGeoCoordinate vertex = vertexCoordinate(i);
if (i == 0) {
// This avoids a nan calculation that comes out of convertGeoToNed
x = y = 0;
} else {
convertGeoToNed(vertex, tangentOrigin, &y, &x, &down);
}
nedPolygon += QPointF(x, y);
}
}
return nedPolygon;
}
void QGCMapPolygon::offset(double distance)
{
QList<QGeoCoordinate> rgNewPolygon;
// I'm sure there is some beautiful famous algorithm to do this, but here is a brute force method
if (count() > 2) {
// Convert the polygon to NED
QList<QPointF> rgNedVertices = nedPolygon();
// Walk the edges, offsetting by the specified distance
QList<QLineF> rgOffsetEdges;
for (int i=0; i<rgNedVertices.count(); i++) {
int lastIndex = i == rgNedVertices.count() - 1 ? 0 : i + 1;
QLineF offsetEdge;
QLineF originalEdge(rgNedVertices[i], rgNedVertices[lastIndex]);
QLineF workerLine = originalEdge;
workerLine.setLength(distance);
workerLine.setAngle(workerLine.angle() - 90.0);
offsetEdge.setP1(workerLine.p2());
workerLine.setPoints(originalEdge.p2(), originalEdge.p1());
workerLine.setLength(distance);
workerLine.setAngle(workerLine.angle() + 90.0);
offsetEdge.setP2(workerLine.p2());
rgOffsetEdges.append(offsetEdge);
}
// Intersect the offset edges to generate new vertices
QPointF newVertex;
QGeoCoordinate tangentOrigin = vertexCoordinate(0);
for (int i=0; i<rgOffsetEdges.count(); i++) {
int prevIndex = i == 0 ? rgOffsetEdges.count() - 1 : i - 1;
if (rgOffsetEdges[prevIndex].intersect(rgOffsetEdges[i], &newVertex) == QLineF::NoIntersection) {
// FIXME: Better error handling?
qWarning("Intersection failed");
return;
}
QGeoCoordinate coord;
convertNedToGeo(newVertex.y(), newVertex.x(), 0, tangentOrigin, &coord);
rgNewPolygon.append(coord);
}
}
// Update internals
clear();
for (int i=0; i<rgNewPolygon.count(); i++) {
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<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;
}
clear();
appendVertices(rgCoords);
return true;
}
double QGCMapPolygon::area(void) const
{
// https://www.mathopenref.com/coordpolygonarea2.html
double coveredArea = 0.0;
QList<QPointF> nedVertices = nedPolygon();
for (int i=0; i<nedVertices.count(); i++) {
if (i != 0) {
coveredArea += nedVertices[i - 1].x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices[i -1].y();
} else {
coveredArea += nedVertices.last().x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices.last().y();
}
}
return 0.5 * fabs(coveredArea);
}