AirMapFlightPlanManager.cc 40.1 KB
Newer Older
Gus Grubba's avatar
Gus Grubba committed
1 2 3 4 5 6 7 8 9 10 11 12
/****************************************************************************
 *
 *   (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 "AirMapFlightPlanManager.h"
#include "AirMapManager.h"
#include "AirMapRulesetsManager.h"
13
#include "AirMapAdvisoryManager.h"
Gus Grubba's avatar
Gus Grubba committed
14
#include "QGCApplication.h"
15
#include "SettingsManager.h"
Gus Grubba's avatar
Gus Grubba committed
16

17
#include "PlanMasterController.h"
Gus Grubba's avatar
Gus Grubba committed
18 19 20 21
#include "QGCMAVLink.h"

#include "airmap/date_time.h"
#include "airmap/flight_plans.h"
22
#include "airmap/flights.h"
Gus Grubba's avatar
Gus Grubba committed
23
#include "airmap/geometry.h"
24
#include "airmap/pilots.h"
Gus Grubba's avatar
Gus Grubba committed
25 26 27

using namespace airmap;

Gus Grubba's avatar
Gus Grubba committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
//-----------------------------------------------------------------------------
AirMapFlightAuthorization::AirMapFlightAuthorization(const Evaluation::Authorization auth, QObject *parent)
    : AirspaceFlightAuthorization(parent)
    , _auth(auth)
{
}

//-----------------------------------------------------------------------------
AirspaceFlightAuthorization::AuthorizationStatus
AirMapFlightAuthorization::status()
{
    switch(_auth.status) {
    case Evaluation::Authorization::Status::accepted:
        return AirspaceFlightAuthorization::Accepted;
    case Evaluation::Authorization::Status::rejected:
        return AirspaceFlightAuthorization::Rejected;
    case Evaluation::Authorization::Status::pending:
        return AirspaceFlightAuthorization::Pending;
    case Evaluation::Authorization::Status::accepted_upon_submission:
        return AirspaceFlightAuthorization::AcceptedOnSubmission;
    case Evaluation::Authorization::Status::rejected_upon_submission:
        return AirspaceFlightAuthorization::RejectedOnSubmission;
    }
    return AirspaceFlightAuthorization::Unknown;
}

Gus Grubba's avatar
Gus Grubba committed
54 55 56
//-----------------------------------------------------------------------------
AirMapFlightInfo::AirMapFlightInfo(const airmap::Flight& flight, QObject *parent)
    : AirspaceFlightInfo(parent)
Gus Grubba's avatar
Gus Grubba committed
57
    , _flight(flight)
Gus Grubba's avatar
Gus Grubba committed
58
{
59 60 61 62 63 64 65 66 67 68 69 70 71 72
    //-- Load bounding box geometry
    const Geometry& geometry = flight.geometry;
    if(geometry.type() == Geometry::Type::polygon) {
        const Geometry::Polygon& polygon = geometry.details_for_polygon();
        for (const auto& vertex :  polygon.outer_ring.coordinates) {
            QGeoCoordinate coord;
            if (vertex.altitude) {
                coord = QGeoCoordinate(vertex.latitude, vertex.longitude, vertex.altitude.get());
            } else {
                coord = QGeoCoordinate(vertex.latitude, vertex.longitude);
            }
            _boundingBox.append(QVariant::fromValue(coord));
        }
    }
Gus Grubba's avatar
Gus Grubba committed
73 74
}

Gus Grubba's avatar
Gus Grubba committed
75 76 77 78
//-----------------------------------------------------------------------------
QString
AirMapFlightInfo::createdTime()
{
Gus Grubba's avatar
Gus Grubba committed
79
    return QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(airmap::milliseconds_since_epoch(_flight.created_at))).toString("yyyy MM dd - hh:mm:ss");
Gus Grubba's avatar
Gus Grubba committed
80 81 82 83 84 85
}

//-----------------------------------------------------------------------------
QString
AirMapFlightInfo::startTime()
{
Gus Grubba's avatar
Gus Grubba committed
86
    return QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(airmap::milliseconds_since_epoch(_flight.start_time))).toString("yyyy MM dd - hh:mm:ss");
Gus Grubba's avatar
Gus Grubba committed
87 88
}

89 90 91 92
//-----------------------------------------------------------------------------
QDateTime
AirMapFlightInfo::qStartTime()
{
Gus Grubba's avatar
Gus Grubba committed
93
    return QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(airmap::milliseconds_since_epoch(_flight.start_time)));
94 95 96 97 98 99
}

//-----------------------------------------------------------------------------
bool
AirMapFlightInfo::active()
{
Gus Grubba's avatar
Gus Grubba committed
100
    QDateTime end = QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(airmap::milliseconds_since_epoch(_flight.end_time)));
101 102 103 104 105 106 107 108 109 110 111 112
    QDateTime now = QDateTime::currentDateTime();
    return end > now;
}

//-----------------------------------------------------------------------------
void
AirMapFlightInfo::setEndFlight(DateTime end)
{
    _flight.end_time = end;
    emit activeChanged();
}

Gus Grubba's avatar
Gus Grubba committed
113 114 115 116
//-----------------------------------------------------------------------------
QString
AirMapFlightInfo::endTime()
{
Gus Grubba's avatar
Gus Grubba committed
117
    return QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(airmap::milliseconds_since_epoch(_flight.end_time))).toString("yyyy MM dd - hh:mm:ss");
Gus Grubba's avatar
Gus Grubba committed
118 119
}

Gus Grubba's avatar
Gus Grubba committed
120 121 122 123 124 125
//-----------------------------------------------------------------------------
AirMapFlightPlanManager::AirMapFlightPlanManager(AirMapSharedState& shared, QObject *parent)
    : AirspaceFlightPlanProvider(parent)
    , _shared(shared)
{
    connect(&_pollTimer, &QTimer::timeout, this, &AirMapFlightPlanManager::_pollBriefing);
Gus Grubba's avatar
Gus Grubba committed
126
    _flightStartTime = QDateTime::currentDateTime().addSecs(60);
Gus Grubba's avatar
Gus Grubba committed
127 128
}

Gus Grubba's avatar
Gus Grubba committed
129 130 131 132 133 134 135
//-----------------------------------------------------------------------------
AirMapFlightPlanManager::~AirMapFlightPlanManager()
{
    _advisories.deleteListAndContents();
    _rulesets.deleteListAndContents();
}

Gus Grubba's avatar
Gus Grubba committed
136 137
//-----------------------------------------------------------------------------
void
138 139
AirMapFlightPlanManager::setFlightStartTime(QDateTime start)
{
Gus Grubba's avatar
Gus Grubba committed
140
    if(start < QDateTime::currentDateTime()) {
141
        start = QDateTime::currentDateTime().addSecs(1);
Gus Grubba's avatar
Gus Grubba committed
142 143 144
    }
    if(_flightStartTime != start) {
        _flightStartTime = start;
145 146
        emit flightStartTimeChanged();
    }
147
    qCDebug(AirMapManagerLog) << "Set time start time" << _flightStartTime;
148 149 150 151
}

//-----------------------------------------------------------------------------
void
152
AirMapFlightPlanManager::setFlightStartsNow(bool now)
153
{
154 155 156 157 158 159 160 161 162 163 164
    _flightStartsNow = now;
    emit flightStartsNowChanged();
}

//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::setFlightDuration(int seconds)
{
    _flightDuration = seconds;
    if(_flightDuration < 30) {
        _flightDuration = 30;
165
    }
166 167
    emit flightDurationChanged();
    qCDebug(AirMapManagerLog) << "Set time duration" << _flightDuration;
168 169
}

170 171 172 173
//-----------------------------------------------------------------------------
QDateTime
AirMapFlightPlanManager::flightStartTime() const
{
Gus Grubba's avatar
Gus Grubba committed
174
    return _flightStartTime;
175 176 177
}

//-----------------------------------------------------------------------------
178 179
int
AirMapFlightPlanManager::flightDuration() const
180
{
181
    return _flightDuration;
182 183
}

184 185
//-----------------------------------------------------------------------------
void
186
AirMapFlightPlanManager::startFlightPlanning(PlanMasterController *planController)
Gus Grubba's avatar
Gus Grubba committed
187 188 189 190 191 192 193
{
    if (!_shared.client()) {
        qCDebug(AirMapManagerLog) << "No AirMap client instance. Will not create a flight";
        return;
    }

    if (_state != State::Idle) {
Gus Grubba's avatar
Gus Grubba committed
194
        qCWarning(AirMapManagerLog) << "AirMapFlightPlanManager::startFlightPlanning: State not idle";
Gus Grubba's avatar
Gus Grubba committed
195 196 197
        return;
    }

198
    //-- TODO: Check if there is an ongoing flight plan and do something about it (Delete it?)
199 200

    /*
201
     * if(!flightPlanID().isEmpty()) {
202 203 204 205
     *     do something;
     * }
     */

