434 lines
12 KiB
C++
434 lines
12 KiB
C++
/****************************************************************************
|
|
*
|
|
* (c) 2009-2020 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)
|
|
, _resetActive (false)
|
|
{
|
|
_init();
|
|
}
|
|
|
|
QGCMapPolyline::QGCMapPolyline(const QGCMapPolyline& other, QObject* parent)
|
|
: QObject (parent)
|
|
, _dirty (false)
|
|
, _interactive (false)
|
|
, _resetActive (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);
|
|
|
|
connect(this, &QGCMapPolyline::countChanged, this, &QGCMapPolyline::isValidChanged);
|
|
connect(this, &QGCMapPolyline::countChanged, this, &QGCMapPolyline::isEmptyChanged);
|
|
}
|
|
|
|
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)
|
|
{
|
|
_beginResetIfNotActive();
|
|
|
|
_polylinePath.clear();
|
|
_polylineModel.clearAndDeleteContents();
|
|
for (const QGeoCoordinate& coord: path) {
|
|
_polylinePath.append(QVariant::fromValue(coord));
|
|
_polylineModel.append(new QGCQGeoCoordinate(coord, this));
|
|
}
|
|
|
|
setDirty(true);
|
|
|
|
_endResetIfNotActive();
|
|
}
|
|
|
|
void QGCMapPolyline::setPath(const QVariantList& path)
|
|
{
|
|
_beginResetIfNotActive();
|
|
|
|
_polylinePath = path;
|
|
_polylineModel.clearAndDeleteContents();
|
|
for (int i=0; i<_polylinePath.count(); i++) {
|
|
_polylineModel.append(new QGCQGeoCoordinate(_polylinePath[i].value<QGeoCoordinate>(), this));
|
|
}
|
|
setDirty(true);
|
|
|
|
_endResetIfNotActive();
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
_beginResetIfNotActive();
|
|
|
|
QString errorString;
|
|
QList<QGeoCoordinate> rgCoords;
|
|
if (!KMLFileHelper::loadPolylineFromFile(kmlFile, rgCoords, errorString)) {
|
|
qgcApp()->showMessage(errorString);
|
|
return false;
|
|
}
|
|
|
|
clear();
|
|
appendVertices(rgCoords);
|
|
|
|
_endResetIfNotActive();
|
|
|
|
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)
|
|
{
|
|
_beginResetIfNotActive();
|
|
|
|
QList<QObject*> objects;
|
|
for (const QGeoCoordinate& coordinate: coordinates) {
|
|
objects.append(new QGCQGeoCoordinate(coordinate, this));
|
|
_polylinePath.append(QVariant::fromValue(coordinate));
|
|
}
|
|
_polylineModel.append(objects);
|
|
|
|
_endResetIfNotActive();
|
|
}
|
|
|
|
void QGCMapPolyline::beginReset(void)
|
|
{
|
|
_resetActive = true;
|
|
_polylineModel.beginReset();
|
|
}
|
|
|
|
void QGCMapPolyline::endReset(void)
|
|
{
|
|
_resetActive = false;
|
|
_polylineModel.endReset();
|
|
emit pathChanged();
|
|
}
|
|
|
|
void QGCMapPolyline::_beginResetIfNotActive(void)
|
|
{
|
|
if (!_resetActive) {
|
|
beginReset();
|
|
}
|
|
}
|
|
|
|
void QGCMapPolyline::_endResetIfNotActive(void)
|
|
{
|
|
if (!_resetActive) {
|
|
endReset();
|
|
}
|
|
}
|