#include "WimaArea.h" /*! * \variable WimaArea::epsilonMeter * \brief The accuracy used for distance calculations (unit: m). */ const double WimaArea::epsilonMeter = 1e-5; /*! * \variable WimaArea::maxAltitudeName * \brief A string containing the name of the \c _maxAltitude member. Among * other used for storing. */ const char *WimaArea::maxAltitudeName = "maxAltitude"; /*! * \variable WimaArea::wimaAreaName * \brief A string containing the name of this \c WimaArea member. Among other * used for storing. */ const char *WimaArea::wimaAreaName = "WimaArea"; /*! * \variable WimaArea::areaTypeName * \brief A string containing \c {"AreaType"}. Among other used for stroing. */ const char *WimaArea::areaTypeName = "AreaType"; const char *WimaArea::borderPolygonOffsetName = "BorderPolygonOffset"; const char *WimaArea::showBorderPolygonName = "ShowBorderPolygon"; const char *WimaArea::settingsGroup = "MeasurementArea"; // Constructors WimaArea::WimaArea(QObject *parent) : QGCMapPolygon(parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/WimaArea.SettingsGroup.json"), this /* QObject parent */)), _borderPolygonOffset(SettingsFact(settingsGroup, _metaDataMap[borderPolygonOffsetName], this /* QObject parent */)), _showBorderPolygon(SettingsFact(settingsGroup, _metaDataMap[showBorderPolygonName], this /* QObject parent */)), _borderPolygon(QGCMapPolygon(this)), _wimaAreaInteractive(false) { init(); _maxAltitude = 30; } WimaArea::WimaArea(const WimaArea &other, QObject *parent) : QGCMapPolygon(parent), _metaDataMap(FactMetaData::createMapFromJsonFile( QStringLiteral(":/json/WimaArea.SettingsGroup.json"), this /* QObject parent */)), _borderPolygonOffset(SettingsFact(settingsGroup, _metaDataMap[borderPolygonOffsetName], this /* QObject parent */)), _showBorderPolygon(SettingsFact(settingsGroup, _metaDataMap[showBorderPolygonName], this /* QObject parent */)), _borderPolygon(QGCMapPolygon(this)), _wimaAreaInteractive(false) { init(); *this = other; } /*! *\fn WimaArea &WimaArea::operator=(const WimaArea &other) * * Assigns \a other to this \c WimaArea and returns a reference to this \c *WimaArea. * * Copies only path and maximum altitude. */ WimaArea &WimaArea::operator=(const WimaArea &other) { QGCMapPolygon::operator=(other); this->setMaxAltitude(other._maxAltitude); this->setPath(other.path()); return *this; } void WimaArea::setWimaAreaInteractive(bool interactive) { if (WimaArea::_wimaAreaInteractive != interactive) { WimaArea::_wimaAreaInteractive = interactive; emit WimaArea::wimaAreaInteractiveChanged(); } } /*! \fn void WimaArea::setMaxAltitude(double altitude) Sets the \c _maxAltitude member to \a altitude and emits the signal \c maxAltitudeChanged() if \c _maxAltitude is not equal to altitude. */ void WimaArea::setMaxAltitude(double altitude) { if (altitude > 0 && qFuzzyCompare(altitude, _maxAltitude)) { _maxAltitude = altitude; emit maxAltitudeChanged(); } } void WimaArea::setShowBorderPolygon(bool showBorderPolygon) { _showBorderPolygon.setRawValue(showBorderPolygon); } void WimaArea::setBorderPolygonOffset(double offset) { if (!qFuzzyCompare(_borderPolygonOffset.rawValue().toDouble(), offset)) { _borderPolygonOffset.setRawValue(offset); emit borderPolygonOffsetChanged(); } } void WimaArea::recalcPolygons() { if (_showBorderPolygon.rawValue().toBool() == true) { if (_borderPolygon.count() >= 3) { //_borderPolygon.verifyClockwiseWinding(); // causes seg. fault this->setPath(_borderPolygon.coordinateList()); this->offset(-_borderPolygonOffset.rawValue().toDouble()); } } else { if (this->count() >= 3) { // this->verifyClockwiseWinding(); // causes seg. fault _borderPolygon.setPath(this->coordinateList()); _borderPolygon.offset(_borderPolygonOffset.rawValue().toDouble()); } emit borderPolygonChanged(); } } void WimaArea::updatePolygonConnections(QVariant showBorderPolygon) { if (showBorderPolygon.toBool() == true) { connect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); disconnect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); } else { disconnect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); connect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); } } void WimaArea::recalcInteractivity() { if (_wimaAreaInteractive == false) { QGCMapPolygon::setInteractive(false); _borderPolygon.setInteractive(false); } else { if (_showBorderPolygon.rawValue().toBool() == true) { _borderPolygon.setInteractive(true); QGCMapPolygon::setInteractive(false); } else { _borderPolygon.setInteractive(false); QGCMapPolygon::setInteractive(true); } } } /*! * \fn int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) * const Returns the index of the vertex (element of the polygon path) which has * the least distance to \a coordinate. * * \sa QGeoCoordinate */ int WimaArea::getClosestVertexIndex(const QGeoCoordinate &coordinate) const { if (this->count() == 0) { qWarning("Polygon count == 0!"); return -1; } else if (this->count() == 1) { return 0; } else { int index = 0; double min_dist = coordinate.distanceTo(this->vertexCoordinate(index)); for (int i = 1; i < this->count(); i++) { double dist = coordinate.distanceTo(this->vertexCoordinate(i)); if (dist < min_dist) { min_dist = dist; index = i; } } return index; } } /*! * \fn QGeoCoordinate WimaArea::getClosestVertex(const QGeoCoordinate& * coordinate) const Returns the vertex of the polygon path with the least * distance to \a coordinate. * * \sa QGeoCoordinate */ QGeoCoordinate WimaArea::getClosestVertex(const QGeoCoordinate &coordinate) const { return this->vertexCoordinate(getClosestVertexIndex(coordinate)); } /*! * \fn QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &area) * Converts the \c WimaArea \a area to \c QGCMapPolygon by copying the path * only. */ QGCMapPolygon WimaArea::toQGCPolygon(const WimaArea &area) { QGCMapPolygon qgcPoly; qgcPoly.setPath(area.path()); return QGCMapPolygon(qgcPoly); } /*! * \fn QGCMapPolygon WimaArea::toQGCPolygon() const * Converts the calling \c WimaArea to \c QGCMapPolygon by copying the path * only. */ QGCMapPolygon WimaArea::toQGCPolygon() const { return toQGCPolygon(*this); } /*! * \fn bool WimaArea::join(WimaArea &area1, WimaArea &area2, WimaArea * &joinedArea, QString &errorString) Joins the areas \a area1 and \a area2 such * that a \l {Simple Polygon} is created. Stores the result inside \a * joinedArea. Stores error messages in \a errorString. Returns \c true if the * algorithm was able to join the areas; false else. The algorithm will be able * to join the areas, if either their edges intersect with each other, or one * area contains the other. */ bool WimaArea::join(const WimaArea &area1, const WimaArea &area2, WimaArea &joinedArea, QString &errorString) { using namespace GeoUtilities; using namespace PolygonCalculus; Q_UNUSED(errorString); QList GeoPolygon1 = area1.coordinateList(); QList GeoPolygon2 = area2.coordinateList(); // qWarning("befor joining"); // qWarning() << GeoPolygon1; // qWarning() << GeoPolygon2; QGeoCoordinate origin = GeoPolygon1[0]; // QGeoCoordinate tset = GeoPolygon1[2]; // qWarning() << tset;qWarning() << toGeo(toCartesian2D(tset, origin), // origin); QPolygonF polygon1; toCartesianList(GeoPolygon1, origin, polygon1); QPolygonF polygon2; toCartesianList(GeoPolygon2, origin, polygon2); // qWarning("after 1 transform"); // qWarning() << polygon1; // qWarning() << polygon2; QPolygonF joinedPolygon; JoinPolygonError retValue = PolygonCalculus::join(polygon1, polygon2, joinedPolygon); // qWarning("after joining"); // qWarning() << joinedPolygon; if (retValue == JoinPolygonError::Disjoint) { qWarning("Polygons are disjoint."); } else if (retValue == JoinPolygonError::NotSimplePolygon) { qWarning("Not a simple polygon."); } else if (retValue == JoinPolygonError::PathSizeLow) { qWarning("Polygon vertex count is low."); } else { QList path; toGeoList(joinedPolygon, origin, path); // qWarning("after transform"); // qWarning() << path; joinedArea.setPath(path); return true; } return false; } /*! * \fn bool WimaArea::join(WimaArea &area1, WimaArea &area2, WimaArea * &joinedArea) Joins the areas \a area1 and \a area2 such that a \l {Simple * Polygon} is created. Stores the result inside \a joinedArea. Returns \c true * if the algorithm was able to join the areas; false else. The algorithm will * be able to join the areas, if either their edges intersect with each other, * or one area contains the other. */ bool WimaArea::join(const WimaArea &area1, const WimaArea &area2, WimaArea &joinedArea) { QString dummy; return join(area1, area2, joinedArea, dummy); } /*! * \fn bool WimaArea::join(WimaArea &area) * Joins the calling \c WimaArea and the \a area such that a \l {Simple Polygon} * is created. Overwrites the calling \c WimaArea with the result, if the * algorithm was successful. Returns \c true if the algorithm was able to join * the areas; false else. The algorithm will be able to join the areas, if * either their edges intersect with each other, or one area contains the other. */ bool WimaArea::join(WimaArea &area) { WimaArea joinedArea; if (join(*this, area, joinedArea)) { // qWarning("WimaArea::join(WimaArea &area)"); // qWarning() << joinedArea.coordinateList(); this->setPath(joinedArea.path()); return true; } else { return false; } } /*! * \fn bool WimaArea::join(WimaArea &area, QString &errorString) * Joins the calling \c WimaArea and the \a area such that a \l {Simple Polygon} * is created. Overwrites the calling \c WimaArea with the result, if the * algorithm was successful. * * Returns \c true if the algorithm was able to join the areas; false else. * Stores error messages in \a errorString. * * The algorithm will be able to join the areas, if either their edges intersect * with each other, or one area contains the other. */ bool WimaArea::join(WimaArea &area, QString &errorString) { WimaArea joinedArea; if (join(*this, area, joinedArea, errorString)) { this->setPath(joinedArea.path()); return true; } else { return false; } } /*! * \fn int WimaArea::nextVertexIndex(int index) const * Returns the index of the next vertex (of the areas path), which is \a index + * 1 if \a index is smaller than \c {area.count() - 1}, or 0 if \a index equals * \c {area.count() - 1}, or -1 if the \a index is out of bounds. \note The * function \c {area.count()} (derived from \c QGCMapPolygon) returns the number * of vertices defining the area. */ int WimaArea::nextVertexIndex(int index) const { if (index >= 0 && index < count() - 1) { return index + 1; } else if (index == count() - 1) { return 0; } else { qWarning( "WimaArea::nextVertexIndex(): Index out of bounds! index:count = %i:%i", index, count()); return -1; } } /*! * \fn int WimaArea::previousVertexIndex(int index) const * Returns the index of the previous vertex (of the areas path), which is \a * index - 1 if \a index is larger 0, or \c {area.count() - 1} if \a index * equals 0, or -1 if the \a index is out of bounds. \note The function \c * {area.count()} (derived from \c QGCMapPolygon) returns the number of vertices * defining the area. */ int WimaArea::previousVertexIndex(int index) const { if (index > 0 && index < count()) { return index - 1; } else if (index == 0) { return count() - 1; } else { qWarning("WimaArea::previousVertexIndex(): Index out of bounds! " "index:count = %i:%i", index, count()); return -1; } } /*! * \fn bool WimaArea::isSelfIntersecting() * Returns \c true if the calling area is self intersecting, \c false else. * \note If the calling area is self intersecting, it's not a \l {Simple * Polygon}. */ bool WimaArea::isSimplePolygon() const { using namespace PolygonCalculus; using namespace GeoUtilities; if (this->count() > 2) { QPolygonF polygon; toCartesianList(this->coordinateList(), this->vertexCoordinate(0), polygon); return PolygonCalculus::isSimplePolygon(polygon); } else return false; } bool WimaArea::containsCoordinate(const QGeoCoordinate &coordinate) const { using namespace PlanimetryCalculus; using namespace PolygonCalculus; using namespace GeoUtilities; if (this->count() > 2) { QPolygonF polygon; toCartesianList(this->coordinateList(), coordinate, polygon); return PlanimetryCalculus::contains(polygon, QPointF(0, 0)); } else return false; } /*! * \fn void WimaArea::saveToJson(QJsonObject &json) * Saves the calling area to \c QJsonObject object and stores it inside \a json. * * \sa QJsonObject */ void WimaArea::saveToJson(QJsonObject &json) { this->QGCMapPolygon::saveToJson(json); json[maxAltitudeName] = _maxAltitude; json[borderPolygonOffsetName] = _borderPolygonOffset.rawValue().toDouble(); json[showBorderPolygonName] = _showBorderPolygon.rawValue().toDouble(); json[areaTypeName] = wimaAreaName; } /*! * \fn bool WimaArea::loadFromJson(const QJsonObject &json, QString& * errorString) Loads data from \a json and stores it inside the calling area. * Returns \c true if loading was successful, \c false else. * Stores error messages inside \a errorString. * * \sa QJsonObject */ bool WimaArea::loadFromJson(const QJsonObject &json, QString &errorString) { if (this->QGCMapPolygon::loadFromJson(json, false /*no poly required*/, errorString)) { if (json.contains(maxAltitudeName) && json[maxAltitudeName].isDouble()) { _maxAltitude = json[maxAltitudeName].toDouble(); } else { errorString.append(tr("Could not load Maximum Altitude value!\n")); return false; } if (json.contains(borderPolygonOffsetName) && json[borderPolygonOffsetName].isDouble()) { _borderPolygonOffset.setRawValue( json[borderPolygonOffsetName].toDouble()); } else { errorString.append(tr("Could not load border polygon offset value!\n")); return false; } if (json.contains(showBorderPolygonName) && json[showBorderPolygonName].isDouble()) { _showBorderPolygon.setRawValue(json[showBorderPolygonName].toBool()); } else { errorString.append(tr("Could not load border polygon offset value!\n")); return false; } } else { qWarning() << errorString; return false; } return true; } /*! * \fn void WimaArea::init() * Funtion to be called during construction. */ void WimaArea::init() { this->setObjectName(wimaAreaName); if (_showBorderPolygon.rawValue().toBool() == true) { connect(&_borderPolygon, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); } else { connect(this, &QGCMapPolygon::pathChanged, this, &WimaArea::recalcPolygons); } connect(&_borderPolygonOffset, &SettingsFact::rawValueChanged, this, &WimaArea::recalcPolygons); connect(&_showBorderPolygon, &SettingsFact::rawValueChanged, this, &WimaArea::updatePolygonConnections); connect(&_showBorderPolygon, &SettingsFact::rawValueChanged, this, &WimaArea::recalcInteractivity); connect(this, &WimaArea::wimaAreaInteractiveChanged, this, &WimaArea::recalcInteractivity); } // QDoc Documentation /*! \group WimaAreaGroup \title Group of WimaAreas Every \c WimaArea of the equally named group uses a \l {Simple Polygon} derived from \c {QGCMapPolygon} to define areas inside which certain taskts are performed. */ /*! \class WimaArea \inmodule Wima \ingroup WimaArea \brief The \c WimaArea class provides the a base class for all areas used within the Wima extension. \c WimaArea uses a \l {Simple Polygon} derived from \c {QGCMapPolygon} to define areas inside which certain taskts are performed. The polygon (often refered to as the path) can be displayed visually on a map. */ /*! \variable WimaArea::_maxAltitude \brief The maximum altitude vehicles are allowed to fly inside this area. */ /*! \property WimaArea::maxAltitude \brief The maximum altitude at which vehicles are allowed to fly. */ /*! \property WimaArea::mapVisualQML \brief A string containing the name of the QML file used to displays this area on a map. */ /*! \property WimaArea::editorQML \brief A string containing the name of the QML file allowing to edit the area's properties. */ /*! \externalpage https://en.wikipedia.org/wiki/Simple_polygon \title Simple Polygon */ /*! \externalpage https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm \title Dijkstra Algorithm */