206 207
    if(!_planController) {
        _planController = planController;
208
        //-- Get notified of mission changes
209
        connect(planController->missionController(), &MissionController::missionBoundingCubeChanged, this, &AirMapFlightPlanManager::_missionChanged);
Gus Grubba's avatar
Gus Grubba committed
210
    }
Gus Grubba's avatar
Gus Grubba committed
211 212
    //-- Set initial flight start time
    setFlightStartTime(QDateTime::currentDateTime().addSecs(5 * 60));
Gus Grubba's avatar
Gus Grubba committed
213 214
}

215 216 217 218
//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::submitFlightPlan()
{
219
    if(flightPlanID().isEmpty()) {
220 221 222
        qCWarning(AirMapManagerLog) << "Submit flight with no flight plan.";
        return;
    }
Gus Grubba's avatar
Gus Grubba committed
223
    _flightId.clear();
Gus Grubba's avatar
Gus Grubba committed
224
    emit flightIDChanged(_flightId);
225 226 227
    _state = State::FlightSubmit;
    FlightPlans::Submit::Parameters params;
    params.authorization = _shared.loginToken().toStdString();
228
    params.id            = flightPlanID().toStdString();
229 230 231 232 233
    std::weak_ptr<LifetimeChecker> isAlive(_instance);
    _shared.client()->flight_plans().submit(params, [this, isAlive](const FlightPlans::Submit::Result& result) {
        if (!isAlive.lock()) return;
        if (_state != State::FlightSubmit) return;
        if (result) {
234 235 236
            _flightPlan = result.value();
            _flightId = QString::fromStdString(_flightPlan.flight_id.get());
            _state = State::Idle;
237
            _pollBriefing();
Gus Grubba's avatar
Gus Grubba committed
238
            emit flightIDChanged(_flightId);
239 240 241 242 243
        } else {
            QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
            emit error("Failed to submit Flight Plan",
                    QString::fromStdString(result.error().message()), description);
            _state = State::Idle;
Gus Grubba's avatar
Gus Grubba committed
244 245
            _flightPermitStatus = AirspaceFlightPlanProvider::PermitRejected;
            emit flightPermitStatusChanged();
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
        }
    });
}

//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::updateFlightPlan()
{
    //-- Are we enabled?
    if(!qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableAirMap()->rawValue().toBool()) {
        return;
    }
    //-- Do we have a license?
    if(!_shared.hasAPIKey()) {
        return;
    }
    _flightPermitStatus = AirspaceFlightPlanProvider::PermitPending;
    emit flightPermitStatusChanged();
264
    _updateFlightPlan(true);
265 266
}

