CorridorScanComplexItem.cc 15.3 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#include "CorridorScanComplexItem.h"
#include "JsonHelper.h"
#include "MissionController.h"
#include "QGCGeo.h"
#include "QGCQGeoCoordinate.h"
#include "SettingsManager.h"
#include "AppSettings.h"
#include "QGCQGeoCoordinate.h"
18
#include "PlanMasterController.h"
19
#include "QGCApplication.h"
20 21 22 23 24

#include <QPolygonF>

QGC_LOGGING_CATEGORY(CorridorScanComplexItemLog, "CorridorScanComplexItemLog")

25 26
const char* CorridorScanComplexItem::settingsGroup =            "CorridorScan";
const char* CorridorScanComplexItem::corridorWidthName =        "CorridorWidth";
27
const char* CorridorScanComplexItem::_jsonEntryPointKey =       "EntryPoint";
28

29
const char* CorridorScanComplexItem::jsonComplexItemTypeValue = "CorridorScan";
30

31 32
CorridorScanComplexItem::CorridorScanComplexItem(PlanMasterController* masterController, bool flyView, const QString& kmlFile, QObject* parent)
    : TransectStyleComplexItem  (masterController, flyView, settingsGroup, parent)
33 34 35
    , _entryPoint               (0)
    , _metaDataMap              (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/CorridorScan.SettingsGroup.json"), this))
    , _corridorWidthFact        (settingsGroup, _metaDataMap[corridorWidthName])
36 37 38
{
    _editorQml = "qrc:/qml/CorridorScanEditor.qml";

DonLakeFlyer's avatar
DonLakeFlyer committed
39 40 41 42 43
    // We override the altitude to the mission default
    if (_cameraCalc.isManualCamera() || !_cameraCalc.valueSetIsDistance()->rawValue().toBool()) {
        _cameraCalc.distanceToSurface()->setRawValue(qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue());
    }

DoinLakeFlyer's avatar
DoinLakeFlyer committed
44 45
    connect(&_corridorWidthFact,    &Fact::valueChanged,                            this, &CorridorScanComplexItem::_setDirty);
    connect(&_corridorPolyline,     &QGCMapPolyline::pathChanged,                   this, &CorridorScanComplexItem::_setDirty);
46

DoinLakeFlyer's avatar
DoinLakeFlyer committed
47 48
    connect(&_cameraCalc,           &CameraCalc::distanceToSurfaceRelativeChanged,  this, &CorridorScanComplexItem::coordinateHasRelativeAltitudeChanged);
    connect(&_cameraCalc,           &CameraCalc::distanceToSurfaceRelativeChanged,  this, &CorridorScanComplexItem::exitCoordinateHasRelativeAltitudeChanged);
49

DoinLakeFlyer's avatar
DoinLakeFlyer committed
50
    connect(&_corridorPolyline,     &QGCMapPolyline::dirtyChanged,                  this, &CorridorScanComplexItem::_polylineDirtyChanged);
51

DoinLakeFlyer's avatar
DoinLakeFlyer committed
52 53
    connect(&_corridorPolyline,     &QGCMapPolyline::pathChanged,                   this, &CorridorScanComplexItem::_rebuildCorridorPolygon);
    connect(&_corridorWidthFact,    &Fact::valueChanged,                            this, &CorridorScanComplexItem::_rebuildCorridorPolygon);
54

DoinLakeFlyer's avatar
DoinLakeFlyer committed
55 56
    connect(&_corridorPolyline,     &QGCMapPolyline::isValidChanged,                this, &CorridorScanComplexItem::_updateWizardMode);
    connect(&_corridorPolyline,     &QGCMapPolyline::traceModeChanged,              this, &CorridorScanComplexItem::_updateWizardMode);
DonLakeFlyer's avatar
DonLakeFlyer committed
57

58 59 60 61 62
    if (!kmlFile.isEmpty()) {
        _corridorPolyline.loadKMLFile(kmlFile);
        _corridorPolyline.setDirty(false);
    }
    setDirty(false);
63 64
}

65
void CorridorScanComplexItem::save(QJsonArray&  planItems)
66 67 68
{
    QJsonObject saveObject;

Don Gagne's avatar
Don Gagne committed
69
    TransectStyleComplexItem::_save(saveObject);
70 71

    saveObject[JsonHelper::jsonVersionKey] =                    2;
72 73
    saveObject[VisualMissionItem::jsonTypeKey] =                VisualMissionItem::jsonTypeComplexItemValue;
    saveObject[ComplexMissionItem::jsonComplexItemTypeKey] =    jsonComplexItemTypeValue;
74
    saveObject[corridorWidthName] =                             _corridorWidthFact.rawValue().toDouble();
75
    saveObject[_jsonEntryPointKey] =                            _entryPoint;
76 77 78

    _corridorPolyline.saveToJson(saveObject);

79
    planItems.append(saveObject);
80 81 82 83
}

bool CorridorScanComplexItem::load(const QJsonObject& complexObject, int sequenceNumber, QString& errorString)
{
84 85 86
    // We don't recalc while loading since all the information we need is specified in the file
    _ignoreRecalc = true;

87 88 89 90
    QList<JsonHelper::KeyValidateInfo> keyInfoList = {
        { JsonHelper::jsonVersionKey,                   QJsonValue::Double, true },
        { VisualMissionItem::jsonTypeKey,               QJsonValue::String, true },
        { ComplexMissionItem::jsonComplexItemTypeKey,   QJsonValue::String, true },
91
        { corridorWidthName,                            QJsonValue::Double, true },
92
        { _jsonEntryPointKey,                           QJsonValue::Double, true },
93 94 95
        { QGCMapPolyline::jsonPolylineKey,              QJsonValue::Array,  true },
    };
    if (!JsonHelper::validateKeys(complexObject, keyInfoList, errorString)) {
96
        _ignoreRecalc = false;
97 98 99
        return false;
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
100
    if (!_corridorPolyline.loadFromJson(complexObject, true, errorString)) {
101
        _ignoreRecalc = false;
DonLakeFlyer's avatar
DonLakeFlyer committed
102 103
        return false;
    }
104 105 106 107 108

    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);
109
        _ignoreRecalc = false;
110 111 112 113
        return false;
    }

    int version = complexObject[JsonHelper::jsonVersionKey].toInt();
114
    if (version != 2) {
115
        errorString = tr("%1 complex item version %2 not supported").arg(jsonComplexItemTypeValue).arg(version);
116
        _ignoreRecalc = false;
117 118 119 120 121
        return false;
    }

    setSequenceNumber(sequenceNumber);

Don Gagne's avatar
Don Gagne committed
122
    if (!_load(complexObject, false /* forPresets */, errorString)) {
123
        _ignoreRecalc = false;
124 125 126
        return false;
    }

127 128
    _corridorWidthFact.setRawValue      (complexObject[corridorWidthName].toDouble());

129
    _entryPoint = complexObject[_jsonEntryPointKey].toInt();
130

131 132
    _ignoreRecalc = false;

133 134 135 136 137 138
    _recalcComplexDistance();
    if (_cameraShots == 0) {
        // Shot count was possibly not available from plan file
        _recalcCameraShots();
    }

139 140 141 142 143 144 145 146
    return true;
}

