QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
CorridorScanComplexItem.cc
Go to the documentation of this file.
2#include "JsonParsing.h"
3#include "SettingsManager.h"
4#include "AppSettings.h"
6#include "AppMessages.h"
7#include "QGCApplication.h"
9
10#include <QtCore/QJsonArray>
11
12QGC_LOGGING_CATEGORY(CorridorScanComplexItemLog, "Plan.CorridorScanComplexItemL")
13
14CorridorScanComplexItem::CorridorScanComplexItem(PlanMasterController* masterController, bool flyView, const QString& kmlOrShpFile)
15 : TransectStyleComplexItem (masterController, flyView, settingsGroup)
16 , _entryPointLocation (EntryPointDefaultOrder)
17 , _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CorridorScan.SettingsGroup.json"), this))
18 , _corridorWidthFact (settingsGroup, _metaDataMap[corridorWidthName])
19{
20 _editorQml = "qrc:/qml/QGroundControl/PlanView/CorridorScanEditor.qml";
21
22 // We override the altitude to the mission default
23 if (_cameraCalc.isManualCamera() || !_cameraCalc.valueSetIsDistance()->rawValue().toBool()) {
24 _cameraCalc.distanceToSurface()->setRawValue(SettingsManager::instance()->appSettings()->defaultMissionItemAltitude()->rawValue());
25 }
26
27 connect(&_corridorWidthFact, &Fact::valueChanged, this, &CorridorScanComplexItem::_setDirty);
28 connect(&_corridorPolyline, &QGCMapPolyline::pathChanged, this, &CorridorScanComplexItem::_setDirty);
29
30 connect(&_corridorPolyline, &QGCMapPolyline::dirtyChanged, this, &CorridorScanComplexItem::_polylineDirtyChanged);
31
32 connect(&_corridorPolyline, &QGCMapPolyline::pathChanged, this, &CorridorScanComplexItem::_rebuildCorridorPolygon);
33 connect(&_corridorWidthFact, &Fact::valueChanged, this, &CorridorScanComplexItem::_rebuildCorridorPolygon);
34
35 connect(&_corridorPolyline, &QGCMapPolyline::isValidChanged, this, &CorridorScanComplexItem::_updateWizardMode);
36 connect(&_corridorPolyline, &QGCMapPolyline::traceModeChanged, this, &CorridorScanComplexItem::_updateWizardMode);
37
38 if (!kmlOrShpFile.isEmpty()) {
39 _corridorPolyline.loadKMLOrSHPFile(kmlOrShpFile);
40 _corridorPolyline.setDirty(false);
41 }
42 setDirty(false);
43}
44
45void CorridorScanComplexItem::save(QJsonArray& planItems)
46{
47 QJsonObject saveObject;
48
49 _saveCommon(saveObject);
50 planItems.append(saveObject);
51}
52
53void CorridorScanComplexItem::savePreset(const QString& presetName)
54{
55 QJsonObject saveObject;
56
57 _saveCommon(saveObject);
58 _savePresetJson(presetName, saveObject);
59}
60
61void CorridorScanComplexItem::_saveCommon(QJsonObject& saveObject)
62{
64
65 saveObject[JsonParsing::jsonVersionKey] = 2;
68 saveObject[corridorWidthName] = _corridorWidthFact.rawValue().toDouble();
69 saveObject[_jsonEntryPointKey] = static_cast<int>(_entryPointLocation);
70
71 _corridorPolyline.saveToJson(saveObject);
72}
73
74void CorridorScanComplexItem::loadPreset(const QString& presetName)
75{
76 QString errorString;
77
78 QJsonObject presetObject = _loadPresetJson(presetName);
79 if (!_loadWorker(presetObject, 0, errorString, true /* forPresets */)) {
80 QGC::showAppMessage(QStringLiteral("Internal Error: Preset load failed. Name: %1 Error: %2").arg(presetName).arg(errorString));
81 }
83}
84
85bool CorridorScanComplexItem::_loadWorker(const QJsonObject& complexObject, int sequenceNumber, QString& errorString, bool forPresets)
86{
87 _ignoreRecalc = !forPresets;
88
89 QList<JsonParsing::KeyValidateInfo> keyInfoList = {
90 { JsonParsing::jsonVersionKey, QJsonValue::Double, true },
91 { VisualMissionItem::jsonTypeKey, QJsonValue::String, true },
92 { ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true },
93 { corridorWidthName, QJsonValue::Double, true },
94 { _jsonEntryPointKey, QJsonValue::Double, true },
95 { QGCMapPolyline::jsonPolylineKey, QJsonValue::Array, true },
96 };
97 if (!JsonParsing::validateKeys(complexObject, keyInfoList, errorString)) {
98 _ignoreRecalc = false;
99 return false;
100 }
101
102 QString itemType = complexObject[VisualMissionItem::jsonTypeKey].toString();
103 QString complexType = complexObject[ComplexMissionItem::jsonComplexItemTypeKey].toString();
105 errorString = tr("%1 does not support loading this complex mission item type: %2:%3").arg(qgcApp()->applicationName()).arg(itemType).arg(complexType);
106 _ignoreRecalc = false;
107 return false;
108 }
109
110 int version = complexObject[JsonParsing::jsonVersionKey].toInt();
111 if (version != 2) {
112 errorString = tr("%1 complex item version %2 not supported").arg(jsonComplexItemTypeValue).arg(version);
113 _ignoreRecalc = false;
114 return false;
115 }
116
117 if (!forPresets) {
118 if (!_corridorPolyline.loadFromJson(complexObject, true, errorString)) {
119 _ignoreRecalc = false;
120 return false;
121 }
122 }
123
125
126 if (!_load(complexObject, forPresets, errorString)) {
127 _ignoreRecalc = false;
128 return false;
129 }
130
131 _corridorWidthFact.setRawValue(complexObject[corridorWidthName].toDouble());
132
133 _entryPointLocation = static_cast<EntryPointLocation>(complexObject[_jsonEntryPointKey].toInt());
134
135 _ignoreRecalc = false;
136
138 if (_cameraShots == 0) {
139 // Shot count was possibly not available from plan file
140 _recalcCameraShots();
141 }
142
143 return true;
144}
145
146bool CorridorScanComplexItem::load(const QJsonObject& complexObject, int sequenceNumber, QString& errorString)
147{
148 return _loadWorker(complexObject, sequenceNumber, errorString, false /* forPresets */);
149}
150
152{
153 return _corridorPolyline.count() > 1;
154}
155
156void CorridorScanComplexItem::setCoordinate(const QGeoCoordinate& coordinate)
157{
158 if (!coordinate.isValid() || !_entryCoordinate.isValid() || _corridorPolyline.count() < 2) {
159 return;
160 }
161
162 const double distanceMeters = _entryCoordinate.distanceTo(coordinate);
163 const double azimuthDegrees = _entryCoordinate.azimuthTo(coordinate);
164 const QList<QGeoCoordinate> vertices = _corridorPolyline.coordinateList();
165
166 QList<QGeoCoordinate> translatedVertices;
167 translatedVertices.reserve(vertices.count());
168 for (const QGeoCoordinate& vertex: vertices) {
169 translatedVertices.append(vertex.atDistanceAndAzimuth(distanceMeters, azimuthDegrees));
170 }
171
172 _corridorPolyline.setPath(translatedVertices);
173}
174
175int CorridorScanComplexItem::_calcTransectCount(void) const
176{
177 double fullWidth = _corridorWidthFact.rawValue().toDouble();
178 return fullWidth > 0.0 ? qCeil(fullWidth / _calcTransectSpacing()) : 1;
179}
180
181void CorridorScanComplexItem::_polylineDirtyChanged(bool dirty)
182{
183 if (dirty) {
184 setDirty(true);
185 }
186}
187
189{
190 int modeAsInt = static_cast<int>(_entryPointLocation);
191
192 if (_calcTransectCount() < 2) {
193 // A single transect has no "opposite side of center" so we need to bump by 2 to get to the opposite end of the scan
194 modeAsInt += 2;
195 } else {
196 modeAsInt++;
197 }
198
200 modeAsInt = 0;
201 }
202
203 _entryPointLocation = static_cast<EntryPointLocation>(modeAsInt);
204
206}
207
208void CorridorScanComplexItem::_rebuildCorridorPolygon(void)
209{
210 if (_corridorPolyline.count() < 2) {
212 return;
213 }
214
215 double halfWidth = _corridorWidthFact.rawValue().toDouble() / 2.0;
216
217 QList<QGeoCoordinate> firstSideVertices = _corridorPolyline.offsetPolyline(halfWidth);
218 QList<QGeoCoordinate> secondSideVertices = _corridorPolyline.offsetPolyline(-halfWidth);
219
221
222 QList<QGeoCoordinate> rgCoord;
223 for (const QGeoCoordinate& vertex: firstSideVertices) {
224 rgCoord.append(vertex);
225 }
226 for (int i=secondSideVertices.count() - 1; i >= 0; i--) {
227 rgCoord.append(secondSideVertices[i]);
228 }
230}
231
232void CorridorScanComplexItem::_rebuildTransectsPhase1(void)
233{
234 if (_ignoreRecalc) {
235 return;
236 }
237
238 // If the transects are getting rebuilt then any previsouly loaded mission items are now invalid
240 _loadedMissionItems.clear();
241 _loadedMissionItemsParent->deleteLater();
243 }
244
245 double transectSpacing = _calcTransectSpacing();
246 double fullWidth = _corridorWidthFact.rawValue().toDouble();
247 double halfWidth = fullWidth / 2.0;
248 int transectCount = _calcTransectCount();
249 double normalizedTransectPosition = transectSpacing / 2.0;
250
251 if (_corridorPolyline.count() >= 2) {
252 // First build up the transects all going the same direction
253 //qDebug() << "_rebuildTransectsPhase1";
254 for (int i=0; i<transectCount; i++) {
255 //qDebug() << "start transect";
256 double offsetDistance;
257 if (transectCount == 1) {
258 // Single transect is flown over scan line
259 offsetDistance = 0;
260 } else {
261 // Convert from normalized to absolute transect offset distance
262 offsetDistance = halfWidth - normalizedTransectPosition;
263 }
264
265 // Turn transect into CoordInfo transect
266 QList<TransectStyleComplexItem::CoordInfo_t> transect;
267 QList<QGeoCoordinate> transectCoords = _corridorPolyline.offsetPolyline(offsetDistance);
268 for (int j=1; j<transectCoords.count() - 1; j++) {
269 TransectStyleComplexItem::CoordInfo_t coordInfo = { transectCoords[j], CoordTypeInterior };
270 transect.append(coordInfo);
271 }
272 TransectStyleComplexItem::CoordInfo_t coordInfo = { transectCoords.first(), CoordTypeSurveyEntry };
273 transect.prepend(coordInfo);
274 coordInfo = { transectCoords.last(), CoordTypeSurveyExit };
275 transect.append(coordInfo);
276
277 // Extend the transect ends for turnaround
278 if (_hasTurnaround()) {
279 QGeoCoordinate turnaroundCoord;
281
282 double azimuth = transectCoords[0].azimuthTo(transectCoords[1]);
283 turnaroundCoord = transectCoords[0].atDistanceAndAzimuth(-turnAroundDistance, azimuth);
284 turnaroundCoord.setAltitude(qQNaN());
285 TransectStyleComplexItem::CoordInfo_t turnaroundCoordInfo = { turnaroundCoord, CoordTypeTurnaround };
286 transect.prepend(turnaroundCoordInfo);
287
288 azimuth = transectCoords.last().azimuthTo(transectCoords[transectCoords.count() - 2]);
289 turnaroundCoord = transectCoords.last().atDistanceAndAzimuth(-turnAroundDistance, azimuth);
290 turnaroundCoord.setAltitude(qQNaN());
291 coordInfo = { turnaroundCoord, CoordTypeTurnaround };
292 transect.append(coordInfo);
293 }
294
295#if 0
296 qDebug() << "transect debug";
297 for (const TransectStyleComplexItem::CoordInfo_t& coordInfo: transect) {
298 qDebug() << coordInfo.coordType;
299 }
300#endif
301
302 _transects.append(transect);
303 normalizedTransectPosition += transectSpacing;
304 }
305
306 // Now deal with fixing up the entry point:
307 // 0: Leave alone
308 // 1: Start at same end, opposite side of center
309 // 2: Start at opposite end, same side
310 // 3: Start at opposite end, opposite side
311
312 bool reverseTransects = false;
313 bool reverseVertices = false;
314 switch (_entryPointLocation) {
316 reverseTransects = false;
317 reverseVertices = false;
318 break;
320 reverseTransects = true;
321 reverseVertices = false;
322 break;
324 reverseTransects = false;
325 reverseVertices = true;
326 break;
328 reverseTransects = true;
329 reverseVertices = true;
330 break;
331 }
332 if (reverseTransects) {
333 QList<QList<TransectStyleComplexItem::CoordInfo_t>> reversedTransects;
334 for (const QList<TransectStyleComplexItem::CoordInfo_t>& transect: _transects) {
335 reversedTransects.prepend(transect);
336 }
337 _transects = reversedTransects;
338 }
339 if (reverseVertices) {
340 for (int i=0; i<_transects.count(); i++) {
341 QList<TransectStyleComplexItem::CoordInfo_t> reversedVertices;
342 for (const TransectStyleComplexItem::CoordInfo_t& vertex: _transects[i]) {
343 reversedVertices.prepend(vertex);
344 }
345 _transects[i] = reversedVertices;
346 }
347 }
348
349 // Adjust to lawnmower pattern
350 reverseVertices = false;
351 for (int i=0; i<_transects.count(); i++) {
352 // We must reverse the vertices for every other transect in order to make a lawnmower pattern
353 QList<TransectStyleComplexItem::CoordInfo_t> transectVertices = _transects[i];
354 if (reverseVertices) {
355 reverseVertices = false;
356 QList<TransectStyleComplexItem::CoordInfo_t> reversedVertices;
357 for (int j=transectVertices.count()-1; j>=0; j--) {
358 reversedVertices.append(transectVertices[j]);
359
360 // as we are flying the transect reversed, we also need to swap entry and exit coordinate types
361 if (reversedVertices.last().coordType == CoordTypeSurveyEntry) {
362 reversedVertices.last().coordType = CoordTypeSurveyExit;
363 } else if (reversedVertices.last().coordType == CoordTypeSurveyExit) {
364 reversedVertices.last().coordType = CoordTypeSurveyEntry;
365 }
366 }
367
368 transectVertices = reversedVertices;
369 } else {
370 reverseVertices = true;
371 }
372 _transects[i] = transectVertices;
373 }
374 }
375}
376
377void CorridorScanComplexItem::_recalcCameraShots(void)
378{
380 if (triggerDistance == 0) {
381 _cameraShots = 0;
382 } else {
385 } else {
386 int singleTransectImageCount = qCeil(_corridorPolyline.length() / triggerDistance);
387 _cameraShots = singleTransectImageCount * _calcTransectCount();
388 }
389 }
390 emit cameraShotsChanged();
391}
392
397
402
403double CorridorScanComplexItem::_calcTransectSpacing(void) const
404{
405 double transectSpacing = _cameraCalc.adjustedFootprintSide()->rawValue().toDouble();
406 if (transectSpacing < _minimumTransectSpacingMeters) {
407 // We can't let spacing get too small otherwise we will end up with too many transects.
408 // So we limit the spacing to be above a small increment and below that value we set to huge spacing
409 // which will cause a single transect to be added instead of having things blow up.
410 transectSpacing = _forceLargeTransectSpacingMeters;
411 }
412
413 return transectSpacing;
414}
415
416void CorridorScanComplexItem::_updateWizardMode(void)
417{
418 if (_corridorPolyline.isValid() && !_corridorPolyline.traceMode()) {
419 setWizardMode(false);
420 }
421}
#define qgcApp()
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Fact * adjustedFootprintSide(void)
Definition CameraCalc.h:57
Fact * adjustedFootprintFrontal(void)
Definition CameraCalc.h:58
void _savePresetJson(const QString &name, QJsonObject &presetObject)
static constexpr const char * jsonComplexItemTypeKey
This mission item attribute specifies the type of the complex item.
QJsonObject _loadPresetJson(const QString &name)
bool load(const QJsonObject &complexObject, int sequenceNumber, QString &errorString) final
void savePreset(const QString &name)
void setCoordinate(const QGeoCoordinate &coordinate) final
void save(QJsonArray &planItems) final
static constexpr const char * corridorWidthName
bool specifiesCoordinate(void) const final
void loadPreset(const QString &name)
Q_INVOKABLE void rotateEntryPoint(void)
static constexpr const char * jsonComplexItemTypeValue
ReadyForSaveState readyForSaveState(void) const final
Holds the meta data associated with a Fact.
void setRawValue(const QVariant &value)
Definition Fact.cc:128
QVariant rawValue() const
Value after translation.
Definition Fact.h:85
void valueChanged(const QVariant &value)
This signal is only meant for use by the QT property system. It should not be connected to by client ...
Master controller for mission, fence, rally.
Q_INVOKABLE void clear(void)
Q_INVOKABLE void appendVertices(const QVariantList &varCoords)
QList< QGeoCoordinate > offsetPolyline(double distance)
void dirtyChanged(bool dirty)
static constexpr const char * jsonPolylineKey
void pathChanged(void)
void traceModeChanged(bool traceMode)
bool isValid(void) const
void setPath(const QList< QGeoCoordinate > &path)
int count(void) const
void saveToJson(QJsonObject &json)
QList< QGeoCoordinate > coordinateList(void) const
Returns the path in a list of QGeoCoordinate's format.
void isValidChanged(void)
bool loadFromJson(const QJsonObject &json, bool required, QString &errorString)
double length(void) const
Returns the length of the polyline in meters.
bool traceMode(void) const
Provides access to group of settings.
static SettingsManager * instance()
static constexpr double _minimumTransectSpacingMeters
QObject * _loadedMissionItemsParent
Parent for all items in _loadedMissionItems for simpler delete.
QList< QList< CoordInfo_t > > _transects
QList< MissionItem * > _loadedMissionItems
Mission items loaded from plan file.
void setSequenceNumber(int sequenceNumber) final
QGeoCoordinate coordinate(void) const final
int sequenceNumber(void) const final
ReadyForSaveState readyForSaveState(void) const override
bool _load(const QJsonObject &complexObject, bool forPresets, QString &errorString)
static constexpr double _forceLargeTransectSpacingMeters
void _save(QJsonObject &saveObject)
@ CoordTypeTurnaround
Turnaround extension waypoint.
@ CoordTypeSurveyExit
Waypoint at exit edge of survey polygon.
@ CoordTypeInterior
Interior waypoint for flight path only (example: interior corridor point)
@ CoordTypeSurveyEntry
Waypoint at entry edge of survey polygon.
static constexpr const char * jsonTypeComplexItemValue
Item type is Complex Item.
void setWizardMode(bool wizardMode)
static constexpr const char * jsonTypeKey
Json file attribute which specifies the item type.
double azimuth(void) const
bool validateKeys(const QJsonObject &jsonObject, const QList< KeyValidateInfo > &keyInfo, QString &errorString)
Validates that all required keys are present and that listed keys have the expected type.
constexpr const char * jsonVersionKey
Definition JsonParsing.h:12
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9