11#include <QtCore/QLineF>
20 , _ignoreCenterUpdates (false)
21 , _interactive (false)
30 , _ignoreCenterUpdates (false)
31 , _interactive (false)
43void QGCMapPolygon::_init(
void)
62 QVariantList vertices = other.
path();
63 QList<QGeoCoordinate> rgCoord;
64 for (
const QVariant& vertexVar: vertices) {
65 rgCoord.append(vertexVar.value<QGeoCoordinate>());
74void QGCMapPolygon::clear(
void)
77 while (_polygonPath.count() > 1) {
78 _polygonPath.takeLast();
97 _polygonPath[vertexIndex] = QVariant::fromValue(coordinate);
101 if (!_deferredPathChanged) {
103 _deferredPathChanged =
true;
104 QTimer::singleShot(0,
this, [
this]() {
106 _deferredPathChanged =
false;
115 if (_dirty !=
dirty) {
124QGeoCoordinate QGCMapPolygon::_coordFromPointF(
const QPointF& point)
const
126 QGeoCoordinate coord;
128 if (_polygonPath.count() > 0) {
129 QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
136QPointF QGCMapPolygon::_pointFFromCoord(
const QGeoCoordinate& coordinate)
const
138 if (_polygonPath.count() > 0) {
140 QGeoCoordinate tangentOrigin = _polygonPath[0].value<QGeoCoordinate>();
143 return QPointF(x, -y);
149QPolygonF QGCMapPolygon::_toPolygonF(
void)
const
153 if (_polygonPath.count() > 2) {
154 for (
int i=0; i<_polygonPath.count(); i++) {
155 polygon.append(_pointFFromCoord(_polygonPath[i].value<QGeoCoordinate>()));
164 if (_polygonPath.count() > 2) {
165 return _toPolygonF().containsPoint(_pointFFromCoord(coordinate), Qt::OddEvenFill);
173 _polygonPath.clear();
175 for(
const QGeoCoordinate& coord:
path) {
176 _polygonPath.append(QVariant::fromValue(coord));
189 for (
int i=0; i<_polygonPath.count(); i++) {
199 QJsonValue jsonValue;
223 for (
int i=0; i<_polygonPath.count(); i++) {
235 QList<QGeoCoordinate> coords;
237 for (
int i=0; i<_polygonPath.count(); i++) {
238 coords.append(_polygonPath[i].value<QGeoCoordinate>());
246 int nextIndex = vertexIndex + 1;
247 if (nextIndex > _polygonPath.length() - 1) {
251 QGeoCoordinate firstVertex = _polygonPath[vertexIndex].value<QGeoCoordinate>();
252 QGeoCoordinate nextVertex = _polygonPath[nextIndex].value<QGeoCoordinate>();
254 double distance = firstVertex.distanceTo(nextVertex);
255 double azimuth = firstVertex.azimuthTo(nextVertex);
256 QGeoCoordinate newVertex = firstVertex.atDistanceAndAzimuth(distance / 2, azimuth);
258 if (nextIndex == 0) {
262 _polygonPath.insert(nextIndex, QVariant::fromValue(newVertex));
264 if (0 <= _selectedVertexIndex && vertexIndex < _selectedVertexIndex) {
272 _polygonPath.append(QVariant::fromValue(coordinate));
274 if (!_deferredPathChanged) {
276 _deferredPathChanged =
true;
277 QTimer::singleShot(0,
this, [
this]() {
279 _deferredPathChanged =
false;
286 QList<QObject*> objects;
289 for (
const QGeoCoordinate& coordinate: coordinates) {
291 _polygonPath.append(QVariant::fromValue(coordinate));
293 _polygonModel.
append(objects);
301 QList<QGeoCoordinate> rgCoords;
302 for (
const QVariant& varCoord: varCoords) {
303 rgCoords.append(varCoord.value<QGeoCoordinate>());
308void QGCMapPolygon::_polygonModelDirtyChanged(
bool dirty)
317 if (vertexIndex < 0 || vertexIndex >= _polygonPath.length()) {
318 qCWarning(QGCMapPolygonLog) <<
"Call to removePolygonCoordinate with bad vertexIndex:count" << vertexIndex << _polygonPath.length();
322 if (_polygonPath.length() <= 3) {
327 QObject* coordObj = _polygonModel.
removeAt(vertexIndex);
328 coordObj->deleteLater();
329 if(vertexIndex == _selectedVertexIndex) {
331 }
else if (vertexIndex < _selectedVertexIndex) {
335 _polygonPath.removeAt(vertexIndex);
339void QGCMapPolygon::_polygonModelCountChanged(
int count)
344void QGCMapPolygon::_updateCenter(
void)
346 if (!_ignoreCenterUpdates) {
349 if (_polygonPath.count() > 2) {
350 QPointF centroid(0, 0);
351 QPolygonF polygonF = _toPolygonF();
352 for (
int i=0; i<polygonF.count(); i++) {
353 centroid += polygonF[i];
355 center = _coordFromPointF(QPointF(centroid.x() / polygonF.count(), centroid.y() / polygonF.count()));
366 if (newCenter != _center) {
367 _ignoreCenterUpdates =
true;
370 double distance = _center.distanceTo(newCenter);
371 double azimuth = _center.azimuthTo(newCenter);
373 for (
int i=0; i<
count(); i++) {
374 QGeoCoordinate oldVertex = _polygonPath[i].value<QGeoCoordinate>();
375 QGeoCoordinate newVertex = oldVertex.atDistanceAndAzimuth(distance, azimuth);
381 if (!_deferredPathChanged) {
383 _deferredPathChanged =
true;
384 QTimer::singleShot(0,
this, [
this]() {
386 _deferredPathChanged =
false;
391 _ignoreCenterUpdates =
false;
394 if (!_deferredPathChanged) {
396 _deferredPathChanged =
true;
397 QTimer::singleShot(0,
this, [
this, newCenter]() {
399 _deferredPathChanged =
false;
423 if (vertex >= 0 && vertex < _polygonPath.count()) {
424 return _polygonPath[vertex].value<QGeoCoordinate>();
426 qCWarning(QGCMapPolygonLog) <<
"QGCMapPolygon::vertexCoordinate bad vertex requested:count" << vertex << _polygonPath.count();
427 return QGeoCoordinate();
438 for (
int i=0; i<_polygonModel.
count(); i++) {
457 QList<QGeoCoordinate> rgNewPolygon;
466 QList<QLineF> rgOffsetEdges;
467 for (
int i=0; i<rgNedVertices.count(); i++) {
468 int lastIndex = i == rgNedVertices.count() - 1 ? 0 : i + 1;
470 QLineF originalEdge(rgNedVertices[i], rgNedVertices[lastIndex]);
472 QLineF workerLine = originalEdge;
473 workerLine.setLength(distance);
474 workerLine.setAngle(workerLine.angle() - 90.0);
475 offsetEdge.setP1(workerLine.p2());
477 workerLine.setPoints(originalEdge.p2(), originalEdge.p1());
478 workerLine.setLength(distance);
479 workerLine.setAngle(workerLine.angle() + 90.0);
480 offsetEdge.setP2(workerLine.p2());
482 rgOffsetEdges.append(offsetEdge);
488 for (
int i=0; i<rgOffsetEdges.count(); i++) {
489 int prevIndex = i == 0 ? rgOffsetEdges.count() - 1 : i - 1;
490 auto intersect = rgOffsetEdges[prevIndex].intersects(rgOffsetEdges[i], &newVertex);
491 if (intersect == QLineF::NoIntersection) {
493 qCWarning(QGCMapPolygonLog,
"Intersection failed");
496 QGeoCoordinate coord;
498 rgNewPolygon.append(coord);
512 QList<QList<QGeoCoordinate>> polygons;
513 if (!ShapeFileHelper::loadPolygonsFromFile(file, polygons,
errorString)) {
517 if (polygons.isEmpty()) {
518 qgcApp()->showAppMessage(tr(
"No polygons found in file"));
521 const QList<QGeoCoordinate>& rgCoords = polygons.first();
535 if (_polygonPath.count() < 3) {
539 double coveredArea = 0.0;
541 for (
int i=0; i<nedVertices.count(); i++) {
543 coveredArea += nedVertices[i - 1].x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices[i -1].y();
545 coveredArea += nedVertices.last().x() * nedVertices[i].y() - nedVertices[i].x() * nedVertices.last().y();
548 return 0.5 * fabs(coveredArea);
553 if (_polygonPath.count() <= 2) {
558 for (
int i=0; i<_polygonPath.count(); i++) {
559 QGeoCoordinate coord1 = _polygonPath[i].value<QGeoCoordinate>();
560 QGeoCoordinate coord2 = (i == _polygonPath.count() - 1) ? _polygonPath[0].value<QGeoCoordinate>() : _polygonPath[i+1].value<QGeoCoordinate>();
562 sum += (coord2.longitude() - coord1.longitude()) * (coord2.latitude() + coord1.latitude());
568 QList<QGeoCoordinate> rgReversed;
569 for (
const QVariant& varCoord: _polygonPath) {
570 rgReversed.prepend(varCoord.value<QGeoCoordinate>());
594 <!-- specific to Polygon -->
595 <extrude>0</extrude> <!--
boolean -->
596 <tessellate>0</tessellate> <!--
boolean -->
597 <altitudeMode>clampToGround</altitudeMode>
598 <!-- kml:altitudeModeEnum: clampToGround, relativeToGround, or absolute -->
599 <!-- or, substitute gx:altitudeMode: clampToSeaFloor, relativeToSeaFloor -->
602 <coordinates>...</coordinates> <!-- lon,lat[,alt] -->
607 <coordinates>...</coordinates> <!-- lon,lat[,alt] -->
613 QDomElement polygonElement = domDocument.createElement(
"Polygon");
615 domDocument.
addTextElement(polygonElement,
"altitudeMode",
"clampToGround");
617 QDomElement outerBoundaryIsElement = domDocument.createElement(
"outerBoundaryIs");
618 QDomElement linearRingElement = domDocument.createElement(
"LinearRing");
620 outerBoundaryIsElement.appendChild(linearRingElement);
621 polygonElement.appendChild(outerBoundaryIsElement);
624 for (
const QVariant& varCoord : _polygonPath) {
625 coordString += QStringLiteral(
"%1\n").arg(domDocument.
kmlCoordString(varCoord.value<QGeoCoordinate>()));
627 coordString += QStringLiteral(
"%1\n").arg(domDocument.
kmlCoordString(_polygonPath.first().value<QGeoCoordinate>()));
628 domDocument.
addTextElement(linearRingElement,
"coordinates", coordString);
630 return polygonElement;
650 if(index == _selectedVertexIndex)
return;
652 if(-1 <= index && index <
count()) {
653 _selectedVertexIndex = index;
655 qCWarning(QGCMapPolygonLog) << QString(
"QGCMapPolygon: Selected vertex index (%1) is out of bounds! "
656 "Polygon vertices indexes range is [%2..%3].").arg(index).arg(0).arg(
count()-1);
657 _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)
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 interactiveChanged(bool interactive)
~QGCMapPolygon() override
void dirtyChanged(bool dirty)
void selectVertex(int index)
bool loadKMLOrSHPFile(const QString &file)
bool isEmptyChanged(void)
void adjustVertex(int vertexIndex, const QGeoCoordinate coordinate)
double area(void) const
Returns the area of the polygon in meters squared.
void setCenter(QGeoCoordinate newCenter)
void splitPolygonSegment(int vertexIndex)
Splits the segment comprised of vertextIndex -> vertexIndex + 1.
bool traceMode(void) const
QGeoCoordinate center(void) const
void setTraceMode(bool traceMode)
QList< QGeoCoordinate > coordinateList(void) const
Returns the path in a list of QGeoCoordinate's format.
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 containsCoordinate(const QGeoCoordinate &coordinate) const
Returns true if the specified coordinate is within the polygon.
QGeoCoordinate vertexCoordinate(int vertex) const
Returns the QGeoCoordinate for the vertex specified.
void removeVertex(int vertexIndex)
void setPath(const QList< QGeoCoordinate > &path)
QVariantList path(void) const
void showAltColorChanged(bool showAltColor)
const QGCMapPolygon & operator=(const QGCMapPolygon &other)
void verifyClockwiseWinding(void)
Adjust polygon winding order to be clockwise (if needed)
void setInteractive(bool interactive)
bool interactive(void) const
void saveToJson(QJsonObject &json)
QList< QPointF > nedPolygon(void) const
Convert polygon to NED and return (D is ignored)
QDomElement kmlPolygonElement(KMLDomDocument &domDocument)
static constexpr const char * jsonPolygonKey
void centerChanged(QGeoCoordinate center)
bool isValidChanged(void)
void setShowAltColor(bool showAltColor)
void appendVertex(const QGeoCoordinate &coordinate)
bool loadFromJson(const QJsonObject &json, bool required, QString &errorString)
void appendVertices(const QVariantList &varCoords)
int count READ count NOTIFY countChanged(QVariantList path READ path NOTIFY pathChanged) 1(double area READ area NOTIFY pathChanged) 1(QmlObjectListModel *pathModel READ qmlPathModel CONSTANT) 1(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) 1(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged) 1(bool centerDrag READ centerDrag WRITE setCenterDrag NOTIFY centerDragChanged) 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(bool showAltColor READ showAltColor WRITE setShowAltColor NOTIFY showAltColorChanged) 1(int selectedVertex READ selectedVertex WRITE selectVertex NOTIFY selectedVertexChanged) 1 void clear(void)
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)
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)
void convertNedToGeo(double x, double y, double z, const QGeoCoordinate &origin, QGeoCoordinate &coord)