CorridorScanComplexItem.cc 21.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * 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 "QGroundControlQmlGlobal.h"
#include "QGCQGeoCoordinate.h"
#include "SettingsManager.h"
#include "AppSettings.h"
#include "QGCQGeoCoordinate.h"

#include <QPolygonF>

QGC_LOGGING_CATEGORY(CorridorScanComplexItemLog, "CorridorScanComplexItemLog")

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

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

CorridorScanComplexItem::CorridorScanComplexItem(Vehicle* vehicle, QObject* parent)
31
    : TransectStyleComplexItem  (vehicle, settingsGroup, parent)
32
    , _ignoreRecalc             (false)
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());
    }

44 45 46
    connect(&_corridorWidthFact,    &Fact::valueChanged,                                this, &CorridorScanComplexItem::_setDirty);
    connect(&_corridorPolyline,     &QGCMapPolyline::pathChanged,                       this, &CorridorScanComplexItem::_setDirty);

47 48
    connect(&_cameraCalc,           &CameraCalc::distanceToSurfaceRelativeChanged, this, &CorridorScanComplexItem::coordinateHasRelativeAltitudeChanged);
    connect(&_cameraCalc,           &CameraCalc::distanceToSurfaceRelativeChanged, this, &CorridorScanComplexItem::exitCoordinateHasRelativeAltitudeChanged);
49

50
    connect(&_corridorPolyline,     &QGCMapPolyline::dirtyChanged,  this, &CorridorScanComplexItem::_polylineDirtyChanged);
51

52 53
    connect(&_corridorPolyline,     &QGCMapPolyline::pathChanged,   this, &CorridorScanComplexItem::_rebuildCorridorPolygon);
    connect(&_corridorWidthFact,    &Fact::valueChanged,            this, &CorridorScanComplexItem::_rebuildCorridorPolygon);
54 55
}

56
void CorridorScanComplexItem::save(QJsonArray&  planItems)
57 58 59
{
    QJsonObject saveObject;

60 61 62
    _save(saveObject);

    saveObject[JsonHelper::jsonVersionKey] =                    2;
63 64
    saveObject[VisualMissionItem::jsonTypeKey] =                VisualMissionItem::jsonTypeComplexItemValue;
    saveObject[ComplexMissionItem::jsonComplexItemTypeKey] =    jsonComplexItemTypeValue;
65
    saveObject[corridorWidthName] =                             _corridorWidthFact.rawValue().toDouble();
66
    saveObject[_jsonEntryPointKey] =                            _entryPoint;
67 68 69 70 71 72 73

    QJsonObject cameraCalcObject;
    _cameraCalc.save(cameraCalcObject);
    saveObject[_jsonCameraCalcKey] = cameraCalcObject;

    _corridorPolyline.saveToJson(saveObject);

74
    planItems.append(saveObject);
75 76 77 78
}

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

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

DonLakeFlyer's avatar
DonLakeFlyer committed
95
    if (!_corridorPolyline.loadFromJson(complexObject, true, errorString)) {
96
        _ignoreRecalc = false;
DonLakeFlyer's avatar
DonLakeFlyer committed
97 98
        return false;
    }
99 100 101 102 103

    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);
104
        _ignoreRecalc = false;
105 106 107 108
        return false;
    }

    int version = complexObject[JsonHelper::jsonVersionKey].toInt();
109
    if (version != 2) {
110
        errorString = tr("%1 complex item version %2 not supported").arg(jsonComplexItemTypeValue).arg(version);
111
        _ignoreRecalc = false;
112 113 114 115 116
        return false;
    }

    setSequenceNumber(sequenceNumber);

117
    if (!_load(complexObject, errorString)) {
118
        _ignoreRecalc = false;
119 120 121
        return false;
    }

122 123
    _corridorWidthFact.setRawValue      (complexObject[corridorWidthName].toDouble());

124
    _entryPoint = complexObject[_jsonEntryPointKey].toInt();
125

126
    _rebuildTransects();
127

128 129
    _ignoreRecalc = false;

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
    return true;
}

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

