QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCMapPolygon.cc
Go to the documentation of this file.
1#include "QGCMapPolygon.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#include "KMLDomDocument.h"
11
12#include <QtCore/QLineF>
13#include <QMetaMethod>
14
15QGC_LOGGING_CATEGORY(QGCMapPolygonLog, "QMLControls.QGCMapPolygon")
16
18 : QObject (parent)
19 , _dirty (false)
20 , _centerDrag (false)
21 , _ignoreCenterUpdates (false)
22 , _interactive (false)
23{
24 _init();
25}
26
27QGCMapPolygon::QGCMapPolygon(const QGCMapPolygon& other, QObject* parent)
28 : QObject (parent)
29 , _dirty (false)
30 , _centerDrag (false)
31 , _ignoreCenterUpdates (false)
32 , _interactive (false)
33{
34 *this = other;
35
36 _init();
37}
38
40{
41 qgcApp()->removeCompressedSignal(QMetaMethod::fromSignal(&QGCMapPolygon::pathChanged));
42}
43
44void QGCMapPolygon::_init(void)
45{
46 connect(&_polygonModel, &QmlObjectListModel::dirtyChanged, this, &QGCMapPolygon::_polygonModelDirtyChanged);
47 connect(&_polygonModel, &QmlObjectListModel::countChanged, this, &QGCMapPolygon::_polygonModelCountChanged);
48 connect(&_polygonModel, &QmlObjectListModel::modelReset, this, [this]() {
49 emit pathChanged();
50 emit centerChanged(_center);
51 });
52
53 connect(this, &QGCMapPolygon::pathChanged, this, &QGCMapPolygon::_updateCenter);
56
57}
58
60{
61 clear();
62
63 QVariantList vertices = other.path();
64 QList<QGeoCoordinate> rgCoord;
65 for (const QVariant& vertexVar: vertices) {
66 rgCoord.append(vertexVar.value<QGeoCoordinate>());
67 }
68 appendVertices(rgCoord);
69
70 setDirty(true);
71
72 return *this;
73}
74
76{
77 // Bug workaround, see below
78 while (_polygonPath.count() > 1) {
79 _polygonPath.takeLast();
80 }
81 if (_vertexDrag) {
82 emit dragPathChanged();
83 } else {
84 emit pathChanged();
85 }
86
87 // Although this code should remove the polygon from the map it doesn't. There appears
88 // to be a bug in QGCMapPolygon which causes it to not be redrawn if the list is empty. So
89 // we work around it by using the code above to remove all but the last point which in turn
90 // will cause the polygon to go away.
91 _polygonPath.clear();
92
93 _polygonModel.clearAndDeleteContents();
94
95 emit cleared();
96
97 setDirty(true);
98}
99
100void QGCMapPolygon::adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
101{
102 _polygonPath[vertexIndex] = QVariant::fromValue(coordinate);
103 _polygonModel.value<QGCQGeoCoordinate*>(vertexIndex)->setCoordinate(coordinate);
104 if (!_centerDrag) {
105 if (!_deferredPathChanged) {
106 _deferredPathChanged = true;
107 if (_vertexDrag) {
108 // During vertex drag only emit the lightweight visual signal
109 QTimer::singleShot(0, this, [this]() {
110 emit dragPathChanged();
111 _deferredPathChanged = false;
112 });
113 } else {
114 QTimer::singleShot(0, this, [this]() {
115 emit pathChanged();
116 _deferredPathChanged = false;
117 });
118 }
119 }
120 }
121 setDirty(true);
122}
123
125{
126 if (_dirty != dirty) {
127 _dirty = dirty;
128 if (!dirty) {
129 _polygonModel.setDirty(false);
130 }
131 emit dirtyChanged(dirty);
132 }
133}
134
135QGeoCoordinate QGCMapPolygon::_coordFromPointF(const QPointF& point) const
136{
137 QGeoCoordinate coord;
138
139 if (_polygonPath.count() > 0) {
140 QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
141 QGCGeo::convertNedToGeo(-point.y(), point.x(), 0, tangentOrigin, coord);
142 }
143
144 return coord;
145}
146
147QPointF QGCMapPolygon::_pointFFromCoord(const QGeoCoordinate& coordinate) const
148{
149 if (_polygonPath.count() > 0) {
150 double y, x, down;
151 QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
152
153 QGCGeo::convertGeoToNed(coordinate, tangentOrigin, y, x, down);
154 return QPointF(x, -y);
155 }
156
157 return QPointF();
158}
159
160QPolygonF QGCMapPolygon::_toPolygonF(void) const
161{
162 QPolygonF polygon;
163
164 if (_polygonPath.count() > 2) {
165 for (int i=0; i<_polygonPath.count(); i++) {
166 polygon.append(_pointFFromCoord(_polygonPath[i].value<QGeoCoordinate>()));
167 }
168 }
169
170 return polygon;
171}
172
173bool QGCMapPolygon::containsCoordinate(const QGeoCoordinate& coordinate) const
174{
175 if (_polygonPath.count() > 2) {
176 return _toPolygonF().containsPoint(_pointFFromCoord(coordinate), Qt::OddEvenFill);
177 } else {
178 return false;
179 }
180}
181
182void QGCMapPolygon::setPath(const QList<QGeoCoordinate>& path)
183{
184 _polygonPath.clear();
185 _polygonModel.clearAndDeleteContents();
186 for(const QGeoCoordinate& coord: path) {
187 _polygonPath.append(QVariant::fromValue(coord));
188 _polygonModel.append(new QGCQGeoCoordinate(coord, this));
189 }
190
191 setDirty(true);
192 emit pathChanged();
193}
194
195void QGCMapPolygon::setPath(const QVariantList& path)
196{
197 _polygonPath = path;
198
199 _polygonModel.clearAndDeleteContents();
200 for (int i=0; i<_polygonPath.count(); i++) {
201 _polygonModel.append(new QGCQGeoCoordinate(_polygonPath[i].value<QGeoCoordinate>(), this));
202 }
203
204 setDirty(true);
205 emit pathChanged();
206}
207
208void QGCMapPolygon::saveToJson(QJsonObject& json)
209{
210 QJsonValue jsonValue;
211
212 GeoJsonHelper::saveGeoCoordinateArray(_polygonPath, false /* writeAltitude*/, jsonValue);
213 json.insert(jsonPolygonKey, jsonValue);
214 setDirty(false);
215}
216
217bool QGCMapPolygon::loadFromJson(const QJsonObject& json, bool required, QString& errorString)
218{
219 errorString.clear();
220 clear();
221
222 if (required) {
224 return false;
225 }
226 } else if (!json.contains(jsonPolygonKey)) {
227 return true;
228 }
229
230 if (!GeoJsonHelper::loadGeoCoordinateArray(json[jsonPolygonKey], false /* altitudeRequired */, _polygonPath, errorString)) {
231 return false;
232 }
233
234 for (int i=0; i<_polygonPath.count(); i++) {
235 _polygonModel.append(new QGCQGeoCoordinate(_polygonPath[i].value<QGeoCoordinate>(), this));
236 }
237
238 setDirty(false);
239 emit pathChanged();
240
241 return true;
242}
243
244QList<QGeoCoordinate> QGCMapPolygon::coordinateList(void) const
245{
246 QList<QGeoCoordinate> coords;
247
248 for (int i=0; i<_polygonPath.count(); i++) {
249 coords.append(_polygonPath[i].value<QGeoCoordinate>());
250 }
251
252 return coords;
253}
254
256{
257 int nextIndex = vertexIndex + 1;
258 if (nextIndex > _polygonPath.length() - 1) {
259 nextIndex = 0;
260 }
261
262 QGeoCoordinate firstVertex = _polygonPath[vertexIndex].value<QGeoCoordinate>();
263 QGeoCoordinate nextVertex = _polygonPath[nextIndex].value<QGeoCoordinate>();
264
265 double distance = firstVertex.distanceTo(nextVertex);
266 double azimuth = firstVertex.azimuthTo(nextVertex);
267 QGeoCoordinate newVertex = firstVertex.atDistanceAndAzimuth(distance / 2, azimuth);
268
269 if (nextIndex == 0) {
270 appendVertex(newVertex);
271 } else {
272 _polygonModel.insert(nextIndex, new QGCQGeoCoordinate(newVertex, this));
273 _polygonPath.insert(nextIndex, QVariant::fromValue(newVertex));
274 emit pathChanged();
275 if (0 <= _selectedVertexIndex && vertexIndex < _selectedVertexIndex) {
276 selectVertex(_selectedVertexIndex+1);
277 }
278 }
279}
280
281void QGCMapPolygon::appendVertex(const QGeoCoordinate& coordinate)
282{
283 _polygonPath.append(QVariant::fromValue(coordinate));
284 _polygonModel.append(new QGCQGeoCoordinate(coordinate, this));
285 if (!_deferredPathChanged) {
286 // Only update the path once per event loop, to prevent lag-spikes
287 _deferredPathChanged = true;
288 QTimer::singleShot(0, this, [this]() {
289 if (_vertexDrag) {
290 emit dragPathChanged();
291 } else {
292 emit pathChanged();
293 }
294 _deferredPathChanged = false;
295 });
296 }
297}
298
299void QGCMapPolygon::appendVertices(const QList<QGeoCoordinate>& coordinates)
300{
301 QList<QObject*> objects;
302
303 beginReset();
304 for (const QGeoCoordinate& coordinate: coordinates) {
305 objects.append(new QGCQGeoCoordinate(coordinate, this));
306 _polygonPath.append(QVariant::fromValue(coordinate));
307 }
308 _polygonModel.append(objects);
309 endReset();
310
311 if (_vertexDrag) {
312 emit dragPathChanged();
313 } else {
314 emit pathChanged();
315 }
316}
317
318void QGCMapPolygon::appendVertices(const QVariantList& varCoords)
319{
320 QList<QGeoCoordinate> rgCoords;
321 for (const QVariant& varCoord: varCoords) {
322 rgCoords.append(varCoord.value<QGeoCoordinate>());
323 }
324 appendVertices(rgCoords);
325}
326
327void QGCMapPolygon::_polygonModelDirtyChanged(bool dirty)
328{
329 if (dirty) {
330 setDirty(true);
331 }
332}
333
334void QGCMapPolygon::removeVertex(int vertexIndex)
335{
336 if (vertexIndex < 0 || vertexIndex >= _polygonPath.length()) {
337 qCWarning(QGCMapPolygonLog) << "Call to removePolygonCoordinate with bad vertexIndex:count" << vertexIndex << _polygonPath.length();
338 return;
339 }
340
341 if (_polygonPath.length() <= 3) {
342 // Don't allow the user to trash the polygon
343 return;
344 }
345
346 QObject* coordObj = _polygonModel.removeAt(vertexIndex);
347 coordObj->deleteLater();
348 if(vertexIndex == _selectedVertexIndex) {
349 selectVertex(-1);
350 } else if (vertexIndex < _selectedVertexIndex) {
351 selectVertex(_selectedVertexIndex - 1);
352 } // else do nothing - keep current selected vertex
353
354 _polygonPath.removeAt(vertexIndex);
355 emit pathChanged();
356}
357
358void QGCMapPolygon::_polygonModelCountChanged(int count)
359{
360 emit countChanged(count);
361}
362
363void QGCMapPolygon::_updateCenter(void)
364{
365 if (!_ignoreCenterUpdates) {
366 QGeoCoordinate center;
367
368 if (_polygonPath.count() > 2) {
369 QPolygonF polygonF = _toPolygonF();
370 const int n = polygonF.count();
371
372 // Surveyor's (shoelace) formula for polygon centroid
373 double signedArea = 0;
374 double cx = 0;
375 double cy = 0;
376
377 for (int i = 0; i < n; i++) {
378 const int j = (i + 1) % n;
379 const double cross = polygonF[i].x() * polygonF[j].y() - polygonF[j].x() * polygonF[i].y();
380 signedArea += cross;
381 cx += (polygonF[i].x() + polygonF[j].x()) * cross;
382 cy += (polygonF[i].y() + polygonF[j].y()) * cross;
383 }
384
385 if (qAbs(signedArea) < 1e-6) {
386 // Degenerate or near-degenerate polygon (area < 0.5e-6 m²) — fall back to vertex average
387 QPointF avg(0, 0);
388 for (int i = 0; i < n; i++) {
389 avg += polygonF[i];
390 }
391 center = _coordFromPointF(QPointF(avg.x() / n, avg.y() / n));
392 } else {
393 signedArea *= 0.5;
394 cx /= (6.0 * signedArea);
395 cy /= (6.0 * signedArea);
396 center = _coordFromPointF(QPointF(cx, cy));
397 }
398 }
399 if (_center != center) {
400 _center = center;
401 emit centerChanged(center);
402 }
403 }
404}
405
406void QGCMapPolygon::setCenter(QGeoCoordinate newCenter)
407{
408 if (newCenter != _center) {
409 _ignoreCenterUpdates = true;
410
411 // Adjust polygon vertices to new center
412 double distance = _center.distanceTo(newCenter);
413 double azimuth = _center.azimuthTo(newCenter);
414
415 for (int i=0; i<count(); i++) {
416 QGeoCoordinate oldVertex = _polygonPath[i].value<QGeoCoordinate>();
417 QGeoCoordinate newVertex = oldVertex.atDistanceAndAzimuth(distance, azimuth);
418 adjustVertex(i, newVertex);
419 }
420
421 _ignoreCenterUpdates = false;
422 _center = newCenter;
423
424 if (_centerDrag) {
425 // During center drag emit lightweight visual signals only
426 if (!_deferredPathChanged) {
427 _deferredPathChanged = true;
428 QTimer::singleShot(0, this, [this]() {
429 emit dragPathChanged();
430 emit dragCenterChanged(_center);
431 _deferredPathChanged = false;
432 });
433 }
434 } else {
435 if (!_deferredPathChanged) {
436 _deferredPathChanged = true;
437 QTimer::singleShot(0, this, [this]() {
438 emit pathChanged();
439 emit centerChanged(_center);
440 _deferredPathChanged = false;
441 });
442 }
443 }
444 }
445}
446
447void QGCMapPolygon::setCenterDrag(bool centerDrag)
448{
449 if (centerDrag != _centerDrag) {
450 _centerDrag = centerDrag;
451 if (!centerDrag) {
452 // Drag ended. Center edits do not use vertexDrag, so emit pathChanged here.
453 // For interactive center drag, setVertexDrag(false) emits pathChanged.
454 if (!_vertexDrag) {
455 emit pathChanged();
456 }
457 emit centerChanged(_center);
458 }
460 }
461}
462
463void QGCMapPolygon::setVertexDrag(bool vertexDrag)
464{
465 if (_vertexDrag != vertexDrag) {
466 _vertexDrag = vertexDrag;
467 if (!vertexDrag) {
468 // Drag ended - signal path changed so downstream can recalculate
469 emit pathChanged();
470 }
472 }
473}
474
475void QGCMapPolygon::setInteractive(bool interactive)
476{
477 if (_interactive != interactive) {
478 _interactive = interactive;
480 }
481}
482
483QGeoCoordinate QGCMapPolygon::vertexCoordinate(int vertex) const
484{
485 if (vertex >= 0 && vertex < _polygonPath.count()) {
486 return _polygonPath[vertex].value<QGeoCoordinate>();
487 } else {
488 qCWarning(QGCMapPolygonLog) << "QGCMapPolygon::vertexCoordinate bad vertex requested:count" << vertex << _polygonPath.count();
489 return QGeoCoordinate();
490 }
491}
492
493QList<QPointF> QGCMapPolygon::nedPolygon(void) const
494{
495 QList<QPointF> nedPolygon;
496
497 if (count() > 0) {
498 QGeoCoordinate tangentOrigin = vertexCoordinate(0);
499
500 for (int i=0; i<_polygonModel.count(); i++) {
501 double y, x, down;
502 QGeoCoordinate vertex = vertexCoordinate(i);
503 if (i == 0) {
504 // This avoids a nan calculation that comes out of convertGeoToNed
505 x = y = 0;
506 } else {
507 QGCGeo::convertGeoToNed(vertex, tangentOrigin, y, x, down);
508 }
509 nedPolygon += QPointF(x, y);
510 }
511 }
512
513 return nedPolygon;
514}
515
516
517void QGCMapPolygon::offset(double distance)
518{
519 QList<QGeoCoordinate> rgNewPolygon;
520
521 // I'm sure there is some beautiful famous algorithm to do this, but here is a brute force method
522
523 if (count() > 2) {
524 // Convert the polygon to NED
525 QList<QPointF> rgNedVertices = nedPolygon();
526
527 // Walk the edges, offsetting by the specified distance
528 QList<QLineF> rgOffsetEdges;
529 for (int i=0; i<rgNedVertices.count(); i++) {
530 int lastIndex = i == rgNedVertices.count() - 1 ? 0 : i + 1;
531 QLineF offsetEdge;
532 QLineF originalEdge(rgNedVertices[i], rgNedVertices[lastIndex]);
533
534 QLineF workerLine = originalEdge;
535 workerLine.setLength(distance);
536 workerLine.setAngle(workerLine.angle() - 90.0);
537 offsetEdge.setP1(workerLine.p2());
538
539 workerLine.setPoints(originalEdge.p2(), originalEdge.p1());
540 workerLine.setLength(distance);
541 workerLine.setAngle(workerLine.angle() + 90.0);
542 offsetEdge.setP2(workerLine.p2());
543
544 rgOffsetEdges.append(offsetEdge);
545 }
546
547 // Intersect the offset edges to generate new vertices
548 QPointF newVertex;
549 QGeoCoordinate tangentOrigin = vertexCoordinate(0);
550 for (int i=0; i<rgOffsetEdges.count(); i++) {
551 int prevIndex = i == 0 ? rgOffsetEdges.count() - 1 : i - 1;
552 auto intersect = rgOffsetEdges[prevIndex].intersects(rgOffsetEdges[i], &newVertex);
553 if (intersect == QLineF::NoIntersection) {
554 // FIXME: Better error handling?
555 qCWarning(QGCMapPolygonLog, "Intersection failed");
556 return;
557 }
558 QGeoCoordinate coord;
559 QGCGeo::convertNedToGeo(newVertex.y(), newVertex.x(), 0, tangentOrigin, coord);
560 rgNewPolygon.append(coord);
561 }
562 }
563
564 // Update internals
565 beginReset();
566 clear();
567 appendVertices(rgNewPolygon);
568 endReset();
569}
570
571bool QGCMapPolygon::loadKMLOrSHPFile(const QString& file)
572{
573 QString errorString;
574 QList<QList<QGeoCoordinate>> polygons;
577 return false;
578 }
579 if (polygons.isEmpty()) {
580 QGC::showAppMessage(tr("No polygons found in file"));
581 return false;
582 }
583 const QList<QGeoCoordinate>& rgCoords = polygons.first();
584
585 beginReset();
586 clear();
587 appendVertices(rgCoords);
588 endReset();
589
590 return true;
591}
592
593double QGCMapPolygon::area(void) const
594{
595 // https://www.mathopenref.com/coordpolygonarea2.html
596
597 if (_polygonPath.count() < 3) {
598 return 0;
599 }
600
601 double coveredArea = 0.0;
602 QList<QPointF> nedVertices = nedPolygon();
603 for (int i=0; i<nedVertices.count(); i++) {
604 if (i != 0) {
605 coveredArea += nedVertices[i - 1].x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices[i -1].y();
606 } else {
607 coveredArea += nedVertices.last().x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices.last().y();
608 }
609 }
610 return 0.5 * fabs(coveredArea);
611}
612
614{
615 if (_polygonPath.count() <= 2) {
616 return;
617 }
618
619 double sum = 0;
620 for (int i=0; i<_polygonPath.count(); i++) {
621 QGeoCoordinate coord1 = _polygonPath[i].value<QGeoCoordinate>();
622 QGeoCoordinate coord2 = (i == _polygonPath.count() - 1) ? _polygonPath[0].value<QGeoCoordinate>() : _polygonPath[i+1].value<QGeoCoordinate>();
623
624 sum += (coord2.longitude() - coord1.longitude()) * (coord2.latitude() + coord1.latitude());
625 }
626
627 if (sum < 0.0) {
628 // Winding is counter-clockwise and needs reversal
629
630 QList<QGeoCoordinate> rgReversed;
631 for (const QVariant& varCoord: _polygonPath) {
632 rgReversed.prepend(varCoord.value<QGeoCoordinate>());
633 }
634
635 beginReset();
636 clear();
637 appendVertices(rgReversed);
638 endReset();
639 }
640}
641
643{
644 _polygonModel.beginResetModel();
645}
646
648{
649 _polygonModel.endResetModel();
650}
651
653{
654#if 0
655 <Polygon id="ID">
656 <!-- specific to Polygon -->
657 <extrude>0</extrude> <!-- boolean -->
658 <tessellate>0</tessellate> <!-- boolean -->
659 <altitudeMode>clampToGround</altitudeMode>
660 <!-- kml:altitudeModeEnum: clampToGround, relativeToGround, or absolute -->
661 <!-- or, substitute gx:altitudeMode: clampToSeaFloor, relativeToSeaFloor -->
662 <outerBoundaryIs>
663 <LinearRing>
664 <coordinates>...</coordinates> <!-- lon,lat[,alt] -->
665 </LinearRing>
666 </outerBoundaryIs>
667 <innerBoundaryIs>
668 <LinearRing>
669 <coordinates>...</coordinates> <!-- lon,lat[,alt] -->
670 </LinearRing>
671 </innerBoundaryIs>
672 </Polygon>
673#endif
674
675 QDomElement polygonElement = domDocument.createElement("Polygon");
676
677 domDocument.addTextElement(polygonElement, "altitudeMode", "clampToGround");
678
679 QDomElement outerBoundaryIsElement = domDocument.createElement("outerBoundaryIs");
680 QDomElement linearRingElement = domDocument.createElement("LinearRing");
681
682 outerBoundaryIsElement.appendChild(linearRingElement);
683 polygonElement.appendChild(outerBoundaryIsElement);
684
685 QString coordString;
686 for (const QVariant& varCoord : _polygonPath) {
687 coordString += QStringLiteral("%1\n").arg(domDocument.kmlCoordString(varCoord.value<QGeoCoordinate>()));
688 }
689 coordString += QStringLiteral("%1\n").arg(domDocument.kmlCoordString(_polygonPath.first().value<QGeoCoordinate>()));
690 domDocument.addTextElement(linearRingElement, "coordinates", coordString);
691
692 return polygonElement;
693}
694
695void QGCMapPolygon::setTraceMode(bool traceMode)
696{
697 if (traceMode != _traceMode) {
698 _traceMode = traceMode;
700 }
701}
702
703void QGCMapPolygon::setShowAltColor(bool showAltColor){
704 if (showAltColor != _showAltColor) {
705 _showAltColor = showAltColor;
707 }
708}
709
711{
712 if(index == _selectedVertexIndex) return; // do nothing
713
714 if(-1 <= index && index < count()) {
715 _selectedVertexIndex = index;
716 } else {
717 qCWarning(QGCMapPolygonLog) << QString("QGCMapPolygon: Selected vertex index (%1) is out of bounds! "
718 "Polygon vertices indexes range is [%2..%3].").arg(index).arg(0).arg(count()-1);
719 _selectedVertexIndex = -1; // deselect vertex
720 }
721
722 emit selectedVertexChanged(_selectedVertexIndex);
723}
#define qgcApp()
QString errorString
Geographic coordinate conversion utilities using GeographicLib.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Used to convert a Plan to a KML document.
static QString kmlCoordString(const QGeoCoordinate &coord)
void addTextElement(QDomElement &parentElement, const QString &name, const QString &value)
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
The QGCMapPolygon class provides a polygon which can be displayed on a map using a map visuals contro...
void cleared(void)
void interactiveChanged(bool interactive)
~QGCMapPolygon() override
void dirtyChanged(bool dirty)
void selectVertex(int index)
Q_INVOKABLE bool loadKMLOrSHPFile(const QString &file)
bool isEmptyChanged(void)
Q_INVOKABLE void adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
double area(void) const
Returns the area of the polygon in meters squared.
void setCenter(QGeoCoordinate newCenter)
Q_INVOKABLE void splitPolygonSegment(int vertexIndex)
Splits the segment comprised of vertextIndex -> vertexIndex + 1.
bool traceMode(void) const
void vertexDragChanged(bool vertexDrag)
QGeoCoordinate center(void) const
void setTraceMode(bool traceMode)
QList< QGeoCoordinate > coordinateList(void) const
Returns the path in a list of QGeoCoordinate's format.
void setVertexDrag(bool vertexDrag)
void dragPathChanged(void)
Q_INVOKABLE void offset(double distance)
Offsets the current polygon edges by the specified distance in meters.
void setDirty(bool dirty)
bool centerDrag(void) const
void selectedVertexChanged(int index)
void traceModeChanged(bool traceMode)
bool showAltColor(void) const
bool dirty(void) const
Q_INVOKABLE bool containsCoordinate(const QGeoCoordinate &coordinate) const
Returns true if the specified coordinate is within the polygon.
Q_INVOKABLE QGeoCoordinate vertexCoordinate(int vertex) const
Returns the QGeoCoordinate for the vertex specified.
Q_INVOKABLE void removeVertex(int vertexIndex)
void setPath(const QList< QGeoCoordinate > &path)
QVariantList path(void) const
void showAltColorChanged(bool showAltColor)
void dragCenterChanged(QGeoCoordinate center)
const QGCMapPolygon & operator=(const QGCMapPolygon &other)
Q_INVOKABLE void verifyClockwiseWinding(void)
Adjust polygon winding order to be clockwise (if needed)
void setInteractive(bool interactive)
Q_INVOKABLE void beginReset(void)
bool interactive(void) const
void saveToJson(QJsonObject &json)
QList< QPointF > nedPolygon(void) const
Convert polygon to NED and return (D is ignored)
Q_INVOKABLE void endReset(void)
QDomElement kmlPolygonElement(KMLDomDocument &domDocument)
static constexpr const char * jsonPolygonKey
void centerChanged(QGeoCoordinate center)
Q_INVOKABLE void clear(void)
bool isValidChanged(void)
void setShowAltColor(bool showAltColor)
Q_INVOKABLE void appendVertex(const QGeoCoordinate &coordinate)
void pathChanged(void)
bool vertexDrag(void) const
void countChanged(int count)
int count(void) const
bool loadFromJson(const QJsonObject &json, bool required, QString &errorString)
Q_INVOKABLE void appendVertices(const QVariantList &varCoords)
void setCenterDrag(bool centerDrag)
QGCMapPolygon(QObject *parent=nullptr)
void centerDragChanged(bool centerDrag)
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)
int count() const override final
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
void insert(int index, QObject *object)
static bool loadPolygonsFromFile(const QString &file, QList< QList< QGeoCoordinate > > &polygons, 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