bool CorridorScanComplexItem::specifiesCoordinate(void) const
{
    return _corridorPolyline.count() > 1;
}

147
int CorridorScanComplexItem::_calcTransectCount(void) const
148 149
{
    double fullWidth = _corridorWidthFact.rawValue().toDouble();
150
    return fullWidth > 0.0 ? qCeil(fullWidth / _calcTransectSpacing()) : 1;
151 152
}

153 154
void CorridorScanComplexItem::applyNewAltitude(double newAltitude)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
155 156 157
    _cameraCalc.valueSetIsDistance()->setRawValue(true);
    _cameraCalc.distanceToSurface()->setRawValue(newAltitude);
    _cameraCalc.setDistanceToSurfaceRelative(true);
158 159 160 161 162 163 164 165 166 167 168
}

void CorridorScanComplexItem::_polylineDirtyChanged(bool dirty)
{
    if (dirty) {
        setDirty(true);
    }
}

void CorridorScanComplexItem::rotateEntryPoint(void)
{
169 170 171
    _entryPoint++;
    if (_entryPoint > 3) {
        _entryPoint = 0;
172
    }
173

174
    _rebuildTransects();
175 176 177 178 179
}

void CorridorScanComplexItem::_rebuildCorridorPolygon(void)
{
    if (_corridorPolyline.count() < 2) {
180
        _surveyAreaPolygon.clear();
181 182 183 184 185 186 187 188
        return;
    }

    double halfWidth = _corridorWidthFact.rawValue().toDouble() / 2.0;

    QList<QGeoCoordinate> firstSideVertices = _corridorPolyline.offsetPolyline(halfWidth);
    QList<QGeoCoordinate> secondSideVertices = _corridorPolyline.offsetPolyline(-halfWidth);

189
    _surveyAreaPolygon.clear();
DonLakeFlyer's avatar
DonLakeFlyer committed
190 191

    QList<QGeoCoordinate> rgCoord;
192
    for (const QGeoCoordinate& vertex: firstSideVertices) {
DonLakeFlyer's avatar
DonLakeFlyer committed
193
        rgCoord.append(vertex);
194 195
    }
    for (int i=secondSideVertices.count() - 1; i >= 0; i--) {
DonLakeFlyer's avatar
DonLakeFlyer committed
196
        rgCoord.append(secondSideVertices[i]);
197
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
198
    _surveyAreaPolygon.appendVertices(rgCoord);
199 200
}

201
void CorridorScanComplexItem::_rebuildTransectsPhase1(void)
202
{
203 204 205 206 207 208 209 210
    if (_ignoreRecalc) {
        return;
    }

    // If the transects are getting rebuilt then any previsouly loaded mission items are now invalid
    if (_loadedMissionItemsParent) {
        _loadedMissionItems.clear();
        _loadedMissionItemsParent->deleteLater();
211
        _loadedMissionItemsParent = nullptr;
212 213
    }

214
    _transects.clear();
215
    _transectsPathHeightInfo.clear();
216

217
    double transectSpacing = _calcTransectSpacing();
218 219
    double fullWidth = _corridorWidthFact.rawValue().toDouble();
    double halfWidth = fullWidth / 2.0;
220
    int transectCount = _calcTransectCount();
221 222
    double normalizedTransectPosition = transectSpacing / 2.0;

223 224
    if (_corridorPolyline.count() >= 2) {
        // First build up the transects all going the same direction
225
        //qDebug() << "_rebuildTransectsPhase1";
226
        for (int i=0; i<transectCount; i++) {
227
            //qDebug() << "start transect";
228 229 230 231 232 233 234 235 236
            double offsetDistance;
            if (transectCount == 1) {
                // Single transect is flown over scan line
                offsetDistance = 0;
            } else {
                // Convert from normalized to absolute transect offset distance
                offsetDistance = halfWidth - normalizedTransectPosition;
            }

237 238 239 240 241 242 243
            // Turn transect into CoordInfo transect
            QList<TransectStyleComplexItem::CoordInfo_t> transect;
            QList<QGeoCoordinate> transectCoords = _corridorPolyline.offsetPolyline(offsetDistance);
            for (int j=1; j<transectCoords.count() - 1; j++) {
                TransectStyleComplexItem::CoordInfo_t coordInfo = { transectCoords[j], CoordTypeInterior };
                transect.append(coordInfo);
            }
244
            TransectStyleComplexItem::CoordInfo_t coordInfo = { transectCoords.first(), CoordTypeSurveyEntry };
245
            transect.prepend(coordInfo);
246
            coordInfo = { transectCoords.last(), CoordTypeSurveyExit };
247 248 249
            transect.append(coordInfo);

            // Extend the transect ends for turnaround
250
            if (_hasTurnaround()) {
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
                 QGeoCoordinate turnaroundCoord;
                 double turnAroundDistance = _turnAroundDistanceFact.rawValue().toDouble();

                 double azimuth = transectCoords[0].azimuthTo(transectCoords[1]);
                 turnaroundCoord = transectCoords[0].atDistanceAndAzimuth(-turnAroundDistance, azimuth);
                 turnaroundCoord.setAltitude(qQNaN());
                 TransectStyleComplexItem::CoordInfo_t coordInfo = { turnaroundCoord, CoordTypeTurnaround };
                 transect.prepend(coordInfo);

                 azimuth = transectCoords.last().azimuthTo(transectCoords[transectCoords.count() - 2]);
                 turnaroundCoord = transectCoords.last().atDistanceAndAzimuth(-turnAroundDistance, azimuth);
                 turnaroundCoord.setAltitude(qQNaN());
                 coordInfo = { turnaroundCoord, CoordTypeTurnaround };
                 transect.append(coordInfo);
            }

#if 0
            qDebug() << "transect debug";
269
            for (const TransectStyleComplexItem::CoordInfo_t& coordInfo: transect) {
270
                qDebug() << coordInfo.coordType;
271
            }
272
#endif
273

274
            _transects.append(transect);
275
            normalizedTransectPosition += transectSpacing;
276 277
        }

278 279 280 281 282 283 284 285 286 287 288
        // Now deal with fixing up the entry point:
        //  0: Leave alone
        //  1: Start at same end, opposite side of center
        //  2: Start at opposite end, same side
        //  3: Start at opposite end, opposite side

        bool reverseTransects = false;
        bool reverseVertices = false;
        switch (_entryPoint) {
        case 0:
            reverseTransects = false;
289
            reverseVertices = false;
290 291 292 293 294 295 296 297 298 299 300
            break;
        case 1:
            reverseTransects = true;
            reverseVertices = false;
            break;
        case 2:
            reverseTransects = false;
            reverseVertices = true;
            break;
        case 3:
            reverseTransects = true;
301
            reverseVertices = true;
302
            break;
303
        }
304
        if (reverseTransects) {
305
            QList<QList<TransectStyleComplexItem::CoordInfo_t>> reversedTransects;
306
            for (const QList<TransectStyleComplexItem::CoordInfo_t>& transect: _transects) {
307 308
                reversedTransects.prepend(transect);
            }
309
            _transects = reversedTransects;
310 311
        }
        if (reverseVertices) {
312 313
            for (int i=0; i<_transects.count(); i++) {
                QList<TransectStyleComplexItem::CoordInfo_t> reversedVertices;
314
                for (const TransectStyleComplexItem::CoordInfo_t& vertex: _transects[i]) {
315 316
                    reversedVertices.prepend(vertex);
                }
317
                _transects[i] = reversedVertices;
318
            }
319 320
        }

321
        // Adjust to lawnmower pattern
322
        reverseVertices = false;
323
        for (int i=0; i<_transects.count(); i++) {
324
            // We must reverse the vertices for every other transect in order to make a lawnmower pattern
325
            QList<TransectStyleComplexItem::CoordInfo_t> transectVertices = _transects[i];
326 327
            if (reverseVertices) {
                reverseVertices = false;
328
                QList<TransectStyleComplexItem::CoordInfo_t> reversedVertices;
329 330 331 332 333 334 335
                for (int j=transectVertices.count()-1; j>=0; j--) {
                    reversedVertices.append(transectVertices[j]);
                }
                transectVertices = reversedVertices;
            } else {
                reverseVertices = true;
            }
336
            _transects[i] = transectVertices;
337
        }
338
    }
339
}
340

341
void CorridorScanComplexItem::_recalcComplexDistance(void)
342
{
343
    _complexDistance = 0;
344
    for (int i=0; i<_visualTransectPoints.count() - 1; i++) {
345
        _complexDistance += _visualTransectPoints[i].value<QGeoCoordinate>().distanceTo(_visualTransectPoints[i+1].value<QGeoCoordinate>());
346
    }
347 348
    emit complexDistanceChanged();
}
349

350 351
void CorridorScanComplexItem::_recalcCameraShots(void)
{
352 353 354
    double triggerDistance = _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble();
    if (triggerDistance == 0) {
        _cameraShots = 0;
355
    } else {
356 357 358 359
        if (_cameraTriggerInTurnAroundFact.rawValue().toBool()) {
            _cameraShots = qCeil(_complexDistance / triggerDistance);
        } else {
            int singleTransectImageCount = qCeil(_corridorPolyline.length() / triggerDistance);
360
            _cameraShots = singleTransectImageCount * _calcTransectCount();
361
        }
362 363 364 365
    }
    emit cameraShotsChanged();
}

DonLakeFlyer's avatar
DonLakeFlyer committed
366
CorridorScanComplexItem::ReadyForSaveState CorridorScanComplexItem::readyForSaveState(void) const
367
{
DonLakeFlyer's avatar
DonLakeFlyer committed
368
    return TransectStyleComplexItem::readyForSaveState();
369
}
370 371 372

double CorridorScanComplexItem::timeBetweenShots(void)
{
373
    return _cruiseSpeed == 0 ? 0 : _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble() / _cruiseSpeed;
374
}
375

376
double CorridorScanComplexItem::_calcTransectSpacing(void) const
377 378 379 380 381 382 383 384 385 386 387
{
    double transectSpacing = _cameraCalc.adjustedFootprintSide()->rawValue().toDouble();
    if (transectSpacing < 0.5) {
        // We can't let spacing get too small otherwise we will end up with too many transects.
        // So we limit to 0.5 meter spacing as min and set to huge value which will cause a single
        // transect to be added.
        transectSpacing = 100000;
    }

    return transectSpacing;
}
DoinLakeFlyer's avatar
DoinLakeFlyer committed
388 389 390 391 392 393 394

void CorridorScanComplexItem::_updateWizardMode(void)
{
    if (_corridorPolyline.isValid() && !_corridorPolyline.traceMode()) {
        setWizardMode(false);
    }
}