int CorridorScanComplexItem::_transectCount(void) const
{
    double transectSpacing = _cameraCalc.adjustedFootprintSide()->rawValue().toDouble();
    double fullWidth = _corridorWidthFact.rawValue().toDouble();
    return fullWidth > 0.0 ? qCeil(fullWidth / transectSpacing) : 1;
}

145 146 147 148 149 150 151 152 153 154 155 156 157 158
void CorridorScanComplexItem::_appendLoadedMissionItems(QList<MissionItem*>& items, QObject* missionItemParent)
{
    qCDebug(CorridorScanComplexItemLog) << "_appendLoadedMissionItems";

    int seqNum = _sequenceNumber;

    foreach (const MissionItem* loadedMissionItem, _loadedMissionItems) {
        MissionItem* item = new MissionItem(*loadedMissionItem, missionItemParent);
        item->setSequenceNumber(seqNum++);
        items.append(item);
    }
}

void CorridorScanComplexItem::_buildAndAppendMissionItems(QList<MissionItem*>& items, QObject* missionItemParent)
159
{
160 161
    qCDebug(CorridorScanComplexItemLog) << "_buildAndAppendMissionItems";

162 163 164 165 166 167
    // Now build the mission items from the transect points

    MissionItem* item;
    int seqNum =                    _sequenceNumber;
    bool imagesEverywhere =         _cameraTriggerInTurnAroundFact.rawValue().toBool();
    bool addTriggerAtBeginning =    imagesEverywhere;
168
    bool firstOverallPoint =        true;
169 170 171

    MAV_FRAME mavFrame = followTerrain() || !_cameraCalc.distanceToSurfaceRelative() ? MAV_FRAME_GLOBAL : MAV_FRAME_GLOBAL_RELATIVE_ALT;

172 173 174 175 176 177 178
    //qDebug() << "_buildAndAppendMissionItems";
    foreach (const QList<TransectStyleComplexItem::CoordInfo_t>& transect, _transects) {
        bool entryPoint = true;

        //qDebug() << "start transect";
        foreach (const CoordInfo_t& transectCoordInfo, transect) {
            //qDebug() << transectCoordInfo.coordType;
179

180
            item = new MissionItem(seqNum++,
181 182 183 184 185 186 187 188 189 190 191
                                   MAV_CMD_NAV_WAYPOINT,
                                   mavFrame,
                                   0,                                          // No hold time
                                   0.0,                                        // No acceptance radius specified
                                   0.0,                                        // Pass through waypoint
                                   std::numeric_limits<double>::quiet_NaN(),   // Yaw unchanged
                                   transectCoordInfo.coord.latitude(),
                                   transectCoordInfo.coord.longitude(),
                                   transectCoordInfo.coord.altitude(),
                                   true,                                       // autoContinue
                                   false,                                      // isCurrentItem
192
                                   missionItemParent);
193 194
            items.append(item);

195
            if (firstOverallPoint && addTriggerAtBeginning) {
196
                // Start triggering
197
                addTriggerAtBeginning = false;
198 199 200 201 202 203 204 205 206 207 208 209
                item = new MissionItem(seqNum++,
                                       MAV_CMD_DO_SET_CAM_TRIGG_DIST,
                                       MAV_FRAME_MISSION,
                                       _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble(),   // trigger distance
                                       0,                                                               // shutter integration (ignore)
                                       1,                                                               // trigger immediately when starting
                                       0, 0, 0, 0,                                                      // param 4-7 unused
                                       true,                                                            // autoContinue
                                       false,                                                           // isCurrentItem
                                       missionItemParent);
                items.append(item);
            }
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
            firstOverallPoint = false;

            if (transectCoordInfo.coordType == TransectStyleComplexItem::CoordTypeSurveyEdge && !imagesEverywhere) {
                if (entryPoint) {
                    // Start of transect, start triggering
                    item = new MissionItem(seqNum++,
                                           MAV_CMD_DO_SET_CAM_TRIGG_DIST,
                                           MAV_FRAME_MISSION,
                                           _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble(),   // trigger distance
                                           0,                                                               // shutter integration (ignore)
                                           1,                                                               // trigger immediately when starting
                                           0, 0, 0, 0,                                                      // param 4-7 unused
                                           true,                                                            // autoContinue
                                           false,                                                           // isCurrentItem
                                           missionItemParent);
                    items.append(item);
                } else {
                    // End of transect, stop triggering
                    item = new MissionItem(seqNum++,
                                           MAV_CMD_DO_SET_CAM_TRIGG_DIST,
                                           MAV_FRAME_MISSION,
                                           0,           // stop triggering
                                           0,           // shutter integration (ignore)
                                           0,           // trigger immediately when starting
                                           0, 0, 0, 0,  // param 4-7 unused
                                           true,        // autoContinue
                                           false,       // isCurrentItem
                                           missionItemParent);
                    items.append(item);
                }
                entryPoint = !entryPoint;
            }
242
        }
243
    }
244 245

    if (imagesEverywhere) {
246
        // Stop triggering
247 248 249 250 251 252 253 254 255 256 257 258
        MissionItem* item = new MissionItem(seqNum++,
                                            MAV_CMD_DO_SET_CAM_TRIGG_DIST,
                                            MAV_FRAME_MISSION,
                                            0,           // stop triggering
                                            0,           // shutter integration (ignore)
                                            0,           // trigger immediately when starting
                                            0, 0, 0, 0,  // param 4-7 unused
                                            true,        // autoContinue
                                            false,       // isCurrentItem
                                            missionItemParent);
        items.append(item);
    }
259 260
}

