12#include <QtCore/QLineF>
21 , _ignoreCenterUpdates (false)
22 , _interactive (false)
31 , _ignoreCenterUpdates (false)
32 , _interactive (false)
44void QGCMapPolygon::_init(
void)
63 QVariantList vertices = other.
path();
64 QList<QGeoCoordinate> rgCoord;
65 for (
const QVariant& vertexVar: vertices) {
66 rgCoord.append(vertexVar.value<QGeoCoordinate>());
78 while (_polygonPath.count() > 1) {
79 _polygonPath.takeLast();
102 _polygonPath[vertexIndex] = QVariant::fromValue(coordinate);
105 if (!_deferredPathChanged) {
106 _deferredPathChanged =
true;
109 QTimer::singleShot(0,
this, [
this]() {
111 _deferredPathChanged =
false;
114 QTimer::singleShot(0,
this, [
this]() {
116 _deferredPathChanged =
false;
126 if (_dirty !=
dirty) {
135QGeoCoordinate QGCMapPolygon::_coordFromPointF(
const QPointF& point)
const
137 QGeoCoordinate coord;
139 if (_polygonPath.count() > 0) {
140 QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
147QPointF QGCMapPolygon::_pointFFromCoord(
const QGeoCoordinate& coordinate)
const
149 if (_polygonPath.count() > 0) {
151 QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
154 return QPointF(x, -y);
160QPolygonF QGCMapPolygon::_toPolygonF(
void)
const
164 if (_polygonPath.count() > 2) {
165 for (
int i=0; i<_polygonPath.count(); i++) {
166 polygon.append(_pointFFromCoord(_polygonPath[i].value<QGeoCoordinate>()));
175 if (_polygonPath.count() > 2) {
176 return _toPolygonF().containsPoint(_pointFFromCoord(coordinate), Qt::OddEvenFill);
184 _polygonPath.clear();
186 for(
const QGeoCoordinate& coord:
path) {
187 _polygonPath.append(QVariant::fromValue(coord));
200 for (
int i=0; i<_polygonPath.count(); i++) {
210 QJsonValue jsonValue;
234 for (
int i=0; i<_polygonPath.count(); i++) {
246 QList<QGeoCoordinate> coords;
248 for (
int i=0; i<_polygonPath.count(); i++) {
249 coords.append(_polygonPath[i].value<QGeoCoordinate>());
257 int nextIndex = vertexIndex + 1;
258 if (nextIndex > _polygonPath.length() - 1) {
262 QGeoCoordinate firstVertex = _polygonPath[vertexIndex].value<QGeoCoordinate>();
263 QGeoCoordinate nextVertex = _polygonPath[nextIndex].value<QGeoCoordinate>();
265 double distance = firstVertex.distanceTo(nextVertex);
266 double azimuth = firstVertex.azimuthTo(nextVertex);
267 QGeoCoordinate newVertex = firstVertex.atDistanceAndAzimuth(distance / 2, azimuth);
269 if (nextIndex == 0) {
273 _polygonPath.insert(nextIndex, QVariant::fromValue(newVertex));
275 if (0 <= _selectedVertexIndex && vertexIndex < _selectedVertexIndex) {
283 _polygonPath.append(QVariant::fromValue(coordinate));
285 if (!_deferredPathChanged) {
287 _deferredPathChanged =
true;
288 QTimer::singleShot(0,
this, [
this]() {
294 _deferredPathChanged =
false;
301 QList<QObject*> objects;
304 for (
const QGeoCoordinate& coordinate: coordinates) {
306 _polygonPath.append(QVariant::fromValue(coordinate));
308 _polygonModel.
append(objects);
320 QList<QGeoCoordinate> rgCoords;
321 for (
const QVariant& varCoord: varCoords) {
322 rgCoords.append(varCoord.value<QGeoCoordinate>());
327void QGCMapPolygon::_polygonModelDirtyChanged(
bool dirty)
336 if (vertexIndex < 0 || vertexIndex >= _polygonPath.length()) {
337 qCWarning(QGCMapPolygonLog) <<
"Call to removePolygonCoordinate with bad vertexIndex:count" << vertexIndex << _polygonPath.length();
341 if (_polygonPath.length() <= 3) {
346 QObject* coordObj = _polygonModel.
removeAt(vertexIndex);
347 coordObj->deleteLater();
348 if(vertexIndex == _selectedVertexIndex) {
350 }
else if (vertexIndex < _selectedVertexIndex) {
354 _polygonPath.removeAt(vertexIndex);
358void QGCMapPolygon::_polygonModelCountChanged(
int count)
363void QGCMapPolygon::_updateCenter(
void)
365 if (!_ignoreCenterUpdates) {
368 if (_polygonPath.count() > 2) {
369 QPolygonF polygonF = _toPolygonF();
370 const int n = polygonF.count();
373 double signedArea = 0;
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();
381 cx += (polygonF[i].x() + polygonF[j].x()) * cross;
382 cy += (polygonF[i].y() + polygonF[j].y()) * cross;
385 if (qAbs(signedArea) < 1e-6) {
388 for (
int i = 0; i < n; i++) {
391 center = _coordFromPointF(QPointF(avg.x() / n, avg.y() / n));
394 cx /= (6.0 * signedArea);
395 cy /= (6.0 * signedArea);
396 center = _coordFromPointF(QPointF(cx, cy));
408 if (newCenter != _center) {
409 _ignoreCenterUpdates =
true;
412 double distance = _center.distanceTo(newCenter);
413 double azimuth = _center.azimuthTo(newCenter);
415 for (
int i=0; i<
count(); i++) {
416 QGeoCoordinate oldVertex = _polygonPath[i].value<QGeoCoordinate>();
417 QGeoCoordinate newVertex = oldVertex.atDistanceAndAzimuth(distance, azimuth);
421 _ignoreCenterUpdates =
false;
426 if (!_deferredPathChanged) {
427 _deferredPathChanged =
true;
428 QTimer::singleShot(0,
this, [
this]() {
431 _deferredPathChanged =
false;
435 if (!_deferredPathChanged) {
436 _deferredPathChanged =
true;
437 QTimer::singleShot(0,
this, [
this]() {
440 _deferredPathChanged =
false;
485 if (vertex >= 0 && vertex < _polygonPath.count()) {
486 return _polygonPath[vertex].value<QGeoCoordinate>();
488 qCWarning(QGCMapPolygonLog) <<
"QGCMapPolygon::vertexCoordinate bad vertex requested:count" << vertex << _polygonPath.count();
489 return QGeoCoordinate();
500 for (
int i=0; i<_polygonModel.
count(); i++) {
519 QList<QGeoCoordinate> rgNewPolygon;
528 QList<QLineF> rgOffsetEdges;
529 for (
int i=0; i<rgNedVertices.count(); i++) {
530 int lastIndex = i == rgNedVertices.count() - 1 ? 0 : i + 1;
532 QLineF originalEdge(rgNedVertices[i], rgNedVertices[lastIndex]);
534 QLineF workerLine = originalEdge;
535 workerLine.setLength(distance);
536 workerLine.setAngle(workerLine.angle() - 90.0);
537 offsetEdge.setP1(workerLine.p2());
539 workerLine.setPoints(originalEdge.p2(), originalEdge.p1());
540 workerLine.setLength(distance);
541 workerLine.setAngle(workerLine.angle() + 90.0);
542 offsetEdge.setP2(workerLine.p2());
544 rgOffsetEdges.append(offsetEdge);
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) {
555 qCWarning(QGCMapPolygonLog,
"Intersection failed");
558 QGeoCoordinate coord;
560 rgNewPolygon.append(coord);
574 QList<QList<QGeoCoordinate>> polygons;
579 if (polygons.isEmpty()) {
583 const QList<QGeoCoordinate>& rgCoords = polygons.first();
597 if (_polygonPath.count() < 3) {
601 double coveredArea = 0.0;
603 for (
int i=0; i<nedVertices.count(); i++) {
605 coveredArea += nedVertices[i - 1].x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices[i -1].y();
607 coveredArea += nedVertices.last().x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices.last().y();
610 return 0.5 * fabs(coveredArea);
615 if (_polygonPath.count() <= 2) {
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>();
624 sum += (coord2.longitude() - coord1.longitude()) * (coord2.latitude() + coord1.latitude());
630 QList<QGeoCoordinate> rgReversed;
631 for (
const QVariant& varCoord: _polygonPath) {
632 rgReversed.prepend(varCoord.value<QGeoCoordinate>());
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 -->
664 <coordinates>...</coordinates> <!-- lon,lat[,alt] -->
669 <coordinates>...</coordinates> <!-- lon,lat[,alt] -->
675 QDomElement polygonElement = domDocument.createElement(
"Polygon");
677 domDocument.
addTextElement(polygonElement,
"altitudeMode",
"clampToGround");
679 QDomElement outerBoundaryIsElement = domDocument.createElement(
"outerBoundaryIs");
680 QDomElement linearRingElement = domDocument.createElement(
"LinearRing");
682 outerBoundaryIsElement.appendChild(linearRingElement);
683 polygonElement.appendChild(outerBoundaryIsElement);
686 for (
const QVariant& varCoord : _polygonPath) {
687 coordString += QStringLiteral(
"%1\n").arg(domDocument.
kmlCoordString(varCoord.value<QGeoCoordinate>()));
689 coordString += QStringLiteral(
"%1\n").arg(domDocument.
kmlCoordString(_polygonPath.first().value<QGeoCoordinate>()));
690 domDocument.
addTextElement(linearRingElement,
"coordinates", coordString);
692 return polygonElement;
712 if(index == _selectedVertexIndex)
return;
714 if(-1 <= index && index <
count()) {
715 _selectedVertexIndex = index;
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;
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 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
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)
bool vertexDrag(void) const
void countChanged(int count)
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.
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)
void convertNedToGeo(double x, double y, double z, const QGeoCoordinate &origin, QGeoCoordinate &coord)
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.