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

391 lines
11 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 "QGCMapPolyline.h"
#include "QGCGeo.h"
#include "JsonHelper.h"
#include "QGCQGeoCoordinate.h"
#include "QGCApplication.h"
#include "KMLFileHelper.h"
#include <QGeoRectangle>
#include <QDebug>
#include <QJsonArray>
#include <QLineF>
#include <QFile>
#include <QDomDocument>
const char* QGCMapPolyline::jsonPolylineKey = "polyline";
QGCMapPolyline::QGCMapPolyline(QObject* parent)
: QObject (parent)
, _dirty (false)
, _interactive (false)
{
_init();
}
QGCMapPolyline::QGCMapPolyline(const QGCMapPolyline& other, QObject* parent)
: QObject (parent)
, _dirty (false)
, _interactive (false)
{
*this = other;
_init();
}
const QGCMapPolyline& QGCMapPolyline::operator=(const QGCMapPolyline& other)
{
clear();
QVariantList vertices = other.path();
for (int i=0; i<vertices.count(); i++) {
appendVertex(vertices[i].value<QGeoCoordinate>());
}
setDirty(true);
return *this;
}
void QGCMapPolyline::_init(void)
{
connect(&_polylineModel, &QmlObjectListModel::dirtyChanged, this, &QGCMapPolyline::_polylineModelDirtyChanged);
connect(&_polylineModel, &QmlObjectListModel::countChanged, this, &QGCMapPolyline::_polylineModelCountChanged);
}
void QGCMapPolyline::clear(void)
{
_polylinePath.clear();
emit pathChanged();
_polylineModel.clearAndDeleteContents();
emit cleared();
setDirty(true);
}
void QGCMapPolyline::adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
{
_polylinePath[vertexIndex] = QVariant::fromValue(coordinate);
emit pathChanged();
_polylineModel.value<QGCQGeoCoordinate*>(vertexIndex)->setCoordinate(coordinate);
setDirty(true);
}
void QGCMapPolyline::setDirty(bool dirty)
{
if (_dirty != dirty) {
_dirty = dirty;
if (!dirty) {
_polylineModel.setDirty(false);
}
emit dirtyChanged(dirty);
}
}
QGeoCoordinate QGCMapPolyline::_coordFromPointF(const QPointF& point) const
{
QGeoCoordinate coord;
if (_polylinePath.count() > 0) {
QGeoCoordinate tangentOrigin = _polylinePath[0].value<QGeoCoordinate>();
convertNedToGeo(-point.y(), point.x(), 0, tangentOrigin, &coord);
}
return coord;
}
QPointF QGCMapPolyline::_pointFFromCoord(const QGeoCoordinate& coordinate) const
{
if (_polylinePath.count() > 0) {
double y, x, down;
QGeoCoordinate tangentOrigin = _polylinePath[0].value<QGeoCoordinate>();
convertGeoToNed(coordinate, tangentOrigin, &y, &x, &down);
return QPointF(x, -y);
}
return QPointF();
}
void QGCMapPolyline::setPath(const QList<QGeoCoordinate>& path)
{
_polylinePath.clear();
_polylineModel.clearAndDeleteContents();
foreach (const QGeoCoordinate& coord, path) {
_polylinePath.append(QVariant::fromValue(coord));
_polylineModel.append(new QGCQGeoCoordinate(coord, this));
}
setDirty(true);
emit pathChanged();
}
void QGCMapPolyline::setPath(const QVariantList& path)
{
_polylinePath = path;
_polylineModel.clearAndDeleteContents();
for (int i=0; i<_polylinePath.count(); i++) {
_polylineModel.append(new QGCQGeoCoordinate(_polylinePath[i].value<QGeoCoordinate>(), this));
}
setDirty(true);
emit pathChanged();
}
void QGCMapPolyline::saveToJson(QJsonObject& json)
{
QJsonValue jsonValue;
JsonHelper::saveGeoCoordinateArray(_polylinePath, false /* writeAltitude*/, jsonValue);
json.insert(jsonPolylineKey, jsonValue);
setDirty(false);
}
bool QGCMapPolyline::loadFromJson(const QJsonObject& json, bool required, QString& errorString)
{
errorString.clear();
clear();
if (required) {
if (!JsonHelper::validateRequiredKeys(json, QStringList(jsonPolylineKey), errorString)) {
return false;
}
} else if (!json.contains(jsonPolylineKey)) {
return true;
}
if (!JsonHelper::loadGeoCoordinateArray(json[jsonPolylineKey], false /* altitudeRequired */, _polylinePath, errorString)) {
return false;
}
for (int i=0; i<_polylinePath.count(); i++) {
_polylineModel.append(new QGCQGeoCoordinate(_polylinePath[i].value<QGeoCoordinate>(), this));
}
setDirty(false);
emit pathChanged();
return true;
}
QList<QGeoCoordinate> QGCMapPolyline::coordinateList(void) const
{
QList<QGeoCoordinate> coords;
for (int i=0; i<_polylinePath.count(); i++) {
coords.append(_polylinePath[i].value<QGeoCoordinate>());
}
return coords;
}
void QGCMapPolyline::splitSegment(int vertexIndex)
{
int nextIndex = vertexIndex + 1;
if (nextIndex > _polylinePath.length() - 1) {
return;
}
QGeoCoordinate firstVertex = _polylinePath[vertexIndex].value<QGeoCoordinate>();
QGeoCoordinate nextVertex = _polylinePath[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 {
_polylineModel.insert(nextIndex, new QGCQGeoCoordinate(newVertex, this));
_polylinePath.insert(nextIndex, QVariant::fromValue(newVertex));
emit pathChanged();
}
}
void QGCMapPolyline::appendVertex(const QGeoCoordinate& coordinate)
{
_polylinePath.append(QVariant::fromValue(coordinate));
_polylineModel.append(new QGCQGeoCoordinate(coordinate, this));
emit pathChanged();
}
void QGCMapPolyline::removeVertex(int vertexIndex)
{
if (vertexIndex < 0 || vertexIndex > _polylinePath.length() - 1) {
qWarning() << "Call to removeVertex with bad vertexIndex:count" << vertexIndex << _polylinePath.length();
return;
}
if (_polylinePath.length() <= 2) {
// Don't allow the user to trash the polyline
return;
}
QObject* coordObj = _polylineModel.removeAt(vertexIndex);
coordObj->deleteLater();
_polylinePath.removeAt(vertexIndex);
emit pathChanged();
}
void QGCMapPolyline::setInteractive(bool interactive)
{
if (_interactive != interactive) {
_interactive = interactive;
emit interactiveChanged(interactive);
}
}
QGeoCoordinate QGCMapPolyline::vertexCoordinate(int vertex) const
{
if (vertex >= 0 && vertex < _polylinePath.count()) {
return _polylinePath[vertex].value<QGeoCoordinate>();
} else {
qWarning() << "QGCMapPolyline::vertexCoordinate bad vertex requested";
return QGeoCoordinate();
}
}
QList<QPointF> QGCMapPolyline::nedPolyline(void)
{
QList<QPointF> nedPolyline;
if (count() > 0) {
QGeoCoordinate tangentOrigin = vertexCoordinate(0);
for (int i=0; i<_polylinePath.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);
}
nedPolyline += QPointF(x, y);
}
}
return nedPolyline;
}
QList<QGeoCoordinate> QGCMapPolyline::offsetPolyline(double distance)
{
QList<QGeoCoordinate> rgNewPolyline;
// I'm sure there is some beautiful famous algorithm to do this, but here is a brute force method
if (count() > 1) {
// Convert the polygon to NED
QList<QPointF> rgNedVertices = nedPolyline();
// Walk the edges, offsetting by the specified distance
QList<QLineF> rgOffsetEdges;
for (int i=0; i<rgNedVertices.count() - 1; i++) {
QLineF offsetEdge;
QLineF originalEdge(rgNedVertices[i], rgNedVertices[i + 1]);
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);
}
QGeoCoordinate tangentOrigin = vertexCoordinate(0);
// Add first vertex
QGeoCoordinate coord;
convertNedToGeo(rgOffsetEdges[0].p1().y(), rgOffsetEdges[0].p1().x(), 0, tangentOrigin, &coord);
rgNewPolyline.append(coord);
// Intersect the offset edges to generate new central vertices
QPointF newVertex;
for (int i=1; i<rgOffsetEdges.count(); i++) {
if (rgOffsetEdges[i - 1].intersect(rgOffsetEdges[i], &newVertex) == QLineF::NoIntersection) {
// Two lines are colinear
newVertex = rgOffsetEdges[i].p2();
}
convertNedToGeo(newVertex.y(), newVertex.x(), 0, tangentOrigin, &coord);
rgNewPolyline.append(coord);
}
// Add last vertex
int lastIndex = rgOffsetEdges.count() - 1;
convertNedToGeo(rgOffsetEdges[lastIndex].p2().y(), rgOffsetEdges[lastIndex].p2().x(), 0, tangentOrigin, &coord);
rgNewPolyline.append(coord);
}
return rgNewPolyline;
}
bool QGCMapPolyline::loadKMLFile(const QString& kmlFile)
{
QString errorString;
QList<QGeoCoordinate> rgCoords;
if (!KMLFileHelper::loadPolylineFromFile(kmlFile, rgCoords, errorString)) {
qgcApp()->showMessage(errorString);
return false;
}
clear();
appendVertices(rgCoords);
return true;
}
void QGCMapPolyline::_polylineModelDirtyChanged(bool dirty)
{
if (dirty) {
setDirty(true);
}
}
void QGCMapPolyline::_polylineModelCountChanged(int count)
{
emit countChanged(count);
}
double QGCMapPolyline::length(void) const
{
double length = 0;
for (int i=0; i<_polylinePath.count() - 1; i++) {
QGeoCoordinate from = _polylinePath[i].value<QGeoCoordinate>();
QGeoCoordinate to = _polylinePath[i+1].value<QGeoCoordinate>();
length += from.distanceTo(to);
}
return length;
}
void QGCMapPolyline::appendVertices(const QList<QGeoCoordinate>& coordinates)
{
QList<QObject*> objects;
foreach (const QGeoCoordinate& coordinate, coordinates) {
objects.append(new QGCQGeoCoordinate(coordinate, this));
_polylinePath.append(QVariant::fromValue(coordinate));
}
_polylineModel.append(objects);
emit pathChanged();
}