261 262 263 264 265 266 267 268 269 270 271
void CorridorScanComplexItem::appendMissionItems(QList<MissionItem*>& items, QObject* missionItemParent)
{
    if (_loadedMissionItems.count()) {
        // We have mission items from the loaded plan, use those
        _appendLoadedMissionItems(items, missionItemParent);
    } else {
        // Build the mission items on the fly
        _buildAndAppendMissionItems(items, missionItemParent);
    }
}

272 273
void CorridorScanComplexItem::applyNewAltitude(double newAltitude)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
274 275 276
    _cameraCalc.valueSetIsDistance()->setRawValue(true);
    _cameraCalc.distanceToSurface()->setRawValue(newAltitude);
    _cameraCalc.setDistanceToSurfaceRelative(true);
277 278 279 280 281 282 283 284 285 286 287
}

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

void CorridorScanComplexItem::rotateEntryPoint(void)
{
288 289 290
    _entryPoint++;
    if (_entryPoint > 3) {
        _entryPoint = 0;
291
    }
292

293
    _rebuildTransects();
294 295 296 297 298
}

void CorridorScanComplexItem::_rebuildCorridorPolygon(void)
{
    if (_corridorPolyline.count() < 2) {
299
        _surveyAreaPolygon.clear();
300 301 302 303 304 305 306 307
        return;
    }

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

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

308
    _surveyAreaPolygon.clear();
309
    foreach (const QGeoCoordinate& vertex, firstSideVertices) {
310
        _surveyAreaPolygon.appendVertex(vertex);
311 312
    }
    for (int i=secondSideVertices.count() - 1; i >= 0; i--) {
313
        _surveyAreaPolygon.appendVertex(secondSideVertices[i]);
314 315 316
    }
}

317
void CorridorScanComplexItem::_rebuildTransectsPhase1(void)
318
{
319 320 321 322 323 324 325 326 327 328 329
    if (_ignoreRecalc) {
        return;
    }

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

330
    _transects.clear();
331
    _transectsPathHeightInfo.clear();
332 333 334 335 336 337 338

    double transectSpacing = _cameraCalc.adjustedFootprintSide()->rawValue().toDouble();
    double fullWidth = _corridorWidthFact.rawValue().toDouble();
    double halfWidth = fullWidth / 2.0;
    int transectCount = _transectCount();
    double normalizedTransectPosition = transectSpacing / 2.0;

339 340
    if (_corridorPolyline.count() >= 2) {
        // First build up the transects all going the same direction
341
        //qDebug() << "_rebuildTransectsPhase1";
342
        for (int i=0; i<transectCount; i++) {
343
            //qDebug() << "start transect";
344 345 346 347 348 349 350 351 352
            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;
            }

353 354 355 356 357 358 359 360 361 362 363 364 365
            // 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);
            }
            TransectStyleComplexItem::CoordInfo_t coordInfo = { transectCoords.first(), CoordTypeSurveyEdge };
            transect.prepend(coordInfo);
            coordInfo = { transectCoords.last(), CoordTypeSurveyEdge };
            transect.append(coordInfo);

            // Extend the transect ends for turnaround
366
            if (_hasTurnaround()) {
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
                 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";
            foreach (const TransectStyleComplexItem::CoordInfo_t& coordInfo, transect) {
                qDebug() << coordInfo.coordType;
387
            }
388
#endif
389

390
            _transects.append(transect);
391
            normalizedTransectPosition += transectSpacing;
392 393
        }

394 395 396 397 398 399 400 401 402 403 404
        // 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;
405
            reverseVertices = false;
406 407 408 409 410 411 412 413 414 415 416
            break;
        case 1:
            reverseTransects = true;
            reverseVertices = false;
            break;
        case 2:
            reverseTransects = false;
            reverseVertices = true;
            break;
        case 3:
            reverseTransects = true;
417
            reverseVertices = true;
418
            break;
419
        }