Gus Grubba's avatar
Gus Grubba committed
267 268
//-----------------------------------------------------------------------------
void
269
AirMapFlightPlanManager::endFlight(QString flightID)
Gus Grubba's avatar
Gus Grubba committed
270
{
271 272
    qCDebug(AirMapManagerLog) << "End flight";
    _flightToEnd = flightID;
273
    if (_shared.pilotID().isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
274 275 276 277 278 279 280 281 282 283 284 285
        //-- Need to get the pilot id
        qCDebug(AirMapManagerLog) << "Getting pilot ID";
        _state = State::GetPilotID;
        std::weak_ptr<LifetimeChecker> isAlive(_instance);
        _shared.doRequestWithLogin([this, isAlive](const QString& login_token) {
            if (!isAlive.lock()) return;
            Pilots::Authenticated::Parameters params;
            params.authorization = login_token.toStdString();
            _shared.client()->pilots().authenticated(params, [this, isAlive](const Pilots::Authenticated::Result& result) {
                if (!isAlive.lock()) return;
                if (_state != State::GetPilotID) return;
                if (result) {
286 287 288
                    QString pilotID = QString::fromStdString(result.value().id);
                    _shared.setPilotID(pilotID);
                    qCDebug(AirMapManagerLog) << "Got Pilot ID:" << pilotID;
Gus Grubba's avatar
Gus Grubba committed
289
                    _state = State::Idle;
290
                    _endFlight();
Gus Grubba's avatar
Gus Grubba committed
291 292 293 294 295 296 297 298 299
                } else {
                    _state = State::Idle;
                    QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
                    emit error("Failed to get pilot ID", QString::fromStdString(result.error().message()), description);
                    return;
                }
            });
        });
    } else {
300
        _endFlight();
Gus Grubba's avatar
Gus Grubba committed
301 302 303 304 305
    }
}

//-----------------------------------------------------------------------------
void
306
AirMapFlightPlanManager::_endFlight()
Gus Grubba's avatar
Gus Grubba committed
307
{
308 309
    if(_flightToEnd.isEmpty()) {
        qCDebug(AirMapManagerLog) << "End non existing flight";
Gus Grubba's avatar
Gus Grubba committed
310 311
        return;
    }
Gus Grubba's avatar
Gus Grubba committed
312
    qCDebug(AirMapManagerLog) << "End Flight. State:" << static_cast<int>(_state);
Gus Grubba's avatar
Gus Grubba committed
313
    if(_state != State::Idle) {
314
        QTimer::singleShot(100, this, &AirMapFlightPlanManager::_endFlight);
Gus Grubba's avatar
Gus Grubba committed
315 316
        return;
    }
317 318
    qCDebug(AirMapManagerLog) << "Ending flight:" << _flightToEnd;
    _state = State::FlightEnd;
Gus Grubba's avatar
Gus Grubba committed
319
    std::weak_ptr<LifetimeChecker> isAlive(_instance);
320
    Flights::EndFlight::Parameters params;
Gus Grubba's avatar
Gus Grubba committed
321
    params.authorization = _shared.loginToken().toStdString();
322 323 324
    params.id = _flightToEnd.toStdString();
    //-- End flight
    _shared.client()->flights().end_flight(params, [this, isAlive](const Flights::EndFlight::Result& result) {
Gus Grubba's avatar
Gus Grubba committed
325
        if (!isAlive.lock()) return;
326
        if (_state != State::FlightEnd) return;
Gus Grubba's avatar
Gus Grubba committed
327
        if (result) {
328 329 330 331 332 333 334 335
            qCDebug(AirMapManagerLog) << "Flight Ended";
            int idx = _flightList.findFlightID(_flightToEnd);
            if(idx >= 0) {
                AirMapFlightInfo* pInfo = qobject_cast<AirMapFlightInfo*>(_flightList.get(idx));
                if(pInfo) {
                    pInfo->setEndFlight(result.value().end_time);
                }
            }
Gus Grubba's avatar
Gus Grubba committed
336 337
        } else {
            QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
338
            emit error("End flight failed", QString::fromStdString(result.error().message()), description);
Gus Grubba's avatar
Gus Grubba committed
339
        }
340
        _flightToEnd.clear();
Gus Grubba's avatar
Gus Grubba committed
341 342 343 344
        _state = State::Idle;
    });
}

Gus Grubba's avatar
Gus Grubba committed
345
//-----------------------------------------------------------------------------
346 347
bool
AirMapFlightPlanManager::_collectFlightDtata()
Gus Grubba's avatar
Gus Grubba committed
348
{
349 350 351
    if(!_planController || !_planController->missionController()) {
        return false;
    }
Gus Grubba's avatar
Gus Grubba committed
352
    //-- Get flight bounding cube and prepare (box) polygon
353
    QGCGeoBoundingCube bc = *_planController->missionController()->travelBoundingCube();
Gus Grubba's avatar
Gus Grubba committed
354
    if(!bc.isValid() || (fabs(bc.area()) < 0.0001)) {
355
        //-- TODO: If single point, we need to set a point and a radius instead
356 357
        qCDebug(AirMapManagerLog) << "Not enough points for a flight plan.";
        return false;
358
    }
Gus Grubba's avatar
Gus Grubba committed
359
    _flight.maxAltitude   = static_cast<float>(fmax(bc.pointNW.altitude(), bc.pointSE.altitude()));
360
    _flight.takeoffCoord  = _planController->missionController()->takeoffCoordinate();
361
    _flight.coords        = bc.polygon2D();
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
    _flight.bc            = bc;
    emit missionAreaChanged();
    return true;
}

