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