420
        if (reverseTransects) {
421 422
            QList<QList<TransectStyleComplexItem::CoordInfo_t>> reversedTransects;
            foreach (const QList<TransectStyleComplexItem::CoordInfo_t>& transect, _transects) {
423 424
                reversedTransects.prepend(transect);
            }
425
            _transects = reversedTransects;
426 427
        }
        if (reverseVertices) {
428 429 430
            for (int i=0; i<_transects.count(); i++) {
                QList<TransectStyleComplexItem::CoordInfo_t> reversedVertices;
                foreach (const TransectStyleComplexItem::CoordInfo_t& vertex, _transects[i]) {
431 432
                    reversedVertices.prepend(vertex);
                }
433
                _transects[i] = reversedVertices;
434
            }
435 436
        }

437
        // Adjust to lawnmower pattern
438
        reverseVertices = false;
439
        for (int i=0; i<_transects.count(); i++) {
440
            // We must reverse the vertices for every other transect in order to make a lawnmower pattern
441
            QList<TransectStyleComplexItem::CoordInfo_t> transectVertices = _transects[i];
442 443
            if (reverseVertices) {
                reverseVertices = false;
444
                QList<TransectStyleComplexItem::CoordInfo_t> reversedVertices;
445 446 447 448 449 450 451
                for (int j=transectVertices.count()-1; j>=0; j--) {
                    reversedVertices.append(transectVertices[j]);
                }
                transectVertices = reversedVertices;
            } else {
                reverseVertices = true;
            }
452
            _transects[i] = transectVertices;
453
        }
454
    }
455
}
456

457 458
void CorridorScanComplexItem::_rebuildTransectsPhase2(void)
{
459 460
    // Calculate distance flown for complex item
    _complexDistance = 0;
461 462
    for (int i=0; i<_visualTransectPoints.count() - 2; i++) {
        _complexDistance += _visualTransectPoints[i].value<QGeoCoordinate>().distanceTo(_visualTransectPoints[i+1].value<QGeoCoordinate>());
463 464
    }

465
    if (_cameraTriggerInTurnAroundFact.rawValue().toBool()) {
466
        _cameraShots = qCeil(_complexDistance / _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble());
467 468 469
    } else {
        int singleTransectImageCount = qCeil(_corridorPolyline.length() / _cameraCalc.adjustedFootprintFrontal()->rawValue().toDouble());
        _cameraShots = singleTransectImageCount * _transectCount();
470 471
    }

472 473
    _coordinate = _visualTransectPoints.count() ? _visualTransectPoints.first().value<QGeoCoordinate>() : QGeoCoordinate();
    _exitCoordinate = _visualTransectPoints.count() ? _visualTransectPoints.last().value<QGeoCoordinate>() : QGeoCoordinate();
474 475

    emit cameraShotsChanged();
476
    emit complexDistanceChanged();
477 478 479 480
    emit coordinateChanged(_coordinate);
    emit exitCoordinateChanged(_exitCoordinate);
}

481 482 483 484
bool CorridorScanComplexItem::readyForSave(void) const
{
    return TransectStyleComplexItem::readyForSave();
}