//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::_createFlightPlan()
{
    _flight.reset();

    //-- Get flight data
    if(!_collectFlightDtata()) {
        return;
    }
Gus Grubba's avatar
Gus Grubba committed
377 378 379

    qCDebug(AirMapManagerLog) << "About to create flight plan";
    qCDebug(AirMapManagerLog) << "Takeoff:     " << _flight.takeoffCoord;
380
    qCDebug(AirMapManagerLog) << "Bounding box:" << _flight.bc.pointNW << _flight.bc.pointSE;
381
    qCDebug(AirMapManagerLog) << "Flight Start:" << flightStartTime().toString();
382
    qCDebug(AirMapManagerLog) << "Flight Duration:  " << flightDuration();
Gus Grubba's avatar
Gus Grubba committed
383

384
    if (_shared.pilotID().isEmpty() && !_shared.settings().userName.isEmpty() && !_shared.settings().password.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
385 386 387 388 389 390 391 392 393 394 395 396
        //-- Need to get the pilot id before uploading the flight plan
        qCDebug(AirMapManagerLog) << "Getting pilot ID";
        _state = State::GetPilotID;
        std::weak_ptr<LifetimeChecker> isAlive(_instance);
        _shared.doRequestWithLogin([this, isAlive](const QString& login_token) {
            if (!isAlive.lock()) return;
            Pilots::Authenticated::Parameters params;
            params.authorization = login_token.toStdString();
            _shared.client()->pilots().authenticated(params, [this, isAlive](const Pilots::Authenticated::Result& result) {
                if (!isAlive.lock()) return;
                if (_state != State::GetPilotID) return;
                if (result) {
397 398 399
                    QString pilotID = QString::fromStdString(result.value().id);
                    _shared.setPilotID(pilotID);
                    qCDebug(AirMapManagerLog) << "Got Pilot ID:" << pilotID;
400
                    _state = State::Idle;
Gus Grubba's avatar
Gus Grubba committed
401 402
                    _uploadFlightPlan();
                } else {
403
                    _flightPermitStatus = AirspaceFlightPlanProvider::PermitNone;
Gus Grubba's avatar
Gus Grubba committed
404 405 406 407
                    emit flightPermitStatusChanged();
                    _state = State::Idle;
                    QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
                    emit error("Failed to create Flight Plan", QString::fromStdString(result.error().message()), description);
408
                    return;
Gus Grubba's avatar
Gus Grubba committed
409 410 411 412 413 414 415 416 417 418 419
                }
            });
        });
    } else {
        _uploadFlightPlan();
    }

    _flightPermitStatus = AirspaceFlightPlanProvider::PermitPending;
    emit flightPermitStatusChanged();
}

