/**************************************************************************** * * (c) 2009-2016 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ /// @file /// @author Don Gagne #include "GeoFenceController.h" #include "Vehicle.h" #include "FirmwarePlugin.h" #include "MAVLinkProtocol.h" #include "QGCApplication.h" #include "ParameterManager.h" #include "JsonHelper.h" #include "QGCQGeoCoordinate.h" #include "AppSettings.h" #include "PlanMasterController.h" #ifndef __mobile__ #include "MainWindow.h" #include "QGCQFileDialog.h" #endif #include #include QGC_LOGGING_CATEGORY(GeoFenceControllerLog, "GeoFenceControllerLog") const char* GeoFenceController::_jsonFileTypeValue = "GeoFence"; const char* GeoFenceController::_jsonBreachReturnKey = "breachReturn"; GeoFenceController::GeoFenceController(PlanMasterController* masterController, QObject* parent) : PlanElementController(masterController, parent) , _geoFenceManager(_managerVehicle->geoFenceManager()) , _dirty(false) , _itemsRequested(false) { connect(&_polygons, &QmlObjectListModel::countChanged, this, &GeoFenceController::_updateContainsItems); connect(&_circles, &QmlObjectListModel::countChanged, this, &GeoFenceController::_updateContainsItems); managerVehicleChanged(_managerVehicle); } GeoFenceController::~GeoFenceController() { } void GeoFenceController::start(bool editMode) { qCDebug(GeoFenceControllerLog) << "start editMode" << editMode; PlanElementController::start(editMode); _init(); } void GeoFenceController::_init(void) { } void GeoFenceController::setBreachReturnPoint(const QGeoCoordinate& breachReturnPoint) { if (_breachReturnPoint != breachReturnPoint) { _breachReturnPoint = breachReturnPoint; setDirty(true); emit breachReturnPointChanged(breachReturnPoint); } } void GeoFenceController::_signalAll(void) { emit breachReturnPointChanged(breachReturnPoint()); emit dirtyChanged(dirty()); emit supportedChanged(supported()); } void GeoFenceController::managerVehicleChanged(Vehicle* managerVehicle) { if (_managerVehicle) { _geoFenceManager->disconnect(this); _managerVehicle->disconnect(this); _managerVehicle = NULL; _geoFenceManager = NULL; } _managerVehicle = managerVehicle; if (!_managerVehicle) { qWarning() << "GeoFenceController::managerVehicleChanged managerVehicle=NULL"; return; } _geoFenceManager = _managerVehicle->geoFenceManager(); connect(_geoFenceManager, &GeoFenceManager::loadComplete, this, &GeoFenceController::_managerLoadComplete); connect(_geoFenceManager, &GeoFenceManager::sendComplete, this, &GeoFenceController::_managerSendComplete); connect(_geoFenceManager, &GeoFenceManager::removeAllComplete, this, &GeoFenceController::_managerRemoveAllComplete); connect(_geoFenceManager, &GeoFenceManager::inProgressChanged, this, &GeoFenceController::syncInProgressChanged); connect(_managerVehicle, &Vehicle::capabilityBitsChanged, this, &RallyPointController::supportedChanged); emit supportedChanged(supported()); _signalAll(); } bool GeoFenceController::load(const QJsonObject& json, QString& errorString) { Q_UNUSED(json); Q_UNUSED(errorString); #if 0 QString errorStr; QString errorMessage = tr("GeoFence: %1"); if (json.contains(_jsonBreachReturnKey) && !JsonHelper::loadGeoCoordinate(json[_jsonBreachReturnKey], false /* altitudeRequired */, _breachReturnPoint, errorStr)) { errorString = errorMessage.arg(errorStr); return false; } if (!_mapPolygon.loadFromJson(json, true, errorStr)) { errorString = errorMessage.arg(errorStr); return false; } _mapPolygon.setDirty(false); setDirty(false); _signalAll(); #endif return true; } void GeoFenceController::save(QJsonObject& json) { Q_UNUSED(json); #if 0 json[JsonHelper::jsonVersionKey] = 1; if (_breachReturnPoint.isValid()) { QJsonValue jsonBreachReturn; JsonHelper::saveGeoCoordinate(_breachReturnPoint, false /* writeAltitude */, jsonBreachReturn); json[_jsonBreachReturnKey] = jsonBreachReturn; } _mapPolygon.saveToJson(json); #endif } void GeoFenceController::removeAll(void) { setBreachReturnPoint(QGeoCoordinate()); _polygons.clearAndDeleteContents(); _circles.clearAndDeleteContents(); } void GeoFenceController::removeAllFromVehicle(void) { if (_masterController->offline()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::removeAllFromVehicle called while offline"; } else if (syncInProgress()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::removeAllFromVehicle called while syncInProgress"; } else { _geoFenceManager->removeAll(); } } void GeoFenceController::loadFromVehicle(void) { if (_masterController->offline()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::loadFromVehicle called while offline"; } else if (syncInProgress()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::loadFromVehicle called while syncInProgress"; } else { _itemsRequested = true; _geoFenceManager->loadFromVehicle(); } } void GeoFenceController::sendToVehicle(void) { if (_masterController->offline()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::sendToVehicle called while offline"; } else if (syncInProgress()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::sendToVehicle called while syncInProgress"; } else { qCDebug(GeoFenceControllerLog) << "GeoFenceController::sendToVehicle"; _geoFenceManager->sendToVehicle(_breachReturnPoint, _polygons, _circles); setDirty(false); } } bool GeoFenceController::syncInProgress(void) const { return _geoFenceManager->inProgress(); } bool GeoFenceController::dirty(void) const { return _dirty; } void GeoFenceController::setDirty(bool dirty) { if (dirty != _dirty) { _dirty = dirty; if (!dirty) { for (int i=0; i<_polygons.count(); i++) { QGCFencePolygon* polygon = _polygons.value(i); polygon->setDirty(false); } for (int i=0; i<_circles.count(); i++) { QGCFenceCircle* circle = _circles.value(i); circle->setDirty(false); } } emit dirtyChanged(dirty); } } void GeoFenceController::_polygonDirtyChanged(bool dirty) { if (dirty) { setDirty(true); } } void GeoFenceController::_setDirty(void) { setDirty(true); } void GeoFenceController::_setFenceFromManager(const QList& polygons, const QList& circles) { _polygons.clearAndDeleteContents(); _circles.clearAndDeleteContents(); for (int i=0; ibreachReturnPoint()); _setFenceFromManager(_geoFenceManager->polygons(), _geoFenceManager->circles()); setDirty(false); _signalAll(); emit loadComplete(); } _itemsRequested = false; } void GeoFenceController::_managerSendComplete(bool error) { // Fly view always reloads on manager sendComplete if (!error && !_editMode) { showPlanFromManagerVehicle(); } } void GeoFenceController::_managerRemoveAllComplete(bool error) { if (!error) { // Remove all from vehicle so we always update showPlanFromManagerVehicle(); } } bool GeoFenceController::containsItems(void) const { return _polygons.count() > 0 || _circles.count() > 0; } void GeoFenceController::_updateContainsItems(void) { emit containsItemsChanged(containsItems()); } bool GeoFenceController::showPlanFromManagerVehicle(void) { qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle" << _editMode; if (_masterController->offline()) { qCWarning(GeoFenceControllerLog) << "GeoFenceController::showPlanFromManagerVehicle called while offline"; return true; // stops further propagation of showPlanFromManagerVehicle due to error } else { _itemsRequested = true; if (!_managerVehicle->initialPlanRequestComplete()) { // The vehicle hasn't completed initial load, we can just wait for loadComplete to be signalled automatically qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle: !initialPlanRequestComplete, wait for signal"; return true; } else if (syncInProgress()) { // If the sync is already in progress, _loadComplete will be called automatically when it is done. So no need to do anything. qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle: syncInProgress wait for signal"; return true; } else { // Fake a _loadComplete with the current items qCDebug(GeoFenceControllerLog) << "showPlanFromManagerVehicle: sync complete simulate signal"; _itemsRequested = true; _managerLoadComplete(); return false; } } } void GeoFenceController::addInclusionPolygon(QGeoCoordinate topLeft, QGeoCoordinate bottomRight) { QGeoCoordinate topRight(topLeft.latitude(), bottomRight.longitude()); QGeoCoordinate bottomLeft(bottomRight.latitude(), topLeft.longitude()); double halfWidthMeters = topLeft.distanceTo(topRight) / 2.0; double halfHeightMeters = topLeft.distanceTo(bottomLeft) / 2.0; QGeoCoordinate centerLeftEdge = topLeft.atDistanceAndAzimuth(halfHeightMeters, 180); QGeoCoordinate centerTopEdge = topLeft.atDistanceAndAzimuth(halfWidthMeters, 90); QGeoCoordinate center(centerLeftEdge.latitude(), centerTopEdge.longitude()); // Initial polygon is inset to take 3/4s of viewport with max width/height of 3000 meters halfWidthMeters = qMin(halfWidthMeters * 0.75, 1500.0); halfHeightMeters = qMin(halfHeightMeters * 0.75, 1500.0); // Initial polygon has max width and height of 3000 meters topLeft = center.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0); topRight = center.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0); bottomLeft = center.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180); bottomRight = center.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180); QGCFencePolygon* polygon = new QGCFencePolygon(true /* inclusion */, this); polygon->appendVertex(topLeft); polygon->appendVertex(topRight); polygon->appendVertex(bottomRight); polygon->appendVertex(bottomLeft); _polygons.append(polygon); clearAllInteractive(); polygon->setInteractive(true); } void GeoFenceController::addInclusionCircle(QGeoCoordinate topLeft, QGeoCoordinate bottomRight) { QGeoCoordinate topRight(topLeft.latitude(), bottomRight.longitude()); QGeoCoordinate bottomLeft(bottomRight.latitude(), topLeft.longitude()); // Initial radius is inset to take 3/4s of viewport and max of 1500 meters double halfWidthMeters = topLeft.distanceTo(topRight) / 2.0; double halfHeightMeters = topLeft.distanceTo(bottomLeft) / 2.0; double radius = qMin(qMin(halfWidthMeters, halfHeightMeters) * 0.75, 1500.0); QGeoCoordinate centerLeftEdge = topLeft.atDistanceAndAzimuth(halfHeightMeters, 180); QGeoCoordinate centerTopEdge = topLeft.atDistanceAndAzimuth(halfWidthMeters, 90); QGeoCoordinate center(centerLeftEdge.latitude(), centerTopEdge.longitude()); QGCFenceCircle* circle = new QGCFenceCircle(center, radius, true /* inclusion */, this); _circles.append(circle); clearAllInteractive(); circle->setInteractive(true); } void GeoFenceController::deletePolygon(int index) { if (index < 0 || index > _polygons.count() - 1) { return; } QGCFencePolygon* polygon = qobject_cast(_polygons.removeAt(index)); polygon->deleteLater(); } void GeoFenceController::deleteCircle(int index) { if (index < 0 || index > _circles.count() - 1) { return; } QGCFenceCircle* circle = qobject_cast(_circles.removeAt(index)); circle->deleteLater(); } void GeoFenceController::clearAllInteractive(void) { for (int i=0; i<_polygons.count(); i++) { _polygons.value(i)->setInteractive(false); } for (int i=0; i<_circles.count(); i++) { _circles.value(i)->setInteractive(false); } } bool GeoFenceController::supported(void) const { return (_managerVehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_MISSION_FENCE) && (_managerVehicle->maxProtoVersion() >= 200); }