Newer
Older
#include "geometry/MeasurementArea.h"
#include "geometry/SafeArea.h"
Valentin Platzgummer
committed
#include "geometry/geometry.h"
#include "QGCApplication.h"
#include "QGCLoggingCategory.h"
#include "QGCQGeoCoordinate.h"
QGC_LOGGING_CATEGORY(AreaDataLog, "AreaDataLog")
const char *areaListKey = "AreaList";
AreaData::AreaData(QObject *parent) : QObject(parent) {}
AreaData::AreaData(const AreaData &other, QObject *parent) : QObject(parent) {
AreaData &AreaData::operator=(const AreaData &other) {
this->clear();
// Clone elements.
for (int i = 0; i < other._areaList.count(); ++i) {
auto obj = other._areaList[i];
auto area = qobject_cast<const GeoArea *>(obj);
this->insert(area->clone(this));
_origin = other._origin;
bool AreaData::insert(GeoArea *areaData) {
if (areaData != nullptr) {
if (Q_LIKELY(!this->_areaList.contains(areaData))) {
_areaList.append(areaData);
Valentin Platzgummer
committed
emit areaListChanged();
auto *measurementArea = qobject_cast<MeasurementArea *>(areaData);
if (measurementArea != nullptr) {
connect(measurementArea, &MeasurementArea::centerChanged, this,
&AreaData::_updateOrigin);
_setOrigin(measurementArea->center());
}
void AreaData::remove(GeoArea *areaData) {
int index = _areaList.indexOf(areaData);
if (index >= 0) {
QObject *obj = _areaList.removeAt(index);
auto *measurementArea = qobject_cast<MeasurementArea *>(areaData);
if (measurementArea != nullptr) {
disconnect(measurementArea, &MeasurementArea::centerChanged, this,
&AreaData::_updateOrigin);
_setOrigin(QGeoCoordinate());
}
if (obj->parent() == nullptr || obj->parent() == this) {
void AreaData::clear() {
if (_areaList.count() > 0) {
while (_areaList.count() > 0) {
remove(_areaList.value<GeoArea *>(0));
}
emit areaListChanged();
}
_errorString.clear();
QmlObjectListModel *AreaData::areaList() { return &_areaList; }
const QmlObjectListModel *AreaData::areaList() const { return &_areaList; }
Valentin Platzgummer
committed
QGeoCoordinate AreaData::origin() const { return _origin; }
bool AreaData::isCorrect(bool showError) {
if (!initialized()) {
qCWarning(AreaDataLog) << "isCorrect(): not initialized";
return false;
}
// Check if areas are correct
return false;
}
// Check if areas where added.
MeasurementArea *measurementArea = nullptr;
SafeArea *safeArea = nullptr;
if (!_getAreas(&measurementArea, &safeArea, showError)) {
return false;
}
// Check if measurement area is covered by safe area.
if (!_origin.isValid()) {
qCWarning(AreaDataLog) << "isCorrect(): origin invalid";
return false;
}
const auto &origin = this->origin();
Valentin Platzgummer
committed
geometry::FPolygon safeAreaENU;
geometry::areaToEnu(origin, safeArea->pathModel(), safeAreaENU);
geometry::FPolygon measurementAreaENU;
geometry::areaToEnu(origin, measurementArea->pathModel(), measurementAreaENU);
// qDebug() << "origin" << origin;
// std::stringstream ss;
// ss << "measurementAreaENU: " << bg::wkt(measurementAreaENU) << std::endl;
// ss << "safeAreaENU: " << bg::wkt(safeAreaENU) << std::endl;
// qDebug() << ss.str().c_str();
if (!bg::covered_by(measurementAreaENU, safeAreaENU)) {
_processError(tr("Measurement Area is not covered by Safe "
"Area. Please adjust "
"the areas accordingly.\n"),
return false;
}
return true;
}
bool AreaData::initialize(const QGeoCoordinate &bottomLeft,
const QGeoCoordinate &topRight) {
// bottomLeft and topRight define the bounding box.
if (bottomLeft.isValid() && topRight.isValid() && bottomLeft != topRight) {
auto *measurementArea = getGeoArea<MeasurementArea *>(_areaList);
auto *safeArea = getGeoArea<SafeArea *>(_areaList);
if (safeArea == nullptr) {
safeArea = new SafeArea(this);
if (!insert(safeArea)) {
safeArea->deleteLater();
qCCritical(AreaDataLog)
<< "initialize(): safeArea == nullptr, but insert() failed.";
return false;
}
}
if (measurementArea == nullptr) {
measurementArea = new MeasurementArea(this);
if (!insert(measurementArea)) {
measurementArea->deleteLater();
qCCritical(AreaDataLog) << "initialize(): measurementArea == nullptr, "
"but insert() failed.";
return false;
}
}
// Fit safe area to bounding box.
safeArea->clear();
safeArea->appendVertex(bottomLeft);
safeArea->appendVertex(
QGeoCoordinate(topRight.latitude(), bottomLeft.longitude()));
safeArea->appendVertex(topRight);
safeArea->appendVertex(
QGeoCoordinate(bottomLeft.latitude(), topRight.longitude()));
// Put measurement area inside safeArea;
measurementArea->clear();
measurementArea->appendVertex(QGeoCoordinate(
0.8 * bottomLeft.latitude() + 0.2 * topRight.latitude(),
0.8 * bottomLeft.longitude() + 0.2 * topRight.longitude()));
measurementArea->appendVertex(QGeoCoordinate(
0.2 * bottomLeft.latitude() + 0.8 * topRight.latitude(),
0.8 * bottomLeft.longitude() + 0.2 * topRight.longitude()));
measurementArea->appendVertex(QGeoCoordinate(
0.2 * bottomLeft.latitude() + 0.8 * topRight.latitude(),
0.2 * bottomLeft.longitude() + 0.8 * topRight.longitude()));
measurementArea->appendVertex(QGeoCoordinate(
0.8 * bottomLeft.latitude() + 0.2 * topRight.latitude(),
0.2 * bottomLeft.longitude() + 0.8 * topRight.longitude()));
// Set depot
safeArea->setDepot(QGeoCoordinate(
safeArea->vertexCoordinate(0).latitude() * 0.5 +
measurementArea->vertexCoordinate(0).latitude() * 0.5,
safeArea->vertexCoordinate(0).longitude() * 0.5 +
measurementArea->vertexCoordinate(0).longitude() * 0.5));
return true;
} else {
qCWarning(AreaDataLog)
<< "initialize(): bounding box invaldid (bottomLeft, topRight) "
<< bottomLeft << "," << topRight;
return false;
}
}
bool AreaData::initialized() {
auto measurementArea = getGeoArea<MeasurementArea *>(_areaList);
auto safeArea = getGeoArea<SafeArea *>(_areaList);
return measurementArea != nullptr && safeArea != nullptr &&
measurementArea->count() >= 3 && safeArea->count() >= 3;
}
void AreaData::intersection(bool showError) {
if (initialized() && _areasCorrect(showError)) {
MeasurementArea *measurementArea = nullptr;
SafeArea *safeArea = nullptr;
if (_getAreas(&measurementArea, &safeArea, showError)) {
// convert to ENU
const auto origin = this->origin();
Valentin Platzgummer
committed
geometry::FPolygon safeAreaENU;
geometry::areaToEnu(origin, safeArea->pathModel(), safeAreaENU);
geometry::FPolygon measurementAreaENU;
geometry::areaToEnu(origin, measurementArea->pathModel(),
measurementAreaENU);
// do intersection
Valentin Platzgummer
committed
std::deque<geometry::FPolygon> outputENU;
boost::geometry::intersection(measurementAreaENU, safeAreaENU, outputENU);
if (outputENU.size() < 1 || outputENU[0].outer().size() < 4) {
_processError("Intersection did't deliver any result. The Measurement "
"Area(s) and "
"The Safe Area must touch each other.",
showError);
return;
}
if (outputENU[0].inners().size() > 0 || outputENU.size() > 1) {
_processError(
"Hint: Only simple polygons can be displayed. If Intersection"
"produces polygons with holes or multi polygons, only "
"partial information can be displayed.",
showError);
}
// Shrink the result if safeAreaENU doesn't cover it.
auto large = std::move(outputENU[0]);
Valentin Platzgummer
committed
geometry::FPolygon small;
while (!bg::covered_by(large, safeAreaENU)) {
Valentin Platzgummer
committed
geometry::offsetPolygon(large, small, -0.1);
large = std::move(small);
}
// Check if result is different from input.
if (!bg::equals(large, measurementAreaENU)) {
// Convert.
measurementArea->clear();
for (auto it = large.outer().begin(); it != large.outer().end() - 1;
++it) {
QGeoCoordinate c;
Valentin Platzgummer
committed
geometry::fromENU(origin, *it, c);
measurementArea->appendVertex(c);
}
QVector<MeasurementArea *> AreaData::measurementAreaArray() {
return getGeoAreaList<MeasurementArea *, QVector>(_areaList);
}
QVector<SafeArea *> AreaData::safeAreaArray() {
return getGeoAreaList<SafeArea *, QVector>(_areaList);
QmlObjectListModel *AreaData::measurementAreaList() {
_measurementAreaList.clear();
auto array = getGeoAreaList<MeasurementArea *, QVector>(_areaList);
for (auto area : array)
_measurementAreaList.append(area);
return &_measurementAreaList;
}
QmlObjectListModel *AreaData::safeAreaList() {
_safeAreaList.clear();
auto array = getGeoAreaList<SafeArea *, QVector>(_areaList);
for (auto &area : array)
_safeAreaList.append(area);
return &_safeAreaList;
}
bool AreaData::operator==(const AreaData &other) const {
if (_areaList.count() == other._areaList.count()) {
for (int i = 0; i < _areaList.count(); ++i) {
if (_areaList[i] != other._areaList[i]) {
return false;
}
}
return true;
} else {
return false;
}
}
bool AreaData::operator!=(const AreaData &other) const {
return !(*this == other);
}
bool AreaData::load(const QJsonObject &obj, QString &errorString) {
bool returnValue = true;
// load areaList.
if (obj.contains(areaListKey) && obj[areaListKey].isArray()) {
this->clear();
// iterate over json array
for (const auto valueRef : obj[areaListKey].toArray()) {
const auto jsonArea = valueRef.toObject();
// check if area type key is present
if (jsonArea.contains(GeoArea::areaTypeKey) &&
jsonArea[GeoArea::areaTypeKey].isString()) {
// load MeasurementArea
if (jsonArea[GeoArea::areaTypeKey].toString() ==
auto area = getGeoArea<MeasurementArea *>(_areaList);
if (area == nullptr) {
auto area = new MeasurementArea(this);
QString e;
if (area->loadFromJson(jsonArea, e)) {
this->insert(area);
} else {
returnValue = false;
errorString.append(e);
errorString.append("\n");
area->deleteLater();
} else {
returnValue = false;
errorString.append(
tr("Multiple Measurement Areas detected. Area was ignored."));
else if (jsonArea[GeoArea::areaTypeKey].toString() ==
SafeArea::nameString) {
auto area = getGeoArea<SafeArea *>(_areaList);
if (area == nullptr) {
auto area = new SafeArea(this);
QString e;
if (area->loadFromJson(jsonArea, e)) {
this->insert(area);
} else {
returnValue = false;
errorString.append(e);
errorString.append("\n");
area->deleteLater();
returnValue = false;
errorString.append(
tr("Multiple Safe Areas detected. Area was ignored."));
else {
returnValue = false;
errorString.append(tr("Unknown area type: ") +
jsonArea[GeoArea::areaTypeKey].toString());
} else {
// GeoArea::areaTypeKey missing
returnValue = false;
errorString.append(
"Area type key missing, not able to determine area type.\n");
// AreaList missing
returnValue = false;
errorString.append("Not able to load areas.\n");
}
// load origin
if (obj.contains(originKey) && obj[originKey].isObject()) {
QGeoCoordinate origin;
QString e;
if (JsonHelper::loadGeoCoordinate(obj[originKey], false, origin, e)) {
_origin = origin;
}
}
return returnValue;
bool AreaData::save(QJsonObject &obj) {
QJsonObject temp;
QJsonValue jsonOrigin;
JsonHelper::saveGeoCoordinate(_origin, false, jsonOrigin);
QJsonArray jsonAreaList;
for (int i = 0; i < _areaList.count(); ++i) {
auto qobj = _areaList[i];
auto area = qobject_cast<GeoArea *>(qobj);
QJsonObject jsonArea;
if (area->saveToJson(jsonArea)) {
jsonAreaList.append(jsonArea);
} else {
qDebug(AreaDataLog) << "save(): not able to save area: "
<< area->objectName();
return false;
}
}
temp[areaListKey] = jsonAreaList;
obj = std::move(temp);
void AreaData::_setOrigin(const QGeoCoordinate &origin) {
if (this->_origin != origin) {
this->_origin = origin;
emit originChanged();
}
}
void AreaData::_processError(const QString &str, bool showError) {
this->_errorString = str;
emit error();
qgcApp()->informationMessageBoxOnMainThread(tr("Area Editor"),
this->errorString());
bool AreaData::_areasCorrect(bool showError) {
// Check if areas are correct.
for (int i = 0; i < _areaList.count(); ++i) {
auto *area = _areaList.value<GeoArea *>(i);
if (!area->isCorrect()) {
_processError(area->errorString(), showError);
return true;
}
bool AreaData::_getAreas(MeasurementArea **measurementArea, SafeArea **safeArea,
bool showError) {
*measurementArea = getGeoArea<MeasurementArea *>(_areaList);
if (*measurementArea == nullptr) {
_processError(
tr("Measurement Area is missing. Please define a Measurement Area."),
return false;
}
*safeArea = getGeoArea<SafeArea *>(_areaList);
if (*safeArea == nullptr) {
_processError(tr("Safe Area is missing. Please define a Safe Area."),
return false;
}
return true;
void AreaData::_updateOrigin() {
auto *measurementArea = getGeoArea<MeasurementArea *>(_areaList);
if (measurementArea != nullptr) {
_setOrigin(measurementArea->center());
}
}
QString AreaData::errorString() const { return _errorString; }