420 421
//-----------------------------------------------------------------------------
void
422
AirMapFlightPlanManager::_updateRulesAndFeatures(std::vector<RuleSet::Id>& rulesets, std::unordered_map<std::string, RuleSet::Feature::Value>& features, bool updateFeatures)
423 424 425 426 427 428 429 430
{
    AirMapRulesetsManager* pRulesMgr = dynamic_cast<AirMapRulesetsManager*>(qgcApp()->toolbox()->airspaceManager()->ruleSets());
    if(pRulesMgr) {
        for(int rs = 0; rs < pRulesMgr->ruleSets()->count(); rs++) {
            AirMapRuleSet* ruleSet = qobject_cast<AirMapRuleSet*>(pRulesMgr->ruleSets()->get(rs));
            //-- If this ruleset is selected
            if(ruleSet && ruleSet->selected()) {
                rulesets.push_back(ruleSet->id().toStdString());
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
                //-- Features within each rule (only when updating)
                if(updateFeatures) {
                    for(int r = 0; r < ruleSet->rules()->count(); r++) {
                        AirMapRule* rule = qobject_cast<AirMapRule*>(ruleSet->rules()->get(r));
                        if(rule) {
                            for(int f = 0; f < rule->features()->count(); f++) {
                                AirMapRuleFeature* feature = qobject_cast<AirMapRuleFeature*>(rule->features()->get(f));
                                if(features.find(feature->name().toStdString()) != features.end()) {
                                    qCDebug(AirMapManagerLog) << "Removing duplicate:" << feature->name();
                                    continue;
                                }
                                if(feature && feature->value().isValid()) {
                                    switch(feature->type()) {
                                    case AirspaceRuleFeature::Boolean:
                                        if(feature->value().toInt() == 0 || feature->value().toInt() == 1) {
                                            features[feature->name().toStdString()] = RuleSet::Feature::Value(feature->value().toBool());
                                        } else {
                                            //-- If not set, default to false
                                            features[feature->name().toStdString()] = RuleSet::Feature::Value(false);
                                        }
                                        break;
                                    case AirspaceRuleFeature::Float:
                                        //-- Sanity check for floats
                                        if(std::isfinite(feature->value().toFloat())) {
Gus Grubba's avatar
Gus Grubba committed
455
                                            features[feature->name().toStdString()] = RuleSet::Feature::Value(feature->value().toDouble());
456 457 458 459 460 461 462 463 464 465
                                        }
                                        break;
                                    case AirspaceRuleFeature::String:
                                        //-- Skip empty responses
                                        if(!feature->value().toString().isEmpty()) {
                                            features[feature->name().toStdString()] = RuleSet::Feature::Value(feature->value().toString().toStdString());
                                        }
                                        break;
                                    default:
                                        qCWarning(AirMapManagerLog) << "Unknown type for feature" << feature->name();
466 467 468 469 470 471 472 473 474 475 476
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Gus Grubba's avatar
Gus Grubba committed
477 478 479 480
//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::_updateFlightStartEndTime(DateTime& start_time, DateTime& end_time)
{
481 482
    if(_flightStartsNow || _flightStartTime < QDateTime::currentDateTime()) {
        setFlightStartTime(QDateTime::currentDateTime().addSecs(1));
Gus Grubba's avatar
Gus Grubba committed
483 484 485
    }
    quint64 startt = static_cast<quint64>(_flightStartTime.toUTC().toMSecsSinceEpoch());
    start_time = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast<qint64>(startt)));
486
    quint64 endt = startt + (static_cast<uint64_t>(_flightDuration) * 1000);
Gus Grubba's avatar
Gus Grubba committed
487 488 489
    end_time = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast<qint64>(endt)));
}

Gus Grubba's avatar
Gus Grubba committed
490 491 492 493
//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::_uploadFlightPlan()
{
Gus Grubba's avatar
Gus Grubba committed
494
    qCDebug(AirMapManagerLog) << "Uploading flight plan. State:" << static_cast<int>(_state);
Gus Grubba's avatar
Gus Grubba committed
495 496 497 498
    if(_state != State::Idle) {
        QTimer::singleShot(100, this, &AirMapFlightPlanManager::_uploadFlightPlan);
        return;
    }
499 500
    //-- Reset "relevant" features
    _importantFeatures.clear();
Gus Grubba's avatar
Gus Grubba committed
501 502 503 504 505 506 507
    _state = State::FlightUpload;
    std::weak_ptr<LifetimeChecker> isAlive(_instance);
    _shared.doRequestWithLogin([this, isAlive](const QString& login_token) {
        if (!isAlive.lock()) return;
        if (_state != State::FlightUpload) return;
        FlightPlans::Create::Parameters params;
        params.max_altitude = _flight.maxAltitude;
508
        params.min_altitude = 0.0;
Gus Grubba's avatar
Gus Grubba committed
509
        params.buffer       = 10.f;
Gus Grubba's avatar
Gus Grubba committed
510 511
        params.latitude     = static_cast<float>(_flight.takeoffCoord.latitude());
        params.longitude    = static_cast<float>(_flight.takeoffCoord.longitude());
512
        params.pilot.id     = _shared.pilotID().toStdString();
Gus Grubba's avatar
Gus Grubba committed
513 514
        //-- Handle flight start/end
        _updateFlightStartEndTime(params.start_time, params.end_time);
515 516
        //-- Rules & Features
        _updateRulesAndFeatures(params.rulesets, params.features);
517 518
        //-- Geometry: polygon
        Geometry::Polygon polygon;
Gus Grubba's avatar
Gus Grubba committed
519 520 521 522
        for (const auto& qcoord : _flight.coords) {
            Geometry::Coordinate coord;
            coord.latitude  = qcoord.latitude();
            coord.longitude = qcoord.longitude();
523
            polygon.outer_ring.coordinates.push_back(coord);
Gus Grubba's avatar
Gus Grubba committed
524
        }
525
        params.geometry = Geometry(polygon);
Gus Grubba's avatar
Gus Grubba committed
526 527 528 529 530
        params.authorization = login_token.toStdString();
        //-- Create flight plan
        _shared.client()->flight_plans().create_by_polygon(params, [this, isAlive](const FlightPlans::Create::Result& result) {
            if (!isAlive.lock()) return;
            if (_state != State::FlightUpload) return;
531
            _state = State::Idle;
Gus Grubba's avatar
Gus Grubba committed
532
            if (result) {
533 534
                _flightPlan = result.value();
                qCDebug(AirMapManagerLog) << "Flight plan created:" << flightPlanID();
Gus Grubba's avatar
Gus Grubba committed
535 536 537
                _pollBriefing();
            } else {
                QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
Gus Grubba's avatar
Gus Grubba committed
538
                emit error("Flight Plan creation failed", QString::fromStdString(result.error().message()), description);
Gus Grubba's avatar
Gus Grubba committed
539 540 541 542 543
            }
        });
    });
}

544 545
//-----------------------------------------------------------------------------
void
546
AirMapFlightPlanManager::_updateFlightPlanOnTimer()
547
{
548 549
    _updateFlightPlan(false);
}
550

551 552 553 554
//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::_updateFlightPlan(bool interactive)
{
Gus Grubba's avatar
Gus Grubba committed
555
    qCDebug(AirMapManagerLog) << "Updating flight plan. State:" << static_cast<int>(_state);
Gus Grubba's avatar
Gus Grubba committed
556 557

    if(_state != State::Idle) {
558
        QTimer::singleShot(250, this, &AirMapFlightPlanManager::_updateFlightPlanOnTimer);
Gus Grubba's avatar
Gus Grubba committed
559 560
        return;
    }
561 562 563 564 565
    //-- Get flight data
    if(!_collectFlightDtata()) {
        return;
    }

566 567 568 569
    //-- Update local instance of the flight plan
    _flightPlan.altitude_agl.max  = _flight.maxAltitude;
    _flightPlan.altitude_agl.min  = 0.0f;
    _flightPlan.buffer            = 2.f;
Gus Grubba's avatar
Gus Grubba committed
570 571
    _flightPlan.takeoff.latitude  = static_cast<float>(_flight.takeoffCoord.latitude());
    _flightPlan.takeoff.longitude = static_cast<float>(_flight.takeoffCoord.longitude());
572 573 574
    //-- Rules & Features
    _flightPlan.rulesets.clear();
    _flightPlan.features.clear();
575 576
    //-- If interactive, we collect features otherwise we don't
    _updateRulesAndFeatures(_flightPlan.rulesets, _flightPlan.features, interactive);
Gus Grubba's avatar
Gus Grubba committed
577 578
    //-- Handle flight start/end
    _updateFlightStartEndTime(_flightPlan.start_time, _flightPlan.end_time);
579 580 581 582 583 584 585 586 587
    //-- Geometry: polygon
    Geometry::Polygon polygon;
    for (const auto& qcoord : _flight.coords) {
        Geometry::Coordinate coord;
        coord.latitude  = qcoord.latitude();
        coord.longitude = qcoord.longitude();
        polygon.outer_ring.coordinates.push_back(coord);
    }
    _flightPlan.geometry = Geometry(polygon);
Gus Grubba's avatar
Gus Grubba committed
588

589 590 591 592
    qCDebug(AirMapManagerLog) << "Takeoff:        " << _flight.takeoffCoord;
    qCDebug(AirMapManagerLog) << "Bounding box:   " << _flight.bc.pointNW << _flight.bc.pointSE;
    qCDebug(AirMapManagerLog) << "Flight Start:   " << flightStartTime().toString();
    qCDebug(AirMapManagerLog) << "Flight Duration:" << flightDuration();
Gus Grubba's avatar
Gus Grubba committed
593

594 595 596 597 598
    _state = State::FlightUpdate;
    std::weak_ptr<LifetimeChecker> isAlive(_instance);
    _shared.doRequestWithLogin([this, isAlive](const QString& login_token) {
        if (!isAlive.lock()) return;
        if (_state != State::FlightUpdate) return;
599 600
        FlightPlans::Update::Parameters params = {};
        params.authorization                 = login_token.toStdString();
601
        params.flight_plan                   = _flightPlan;
602 603 604 605
        //-- Update flight plan
        _shared.client()->flight_plans().update(params, [this, isAlive](const FlightPlans::Update::Result& result) {
            if (!isAlive.lock()) return;
            if (_state != State::FlightUpdate) return;
606
            _state = State::Idle;
607
            if (result) {
608
                qCDebug(AirMapManagerLog) << "Flight plan updated:" << flightPlanID();
609 610 611
                _pollBriefing();
            } else {
                QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
Gus Grubba's avatar
Gus Grubba committed
612
                emit error("Flight Plan update failed", QString::fromStdString(result.error().message()), description);
613 614 615 616 617
            }
        });
    });
}

618 619 620 621 622 623 624
//-----------------------------------------------------------------------------
static bool
adv_sort(QObject* a, QObject* b)
{
    AirMapAdvisory* aa = qobject_cast<AirMapAdvisory*>(a);
    AirMapAdvisory* bb = qobject_cast<AirMapAdvisory*>(b);
    if(!aa || !bb) return false;
625
    return static_cast<int>(aa->color()) > static_cast<int>(bb->color());
626 627
}

Gus Grubba's avatar
Gus Grubba committed
628 629 630 631 632 633 634
//-----------------------------------------------------------------------------
static bool
rules_sort(QObject* a, QObject* b)
{
    AirMapRule* aa = qobject_cast<AirMapRule*>(a);
    AirMapRule* bb = qobject_cast<AirMapRule*>(b);
    if(!aa || !bb) return false;
635
    return static_cast<int>(aa->status()) > static_cast<int>(bb->status());
Gus Grubba's avatar
Gus Grubba committed
636 637
}

638 639 640 641 642 643 644 645 646 647 648 649 650
//-----------------------------------------------------------------------------
bool
AirMapFlightPlanManager::_findBriefFeature(const QString& name)
{
    for(int i = 0; i < _briefFeatures.count(); i++ ) {
        AirMapRuleFeature* feature = qobject_cast<AirMapRuleFeature*>(_briefFeatures.get(i));
        if (feature && feature->name() == name) {
            return true;
        }
    }
    return false;
}

Gus Grubba's avatar
Gus Grubba committed
651 652 653 654
//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::_pollBriefing()
{
655
    qCDebug(AirMapManagerLog) << "Poll Briefing. State:" << static_cast<int>(_state);
656 657
    if(_state != State::Idle) {
        QTimer::singleShot(100, this, &AirMapFlightPlanManager::_pollBriefing);
Gus Grubba's avatar
Gus Grubba committed
658 659
        return;
    }
660
    _state = State::FlightPolling;
Gus Grubba's avatar
Gus Grubba committed
661 662
    FlightPlans::RenderBriefing::Parameters params;
    params.authorization = _shared.loginToken().toStdString();
663
    params.id            = flightPlanID().toStdString();
Gus Grubba's avatar
Gus Grubba committed
664 665 666
    std::weak_ptr<LifetimeChecker> isAlive(_instance);
    _shared.client()->flight_plans().render_briefing(params, [this, isAlive](const FlightPlans::RenderBriefing::Result& result) {
        if (!isAlive.lock()) return;
667
        if (_state != State::FlightPolling) return;
Gus Grubba's avatar
Gus Grubba committed
668 669 670
        if (result) {
            const FlightPlan::Briefing& briefing = result.value();
            qCDebug(AirMapManagerLog) << "Flight polling/briefing response";
671 672 673 674
            //-- Collect advisories
            _valid = false;
            _advisories.clearAndDeleteContents();
            const std::vector<Status::Advisory> advisories = briefing.airspace.advisories;
Gus Grubba's avatar
Gus Grubba committed
675
            _airspaceColor = static_cast<AirspaceAdvisoryProvider::AdvisoryColor>(briefing.airspace.color);
676 677 678 679
            for (const auto& advisory : advisories) {
                AirMapAdvisory* pAdvisory = new AirMapAdvisory(this);
                pAdvisory->_id          = QString::fromStdString(advisory.airspace.id());
                pAdvisory->_name        = QString::fromStdString(advisory.airspace.name());
Gus Grubba's avatar
Gus Grubba committed
680 681
                pAdvisory->_type        = static_cast<AirspaceAdvisory::AdvisoryType>(advisory.airspace.type());
                pAdvisory->_color       = static_cast<AirspaceAdvisoryProvider::AdvisoryColor>(advisory.color);
682 683 684 685 686 687 688 689
                _advisories.append(pAdvisory);
                qCDebug(AirMapManagerLog) << "Adding briefing advisory" << pAdvisory->name();
            }
            //-- Sort in order of color (priority)
            _advisories.beginReset();
            std::sort(_advisories.objectList()->begin(), _advisories.objectList()->end(), adv_sort);
            _advisories.endReset();
            _valid = true;
Gus Grubba's avatar
Gus Grubba committed
690
            //-- Collect Rulesets
Gus Grubba's avatar
Gus Grubba committed
691
            _authorizations.clearAndDeleteContents();
692 693 694 695
            _rulesViolation.clearAndDeleteContents();
            _rulesInfo.clearAndDeleteContents();
            _rulesReview.clearAndDeleteContents();
            _rulesFollowing.clearAndDeleteContents();
696
            _briefFeatures.clear();
Gus Grubba's avatar
Gus Grubba committed
697 698 699 700 701 702
            for(const auto& ruleset : briefing.evaluation.rulesets) {
                AirMapRuleSet* pRuleSet = new AirMapRuleSet(this);
                pRuleSet->_id = QString::fromStdString(ruleset.id);
                //-- Iterate Rules
                for (const auto& rule : ruleset.rules) {
                    AirMapRule* pRule = new AirMapRule(rule, this);
703 704 705 706
                    //-- Iterate Rule Features
                    for (const auto& feature : rule.features) {
                        AirMapRuleFeature* pFeature = new AirMapRuleFeature(feature, this);
                        pRule->_features.append(pFeature);
707
                        if(rule.status == RuleSet::Rule::Status::missing_info) {
708 709
                            if(!_findBriefFeature(pFeature->name())) {
                                _briefFeatures.append(pFeature);
Gus Grubba's avatar
Gus Grubba committed
710
                                _importantFeatures.append(pFeature);
711 712 713 714
                                qCDebug(AirMapManagerLog) << "Adding briefing feature" << pFeature->name() << pFeature->description() << pFeature->type();
                            } else {
                                qCDebug(AirMapManagerLog) << "Skipping briefing feature duplicate" << pFeature->name() << pFeature->description() << pFeature->type();
                            }
715
                        }
716
                    }
717 718
                    //-- When a flight is first created, we send no features. That means that all "missing_info" are "relevant" features.
                    //   We keep a list of them so they will be always shown to the user even when they are no longer "missing_info"
Gus Grubba's avatar
Gus Grubba committed
719 720 721 722 723
                    for(const auto& feature : _importantFeatures) {
                        if(!_findBriefFeature(feature->name())) {
                            _briefFeatures.append(feature);
                        }
                    }
Gus Grubba's avatar
Gus Grubba committed
724
                    pRuleSet->_rules.append(pRule);
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
                    //-- Rules separated by status for presentation
                    switch(rule.status) {
                    case RuleSet::Rule::Status::conflicting:
                        _rulesViolation.append(new AirMapRule(rule, this));
                        break;
                    case RuleSet::Rule::Status::not_conflicting:
                        _rulesFollowing.append(new AirMapRule(rule, this));
                        break;
                    case RuleSet::Rule::Status::missing_info:
                        _rulesInfo.append(new AirMapRule(rule, this));
                        break;
                    case RuleSet::Rule::Status::informational:
                        _rulesReview.append(new AirMapRule(rule, this));
                        break;
                    default:
                        break;
                    }
Gus Grubba's avatar
Gus Grubba committed
742 743
                }
                //-- Sort rules by relevance order
744
                pRuleSet->_rules.beginReset();
Gus Grubba's avatar
Gus Grubba committed
745
                std::sort(pRuleSet->_rules.objectList()->begin(), pRuleSet->_rules.objectList()->end(), rules_sort);
746
                pRuleSet->_rules.endReset();
Gus Grubba's avatar
Gus Grubba committed
747 748 749
                _rulesets.append(pRuleSet);
                qCDebug(AirMapManagerLog) << "Adding briefing ruleset" << pRuleSet->id();
            }
750
            //-- Evaluate briefing status
Gus Grubba's avatar
Gus Grubba committed
751
            if (briefing.evaluation.authorizations.size() == 0) {
752
                _flightPermitStatus = AirspaceFlightPlanProvider::PermitNotRequired;
Gus Grubba's avatar
Gus Grubba committed
753
                emit flightPermitStatusChanged();
754
            } else {
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
                bool rejected = false;
                bool accepted = false;
                bool pending  = false;
                for (const auto& authorization : briefing.evaluation.authorizations) {
                    AirMapFlightAuthorization* pAuth = new AirMapFlightAuthorization(authorization, this);
                    _authorizations.append(pAuth);
                    qCDebug(AirMapManagerLog) << "Autorization:" << pAuth->name() << " (" << pAuth->message() << ")" << static_cast<int>(pAuth->status());
                    switch (authorization.status) {
                    case Evaluation::Authorization::Status::accepted:
                    case Evaluation::Authorization::Status::accepted_upon_submission:
                        accepted = true;
                        break;
                    case Evaluation::Authorization::Status::rejected:
                    case Evaluation::Authorization::Status::rejected_upon_submission:
                        rejected = true;
                        break;
                    case Evaluation::Authorization::Status::pending:
                        pending = true;
                        break;
                    }
                }
                qCDebug(AirMapManagerLog) << "Flight approval: accepted=" << accepted << "rejected" << rejected << "pending" << pending;
                if ((rejected || accepted) && !pending) {
                    if (rejected) { // rejected has priority
                        _flightPermitStatus = AirspaceFlightPlanProvider::PermitRejected;
                    } else {
                        _flightPermitStatus = AirspaceFlightPlanProvider::PermitAccepted;
                    }
                    emit flightPermitStatusChanged();
                } else {
                    //-- Pending. Try again.
                    _pollTimer.setSingleShot(true);
                    _pollTimer.start(1000);
                }
Gus Grubba's avatar
Gus Grubba committed
789
            }
790 791 792
            emit advisoryChanged();
            emit rulesChanged();
            _state = State::Idle;
Gus Grubba's avatar
Gus Grubba committed
793
        } else {
794
            _state = State::Idle;
Gus Grubba's avatar
Gus Grubba committed
795 796 797 798 799 800 801 802 803
            QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
            emit error("Brief Request failed",
                    QString::fromStdString(result.error().message()), description);
        }
    });
}

//-----------------------------------------------------------------------------
void
804
AirMapFlightPlanManager::_missionChanged()
Gus Grubba's avatar
Gus Grubba committed
805
{
806 807 808 809 810 811 812 813
    //-- Are we enabled?
    if(!qgcApp()->toolbox()->settingsManager()->airMapSettings()->enableAirMap()->rawValue().toBool()) {
        return;
    }
    //-- Do we have a license?
    if(!_shared.hasAPIKey()) {
        return;
    }
Gus Grubba's avatar
Gus Grubba committed
814
    //-- Creating a new flight plan?
815
    if(_state == State::Idle) {
816
        if(flightPlanID().isEmpty()) {
817 818 819
            _createFlightPlan();
        } else {
            //-- Plan is being modified
820
            _updateFlightPlan();
821
        }
Gus Grubba's avatar
Gus Grubba committed
822 823
    }
}
Gus Grubba's avatar
Gus Grubba committed
824 825 826

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
827
AirMapFlightPlanManager::loadFlightList(QDateTime startTime, QDateTime endTime)
Gus Grubba's avatar
Gus Grubba committed
828
{
Gus Grubba's avatar
Gus Grubba committed
829 830 831 832 833 834 835 836
    //-- TODO: This is not checking if the state is Idle. Again, these need to
    //   queued up and handled by a worker thread.
    qCDebug(AirMapManagerLog) << "Preparing load flight list";
    _loadingFlightList = true;
    emit loadingFlightListChanged();
    _rangeStart = startTime;
    _rangeEnd   = endTime;
    qCDebug(AirMapManagerLog) << "List flights from:" << _rangeStart.toString("yyyy MM dd - hh:mm:ss") << "to" << _rangeEnd.toString("yyyy MM dd - hh:mm:ss");
837
    if (_shared.pilotID().isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
838 839 840 841 842 843 844 845 846 847 848 849
        //-- Need to get the pilot id
        qCDebug(AirMapManagerLog) << "Getting pilot ID";
        _state = State::GetPilotID;
        std::weak_ptr<LifetimeChecker> isAlive(_instance);
        _shared.doRequestWithLogin([this, isAlive](const QString& login_token) {
            if (!isAlive.lock()) return;
            Pilots::Authenticated::Parameters params;
            params.authorization = login_token.toStdString();
            _shared.client()->pilots().authenticated(params, [this, isAlive](const Pilots::Authenticated::Result& result) {
                if (!isAlive.lock()) return;
                if (_state != State::GetPilotID) return;
                if (result) {
850 851 852
                    QString pilotID = QString::fromStdString(result.value().id);
                    _shared.setPilotID(pilotID);
                    qCDebug(AirMapManagerLog) << "Got Pilot ID:" << pilotID;
Gus Grubba's avatar
Gus Grubba committed
853 854 855 856 857 858
                    _state = State::Idle;
                    _loadFlightList();
                } else {
                    _state = State::Idle;
                    QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
                    emit error("Failed to get pilot ID", QString::fromStdString(result.error().message()), description);
Gus Grubba's avatar
Gus Grubba committed
859 860
                    _loadingFlightList = false;
                    emit loadingFlightListChanged();
Gus Grubba's avatar
Gus Grubba committed
861 862 863 864 865 866 867 868 869 870 871 872 873
                    return;
                }
            });
        });
    } else {
        _loadFlightList();
    }
}

//-----------------------------------------------------------------------------
void
AirMapFlightPlanManager::_loadFlightList()
{
874
    qCDebug(AirMapManagerLog) << "Load flight list. State:" << static_cast<int>(_state);
Gus Grubba's avatar
Gus Grubba committed
875 876 877 878
    if(_state != State::Idle) {
        QTimer::singleShot(100, this, &AirMapFlightPlanManager::_loadFlightList);
        return;
    }
Gus Grubba's avatar
Gus Grubba committed
879
    _flightList.clear();
Gus Grubba's avatar
Gus Grubba committed
880 881 882 883 884 885 886 887
    emit flightListChanged();
    _state = State::LoadFlightList;
    std::weak_ptr<LifetimeChecker> isAlive(_instance);
    _shared.doRequestWithLogin([this, isAlive](const QString& login_token) {
        if (!isAlive.lock()) return;
        if (_state != State::LoadFlightList) return;
        Flights::Search::Parameters params;
        params.authorization = login_token.toStdString();
888 889 890 891
        quint64 start   = static_cast<quint64>(_rangeStart.toUTC().toMSecsSinceEpoch());
        quint64 end     = static_cast<quint64>(_rangeEnd.toUTC().toMSecsSinceEpoch());
        params.start_after  = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast<long long>(start)));
        params.start_before = airmap::from_milliseconds_since_epoch(airmap::milliseconds(static_cast<long long>(end)));
Gus Grubba's avatar
Gus Grubba committed
892
        params.limit    = 250;
893
        params.pilot_id = _shared.pilotID().toStdString();
Gus Grubba's avatar
Gus Grubba committed
894 895 896 897 898 899 900 901
        _shared.client()->flights().search(params, [this, isAlive](const Flights::Search::Result& result) {
            if (!isAlive.lock()) return;
            if (_state != State::LoadFlightList) return;
            if (result && result.value().flights.size() > 0) {
                const Flights::Search::Response& response = result.value();
                for (const auto& flight : response.flights) {
                    AirMapFlightInfo* pFlight = new AirMapFlightInfo(flight, this);
                    _flightList.append(pFlight);
902
                    qCDebug(AirMapManagerLog) << "Found:" << pFlight->flightID() << pFlight->flightPlanID() << pFlight->endTime();
Gus Grubba's avatar
Gus Grubba committed
903
                }
904
                _flightList.sortStartFlight();
Gus Grubba's avatar
Gus Grubba committed
905 906 907 908 909 910 911
                emit flightListChanged();
            } else {
                if(!result) {
                    QString description = QString::fromStdString(result.error().description() ? result.error().description().get() : "");
                    emit error("Flight search failed", QString::fromStdString(result.error().message()), description);
                }
            }
Gus Grubba's avatar
Gus Grubba committed
912 913 914
            _state = State::Idle;
            _loadingFlightList = false;
            emit loadingFlightListChanged();
Gus Grubba's avatar
Gus Grubba committed
915 916 917 918
        });
    });
}