#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->_maxAltitude = 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 = toQPolygonF(toCartesian2D(GeoPolygon1, origin)); QPolygonF polygon2 = toQPolygonF(toCartesian2D(GeoPolygon2, origin)); // 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 { QVector path = toGeo(toQPointFList(joinedPolygon), origin); // 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 = toQPolygonF(toCartesian2D(this->coordinateList(), this->vertexCoordinate(0))); 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 = toQPolygonF(toCartesian2D(this->coordinateList(), coordinate)); 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); } /*! * \fn void print(const WimaArea &area) * Prints the data contained in \a area to the console. */ void print(const WimaArea &area) { QString message; print(area, message); qWarning() << message; } /*! * \fn void print(const WimaArea &area) * Prints the data contained in \a area to the \a outputString. */ void print(const WimaArea &area, QString &outputString) { outputString.append(QString("Type: %1\n").arg(area.objectName())); print(static_cast(area), outputString); outputString.append(QString("Maximum Altitude: %1\n").arg(area._maxAltitude)); outputString.append(QString("Border Polygon Offset: %1\n").arg(area._borderPolygonOffset.rawValue().toDouble())); outputString.append(QString("Border Polygon Coordinates\n").arg(area._borderPolygonOffset.rawValue().toDouble())); for (int i = 0; i < area._borderPolygon.count(); i++) { QGeoCoordinate coordinate = area._borderPolygon.vertexCoordinate(i); outputString.append(QString("%1\n").arg(coordinate.toString(QGeoCoordinate::Degrees))); } } // 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 */