Newer
Older
#include "CircularGenerator.h"
#include "LinearGenerator.h"
#include "geometry/GenericCircle.h"
#include "geometry/MeasurementArea.h"
#include "geometry/SafeArea.h"
#include "geometry/clipper/clipper.hpp"
#include "geometry/snake.h"
#include "nemo_interface/SnakeTile.h"
#include "QGCApplication.h"
#include "QGCLoggingCategory.h"
// boost
#include <boost/units/io.hpp>
#include <boost/units/systems/si.hpp>
QGC_LOGGING_CATEGORY(MeasurementComplexItemLog, "MeasurementComplexItemLog")
template <typename T>
constexpr typename std::underlying_type<T>::type integral(T value) {
return static_cast<typename std::underlying_type<T>::type>(value);
}
const char *MeasurementComplexItem::settingsGroup = "MeasurementComplexItem";
const char *MeasurementComplexItem::jsonComplexItemTypeValue =
"MeasurementComplexItem";
const char *MeasurementComplexItem::variantName = "Variant";
const char *MeasurementComplexItem::altitudeName = "Altitude";
const QString MeasurementComplexItem::name(tr("Measurement"));
MeasurementComplexItem::MeasurementComplexItem(
PlanMasterController *masterController, bool flyView,
const QString &kmlOrShpFile, QObject *parent)
_followTerrain(false), _state(STATE::IDLE),
_metaDataMap(FactMetaData::createMapFromJsonFile(
QStringLiteral(":/json/MeasurementComplexItem.SettingsGroup.json"),
this)),
_altitude(settingsGroup, _metaDataMap[altitudeName]),
_variant(settingsGroup, _metaDataMap[variantName]),
_pAreaData(new AreaData(this)), _pEditorData(new AreaData(this)),
_pCurrentData(_pAreaData), _pGenerator(nullptr),
_pWorker(new RoutingThread(this)) {
// Connect facts.
connect(&this->_variant, &Fact::rawValueChanged, this,
connect(this->_pWorker, &RoutingThread::result, this,
// Connect coordinate and exitCoordinate.
connect(this, &MeasurementComplexItem::routeChanged,
[this] { emit this->coordinateChanged(this->coordinate()); });
connect(this, &MeasurementComplexItem::routeChanged,
[this] { emit this->exitCoordinateChanged(this->exitCoordinate()); });
connect(this, &MeasurementComplexItem::routeChanged, [this] {
emit this->exitCoordinateSameAsEntryChanged(
this->exitCoordinateSameAsEntry());
});
// Connect complexDistance.
connect(this, &MeasurementComplexItem::routeChanged,
[this] { emit this->complexDistanceChanged(); });
auto lg = new routing::LinearGenerator(this->_pAreaData, this);
auto cg = new routing::CircularGenerator(this->_pAreaData, this);
qCritical() << "ToDo: _altitude connections missing.";
qCritical() << "ToDo: add generator saveing.";
void MeasurementComplexItem::reverseRoute() { _reverseRoute(); }
AreaData *MeasurementComplexItem::areaData() { return this->_pCurrentData; }
QVariantList MeasurementComplexItem::route() { return _route; }
QStringList MeasurementComplexItem::variantNames() const {
return _variantNames;
}
bool MeasurementComplexItem::load(const QJsonObject &complexObject,
int sequenceNumber, QString &errorString) {
qWarning() << "MeasurementComplexItem::load(): area data load missing.";
qWarning() << "MeasurementComplexItem::load(): mission item load missing.";
qWarning() << "MeasurementComplexItem::load(): add editingStart/Stop.";
// We need to pull version first to determine what validation/conversion
// needs to be performed
QList<JsonHelper::KeyValidateInfo> versionKeyInfoList = {
{JsonHelper::jsonVersionKey, QJsonValue::Double, true},
};
if (!JsonHelper::validateKeys(complexObject, versionKeyInfoList,
errorString)) {
return false;
}
int version = complexObject[JsonHelper::jsonVersionKey].toInt();
if (version != 1) {
errorString = tr("Survey items do not support version %1").arg(version);
return false;
}
QList<JsonHelper::KeyValidateInfo> keyInfoList = {
{VisualMissionItem::jsonTypeKey, QJsonValue::String, true},
{ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true},
{variantName, QJsonValue::Double, false},
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
};
if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) {
return false;
}
QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString();
QString complexType =
complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString();
if (itemType != VisualMissionItem::jsonTypeComplexItemValue ||
complexType != jsonComplexItemTypeValue) {
errorString = tr("%1 does not support loading this complex mission item "
"type: %2:%3")
.arg(qgcApp()->applicationName())
.arg(itemType)
.arg(complexType);
return false;
}
setSequenceNumber(sequenceNumber);
if (!load(complexObject, sequenceNumber, errorString)) {
return false;
}
_variant.setRawValue(complexObject[variantName].toInt());
_altitude.setRawValue(complexObject[altitudeName].toDouble());
double
MeasurementComplexItem::greatestDistanceTo(const QGeoCoordinate &other) const {
double d = -1 * std::numeric_limits<double>::infinity();
if (other.isValid()) {
if (this->_route.size() > 0) {
std::for_each(this->_route.cbegin(), this->_route.cend(),
[&d, &other](const QVariant &variant) {
auto vertex = variant.value<QGeoCoordinate>();
d = std::max(d, vertex.distanceTo(other));
});
}
} else {
qCDebug(MeasurementComplexItemLog)
<< "greatestDistanceTo(): invalid QGeoCoordinate: " << other;
}
return d;
}
bool MeasurementComplexItem::dirty() const { return _dirty; }
bool MeasurementComplexItem::isSimpleItem() const { return false; }
bool MeasurementComplexItem::isStandaloneCoordinate() const { return false; }
QString MeasurementComplexItem::mapVisualQML() const {
return QStringLiteral("MeasurementItemMapVisual.qml");
qWarning() << "MeasurementComplexItem::save(): area data save missing.";
qWarning() << "MeasurementComplexItem::save(): mission item save missing.";
saveObject[JsonHelper::jsonVersionKey] = 1;
saveObject[VisualMissionItem::jsonTypeKey] =
VisualMissionItem::jsonTypeComplexItemValue;
saveObject[ComplexMissionItem::jsonComplexItemTypeKey] =
jsonComplexItemTypeValue;
saveObject[variantName] = double(_variant.rawValue().toUInt());
saveObject[altitudeName] = double(_altitude.rawValue().toUInt());
double MeasurementComplexItem::amslEntryAlt() const {
return _altitude.rawValue().toDouble() +
this->_masterController->missionController()
->plannedHomePosition()
.altitude();
}
double MeasurementComplexItem::amslExitAlt() const { return amslEntryAlt(); }
double MeasurementComplexItem::minAMSLAltitude() const {
return amslEntryAlt();
}
double MeasurementComplexItem::maxAMSLAltitude() const {
return amslEntryAlt();
}
QString MeasurementComplexItem::commandDescription() const {
return QStringLiteral("Measurement");
}
QString MeasurementComplexItem::commandName() const {
return QStringLiteral("Measurement");
}
QString MeasurementComplexItem::abbreviation() const {
return QStringLiteral("M");
}
bool MeasurementComplexItem::specifiesCoordinate() const {
return _route.count() > 0;
}
bool MeasurementComplexItem::specifiesAltitudeOnly() const { return false; }
QGeoCoordinate MeasurementComplexItem::coordinate() const {
return this->_route.size() > 0 ? _route.first().value<QGeoCoordinate>()
: QGeoCoordinate();
}
QGeoCoordinate MeasurementComplexItem::exitCoordinate() const {
return this->_route.size() > 0 ? _route.last().value<QGeoCoordinate>()
: QGeoCoordinate();
}
int MeasurementComplexItem::sequenceNumber() const { return _sequenceNumber; }
double MeasurementComplexItem::specifiedFlightSpeed() {
return std::numeric_limits<double>::quiet_NaN();
}
double MeasurementComplexItem::specifiedGimbalYaw() {
return std::numeric_limits<double>::quiet_NaN();
}
double MeasurementComplexItem::specifiedGimbalPitch() {
return std::numeric_limits<double>::quiet_NaN();
}
void MeasurementComplexItem::appendMissionItems(QList<MissionItem *> &items,
QObject *missionItemParent) {
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
if (ready()) {
qCDebug(MeasurementComplexItemLog) << "appendMissionItems()";
int seqNum = this->_sequenceNumber;
MAV_FRAME mavFrame =
followTerrain() ? MAV_FRAME_GLOBAL : MAV_FRAME_GLOBAL_RELATIVE_ALT;
for (const auto &variant : this->_route) {
auto vertex = variant.value<QGeoCoordinate>();
MissionItem *item = new MissionItem(
seqNum++, MAV_CMD_NAV_WAYPOINT, mavFrame,
0, // hold time
0.0, // No acceptance radius specified
0.0, // Pass through waypoint
std::numeric_limits<double>::quiet_NaN(), // Yaw unchanged
vertex.latitude(), vertex.longitude(), vertex.altitude(),
true, // autoContinue
false, // isCurrentItem
missionItemParent);
items.append(item);
}
} else {
qCDebug(MeasurementComplexItemLog)
<< "appendMissionItems(): called while not ready().";
}
}
void MeasurementComplexItem::setMissionFlightStatus(
const MissionController::MissionFlightStatus_t &missionFlightStatus) {
ComplexMissionItem::setMissionFlightStatus(missionFlightStatus);
void MeasurementComplexItem::applyNewAltitude(double newAltitude) {
this->_altitude.setRawValue(newAltitude);
qWarning() << "applyNewAltitude(): impl. missing.";
}
double MeasurementComplexItem::additionalTimeDelay() const { return 0; }
bool MeasurementComplexItem::_setGenerator(PtrGenerator newG) {
if (this->_pGenerator != newG) {
if (this->_pGenerator != nullptr) {
disconnect(this->_pGenerator, &routing::GeneratorBase::generatorChanged,
if (this->_pGenerator != nullptr) {
connect(this->_pGenerator, &routing::GeneratorBase::generatorChanged,
this, &MeasurementComplexItem::_updateRoute);
}
if (!editing()) {
this->_setState(STATE::IDLE);
_updateRoute();
}
return true;
} else {
return false;
}
}
void MeasurementComplexItem::_setState(MeasurementComplexItem::STATE state) {
if (this->_state != state) {
auto oldState = this->_state;
this->_state = state;
if (_calculating(oldState) != _calculating(state)) {
emit calculatingChanged();
}
if (_editing(oldState) != _editing(state)) {
emit editingChanged();
}
if (_ready(oldState) != _ready(state)) {
emit readyChanged();
}
bool MeasurementComplexItem::_calculating(MeasurementComplexItem::STATE state) {
bool MeasurementComplexItem::_editing(MeasurementComplexItem::STATE state) {
return state == STATE::EDITING;
}
bool MeasurementComplexItem::_ready(MeasurementComplexItem::STATE state) {
return state == STATE::IDLE;
}
void MeasurementComplexItem::_setAreaData(
MeasurementComplexItem::PtrAreaData data) {
if (_pCurrentData != data) {
_pCurrentData = data;
emit areaDataChanged();
}
}
void MeasurementComplexItem::_updateRoute() {
if (!editing()) {
// Reset data.
this->_route.clear();
this->_variantVector.clear();
this->_variantNames.clear();
emit variantNamesChanged();
if (this->_pAreaData->isCorrect()) {
// Prepare data.
auto origin = this->_pAreaData->origin();
origin.setAltitude(0);
if (!origin.isValid()) {
<< "_updateWorker(): origin invalid." << origin;
return;
// Convert safe area.
auto serviceArea =
getGeoArea<const SafeArea *>(*this->_pAreaData->areaList());
auto geoSafeArea = serviceArea->coordinateList();
if (!(geoSafeArea.size() >= 3)) {
qCDebug(MeasurementComplexItemLog)
<< "_updateWorker(): safe area invalid." << geoSafeArea;
return;
}
for (auto &v : geoSafeArea) {
if (v.isValid()) {
v.setAltitude(0);
} else {
qCDebug(MeasurementComplexItemLog)
<< "_updateWorker(): safe area contains invalid coordinate."
<< geoSafeArea;
return;
}
}
// Routing par.
RoutingParameter par;
par.numSolutions = 5;
auto &safeAreaENU = par.safeArea;
snake::areaToEnu(origin, geoSafeArea, safeAreaENU);
// Create generator.
if (this->_pGenerator != nullptr) {
routing::GeneratorBase::Generator g; // Transect generator.
if (this->_pGenerator->get(g)) {
// Start/Restart routing worker.
this->_pWorker->route(par, g);
_setState(STATE::ROUTING);
return;
} else {
qCDebug(MeasurementComplexItemLog)
<< "_updateWorker(): generator creation failed.";
return;
}
<< "_updateWorker(): pGenerator == nullptr, number of registered "
"generators: "
<< this->_generatorList.size();
return;
<< "_updateWorker(): plan data invalid.";
return;
void MeasurementComplexItem::_changeVariant() {
if (ready()) {
auto variant = this->_variant.rawValue().toUInt();
// Find old variant and run. Old run corresponts with empty list.
std::size_t old_variant = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < std::size_t(this->_variantVector.size()); ++i) {
const auto &variantCoordinates = this->_variantVector.at(i);
if (variantCoordinates.isEmpty()) {
old_variant = i;
break;
}
// Swap route.
if (variant != old_variant) {
// Swap in new variant.
if (variant < std::size_t(this->_variantVector.size())) {
if (old_variant != std::numeric_limits<std::size_t>::max()) {
// this->_route containes a route, swap it back to
// this->_solutionVector
auto &oldVariantCoordinates = this->_variantVector[old_variant];
oldVariantCoordinates.swap(this->_route);
}
auto &newVariantCoordinates = this->_variantVector[variant];
this->_route.swap(newVariantCoordinates);
emit routeChanged();
} else { // error
qCDebug(MeasurementComplexItemLog)
<< "Variant out of bounds (variant =" << variant << ").";
qCDebug(MeasurementComplexItemLog) << "Resetting variant to zero.";
disconnect(&this->_variant, &Fact::rawValueChanged, this,
&MeasurementComplexItem::_changeVariant);
this->_variant.setCookedValue(QVariant(0));
connect(&this->_variant, &Fact::rawValueChanged, this,
&MeasurementComplexItem::_changeVariant);
if (this->_variantVector.size() > 0) {
this->_changeVariant();
}
void MeasurementComplexItem::_reverseRoute() {
if (ready()) {
if (this->_route.size() > 0) {
auto &t = this->_route;
std::reverse(t.begin(), t.end());
}
emit routeChanged();
bool MeasurementComplexItem::exitCoordinateSameAsEntry() const {
return this->_route.size() > 0 ? this->_route.first() == this->_route.last()
: false;
}
void MeasurementComplexItem::setDirty(bool dirty) {
if (this->_dirty != dirty) {
this->_dirty = dirty;
emit dirtyChanged(this->_dirty);
}
}
void MeasurementComplexItem::setCoordinate(const QGeoCoordinate &coordinate) {
Q_UNUSED(coordinate);
}
void MeasurementComplexItem::setSequenceNumber(int sequenceNumber) {
if (this->_sequenceNumber != sequenceNumber) {
this->_sequenceNumber = sequenceNumber;
emit sequenceNumberChanged(this->_sequenceNumber);
}
}
QString MeasurementComplexItem::patternName() const { return name; }
double MeasurementComplexItem::complexDistance() const {
double d = 0;
if (this->_route.size() > 1) {
auto vertex = _route.first().value<QGeoCoordinate>();
std::for_each(this->_route.cbegin() + 1, this->_route.cend(),
[&vertex, &d](const QVariant &variant) {
auto otherVertex = variant.value<QGeoCoordinate>();
d += vertex.distanceTo(otherVertex);
vertex = otherVertex;
});
}
return d;
}
int MeasurementComplexItem::lastSequenceNumber() const {
return _sequenceNumber + std::max(0, this->_route.size() - 1);
}
bool MeasurementComplexItem::registerGenerator(const QString &name,
routing::GeneratorBase *g) {
qCDebug(MeasurementComplexItemLog)
<< "registerGenerator(): empty name string.";
qCDebug(MeasurementComplexItemLog)
<< "registerGenerator(): empty generator.";
return false;
}
if (this->_generatorNameList.contains(name)) {
qCDebug(MeasurementComplexItemLog) << "registerGenerator(): generator "
"already registered.";
return false;
} else {
this->_generatorNameList.push_back(name);
this->_generatorList.push_back(g);
if (this->_generatorList.size() == 1) {
}
emit generatorNameListChanged();
return true;
}
}
bool MeasurementComplexItem::unregisterGenerator(const QString &name) {
auto index = this->_generatorNameList.indexOf(name);
if (index >= 0) {
// Is this the current generator?
const auto &g = this->_generatorList.at(index);
if (g == this->_pGenerator) {
if (index > 0) {
_setGenerator(this->_generatorList.at(index - 1));
<< "unregisterGenerator(): last generator unregistered.";
}
}
this->_generatorNameList.removeAt(index);
auto gen = this->_generatorList.takeAt(index);
gen->deleteLater();
emit generatorNameListChanged();
return true;
} else {
<< "unregisterGenerator(): generator " << name << " not registered.";
return false;
}
}
bool MeasurementComplexItem::unregisterGenerator(int index) {
if (index > 0 && index < this->_generatorNameList.size()) {
return unregisterGenerator(this->_generatorNameList.at(index));
} else {
qCDebug(MeasurementComplexItemLog)
<< "unregisterGenerator(): index (" << index
<< ") out"
"of bounds ( "
<< this->_generatorList.size() << " ).";
bool MeasurementComplexItem::switchToGenerator(const QString &name) {
auto index = this->_generatorNameList.indexOf(name);
if (index >= 0) {
_setGenerator(this->_generatorList.at(index));
<< "switchToGenerator(): generator " << name << " not registered.";
return false;
}
}
_setGenerator(this->_generatorList.at(index));
<< ") out"
"of bounds ( "
<< this->_generatorNameList.size() << " ).";
routing::GeneratorBase *MeasurementComplexItem::generator() {
return _pGenerator;
}
return this->_generatorList.indexOf(this->_pGenerator);
}
void MeasurementComplexItem::startEditing() {
if (!editing()) {
*_pEditorData = *_pAreaData;
_setAreaData(_pEditorData);
void MeasurementComplexItem::stopEditing() {
if (editing()) {
bool correct = _pEditorData->isCorrect();
if (correct) {
if (correct && *_pEditorData != *_pAreaData) {
void MeasurementComplexItem::abortEditing() {
if (editing()) {
_setAreaData(_pAreaData);
_setState(STATE::IDLE);
}
}
if (this->_state == STATE::ROUTING) {
// Store solutions.
auto ori = this->_pAreaData->origin();
ori.setAltitude(0);
const auto &transectsENU = pRoute->transects;
QVector<Variant> variantVector;
const auto nSolutions = pRoute->solutionVector.size();
for (std::size_t j = 0; j < nSolutions; ++j) {
Variant var;
const auto &solution = pRoute->solutionVector.at(j);
if (solution.size() > 0) {
const auto &route = solution.at(0);
const auto &path = route.path;
const auto &info = route.info;
if (info.size() > 1) {
// Find index of first waypoint.
std::size_t idxFirst = 0;
const auto &infoFirst = info.at(1);
const auto &firstTransect = transectsENU[infoFirst.index];
if (firstTransect.size() > 0) {
const auto &firstWaypoint = infoFirst.reversed
? firstTransect.back()
: firstTransect.front();
double th = 0.01;
for (std::size_t i = 0; i < path.size(); ++i) {
auto dist = bg::distance(path[i], firstWaypoint);
// Find index of last waypoint.
std::size_t idxLast = path.size() - 1;
const auto &infoLast = info.at(info.size() - 2);
const auto &lastTransect = transectsENU[infoLast.index];
if (lastTransect.size() > 0) {
const auto &lastWaypoint = infoLast.reversed
? lastTransect.front()
: lastTransect.back();
for (long i = path.size() - 1; i >= 0; --i) {
auto dist = bg::distance(path[i], lastWaypoint);
if (dist < th) {
idxLast = i;
break;
}
}
// Convert to geo coordinates.
for (std::size_t i = idxFirst; i <= idxLast; ++i) {
auto &vertex = path[i];
QGeoCoordinate c;
snake::fromENU(ori, vertex, c);
var.append(QVariant::fromValue(c));
}
} else {
qCDebug(MeasurementComplexItemLog)
<< "_setTransects(): lastTransect.size() == 0";
}
if (var.size() > 0) {
variantVector.push_back(std::move(var));
}
// Assign routes if no error occured.
if (variantVector.size() > 0) {
// Swap first route to _route.
this->_variantVector.swap(variantVector);
// Add route variant names.
this->_variantNames.clear();
for (std::size_t i = 1; i <= std::size_t(this->_variantVector.size());
++i) {
this->_variantNames.append(QString::number(i));
}
emit variantNamesChanged();
disconnect(&this->_variant, &Fact::rawValueChanged, this,
&MeasurementComplexItem::_changeVariant);
this->_variant.setCookedValue(QVariant(0));
connect(&this->_variant, &Fact::rawValueChanged, this,
&MeasurementComplexItem::_changeVariant);
this->_route.swap(this->_variantVector.first());
this->_setState(STATE::IDLE);
} else {
qCDebug(MeasurementComplexItemLog)
<< "_setTransects(): failed, variantVector empty.";
this->_setState(STATE::IDLE);
}
Fact *MeasurementComplexItem::variant() { return &_variant; }
Fact *MeasurementComplexItem::altitude() { return &this->_altitude; }
return this->_calculating(this->_state);
}
bool MeasurementComplexItem::editing() const { return _editing(this->_state); }
bool MeasurementComplexItem::ready() const { return _ready(this->_state); }
bool MeasurementComplexItem::followTerrain() const { return _followTerrain; }