QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCMapPolyline.cc
Go to the documentation of this file.
1#include "QGCMapPolyline.h"
2#include "QGCGeo.h"
3#include "GeoJsonHelper.h"
4#include "JsonParsing.h"
5#include "QGCQGeoCoordinate.h"
6#include "AppMessages.h"
7#include "QGCApplication.h"
9#include "ShapeFileHelper.h"
10
11#include <QtCore/QLineF>
12#include <QMetaMethod>
13
14QGC_LOGGING_CATEGORY(QGCMapPolylineLog, "QMLControls.QGCMapPolyline")
15
17 : QObject (parent)
18 , _dirty (false)
19 , _interactive (false)
20{
21 _init();
22}
23
24QGCMapPolyline::QGCMapPolyline(const QGCMapPolyline& other, QObject* parent)
25 : QObject (parent)
26 , _dirty (false)
27 , _interactive (false)
28{
29 *this = other;
30
31 _init();
32}
33
35{
36 qgcApp()->removeCompressedSignal(QMetaMethod::fromSignal(&QGCMapPolyline::pathChanged));
37}
38
40{
41 clear();
42
43 QVariantList vertices = other.path();
44 for (int i=0; i<vertices.count(); i++) {
45 appendVertex(vertices[i].value<QGeoCoordinate>());
46 }
47
48 setDirty(true);
49
50 return *this;
51}
52
53void QGCMapPolyline::_init(void)
54{
55 connect(&_polylineModel, &QmlObjectListModel::dirtyChanged, this, &QGCMapPolyline::_polylineModelDirtyChanged);
56 connect(&_polylineModel, &QmlObjectListModel::countChanged, this, &QGCMapPolyline::_polylineModelCountChanged);
57 connect(&_polylineModel, &QmlObjectListModel::modelReset, this, &QGCMapPolyline::pathChanged);
58
61
62 qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&QGCMapPolyline::pathChanged));
63}
64
66{
67 _polylinePath.clear();
68 emit pathChanged();
69
70 _polylineModel.clearAndDeleteContents();
71
72 emit cleared();
73
74 setDirty(true);
75}
76
77void QGCMapPolyline::adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
78{
79 _polylinePath[vertexIndex] = QVariant::fromValue(coordinate);
80 _polylineModel.value<QGCQGeoCoordinate*>(vertexIndex)->setCoordinate(coordinate);
81 if (!_deferredPathChanged) {
82 _deferredPathChanged = true;
83 if (_vertexDrag) {
84 // During vertex drag only emit the lightweight visual signal
85 QTimer::singleShot(0, this, [this]() {
86 emit dragPathChanged();
87 _deferredPathChanged = false;
88 });
89 } else {
90 QTimer::singleShot(0, this, [this]() {
91 emit pathChanged();
92 _deferredPathChanged = false;
93 });
94 }
95 }
96 setDirty(true);
97}
98
100{
101 if (_dirty != dirty) {
102 _dirty = dirty;
103 if (!dirty) {
104 _polylineModel.setDirty(false);
105 }
106 emit dirtyChanged(dirty);
107 }
108}
109QGeoCoordinate QGCMapPolyline::_coordFromPointF(const QPointF& point) const
110{
111 QGeoCoordinate coord;
112
113 if (_polylinePath.count() > 0) {
114 QGeoCoordinate tangentOrigin = _polylinePath[0].value<QGeoCoordinate>();
115 QGCGeo::convertNedToGeo(-point.y(), point.x(), 0, tangentOrigin, coord);
116 }
117
118 return coord;
119}
120
121QPointF QGCMapPolyline::_pointFFromCoord(const QGeoCoordinate& coordinate) const
122{
123 if (_polylinePath.count() > 0) {
124 double y, x, down;
125 QGeoCoordinate tangentOrigin = _polylinePath[0].value<QGeoCoordinate>();
126
127 QGCGeo::convertGeoToNed(coordinate, tangentOrigin, y, x, down);
128 return QPointF(x, -y);
129 }
130
131 return QPointF();
132}
133
134void QGCMapPolyline::setPath(const QList<QGeoCoordinate>& path)
135{
136 beginReset();
137
138 _polylinePath.clear();
139 _polylineModel.clearAndDeleteContents();
140 for (const QGeoCoordinate& coord: path) {
141 _polylinePath.append(QVariant::fromValue(coord));
142 _polylineModel.append(new QGCQGeoCoordinate(coord, this));
143 }
144
145 setDirty(true);
146
147 endReset();
148}
149
150void QGCMapPolyline::setPath(const QVariantList& path)
151{
152 beginReset();
153
154 _polylinePath = path;
155 _polylineModel.clearAndDeleteContents();
156 for (int i=0; i<_polylinePath.count(); i++) {
157 _polylineModel.append(new QGCQGeoCoordinate(_polylinePath[i].value<QGeoCoordinate>(), this));
158 }
159 setDirty(true);
160
161 endReset();
162}
163
164
165void QGCMapPolyline::saveToJson(QJsonObject& json)
166{
167 QJsonValue jsonValue;
168
169 GeoJsonHelper::saveGeoCoordinateArray(_polylinePath, false /* writeAltitude*/, jsonValue);
170 json.insert(jsonPolylineKey, jsonValue);
171 setDirty(false);
172}
173
174bool QGCMapPolyline::loadFromJson(const QJsonObject& json, bool required, QString& errorString)
175{
176 errorString.clear();
177 clear();
178
179 if (required) {
181 return false;
182 }
183 } else if (!json.contains(jsonPolylineKey)) {
184 return true;
185 }
186
187 if (!GeoJsonHelper::loadGeoCoordinateArray(json[jsonPolylineKey], false /* altitudeRequired */, _polylinePath, errorString)) {
188 return false;
189 }
190
191 for (int i=0; i<_polylinePath.count(); i++) {
192 _polylineModel.append(new QGCQGeoCoordinate(_polylinePath[i].value<QGeoCoordinate>(), this));
193 }
194
195 setDirty(false);
196 emit pathChanged();
197
198 return true;
199}
200
201QList<QGeoCoordinate> QGCMapPolyline::coordinateList(void) const
202{
203 QList<QGeoCoordinate> coords;
204
205 for (int i=0; i<_polylinePath.count(); i++) {
206 coords.append(_polylinePath[i].value<QGeoCoordinate>());
207 }
208
209 return coords;
210}
211
212void QGCMapPolyline::splitSegment(int vertexIndex)
213{
214 int nextIndex = vertexIndex + 1;
215 if (nextIndex > _polylinePath.length() - 1) {
216 return;
217 }
218
219 QGeoCoordinate firstVertex = _polylinePath[vertexIndex].value<QGeoCoordinate>();
220 QGeoCoordinate nextVertex = _polylinePath[nextIndex].value<QGeoCoordinate>();
221
222 double distance = firstVertex.distanceTo(nextVertex);
223 double azimuth = firstVertex.azimuthTo(nextVertex);
224 QGeoCoordinate newVertex = firstVertex.atDistanceAndAzimuth(distance / 2, azimuth);
225
226 if (nextIndex == 0) {
227 appendVertex(newVertex);
228 } else {
229 _polylineModel.insert(nextIndex, new QGCQGeoCoordinate(newVertex, this));
230 _polylinePath.insert(nextIndex, QVariant::fromValue(newVertex));
231 emit pathChanged();
232 }
233}
234
235void QGCMapPolyline::appendVertex(const QGeoCoordinate& coordinate)
236{
237 _polylinePath.append(QVariant::fromValue(coordinate));
238 _polylineModel.append(new QGCQGeoCoordinate(coordinate, this));
239 emit pathChanged();
240}
241
242void QGCMapPolyline::removeVertex(int vertexIndex)
243{
244 if (vertexIndex < 0 || vertexIndex > _polylinePath.length() - 1) {
245 qCWarning(QGCMapPolylineLog) << "Call to removeVertex with bad vertexIndex:count" << vertexIndex << _polylinePath.length();
246 return;
247 }
248
249 if (_polylinePath.length() <= 2) {
250 // Don't allow the user to trash the polyline
251 return;
252 }
253
254 QObject* coordObj = _polylineModel.removeAt(vertexIndex);
255 coordObj->deleteLater();
256 if(vertexIndex == _selectedVertexIndex) {
257 selectVertex(-1);
258 } else if (vertexIndex < _selectedVertexIndex) {
259 selectVertex(_selectedVertexIndex - 1);
260 } // else do nothing - keep current selected vertex
261
262 _polylinePath.removeAt(vertexIndex);
263 emit pathChanged();
264}
265
266void QGCMapPolyline::setInteractive(bool interactive)
267{
268 if (_interactive != interactive) {
269 _interactive = interactive;
271 }
272}
273
274void QGCMapPolyline::setVertexDrag(bool vertexDrag)
275{
276 if (_vertexDrag != vertexDrag) {
277 _vertexDrag = vertexDrag;
278 if (!vertexDrag) {
279 // Drag ended - signal path changed so downstream can recalculate
280 emit pathChanged();
281 }
283 }
284}
285
286QGeoCoordinate QGCMapPolyline::vertexCoordinate(int vertex) const
287{
288 if (vertex >= 0 && vertex < _polylinePath.count()) {
289 return _polylinePath[vertex].value<QGeoCoordinate>();
290 } else {
291 qCWarning(QGCMapPolylineLog) << "QGCMapPolyline::vertexCoordinate bad vertex requested";
292 return QGeoCoordinate();
293 }
294}
295
296QList<QPointF> QGCMapPolyline::nedPolyline(void)
297{
298 QList<QPointF> nedPolyline;
299
300 if (count() > 0) {
301 QGeoCoordinate tangentOrigin = vertexCoordinate(0);
302
303 for (int i=0; i<_polylinePath.count(); i++) {
304 double y, x, down;
305 QGeoCoordinate vertex = vertexCoordinate(i);
306 if (i == 0) {
307 // This avoids a nan calculation that comes out of convertGeoToNed
308 x = y = 0;
309 } else {
310 QGCGeo::convertGeoToNed(vertex, tangentOrigin, y, x, down);
311 }
312 nedPolyline += QPointF(x, y);
313 }
314 }
315
316 return nedPolyline;
317}
318
319QList<QGeoCoordinate> QGCMapPolyline::offsetPolyline(double distance)
320{
321 QList<QGeoCoordinate> rgNewPolyline;
322
323 // I'm sure there is some beautiful famous algorithm to do this, but here is a brute force method
324
325 if (count() > 1) {
326 // Convert the polygon to NED
327 QList<QPointF> rgNedVertices = nedPolyline();
328
329 // Walk the edges, offsetting by the specified distance
330 QList<QLineF> rgOffsetEdges;
331 for (int i=0; i<rgNedVertices.count() - 1; i++) {
332 QLineF offsetEdge;
333 QLineF originalEdge(rgNedVertices[i], rgNedVertices[i + 1]);
334
335 QLineF workerLine = originalEdge;
336 workerLine.setLength(distance);
337 workerLine.setAngle(workerLine.angle() - 90.0);
338 offsetEdge.setP1(workerLine.p2());
339
340 workerLine.setPoints(originalEdge.p2(), originalEdge.p1());
341 workerLine.setLength(distance);
342 workerLine.setAngle(workerLine.angle() + 90.0);
343 offsetEdge.setP2(workerLine.p2());
344
345 rgOffsetEdges.append(offsetEdge);
346 }
347
348 QGeoCoordinate tangentOrigin = vertexCoordinate(0);
349
350 // Add first vertex
351 QGeoCoordinate coord;
352 QGCGeo::convertNedToGeo(rgOffsetEdges[0].p1().y(), rgOffsetEdges[0].p1().x(), 0, tangentOrigin, coord);
353 rgNewPolyline.append(coord);
354
355 // Intersect the offset edges to generate new central vertices
356 QPointF newVertex;
357 for (int i=1; i<rgOffsetEdges.count(); i++) {
358 auto intersect = rgOffsetEdges[i - 1].intersects(rgOffsetEdges[i], &newVertex);
359 if (intersect == QLineF::NoIntersection) {
360 // Two lines are colinear
361 newVertex = rgOffsetEdges[i].p2();
362 }
363 QGCGeo::convertNedToGeo(newVertex.y(), newVertex.x(), 0, tangentOrigin, coord);
364 rgNewPolyline.append(coord);
365 }
366
367 // Add last vertex
368 int lastIndex = rgOffsetEdges.count() - 1;
369 QGCGeo::convertNedToGeo(rgOffsetEdges[lastIndex].p2().y(), rgOffsetEdges[lastIndex].p2().x(), 0, tangentOrigin, coord);
370 rgNewPolyline.append(coord);
371 }
372
373 return rgNewPolyline;
374}
375
376bool QGCMapPolyline::loadKMLOrSHPFile(const QString &file)
377{
378 QString errorString;
379 QList<QList<QGeoCoordinate>> polylines;
382 return false;
383 }
384 if (polylines.isEmpty()) {
385 QGC::showAppMessage(tr("No polylines found in file"));
386 return false;
387 }
388 const QList<QGeoCoordinate>& rgCoords = polylines.first();
389
390 beginReset();
391 clear();
392 appendVertices(rgCoords);
393 endReset();
394
395 return true;
396}
397
398void QGCMapPolyline::_polylineModelDirtyChanged(bool dirty)
399{
400 if (dirty) {
401 setDirty(true);
402 }
403}
404
405void QGCMapPolyline::_polylineModelCountChanged(int count)
406{
407 emit countChanged(count);
408}
409
410
411double QGCMapPolyline::length(void) const
412{
413 double length = 0;
414
415 for (int i=0; i<_polylinePath.count() - 1; i++) {
416 QGeoCoordinate from = _polylinePath[i].value<QGeoCoordinate>();
417 QGeoCoordinate to = _polylinePath[i+1].value<QGeoCoordinate>();
418 length += from.distanceTo(to);
419 }
420
421 return length;
422}
423
424void QGCMapPolyline::appendVertices(const QList<QGeoCoordinate>& coordinates)
425{
426 beginReset();
427
428 QList<QObject*> objects;
429 for (const QGeoCoordinate& coordinate: coordinates) {
430 objects.append(new QGCQGeoCoordinate(coordinate, this));
431 _polylinePath.append(QVariant::fromValue(coordinate));
432 }
433 _polylineModel.append(objects);
434
435 endReset();
436
437 emit pathChanged();
438}
439
441{
442 _polylineModel.beginResetModel();
443}
444
446{
447 _polylineModel.endResetModel();
448}
449
451{
452 if (traceMode != _traceMode) {
453 _traceMode = traceMode;
455 }
456}
457
459{
460 if(index == _selectedVertexIndex) return; // do nothing
461
462 if(-1 <= index && index < count()) {
463 _selectedVertexIndex = index;
464 } else {
465 qCWarning(QGCMapPolylineLog) << QStringLiteral("QGCMapPolyline: Selected vertex index (%1) is out of bounds! "
466 "Polyline vertices indexes range is [%2..%3].").arg(index).arg(0).arg(count()-1);
467 _selectedVertexIndex = -1; // deselect vertex
468 }
469
470 emit selectedVertexChanged(_selectedVertexIndex);
471}
#define qgcApp()
QString errorString
Geographic coordinate conversion utilities using GeographicLib.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void endResetModel()
Depth-counted endResetModel — only the outermost call has effect.
void beginResetModel()
Depth-counted beginResetModel — only the outermost call has effect.
void dirtyChanged(bool dirty)
void countChanged(int count)
QModelIndex index(int row, int column=0, const QModelIndex &parent=QModelIndex()) const override
Q_INVOKABLE void beginReset(void)
QList< QGeoCoordinate > offsetPolyline(double distance)
void dirtyChanged(bool dirty)
static constexpr const char * jsonPolylineKey
QList< QPointF > nedPolyline(void)
Convert polyline to NED and return (D is ignored)
Q_INVOKABLE void clear(void)
Q_INVOKABLE bool loadKMLOrSHPFile(const QString &file)
void cleared(void)
Q_INVOKABLE void splitSegment(int vertexIndex)
Splits the line segment comprised of vertexIndex -> vertexIndex + 1.
void pathChanged(void)
Q_INVOKABLE void removeVertex(int vertexIndex)
void interactiveChanged(bool interactive)
void traceModeChanged(bool traceMode)
Q_INVOKABLE void adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
void dragPathChanged(void)
void setTraceMode(bool traceMode)
void isEmptyChanged(void)
bool dirty(void) const
Q_INVOKABLE QGeoCoordinate vertexCoordinate(int vertex) const
Returns the QGeoCoordinate for the vertex specified.
void selectVertex(int index)
void setPath(const QList< QGeoCoordinate > &path)
void setDirty(bool dirty)
bool interactive(void) const
Q_INVOKABLE void appendVertex(const QGeoCoordinate &coordinate)
int count(void) const
~QGCMapPolyline() override
void vertexDragChanged(bool vertexDrag)
bool vertexDrag(void) const
void saveToJson(QJsonObject &json)
QList< QGeoCoordinate > coordinateList(void) const
Returns the path in a list of QGeoCoordinate's format.
const QGCMapPolyline & operator=(const QGCMapPolyline &other)
Q_INVOKABLE void endReset(void)
Q_INVOKABLE void appendVertices(const QList< QGeoCoordinate > &coordinates)
QVariantList path(void) const
void isValidChanged(void)
bool loadFromJson(const QJsonObject &json, bool required, QString &errorString)
QGCMapPolyline(QObject *parent=nullptr)
double length(void) const
Returns the length of the polyline in meters.
void countChanged(int count)
void setVertexDrag(bool vertexDrag)
bool traceMode(void) const
void selectedVertexChanged(int index)
void setInteractive(bool interactive)
This is a QGeoCoordinate within a QObject such that it can be used on a QmlObjectListModel.
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
T value(int index) const
void setDirty(bool dirty) override final
QObject * removeAt(int index)
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
void insert(int index, QObject *object)
static bool loadPolylinesFromFile(const QString &file, QList< QList< QGeoCoordinate > > &polylines, QString &errorString, double filterMeters=kDefaultVertexFilterMeters)
bool loadGeoCoordinateArray(const QJsonValue &jsonValue, bool altitudeRequired, QVariantList &rgVarPoints, QString &errorString)
Loads a list of QGeoCoordinates (QGC plan format) from a json array.
void saveGeoCoordinateArray(const QVariantList &rgVarPoints, bool writeAltitude, QJsonValue &jsonValue)
Saves a list of QGeoCoordinates (QGC plan format) to a json array.
bool validateRequiredKeys(const QJsonObject &jsonObject, const QStringList &keys, QString &errorString)
Validates that all listed keys are present in the object.
void convertGeoToNed(const QGeoCoordinate &coord, const QGeoCoordinate &origin, double &x, double &y, double &z)
Definition QGCGeo.cc:34
void convertNedToGeo(double x, double y, double z, const QGeoCoordinate &origin, QGeoCoordinate &coord)
Definition QGCGeo.cc:56
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9