Commit 06272ff6 authored by Gus Grubba's avatar Gus Grubba

Merge branch 'master' of https://github.com/mavlink/qgroundcontrol into Airmap

# Conflicts:
#	libs/mavlink/include/mavlink/v2.0
#	src/QmlControls/QmlObjectListModel.h
parents ab0f772c 9b9b9d74
......@@ -41,7 +41,7 @@ You **need to install Qt as described below** instead of using pre-built package
* Windows: Make sure to install VS 2015 32 bit package.
###### Install additional packages:
* Ubuntu: sudo apt-get install speech-dispatcher libudev-dev libsdl2-dev libgstreamer1.0-0 gstreamer1.0-plugins-base libgstreamer-plugins-base1.0-dev gstreamer1.0*
* Ubuntu: sudo apt-get install speech-dispatcher libudev-dev libsdl2-dev
* Fedora: sudo dnf install speech-dispatcher SDL2-devel SDL2 systemd-devel
* Arch Linux: pacman -Sy speech-dispatcher
* Windows: [USB Driver](http://www.pixhawk.org/firmware/downloads) to connect to Pixhawk/PX4Flow/3DR Radio
......
......@@ -346,6 +346,7 @@ INCLUDEPATH += \
src/QtLocationPlugin \
src/QtLocationPlugin/QMLControl \
src/Settings \
src/Terrain \
src/VehicleSetup \
src/ViewWidgets \
src/Audio \
......@@ -587,7 +588,7 @@ HEADERS += \
src/Settings/SettingsManager.h \
src/Settings/UnitsSettings.h \
src/Settings/VideoSettings.h \
src/Terrain.h \
src/Terrain/TerrainQuery.h \
src/Vehicle/MAVLinkLogManager.h \
src/VehicleSetup/JoystickConfigController.h \
src/comm/LinkConfiguration.h \
......@@ -781,7 +782,7 @@ SOURCES += \
src/Settings/SettingsManager.cc \
src/Settings/UnitsSettings.cc \
src/Settings/VideoSettings.cc \
src/Terrain.cc \
src/Terrain/TerrainQuery.cc \
src/Vehicle/MAVLinkLogManager.cc \
src/VehicleSetup/JoystickConfigController.cc \
src/comm/LinkConfiguration.cc \
......
......@@ -18,8 +18,8 @@
AudioOutput::AudioOutput(QGCApplication* app, QGCToolbox* toolbox)
: QGCTool(app, toolbox)
, _tts(new QTextToSpeech(this))
{
_tts = new QTextToSpeech(this);
connect(_tts, &QTextToSpeech::stateChanged, this, &AudioOutput::_stateChanged);
}
......
......@@ -1925,6 +1925,78 @@ This is the ratio of static pressure error to dynamic pressure generated by a wi
<boolean />
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_P_FLPS" type="FLOAT">
<short_desc>Pitch trim increment for flaps configuration</short_desc>
<long_desc>This increment is added to the pitch trim whenever flaps are fully deployed.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_P_VMAX" type="FLOAT">
<short_desc>Pitch trim increment at maximum airspeed</short_desc>
<long_desc>This increment is added to TRIM_PITCH when airspeed is FW_AIRSP_MAX.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_P_VMIN" type="FLOAT">
<short_desc>Pitch trim increment at minimum airspeed</short_desc>
<long_desc>This increment is added to TRIM_PITCH when airspeed is FW_AIRSPD_MIN.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_R_FLPS" type="FLOAT">
<short_desc>Roll trim increment for flaps configuration</short_desc>
<long_desc>This increment is added to TRIM_ROLL whenever flaps are fully deployed.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_R_VMAX" type="FLOAT">
<short_desc>Roll trim increment at maximum airspeed</short_desc>
<long_desc>This increment is added to TRIM_ROLL when airspeed is FW_AIRSP_MAX.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_R_VMIN" type="FLOAT">
<short_desc>Roll trim increment at minimum airspeed</short_desc>
<long_desc>This increment is added to TRIM_ROLL when airspeed is FW_AIRSPD_MIN.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_Y_VMAX" type="FLOAT">
<short_desc>Yaw trim increment at maximum airspeed</short_desc>
<long_desc>This increment is added to TRIM_YAW when airspeed is FW_AIRSP_MAX.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_DTRIM_Y_VMIN" type="FLOAT">
<short_desc>Yaw trim increment at minimum airspeed</short_desc>
<long_desc>This increment is added to TRIM_YAW when airspeed is FW_AIRSPD_MIN.</long_desc>
<min>-0.25</min>
<max>0.25</max>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/fw_att_control</scope>
</parameter>
<parameter default="0.0" name="FW_FLAPERON_SCL" type="FLOAT">
<short_desc>Scale factor for flaperons</short_desc>
<min>0.0</min>
......@@ -2406,7 +2478,7 @@ Set to 0 to disable heading hold</short_desc>
<short_desc>Scale throttle by pressure change</short_desc>
<long_desc>Automatically adjust throttle to account for decreased air density at higher altitudes. Start with a scale factor of 1.0 and adjust for different propulsion systems. When flying without airspeed sensor this will help to keep a constant performance over large altitude ranges. The default value of 0 will disable scaling.</long_desc>
<min>0.0</min>
<max>2.0</max>
<max>10.0</max>
<decimal>1</decimal>
<increment>0.1</increment>
<scope>modules/fw_pos_control_l1</scope>
......@@ -4189,7 +4261,7 @@ default 1.5 turns per second</short_desc>
<boolean />
<scope>modules/mc_att_control</scope>
</parameter>
<parameter default="0." name="MC_DTERM_CUTOFF" type="FLOAT">
<parameter default="30." name="MC_DTERM_CUTOFF" type="FLOAT">
<short_desc>Cutoff frequency for the low pass filter on the D-term in the rate controller</short_desc>
<long_desc>The D-term uses the derivative of the rate and thus is the most susceptible to noise. Therefore, using a D-term filter allows to decrease the driver-level filtering, which leads to reduced control latency and permits to increase the P gains. A value of 0 disables the filter.</long_desc>
<min>0</min>
......@@ -4251,16 +4323,6 @@ default 1.5 turns per second</short_desc>
<increment>0.1</increment>
<scope>modules/mc_att_control</scope>
</parameter>
<parameter default="0.2" name="MC_PITCH_TC" type="FLOAT">
<short_desc>Pitch time constant</short_desc>
<long_desc>Reduce if the system is too twitchy, increase if the response is too slow and sluggish.</long_desc>
<min>0.15</min>
<max>0.25</max>
<unit>s</unit>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/mc_att_control</scope>
</parameter>
<parameter default="0.30" name="MC_PR_INT_LIM" type="FLOAT">
<short_desc>Pitch rate integrator limit</short_desc>
<long_desc>Pitch rate integrator limit. Can be set to increase the amount of integrator available to counteract disturbances or reduced to improve settling time after large pitch moment trim changes.</long_desc>
......@@ -4331,16 +4393,6 @@ default 1.5 turns per second</short_desc>
<increment>0.1</increment>
<scope>modules/mc_att_control</scope>
</parameter>
<parameter default="0.2" name="MC_ROLL_TC" type="FLOAT">
<short_desc>Roll time constant</short_desc>
<long_desc>Reduce if the system is too twitchy, increase if the response is too slow and sluggish.</long_desc>
<min>0.15</min>
<max>0.25</max>
<unit>s</unit>
<decimal>2</decimal>
<increment>0.01</increment>
<scope>modules/mc_att_control</scope>
</parameter>
<parameter default="0.30" name="MC_RR_INT_LIM" type="FLOAT">
<short_desc>Roll rate integrator limit</short_desc>
<long_desc>Roll rate integrator limit. Can be set to increase the amount of integrator available to counteract disturbances or reduced to improve settling time after large roll moment trim changes.</long_desc>
......@@ -8113,7 +8165,7 @@ This is used for gathering replay logs for the ekf2 module</short_desc>
<reboot_required>true</reboot_required>
<scope>modules/sensors</scope>
</parameter>
<parameter default="30.0" name="IMU_GYRO_CUTOFF" type="FLOAT">
<parameter default="80.0" name="IMU_GYRO_CUTOFF" type="FLOAT">
<short_desc>Driver level cutoff frequency for gyro</short_desc>
<long_desc>The cutoff frequency for the 2nd order butterworth filter on the gyro driver. This features is currently supported by the mpu6000 and mpu9250. This only affects the signal sent to the controllers, not the estimators. 0 disables the filter.</long_desc>
<min>0</min>
......@@ -9240,15 +9292,16 @@ This is used for gathering replay logs for the ekf2 module</short_desc>
</parameter>
<parameter default="0" name="UAVCAN_ENABLE" type="INT32">
<short_desc>UAVCAN mode</short_desc>
<long_desc>0 - UAVCAN disabled. 1 - Basic support for UAVCAN actuators and sensors. 2 - Full support for dynamic node ID allocation and firmware update. 3 - Sets the motor control outputs to UAVCAN and enables support for dynamic node ID allocation and firmware update.</long_desc>
<long_desc>0 - UAVCAN disabled. 1 - Enables support for UAVCAN sensors without dynamic node ID allocation and firmware update. 2 - Enables support for UAVCAN sensors with dynamic node ID allocation and firmware update. 3 - Enables support for UAVCAN sensors and actuators with dynamic node ID allocation and firmware update. Also sets the motor control outputs to UAVCAN.</long_desc>
<min>0</min>
<max>3</max>
<reboot_required>true</reboot_required>
<scope>modules/uavcan</scope>
<values>
<value code="0">Disabled</value>
<value code="2">Only Sensors</value>
<value code="3">Sensors and Motors</value>
<value code="1">Sensors Manual Config</value>
<value code="2">Sensors Automatic Config</value>
<value code="3">Sensors and Actuators (ESCs) Automatic Config</value>
</values>
</parameter>
<parameter default="1" name="UAVCAN_ESC_IDLT" type="INT32">
......
......@@ -174,7 +174,7 @@ FlightMap {
}
MapFitFunctions {
id: mapFitFunctions
id: mapFitFunctions // The name for this id cannot be changed without breaking references outside of this code. Beware!
map: _flightMap
usePlannedHomePosition: false
planMasterController: _planMasterController
......
......@@ -146,7 +146,7 @@ Item {
z: QGroundControl.zOrderTopMost
color: mapPal.text
font.pointSize: ScreenTools.largeFontPointSize
text: "The vehicle has failed a pre-arm check. In order to arm the vehicle, resolve the failure or disable the arming check via the Safety tab on the Vehicle Setup page."
text: "The vehicle has failed a pre-arm check. In order to arm the vehicle, resolve the failure."
}
}
Column {
......
......@@ -90,10 +90,6 @@ Item {
}
function addMissionItemCoordsForFit(coordList) {
var homePosition = fitHomePosition()
if (homePosition.isValid) {
coordList.push(homePosition)
}
for (var i=1; i<_missionController.visualItems.count; i++) {
var missionItem = _missionController.visualItems.get(i)
if (missionItem.specifiesCoordinate && !missionItem.isStandaloneCoordinate) {
......
......@@ -25,6 +25,7 @@ const char* Joystick::_throttleModeSettingsKey = "ThrottleMode";
const char* Joystick::_exponentialSettingsKey = "Exponential";
const char* Joystick::_accumulatorSettingsKey = "Accumulator";
const char* Joystick::_deadbandSettingsKey = "Deadband";
const char* Joystick::_circleCorrectionSettingsKey = "Circle_Correction";
const char* Joystick::_txModeSettingsKey = NULL;
const char* Joystick::_fixedWingTXModeSettingsKey = "TXMode_FixedWing";
const char* Joystick::_multiRotorTXModeSettingsKey = "TXMode_MultiRotor";
......@@ -64,6 +65,7 @@ Joystick::Joystick(const QString& name, int axisCount, int buttonCount, int hatC
, _exponential(0)
, _accumulator(false)
, _deadband(false)
, _circleCorrection(false)
, _activeVehicle(NULL)
, _pollingStartedForCalibration(false)
, _multiVehicleManager(multiVehicleManager)
......@@ -124,6 +126,7 @@ void Joystick::_setDefaultCalibration(void) {
_exponential = 0;
_accumulator = false;
_deadband = false;
_circleCorrection = false;
_throttleMode = ThrottleModeCenterZero;
_calibrated = true;
......@@ -188,6 +191,7 @@ void Joystick::_loadSettings(void)
_exponential = settings.value(_exponentialSettingsKey, 0).toFloat();
_accumulator = settings.value(_accumulatorSettingsKey, false).toBool();
_deadband = settings.value(_deadbandSettingsKey, false).toBool();
_circleCorrection = settings.value(_circleCorrectionSettingsKey, false).toBool();
_throttleMode = (ThrottleMode_t)settings.value(_throttleModeSettingsKey, ThrottleModeCenterZero).toInt(&convertOk);
badSettings |= !convertOk;
......@@ -264,9 +268,10 @@ void Joystick::_saveSettings(void)
settings.setValue(_exponentialSettingsKey, _exponential);
settings.setValue(_accumulatorSettingsKey, _accumulator);
settings.setValue(_deadbandSettingsKey, _deadband);
settings.setValue(_circleCorrectionSettingsKey, _circleCorrection);
settings.setValue(_throttleModeSettingsKey, _throttleMode);
qCDebug(JoystickLog) << "_saveSettings calibrated:throttlemode:deadband:txmode" << _calibrated << _throttleMode << _deadband << _transmitterMode;
qCDebug(JoystickLog) << "_saveSettings calibrated:throttlemode:deadband:txmode" << _calibrated << _throttleMode << _deadband << _circleCorrection << _transmitterMode;
QString minTpl ("Axis%1Min");
QString maxTpl ("Axis%1Max");
......@@ -462,17 +467,19 @@ void Joystick::run(void)
throttle = throttle_accu;
}
float roll_limited = std::max(static_cast<float>(-M_PI_4), std::min(roll, static_cast<float>(M_PI_4)));
float pitch_limited = std::max(static_cast<float>(-M_PI_4), std::min(pitch, static_cast<float>(M_PI_4)));
float yaw_limited = std::max(static_cast<float>(-M_PI_4), std::min(yaw, static_cast<float>(M_PI_4)));
float throttle_limited = std::max(static_cast<float>(-M_PI_4), std::min(throttle, static_cast<float>(M_PI_4)));
// Map from unit circle to linear range and limit
roll = std::max(-1.0f, std::min(tanf(asinf(roll_limited)), 1.0f));
pitch = std::max(-1.0f, std::min(tanf(asinf(pitch_limited)), 1.0f));
yaw = std::max(-1.0f, std::min(tanf(asinf(yaw_limited)), 1.0f));
throttle = std::max(-1.0f, std::min(tanf(asinf(throttle_limited)), 1.0f));
if ( _circleCorrection ) {
float roll_limited = std::max(static_cast<float>(-M_PI_4), std::min(roll, static_cast<float>(M_PI_4)));
float pitch_limited = std::max(static_cast<float>(-M_PI_4), std::min(pitch, static_cast<float>(M_PI_4)));
float yaw_limited = std::max(static_cast<float>(-M_PI_4), std::min(yaw, static_cast<float>(M_PI_4)));
float throttle_limited = std::max(static_cast<float>(-M_PI_4), std::min(throttle, static_cast<float>(M_PI_4)));
// Map from unit circle to linear range and limit
roll = std::max(-1.0f, std::min(tanf(asinf(roll_limited)), 1.0f));
pitch = std::max(-1.0f, std::min(tanf(asinf(pitch_limited)), 1.0f));
yaw = std::max(-1.0f, std::min(tanf(asinf(yaw_limited)), 1.0f));
throttle = std::max(-1.0f, std::min(tanf(asinf(throttle_limited)), 1.0f));
}
if ( _exponential != 0 ) {
// Exponential (0% to -50% range like most RC radios)
//_exponential is set by a slider in joystickConfig.qml
......@@ -767,6 +774,19 @@ void Joystick::setDeadband(bool deadband)
_saveSettings();
}
bool Joystick::circleCorrection(void)
{
return _circleCorrection;
}
void Joystick::setCircleCorrection(bool circleCorrection)
{
_circleCorrection = circleCorrection;
_saveSettings();
emit circleCorrectionChanged(_circleCorrection);
}
void Joystick::setCalibrationMode(bool calibrating)
{
_calibrationMode = calibrating;
......
......@@ -76,8 +76,9 @@ public:
Q_PROPERTY(bool negativeThrust READ negativeThrust WRITE setNegativeThrust NOTIFY negativeThrustChanged)
Q_PROPERTY(float exponential READ exponential WRITE setExponential NOTIFY exponentialChanged)
Q_PROPERTY(bool accumulator READ accumulator WRITE setAccumulator NOTIFY accumulatorChanged)
Q_PROPERTY(bool requiresCalibration READ requiresCalibration CONSTANT)
Q_PROPERTY(bool requiresCalibration READ requiresCalibration CONSTANT)
Q_PROPERTY(bool circleCorrection READ circleCorrection WRITE setCircleCorrection NOTIFY circleCorrectionChanged)
// Property accessors
int axisCount(void) { return _axisCount; }
......@@ -120,6 +121,9 @@ public:
bool deadband(void);
void setDeadband(bool accu);
bool circleCorrection(void);
void setCircleCorrection(bool circleCorrection);
void setTXMode(int mode);
int getTXMode(void) { return _transmitterMode; }
......@@ -147,6 +151,8 @@ signals:
void enabledChanged(bool enabled);
void circleCorrectionChanged(bool circleCorrection);
/// Signal containing new joystick information
/// @param roll Range is -1:1, negative meaning roll left, positive meaning roll right
/// @param pitch Range i -1:1, negative meaning pitch down, positive meaning pitch up
......@@ -213,6 +219,7 @@ protected:
float _exponential;
bool _accumulator;
bool _deadband;
bool _circleCorrection;
Vehicle* _activeVehicle;
bool _pollingStartedForCalibration;
......@@ -229,6 +236,7 @@ private:
static const char* _exponentialSettingsKey;
static const char* _accumulatorSettingsKey;
static const char* _deadbandSettingsKey;
static const char* _circleCorrectionSettingsKey;
static const char* _txModeSettingsKey;
static const char* _fixedWingTXModeSettingsKey;
static const char* _multiRotorTXModeSettingsKey;
......
......@@ -138,7 +138,7 @@ bool JsonHelper::validateKeyTypes(const QJsonObject& jsonObject, const QStringLi
QString valueKey = keys[i];
if (jsonObject.contains(valueKey)) {
const QJsonValue& jsonValue = jsonObject[valueKey];
if (types[i] == QJsonValue::Null && jsonValue.type() == QJsonValue::Double) {
if (jsonValue.type() == QJsonValue::Null && types[i] == QJsonValue::Double) {
// Null type signals a NaN on a double value
continue;
}
......
......@@ -123,7 +123,7 @@ public:
static bool parseEnum(const QJsonObject& jsonObject, QStringList& enumStrings, QStringList& enumValues, QString& errorString, QString valueName = QString());
/// Returns NaN if the value is null, or it not the double value
/// Returns NaN if the value is null, or if not, the double value
static double possibleNaNJsonValue(const QJsonValue& value);
static const char* jsonVersionKey;
......
......@@ -36,17 +36,17 @@ public:
Q_INVOKABLE void rotateEntryPoint(void);
// Overrides from ComplexMissionItem
int lastSequenceNumber (void) const final;
bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final;
QString mapVisualQML (void) const final { return QStringLiteral("CorridorScanMapVisual.qml"); }
bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final;
QString mapVisualQML (void) const final { return QStringLiteral("CorridorScanMapVisual.qml"); }
// Overrides from TransectStyleComplexItem
void save (QJsonArray& planItems) final;
bool specifiesCoordinate (void) const final;
void appendMissionItems (QList<MissionItem*>& items, QObject* missionItemParent) final;
void applyNewAltitude (double newAltitude) final;
void save (QJsonArray& missionItems) final;
bool specifiesCoordinate (void) const final;
void appendMissionItems (QList<MissionItem*>& items, QObject* missionItemParent) final;
void applyNewAltitude (double newAltitude) final;
// Overrides from VisualMissionionItem
bool readyForSave (void) const;
static const char* jsonComplexItemTypeValue;
......@@ -54,26 +54,25 @@ public:
static const char* corridorWidthName;
private slots:
void _polylineDirtyChanged (bool dirty);
void _polylineCountChanged (int count);
void _rebuildCorridor (void);
void _polylineDirtyChanged (bool dirty);
void _rebuildCorridorPolygon (void);
// Overrides from TransectStyleComplexItem
virtual void _rebuildTransects (void) final;
void _rebuildTransectsPhase1 (void) final;
void _rebuildTransectsPhase2 (void) final;
private:
int _transectCount (void) const;
void _rebuildCorridorPolygon(void);
int _transectCount (void) const;
void _buildAndAppendMissionItems(QList<MissionItem*>& items, QObject* missionItemParent);
void _appendLoadedMissionItems (QList<MissionItem*>& items, QObject* missionItemParent);
QGCMapPolyline _corridorPolyline;
QList<QList<QGeoCoordinate>> _transectSegments; ///< Internal transect segments including grid exit, turnaround and internal camera points
bool _ignoreRecalc;
int _entryPoint;
int _entryPoint;
QMap<QString, FactMetaData*> _metaDataMap;
SettingsFact _corridorWidthFact;
static const char* _entryPointName;
static const char* _jsonEntryPointKey;
};
......@@ -24,7 +24,12 @@ void CorridorScanComplexItemTest::init(void)
_offlineVehicle = new Vehicle(MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, qgcApp()->toolbox()->firmwarePluginManager(), this);
_corridorItem = new CorridorScanComplexItem(_offlineVehicle, this);
// _corridorItem->setTurnaroundDist(0); // Unit test written for no turnaround distance
// vehicleSpeed need for terrain calcs
MissionController::MissionFlightStatus_t missionFlightStatus;
missionFlightStatus.vehicleSpeed = 5;
_corridorItem->setMissionFlightStatus(missionFlightStatus);
_setPolyline();
_corridorItem->setDirty(false);
......@@ -130,21 +135,78 @@ void CorridorScanComplexItemTest::_testEntryLocation(void)
}
#endif
void CorridorScanComplexItemTest::_waitForReadyForSave(void)
{
int loops = 0;
while (loops++ < 8) {
if (_corridorItem->readyForSave()) {
return;
}
QTest::qWait(500);
}
QVERIFY(false);
}
void CorridorScanComplexItemTest::_testItemCount(void)
{
QList<MissionItem*> items;
_corridorItem->turnAroundDistance()->setRawValue(0);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(true);
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->turnAroundDistance()->setRawValue(0);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(false);
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->turnAroundDistance()->setRawValue(20);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(true);
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->turnAroundDistance()->setRawValue(20);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(false);
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
#if 0
// Terrain queries seem to take random amount of time so these don't work 100%
_corridorItem->setFollowTerrain(true);
_corridorItem->turnAroundDistance()->setRawValue(0);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(true);
_waitForReadyForSave();
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->turnAroundDistance()->setRawValue(0);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(false);
_waitForReadyForSave();
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->turnAroundDistance()->setRawValue(20);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(true);
_waitForReadyForSave();
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
_corridorItem->turnAroundDistance()->setRawValue(20);
_corridorItem->cameraTriggerInTurnAround()->setRawValue(false);
_waitForReadyForSave();
_corridorItem->appendMissionItems(items, this);
QCOMPARE(items.count() - 1, _corridorItem->lastSequenceNumber());
items.clear();
#endif
}
void CorridorScanComplexItemTest::_testPathChanges(void)
......
......@@ -36,6 +36,7 @@ private slots:
private:
void _setPolyline(void);
void _waitForReadyForSave(void);
enum {
corridorPolygonPathChangedIndex = 0,
......
......@@ -281,7 +281,7 @@ void MissionController::convertToKMLDocument(QDomDocument& document)
return;
}
float altitude = missionJson[_jsonPlannedHomePositionKey].toArray()[2].toDouble();
float homeAltitude = missionJson[_jsonPlannedHomePositionKey].toArray()[2].toDouble();
QString coord;
QStringList coords;
......@@ -296,11 +296,12 @@ void MissionController::convertToKMLDocument(QDomDocument& document)
qgcApp()->toolbox()->missionCommandTree()->getUIInfo(_controllerVehicle, item->command());
if (uiInfo && uiInfo->specifiesCoordinate() && !uiInfo->isStandaloneCoordinate()) {
double amslAltitude = item->param7() + (item->frame() == MAV_FRAME_GLOBAL ? 0 : homeAltitude);
coord = QString::number(item->param6(),'f',7) \
+ "," \
+ QString::number(item->param5(),'f',7) \
+ "," \
+ QString::number(item->param7() + altitude,'f',2);
+ QString::number(amslAltitude,'f',2);
coords.append(coord);
}
}
......@@ -347,13 +348,13 @@ int MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int i)
}
}
newItem->setDefaultsForCommand();
if ((MAV_CMD)newItem->command() == MAV_CMD_NAV_WAYPOINT) {
double prevAltitude;
MAV_FRAME prevFrame;
if (newItem->specifiesAltitude()) {
double prevAltitude;
int prevAltitudeMode;
if (_findPreviousAltitude(i, &prevAltitude, &prevFrame)) {
newItem->missionItem().setFrame(prevFrame);
newItem->missionItem().setParam7(prevAltitude);
if (_findPreviousAltitude(i, &prevAltitude, &prevAltitudeMode)) {
newItem->altitude()->setRawValue(prevAltitude);
newItem->setAltitudeMode((SimpleMissionItem::AltitudeMode)prevAltitudeMode);
}
}
newItem->setMissionFlightStatus(_missionFlightStatus);
......@@ -375,12 +376,12 @@ int MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int i)
newItem->setDefaultsForCommand();
newItem->setCoordinate(coordinate);
double prevAltitude;
MAV_FRAME prevFrame;
double prevAltitude;
int prevAltitudeMode;
if (_findPreviousAltitude(i, &prevAltitude, &prevFrame)) {
newItem->missionItem().setFrame(prevFrame);
newItem->missionItem().setParam7(prevAltitude);
if (_findPreviousAltitude(i, &prevAltitude, &prevAltitudeMode)) {
newItem->altitude()->setRawValue(prevAltitude);
newItem->setAltitudeMode((SimpleMissionItem::AltitudeMode)prevAltitudeMode);
}
_visualItems->insert(i, newItem);
......@@ -929,6 +930,18 @@ bool MissionController::loadTextFile(QFile& file, QString& errorString)
return true;
}
bool MissionController::readyForSaveSend(void) const
{
for (int i=0; i<_visualItems->count(); i++) {
VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
if (!visualItem->readyForSave()) {
return false;
}
}
return true;
}
void MissionController::save(QJsonObject& json)
{
json[JsonHelper::jsonVersionKey] = _missionFileVersion;
......@@ -1289,7 +1302,7 @@ void MissionController::_recalcMissionFlightStatus()
}
// Link back to home if first item is takeoff and we have home position
if (firstCoordinateItem && simpleItem && simpleItem->command() == MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF) {
if (firstCoordinateItem && simpleItem && (simpleItem->command() == MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF || simpleItem->command() == MavlinkQmlSingleton::MAV_CMD_NAV_VTOL_TAKEOFF)) {
if (showHomePosition) {
linkStartToHome = true;
if (_controllerVehicle->multiRotor() || _controllerVehicle->vtol()) {
......@@ -1308,9 +1321,15 @@ void MissionController::_recalcMissionFlightStatus()
case MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF:
vtolInHover = false;
break;
case MavlinkQmlSingleton::MAV_CMD_NAV_VTOL_TAKEOFF:
vtolInHover = true;
break;
case MavlinkQmlSingleton::MAV_CMD_NAV_LAND:
vtolInHover = false;
break;
case MavlinkQmlSingleton::MAV_CMD_NAV_VTOL_LAND:
vtolInHover = true;
break;
case MavlinkQmlSingleton::MAV_CMD_DO_VTOL_TRANSITION:
{
int transitionState = simpleItem->missionItem().param1();
......@@ -1656,11 +1675,11 @@ void MissionController::_inProgressChanged(bool inProgress)
emit syncInProgressChanged(inProgress);
}
bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude, MAV_FRAME* prevFrame)
bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude, int* prevAltitudeMode)
{
bool found = false;
double foundAltitude;
MAV_FRAME foundFrame;
int foundAltitudeMode;
if (newIndex > _visualItems->count()) {
return false;
......@@ -1673,9 +1692,9 @@ bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude
if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) {
if (visualItem->isSimpleItem()) {
SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(visualItem);
if ((MAV_CMD)simpleItem->command() == MAV_CMD_NAV_WAYPOINT) {
foundAltitude = simpleItem->exitCoordinate().altitude();
foundFrame = simpleItem->missionItem().frame();
if (simpleItem->specifiesAltitude()) {
foundAltitude = simpleItem->altitude()->rawValue().toDouble();
foundAltitudeMode = simpleItem->altitudeMode();
found = true;
break;
}
......@@ -1685,7 +1704,7 @@ bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude
if (found) {
*prevAltitude = foundAltitude;
*prevFrame = foundFrame;
*prevAltitudeMode = foundAltitudeMode;
}
return found;
......
......@@ -42,7 +42,7 @@ public:
MissionController(PlanMasterController* masterController, QObject* parent = NULL);
~MissionController();
typedef struct {
typedef struct _MissionFlightStatus_t {
double maxTelemetryDistance;
double totalDistance;
double totalTime;
......@@ -120,6 +120,10 @@ public:
/// @param sequenceNumber - index for new item, -1 to clear current item
Q_INVOKABLE void setCurrentPlanViewIndex(int sequenceNumber, bool force);
/// Determines if the mission has all data needed to be saved or sent to the vehicle. Currently the only case where this
/// would return false is when it is still waiting on terrain data to determine correct altitudes.
bool readyForSaveSend(void) const;
/// Sends the mission items to the specified vehicle
static void sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems);
......@@ -226,7 +230,7 @@ private:
void _setupActiveVehicle(Vehicle* activeVehicle, bool forceLoadFromVehicle);
void _calcPrevWaypointValues(double homeAlt, VisualMissionItem* currentItem, VisualMissionItem* prevItem, double* azimuth, double* distance, double* altDifference);
static double _calcDistanceToHome(VisualMissionItem* currentItem, VisualMissionItem* homeItem);
bool _findPreviousAltitude(int newIndex, double* prevAltitude, MAV_FRAME* prevFrame);
bool _findPreviousAltitude(int newIndex, double* prevAltitude, int* prevAltitudeMode);
static double _normalizeLat(double lat);
static double _normalizeLon(double lon);
void _addMissionSettings(QmlObjectListModel* visualItems, bool addToCenter);
......
......@@ -38,10 +38,6 @@ public:
/// from mission start to resumeIndex in the generate mission.
void generateResumeMission(int resumeIndex);
signals:
void currentIndexChanged (int currentIndex);
void lastCurrentIndexChanged (int lastCurrentIndex);
private slots:
void _mavlinkMessageReceived(const mavlink_message_t& message);
......
......@@ -427,6 +427,11 @@ void PlanMasterController::removeAll(void)
_missionController.removeAll();
_geoFenceController.removeAll();
_rallyPointController.removeAll();
if (_offline) {
_missionController.setDirty(false);
_geoFenceController.setDirty(false);
_rallyPointController.setDirty(false);
}
}
void PlanMasterController::removeAllFromVehicle(void)
......
......@@ -47,10 +47,14 @@ public:
/// Should be called immediately upon Component.onCompleted.
/// @param editMode true: controller being used in Plan view, false: controller being used in Fly view
Q_INVOKABLE virtual void start(bool editMode);
Q_INVOKABLE void start(bool editMode);
/// Starts the controller using a single static active vehicle. Will not track global active vehicle changes.
Q_INVOKABLE virtual void startStaticActiveVehicle(Vehicle* vehicle);
Q_INVOKABLE void startStaticActiveVehicle(Vehicle* vehicle);
/// Determines if the plan has all data needed to be saved or sent to the vehicle. Currently the only case where this
/// would return false is when it is still waiting on terrain data to determine correct altitudes.
Q_INVOKABLE bool readyForSaveSend(void) const { return _missionController.readyForSaveSend(); }
/// Sends a plan to the specified file
/// @param[in] vehicle Vehicle we are sending a plan to
......
......@@ -56,9 +56,11 @@ const QGCMapPolygon& QGCMapPolygon::operator=(const QGCMapPolygon& other)
clear();
QVariantList vertices = other.path();
for (int i=0; i<vertices.count(); i++) {
appendVertex(vertices[i].value<QGeoCoordinate>());
QList<QGeoCoordinate> rgCoord;
foreach (const QVariant& vertexVar, vertices) {
rgCoord.append(vertexVar.value<QGeoCoordinate>());
}
appendVertices(rgCoord);
setDirty(true);
......@@ -258,6 +260,18 @@ void QGCMapPolygon::appendVertex(const QGeoCoordinate& coordinate)
emit pathChanged();
}
void QGCMapPolygon::appendVertices(const QList<QGeoCoordinate>& coordinates)
{
QList<QObject*> objects;
foreach (const QGeoCoordinate& coordinate, coordinates) {
objects.append(new QGCQGeoCoordinate(coordinate, this));
_polygonPath.append(QVariant::fromValue(coordinate));
}
_polygonModel.append(objects);
emit pathChanged();
}
void QGCMapPolygon::_polygonModelDirtyChanged(bool dirty)
{
if (dirty) {
......@@ -434,9 +448,7 @@ void QGCMapPolygon::offset(double distance)
// Update internals
clear();
for (int i=0; i<rgNewPolygon.count(); i++) {
appendVertex(rgNewPolygon[i]);
}
appendVertices(rgNewPolygon);
}
bool QGCMapPolygon::loadKMLFile(const QString& kmlFile)
......@@ -509,9 +521,7 @@ bool QGCMapPolygon::loadKMLFile(const QString& kmlFile)
}
clear();
for (int i=0; i<rgCoords.count(); i++) {
appendVertex(rgCoords[i]);
}
appendVertices(rgCoords);
return true;
}
......
......@@ -40,6 +40,7 @@ public:
Q_INVOKABLE void clear(void);
Q_INVOKABLE void appendVertex(const QGeoCoordinate& coordinate);
Q_INVOKABLE void removeVertex(int vertexIndex);
Q_INVOKABLE void appendVertices(const QList<QGeoCoordinate>& coordinates);
/// Adjust the value for the specified coordinate
/// @param vertexIndex Polygon point index to modify (0-based)
......
......@@ -32,6 +32,7 @@ Item {
property int borderWidth: 0
property color borderColor: "black"
property var _gqcView: ggcView
property var _polygonComponent
property var _dragHandlesComponent
property var _splitHandlesComponent
......@@ -172,14 +173,16 @@ Item {
QGCFileDialog {
id: kmlLoadDialog
qgcView: _root.qgcView
qgcView: _qgcView
folder: QGroundControl.settingsManager.appSettings.missionSavePath
title: qsTr("Select KML File")
selectExisting: true
nameFilters: [ qsTr("KML files (*.kml)") ]
fileExtension: "kml"
fileExtension: QGroundControl.settingsManager.appSettings.kmlFileExtension
onAcceptedForLoad: {
mapPolygon.loadKMLFile(file)
mapFitFunctions.fitMapViewportToMissionItems()
close()
}
}
......
......@@ -358,13 +358,13 @@ bool QGCMapPolyline::loadKMLFile(const QString& kmlFile)
return false;
}
QDomNodeList rgNodes = doc.elementsByTagName("Polygon");
QDomNodeList rgNodes = doc.elementsByTagName("LineString");
if (rgNodes.count() == 0) {
qgcApp()->showMessage(tr("Unable to find Polygon node in KML"));
qgcApp()->showMessage(tr("Unable to find LineString node in KML"));
return false;
}
QDomNode coordinatesNode = rgNodes.item(0).namedItem("outerBoundaryIs").namedItem("LinearRing").namedItem("coordinates");
QDomNode coordinatesNode = rgNodes.item(0).namedItem("coordinates");
if (coordinatesNode.isNull()) {
qgcApp()->showMessage(tr("Internal error: Unable to find coordinates node in KML"));
return false;
......@@ -386,29 +386,8 @@ bool QGCMapPolyline::loadKMLFile(const QString& kmlFile)
rgCoords.append(coord);
}
// Determine winding, reverse if needed
double sum = 0;
for (int i=0; i<rgCoords.count(); i++) {
QGeoCoordinate coord1 = rgCoords[i];
QGeoCoordinate coord2 = (i == rgCoords.count() - 1) ? rgCoords[0] : rgCoords[i+1];
sum += (coord2.longitude() - coord1.longitude()) * (coord2.latitude() + coord1.latitude());
}
bool reverse = sum < 0.0;
if (reverse) {
QList<QGeoCoordinate> rgReversed;
for (int i=0; i<rgCoords.count(); i++) {
rgReversed.prepend(rgCoords[i]);
}
rgCoords = rgReversed;
}
clear();
for (int i=0; i<rgCoords.count(); i++) {
appendVertex(rgCoords[i]);
}
appendVertices(rgCoords);
return true;
}
......@@ -438,3 +417,15 @@ double QGCMapPolyline::length(void) const
return length;
}
void QGCMapPolyline::appendVertices(const QList<QGeoCoordinate>& coordinates)
{
QList<QObject*> objects;
foreach (const QGeoCoordinate& coordinate, coordinates) {
objects.append(new QGCQGeoCoordinate(coordinate, this));
_polylinePath.append(QVariant::fromValue(coordinate));
}
_polylineModel.append(objects);
emit pathChanged();
}
......@@ -34,6 +34,7 @@ public:
Q_INVOKABLE void clear(void);
Q_INVOKABLE void appendVertex(const QGeoCoordinate& coordinate);
Q_INVOKABLE void removeVertex(int vertexIndex);
Q_INVOKABLE void appendVertices(const QList<QGeoCoordinate>& coordinates);
/// Adjust the value for the specified coordinate
/// @param vertexIndex Polygon point index to modify (0-based)
......
......@@ -120,13 +120,28 @@ Item {
selectExisting: true
nameFilters: [ qsTr("KML files (*.kml)") ]
onAcceptedForLoad: {
mapPolyline.loadKMLFile(file)
close()
}
}
Menu {
id: menu
property int removeVertex
MenuItem {
text: qsTr("Remove vertex" )
onTriggered: mapPolyline.removeVertex(parent.removeVertex)
}
MenuItem {
text: qsTr("Load KML...")
onTriggered: kmlLoadDialog.openForLoad()
}
}
Component {
id: polylineComponent
......@@ -227,7 +242,15 @@ Item {
}
}
onClicked: mapPolyline.removeVertex(polylineVertex)
onClicked: {
if (polylineVertex == 0) {
menu.removeVertex = polylineVertex
menu.popup()
} else {
mapPolyline.removeVertex(polylineVertex)
}
}
}
}
......@@ -249,6 +272,14 @@ Item {
radius: width / 2
color: "white"
opacity: .90
QGCLabel {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: "..."
color: "black"
visible: polylineVertex == 0
}
}
}
}
......
This diff is collapsed.
......@@ -29,12 +29,21 @@ public:
~SimpleMissionItem();
const SimpleMissionItem& operator=(const SimpleMissionItem& other);
enum AltitudeMode {
AltitudeRelative,
AltitudeAMSL,
AltitudeAboveTerrain
};
Q_ENUM(AltitudeMode)
Q_PROPERTY(QString category READ category NOTIFY commandChanged)
Q_PROPERTY(bool friendlyEditAllowed READ friendlyEditAllowed NOTIFY friendlyEditAllowedChanged)
Q_PROPERTY(bool rawEdit READ rawEdit WRITE setRawEdit NOTIFY rawEditChanged) ///< true: raw item editing with all params
Q_PROPERTY(bool relativeAltitude READ relativeAltitude NOTIFY frameChanged)
Q_PROPERTY(bool specifiesAltitude READ specifiesAltitude NOTIFY commandChanged)
Q_PROPERTY(Fact* altitude READ altitude CONSTANT) ///< Altitude as specified by altitudeMode. Not necessarily true mission item altitude
Q_PROPERTY(AltitudeMode altitudeMode READ altitudeMode WRITE setAltitudeMode NOTIFY altitudeModeChanged)
Q_PROPERTY(Fact* amslAltAboveTerrain READ amslAltAboveTerrain CONSTANT) ///< Actual AMSL altitude for item if altitudeMode == AltitudeAboveTerrain
Q_PROPERTY(MavlinkQmlSingleton::Qml_MAV_CMD command READ command WRITE setCommand NOTIFY commandChanged)
/// Optional sections
......@@ -42,7 +51,6 @@ public:
Q_PROPERTY(QObject* cameraSection READ cameraSection NOTIFY cameraSectionChanged)
// These properties are used to display the editing ui
Q_PROPERTY(QmlObjectListModel* checkboxFacts READ checkboxFacts CONSTANT)
Q_PROPERTY(QmlObjectListModel* comboboxFacts READ comboboxFacts CONSTANT)
Q_PROPERTY(QmlObjectListModel* textFieldFacts READ textFieldFacts CONSTANT)
Q_PROPERTY(QmlObjectListModel* nanFacts READ nanFacts CONSTANT)
......@@ -60,15 +68,20 @@ public:
MavlinkQmlSingleton::Qml_MAV_CMD command(void) const { return (MavlinkQmlSingleton::Qml_MAV_CMD)_missionItem._commandFact.cookedValue().toInt(); }
bool friendlyEditAllowed (void) const;
bool rawEdit (void) const;
bool specifiesAltitude (void) const;
AltitudeMode altitudeMode (void) const { return _altitudeMode; }
Fact* altitude (void) { return &_altitudeFact; }
Fact* amslAltAboveTerrain (void) { return &_amslAltAboveTerrainFact; }
CameraSection* cameraSection (void) { return _cameraSection; }
SpeedSection* speedSection (void) { return _speedSection; }
QmlObjectListModel* textFieldFacts (void) { return &_textFieldFacts; }
QmlObjectListModel* nanFacts (void) { return &_nanFacts; }
QmlObjectListModel* checkboxFacts (void) { return &_checkboxFacts; }
QmlObjectListModel* comboboxFacts (void) { return &_comboboxFacts; }
void setRawEdit(bool rawEdit);
void setAltitudeMode(AltitudeMode altitudeMode);
void setCommandByIndex(int index);
......@@ -82,8 +95,6 @@ public:
bool load(QTextStream &loadStream);
bool load(const QJsonObject& json, int sequenceNumber, QString& errorString);
bool relativeAltitude(void) { return _missionItem.frame() == MAV_FRAME_GLOBAL_RELATIVE_ALT; }
MissionItem& missionItem(void) { return _missionItem; }
const MissionItem& missionItem(void) const { return _missionItem; }
......@@ -107,6 +118,7 @@ public:
void appendMissionItems (QList<MissionItem*>& items, QObject* missionItemParent) final;
void applyNewAltitude (double newAltitude) final;
void setMissionFlightStatus (MissionController::MissionFlightStatus_t& missionFlightStatus) final;
bool readyForSave (void) const final;
bool coordinateHasRelativeAltitude (void) const final { return _missionItem.relativeAltitude(); }
bool exitCoordinateHasRelativeAltitude (void) const final { return coordinateHasRelativeAltitude(); }
......@@ -123,51 +135,51 @@ public slots:
signals:
void commandChanged (int command);
void frameChanged (int frame);
void friendlyEditAllowedChanged (bool friendlyEditAllowed);
void headingDegreesChanged (double heading);
void rawEditChanged (bool rawEdit);
void cameraSectionChanged (QObject* cameraSection);
void speedSectionChanged (QObject* cameraSection);
void altitudeModeChanged (void);
private slots:
void _setDirtyFromSignal (void);
void _sectionDirtyChanged (bool dirty);
void _sendCommandChanged (void);
void _sendCoordinateChanged (void);
void _sendFrameChanged (void);
void _sendFriendlyEditAllowedChanged (void);
void _syncAltitudeRelativeToHomeToFrame (const QVariant& value);
void _syncFrameToAltitudeRelativeToHome (void);
void _updateLastSequenceNumber (void);
void _rebuildFacts (void);
void _setDirty (void);
void _sectionDirtyChanged (bool dirty);
void _sendCommandChanged (void);
void _sendCoordinateChanged (void);
void _sendFriendlyEditAllowedChanged(void);
void _altitudeChanged (void);
void _altitudeModeChanged (void);
void _terrainAltChanged (void);
void _updateLastSequenceNumber (void);
void _rebuildFacts (void);
void _rebuildTextFieldFacts (void);
private:
void _connectSignals (void);
void _setupMetaData (void);
void _updateOptionalSections(void);
void _rebuildTextFieldFacts (void);
void _rebuildNaNFacts (void);
void _rebuildCheckboxFacts (void);
void _rebuildComboBoxFacts (void);
MissionItem _missionItem;
bool _rawEdit;
bool _dirty;
bool _ignoreDirtyChangeSignals;
MissionItem _missionItem;
bool _rawEdit;
bool _dirty;
bool _ignoreDirtyChangeSignals;
SpeedSection* _speedSection;
CameraSection* _cameraSection;
MissionCommandTree* _commandTree;
Fact _altitudeRelativeToHomeFact;
Fact _supportedCommandFact;
Fact _supportedCommandFact;
AltitudeMode _altitudeMode;
Fact _altitudeFact;
Fact _amslAltAboveTerrainFact;
QmlObjectListModel _textFieldFacts;
QmlObjectListModel _nanFacts;
QmlObjectListModel _checkboxFacts;
QmlObjectListModel _comboboxFacts;
static FactMetaData* _altitudeMetaData;
......@@ -185,8 +197,11 @@ private:
FactMetaData _param6MetaData;
FactMetaData _param7MetaData;
bool _syncingAltitudeRelativeToHomeAndFrame; ///< true: already in a sync signal, prevents signal loop
bool _syncingHeadingDegreesAndParam4; ///< true: already in a sync signal, prevents signal loop
bool _syncingHeadingDegreesAndParam4; ///< true: already in a sync signal, prevents signal loop
static const char* _jsonAltitudeModeKey;
static const char* _jsonAltitudeKey;
static const char* _jsonAMSLAltAboveTerrainKey;
};
#endif
......@@ -15,43 +15,37 @@
const SimpleMissionItemTest::ItemInfo_t SimpleMissionItemTest::_rgItemInfo[] = {
{ MAV_CMD_NAV_WAYPOINT, MAV_FRAME_GLOBAL_RELATIVE_ALT },
{ MAV_CMD_NAV_LOITER_UNLIM, MAV_FRAME_GLOBAL_RELATIVE_ALT },
{ MAV_CMD_NAV_LOITER_UNLIM, MAV_FRAME_GLOBAL },
{ MAV_CMD_NAV_LOITER_TURNS, MAV_FRAME_GLOBAL_RELATIVE_ALT },
{ MAV_CMD_NAV_LOITER_TIME, MAV_FRAME_GLOBAL_RELATIVE_ALT },
{ MAV_CMD_NAV_LOITER_TIME, MAV_FRAME_GLOBAL },
{ MAV_CMD_NAV_LAND, MAV_FRAME_GLOBAL_RELATIVE_ALT },
{ MAV_CMD_NAV_TAKEOFF, MAV_FRAME_GLOBAL_RELATIVE_ALT },
{ MAV_CMD_NAV_TAKEOFF, MAV_FRAME_GLOBAL },
{ MAV_CMD_DO_JUMP, MAV_FRAME_MISSION },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesWaypoint[] = {
{ "Altitude", 70.1234567 },
{ "Hold", 10.1234567 },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLoiterUnlim[] = {
{ "Altitude", 70.1234567 },
{ "Radius", 30.1234567 },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLoiterTurns[] = {
{ "Altitude", 70.1234567 },
{ "Radius", 30.1234567 },
{ "Turns", 10.1234567 },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLoiterTime[] = {
{ "Altitude", 70.1234567 },
{ "Radius", 30.1234567 },
{ "Hold", 10.1234567 },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLand[] = {
{ "Altitude", 70.1234567 },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesTakeoff[] = {
{ "Pitch", 10.1234567 },
{ "Altitude", 70.1234567 },
};
const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesDoJump[] = {
......@@ -60,13 +54,14 @@ const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesDoJ
};
const SimpleMissionItemTest::ItemExpected_t SimpleMissionItemTest::_rgItemExpected[] = {
{ sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint)/sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint[0]), SimpleMissionItemTest::_rgFactValuesWaypoint, true },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim[0]), SimpleMissionItemTest::_rgFactValuesLoiterUnlim, true },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns[0]), SimpleMissionItemTest::_rgFactValuesLoiterTurns, true },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime[0]), SimpleMissionItemTest::_rgFactValuesLoiterTime, true },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLand)/sizeof(SimpleMissionItemTest::_rgFactValuesLand[0]), SimpleMissionItemTest::_rgFactValuesLand, true },
{ sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff)/sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff[0]), SimpleMissionItemTest::_rgFactValuesTakeoff, true },
{ sizeof(SimpleMissionItemTest::_rgFactValuesDoJump)/sizeof(SimpleMissionItemTest::_rgFactValuesDoJump[0]), SimpleMissionItemTest::_rgFactValuesDoJump, false },
// Text field facts count Fact Values Altitude Altitude Mode
{ sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint)/sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint[0]), SimpleMissionItemTest::_rgFactValuesWaypoint, 70.1234567, SimpleMissionItem::AltitudeRelative },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim[0]), SimpleMissionItemTest::_rgFactValuesLoiterUnlim, 70.1234567, SimpleMissionItem::AltitudeAMSL },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns[0]), SimpleMissionItemTest::_rgFactValuesLoiterTurns, 70.1234567, SimpleMissionItem::AltitudeRelative },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime[0]), SimpleMissionItemTest::_rgFactValuesLoiterTime, 70.1234567, SimpleMissionItem::AltitudeAMSL },
{ sizeof(SimpleMissionItemTest::_rgFactValuesLand)/sizeof(SimpleMissionItemTest::_rgFactValuesLand[0]), SimpleMissionItemTest::_rgFactValuesLand, 70.1234567, SimpleMissionItem::AltitudeRelative },
{ sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff)/sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff[0]), SimpleMissionItemTest::_rgFactValuesTakeoff, 70.1234567, SimpleMissionItem::AltitudeAMSL },
{ sizeof(SimpleMissionItemTest::_rgFactValuesDoJump)/sizeof(SimpleMissionItemTest::_rgFactValuesDoJump[0]), SimpleMissionItemTest::_rgFactValuesDoJump, qQNaN(), SimpleMissionItem::AltitudeRelative },
};
SimpleMissionItemTest::SimpleMissionItemTest(void)
......@@ -80,12 +75,12 @@ void SimpleMissionItemTest::init(void)
VisualMissionItemTest::init();
rgSimpleItemSignals[commandChangedIndex] = SIGNAL(commandChanged(int));
rgSimpleItemSignals[frameChangedIndex] = SIGNAL(frameChanged(int));
rgSimpleItemSignals[altitudeModeChangedIndex] = SIGNAL(altitudeModeChanged());
rgSimpleItemSignals[friendlyEditAllowedChangedIndex] = SIGNAL(friendlyEditAllowedChanged(bool));
rgSimpleItemSignals[headingDegreesChangedIndex] = SIGNAL(headingDegreesChanged(double));
rgSimpleItemSignals[rawEditChangedIndex] = SIGNAL(rawEditChanged(bool));
rgSimpleItemSignals[cameraSectionChangedIndex] = SIGNAL(rawEditChanged(bool));
rgSimpleItemSignals[speedSectionChangedIndex] = SIGNAL(rawEditChanged(bool));
rgSimpleItemSignals[cameraSectionChangedIndex] = SIGNAL(cameraSectionChanged(QObject*));
rgSimpleItemSignals[speedSectionChangedIndex] = SIGNAL(speedSectionChanged(QObject*));
rgSimpleItemSignals[coordinateHasRelativeAltitudeChangedIndex] = SIGNAL(coordinateHasRelativeAltitudeChanged(bool));
MissionItem missionItem(1, // sequence number
......@@ -164,8 +159,10 @@ void SimpleMissionItemTest::_testEditorFacts(void)
}
QCOMPARE(factCount, expected->cFactValues);
int expectedCount = expected->relativeAltCheckbox ? 1 : 0;
QCOMPARE(simpleMissionItem.checkboxFacts()->count(), expectedCount);
if (!qIsNaN(expected->altValue)) {
QCOMPARE(simpleMissionItem.altitudeMode(), expected->altMode);
QCOMPARE(simpleMissionItem.altitude()->rawValue().toDouble(), expected->altValue);
}
}
delete vehicle;
......@@ -228,18 +225,8 @@ void SimpleMissionItemTest::_testSignals(void)
QVERIFY(_spyVisualItem->checkOnlySignalByMask(dirtyChangedMask));
_spyVisualItem->clearAllSignals();
// Check frameChanged signalling. Calling setFrame should signal:
// frameChanged
// dirtyChanged
// friendlyEditAllowedChanged - this signal is not very smart on when it gets sent
// coordinateHasRelativeAltitudeChanged
missionItem.setFrame(MAV_FRAME_GLOBAL_RELATIVE_ALT);
QVERIFY(_spyVisualItem->checkNoSignals());
QVERIFY(_spySimpleItem->checkNoSignals());
missionItem.setFrame(MAV_FRAME_GLOBAL);
QVERIFY(_spySimpleItem->checkOnlySignalByMask(frameChangedMask | dirtyChangedMask | friendlyEditAllowedChangedMask | coordinateHasRelativeAltitudeChangedMask));
_simpleItem->setAltitudeMode(_simpleItem->altitudeMode() == SimpleMissionItem::AltitudeRelative ? SimpleMissionItem::AltitudeAMSL : SimpleMissionItem::AltitudeRelative);
QVERIFY(_spySimpleItem->checkOnlySignalByMask(dirtyChangedMask | friendlyEditAllowedChangedMask | altitudeModeChangedMask | coordinateHasRelativeAltitudeChangedMask));
_spySimpleItem->clearAllSignals();
_spyVisualItem->clearAllSignals();
......@@ -320,3 +307,18 @@ void SimpleMissionItemTest::_testSpeedSection(void)
QCOMPARE(_spyVisualItem->checkSignalsByMask(specifiedFlightSpeedChangedMask), true);
QCOMPARE(_simpleItem->dirty(), true);
}
void SimpleMissionItemTest::_testAltitudePropogation(void)
{
// Make sure that changes to altitude propogate to param 7 of the mission item
_simpleItem->setAltitudeMode(SimpleMissionItem::AltitudeRelative);
_simpleItem->altitude()->setRawValue(_simpleItem->altitude()->rawValue().toDouble() + 1);
QCOMPARE(_simpleItem->altitude()->rawValue().toDouble(), _simpleItem->missionItem().param7());
QCOMPARE(_simpleItem->missionItem().frame(), MAV_FRAME_GLOBAL_RELATIVE_ALT);
_simpleItem->setAltitudeMode(SimpleMissionItem::AltitudeAMSL);
_simpleItem->altitude()->setRawValue(_simpleItem->altitude()->rawValue().toDouble() + 1);
QCOMPARE(_simpleItem->altitude()->rawValue().toDouble(), _simpleItem->missionItem().param7());
QCOMPARE(_simpleItem->missionItem().frame(), MAV_FRAME_GLOBAL);
}
......@@ -31,11 +31,12 @@ private slots:
void _testSpeedSectionDirty(void);
void _testCameraSection(void);
void _testSpeedSection(void);
void _testAltitudePropogation(void);
private:
enum {
commandChangedIndex = 0,
frameChangedIndex,
altitudeModeChangedIndex,
friendlyEditAllowedChangedIndex,
headingDegreesChangedIndex,
rawEditChangedIndex,
......@@ -47,7 +48,7 @@ private:
enum {
commandChangedMask = 1 << commandChangedIndex,
frameChangedMask = 1 << frameChangedIndex,
altitudeModeChangedMask = 1 << altitudeModeChangedIndex,
friendlyEditAllowedChangedMask = 1 << friendlyEditAllowedChangedIndex,
headingDegreesChangedMask = 1 << headingDegreesChangedIndex,
rawEditChangedMask = 1 << rawEditChangedIndex,
......@@ -70,9 +71,10 @@ private:
} FactValue_t;
typedef struct {
size_t cFactValues;
const FactValue_t* rgFactValues;
bool relativeAltCheckbox;
size_t cFactValues;
const FactValue_t* rgFactValues;
double altValue;
SimpleMissionItem::AltitudeMode altMode;
} ItemExpected_t;
SimpleMissionItem* _simpleItem;
......
......@@ -562,53 +562,6 @@ void SurveyMissionItem::_swapPoints(QList<QPointF>& points, int index1, int inde
points[index2] = temp;
}
QList<QPointF> SurveyMissionItem::_convexPolygon(const QList<QPointF>& polygon)
{
// We use the Graham scan algorithem to convert the possibly concave polygon to a convex polygon
// https://en.wikipedia.org/wiki/Graham_scan
QList<QPointF> workPolygon(polygon);
// First point must be lowest y-coordinate point
for (int i=1; i<workPolygon.count(); i++) {
if (workPolygon[i].y() < workPolygon[0].y()) {
_swapPoints(workPolygon, i, 0);
}
}
// Sort the points by angle with first point
for (int i=1; i<workPolygon.count(); i++) {
qreal angle = _dp(workPolygon[0], workPolygon[i]);
for (int j=i+1; j<workPolygon.count(); j++) {
if (_dp(workPolygon[0], workPolygon[j]) > angle) {
_swapPoints(workPolygon, i, j);
angle = _dp(workPolygon[0], workPolygon[j]);
}
}
}
// Perform the the Graham scan
workPolygon.insert(0, workPolygon.last()); // Sentinel for algo stop
int convexCount = 1; // Number of points on the convex hull.
for (int i=2; i<=polygon.count(); i++) {
while (_ccw(workPolygon[convexCount-1], workPolygon[convexCount], workPolygon[i]) <= 0) {
if (convexCount > 1) {
convexCount -= 1;
} else if (i == polygon.count()) {
break;
} else {
i++;
}
}
convexCount++;
_swapPoints(workPolygon, convexCount, i);
}
return workPolygon.mid(1, convexCount);
}
/// Returns true if the current grid angle generates north/south oriented transects
bool SurveyMissionItem::_gridAngleIsNorthSouthTransects()
{
......@@ -725,8 +678,6 @@ void SurveyMissionItem::_generateGrid(void)
qCDebug(SurveyMissionItemLog) << "vertex:x:y" << vertex << polygonPoints.last().x() << polygonPoints.last().y();
}
polygonPoints = _convexPolygon(polygonPoints);
double coveredArea = 0.0;
for (int i=0; i<polygonPoints.count(); i++) {
if (i != 0) {
......@@ -744,8 +695,6 @@ void SurveyMissionItem::_generateGrid(void)
_adjustTransectsToEntryPointLocation(_transectSegments);
_appendGridPointsFromTransects(_transectSegments);
if (_refly90Degrees) {
QVariantList reflyPointsGeo;
transectSegments.clear();
cameraShots += _gridGenerator(polygonPoints, transectSegments, true /* refly */);
_convertTransectToGeo(transectSegments, tangentOrigin, _reflyTransectSegments);
......@@ -876,28 +825,41 @@ void SurveyMissionItem::_intersectLinesWithRect(const QList<QLineF>& lineList, c
void SurveyMissionItem::_intersectLinesWithPolygon(const QList<QLineF>& lineList, const QPolygonF& polygon, QList<QLineF>& resultLines)
{
resultLines.clear();
for (int i=0; i<lineList.count(); i++) {
int foundCount = 0;
QLineF intersectLine;
const QLineF& line = lineList[i];
QList<QPointF> intersections;
// Intersect the line with all the polygon edges
for (int j=0; j<polygon.count()-1; j++) {
QPointF intersectPoint;
QLineF polygonLine = QLineF(polygon[j], polygon[j+1]);
if (line.intersect(polygonLine, &intersectPoint) == QLineF::BoundedIntersection) {
if (foundCount == 0) {
foundCount++;
intersectLine.setP1(intersectPoint);
} else {
foundCount++;
intersectLine.setP2(intersectPoint);
break;
}
intersections.append(intersectPoint);
}
}
if (foundCount == 2) {
resultLines += intersectLine;
// We now have one or more intersection points all along the same line. Find the two
// which are furthest away from each other to form the transect.
if (intersections.count() > 1) {
QPointF firstPoint;
QPointF secondPoint;
double currentMaxDistance = 0;
for (int i=0; i<intersections.count(); i++) {
for (int j=0; j<intersections.count(); j++) {
QLineF lineTest(intersections[i], intersections[j]);
\
double newMaxDistance = lineTest.length();
if (newMaxDistance > currentMaxDistance) {
firstPoint = intersections[i];
secondPoint = intersections[j];
currentMaxDistance = newMaxDistance;
}
}
}
resultLines += QLineF(firstPoint, secondPoint);
}
}
}
......
......@@ -219,7 +219,6 @@ private:
qreal _ccw(QPointF pt1, QPointF pt2, QPointF pt3);
qreal _dp(QPointF pt1, QPointF pt2);
void _swapPoints(QList<QPointF>& points, int index1, int index2);
QList<QPointF> _convexPolygon(const QList<QPointF>& polygon);
void _reverseTransectOrder(QList<QList<QGeoCoordinate>>& transects);
void _reverseInternalTransectPoints(QList<QList<QGeoCoordinate>>& transects);
void _adjustTransectsToEntryPointLocation(QList<QList<QGeoCoordinate>>& transects);
......
......@@ -34,5 +34,32 @@
"shortDescription": "Refly the pattern at a 90 degree angle",
"type": "bool",
"defaultValue": false
},
{
"name": "TerrainAdjustTolerance",
"shortDescription": "If adjacent terrain waypoints are within this tolerence they will be removed.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m",
"defaultValue": 10
},
{
"name": "TerrainAdjustMaxClimbRate",
"shortDescription": "The maximum climb rate from one waypoint to another when adjusting for terrain. Set to 0 for no max.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m/s",
"defaultValue": 0
},
{
"name": "TerrainAdjustMaxDescentRate",
"shortDescription": "The maximum descent rate from one waypoint to another when adjusting for terrain. Set to 0 for no max.",
"type": "double",
"decimalPlaces": 2,
"min": 0,
"units": "m/s",
"defaultValue": 0
}
]
......@@ -31,7 +31,7 @@ void TransectStyleComplexItemTest::init(void)
_rgSignals[cameraShotsChangedIndex] = SIGNAL(cameraShotsChanged());
_rgSignals[timeBetweenShotsChangedIndex] = SIGNAL(timeBetweenShotsChanged());
_rgSignals[cameraMinTriggerIntervalChangedIndex] = SIGNAL(cameraMinTriggerIntervalChanged(double));
_rgSignals[transectPointsChangedIndex] = SIGNAL(transectPointsChanged());
_rgSignals[visualTransectPointsChangedIndex] = SIGNAL(visualTransectPointsChanged());
_rgSignals[coveredAreaChangedIndex] = SIGNAL(coveredAreaChanged());
_rgSignals[dirtyChangedIndex] = SIGNAL(dirtyChanged(bool));
_rgSignals[complexDistanceChangedIndex] = SIGNAL(complexDistanceChanged());
......@@ -174,7 +174,12 @@ TransectStyleItem::TransectStyleItem(Vehicle* vehicle, QObject* parent)
}
void TransectStyleItem::_rebuildTransects(void)
void TransectStyleItem::_rebuildTransectsPhase1(void)
{
rebuildTransectsCalled = true;
}
void TransectStyleItem::_rebuildTransectsPhase2(void)
{
}
......@@ -42,7 +42,7 @@ private:
cameraShotsChangedIndex = 0,
timeBetweenShotsChangedIndex,
cameraMinTriggerIntervalChangedIndex,
transectPointsChangedIndex,
visualTransectPointsChangedIndex,
coveredAreaChangedIndex,
// These signals are from ComplexItem
dirtyChangedIndex,
......@@ -56,18 +56,18 @@ private:
enum {
// These signals are from TransectStyleComplexItem
cameraShotsChangedMask = 1 << cameraShotsChangedIndex,
timeBetweenShotsChangedMask = 1 << timeBetweenShotsChangedIndex,
cameraMinTriggerIntervalChangedMask = 1 << cameraMinTriggerIntervalChangedIndex,
transectPointsChangedMask = 1 << transectPointsChangedIndex,
coveredAreaChangedMask = 1 << coveredAreaChangedIndex,
cameraShotsChangedMask = 1 << cameraShotsChangedIndex,
timeBetweenShotsChangedMask = 1 << timeBetweenShotsChangedIndex,
cameraMinTriggerIntervalChangedMask = 1 << cameraMinTriggerIntervalChangedIndex,
visualTransectPointsChangedMask = 1 << visualTransectPointsChangedIndex,
coveredAreaChangedMask = 1 << coveredAreaChangedIndex,
// These signals are from ComplexItem
dirtyChangedMask = 1 << dirtyChangedIndex,
complexDistanceChangedMask = 1 << complexDistanceChangedIndex,
greatestDistanceToChangedMask = 1 << greatestDistanceToChangedIndex,
additionalTimeDelayChangedMask = 1 << additionalTimeDelayChangedIndex,
dirtyChangedMask = 1 << dirtyChangedIndex,
complexDistanceChangedMask = 1 << complexDistanceChangedIndex,
greatestDistanceToChangedMask = 1 << greatestDistanceToChangedIndex,
additionalTimeDelayChangedMask = 1 << additionalTimeDelayChangedIndex,
// These signals are from VisualMissionItem
lastSequenceNumberChangedMask = 1 << lastSequenceNumberChangedIndex,
lastSequenceNumberChangedMask = 1 << lastSequenceNumberChangedIndex,
};
static const size_t _cSignals = maxSignalIndex;
......@@ -87,7 +87,6 @@ public:
TransectStyleItem(Vehicle* vehicle, QObject* parent = NULL);
// Overrides from ComplexMissionItem
int lastSequenceNumber (void) const final { return _sequenceNumber; }
QString mapVisualQML (void) const final { return QString(); }
bool load (const QJsonObject& complexObject, int sequenceNumber, QString& errorString) final { Q_UNUSED(complexObject); Q_UNUSED(sequenceNumber); Q_UNUSED(errorString); return false; }
......@@ -101,5 +100,6 @@ public:
private slots:
// Overrides from TransectStyleComplexItem
void _rebuildTransects (void) final;
void _rebuildTransectsPhase1(void) final;
void _rebuildTransectsPhase2(void) final;
};
......@@ -15,7 +15,7 @@
#include "FirmwarePluginManager.h"
#include "QGCApplication.h"
#include "JsonHelper.h"
#include "Terrain.h"
#include "TerrainQuery.h"
const char* VisualMissionItem::jsonTypeKey = "type";
const char* VisualMissionItem::jsonTypeSimpleItemValue = "SimpleItem";
......@@ -38,15 +38,7 @@ VisualMissionItem::VisualMissionItem(Vehicle* vehicle, QObject* parent)
, _lastLatTerrainQuery (0)
, _lastLonTerrainQuery (0)
{
// Don't get terrain altitude information for submarines or boards
if (_vehicle->vehicleType() != MAV_TYPE_SUBMARINE && _vehicle->vehicleType() != MAV_TYPE_SURFACE_BOAT) {
_updateTerrainTimer.setInterval(500);
_updateTerrainTimer.setSingleShot(true);
connect(&_updateTerrainTimer, &QTimer::timeout, this, &VisualMissionItem::_reallyUpdateTerrainAltitude);
connect(this, &VisualMissionItem::coordinateChanged, this, &VisualMissionItem::_updateTerrainAltitude);
}
_commonInit();
}
VisualMissionItem::VisualMissionItem(const VisualMissionItem& other, QObject* parent)
......@@ -63,8 +55,17 @@ VisualMissionItem::VisualMissionItem(const VisualMissionItem& other, QObject* pa
{
*this = other;
_commonInit();
}
void VisualMissionItem::_commonInit(void)
{
// Don't get terrain altitude information for submarines or boats
if (_vehicle->vehicleType() != MAV_TYPE_SUBMARINE && _vehicle->vehicleType() != MAV_TYPE_SURFACE_BOAT) {
_updateTerrainTimer.setInterval(500);
_updateTerrainTimer.setSingleShot(true);
connect(&_updateTerrainTimer, &QTimer::timeout, this, &VisualMissionItem::_reallyUpdateTerrainAltitude);
connect(this, &VisualMissionItem::coordinateChanged, this, &VisualMissionItem::_updateTerrainAltitude);
}
}
......@@ -172,18 +173,18 @@ void VisualMissionItem::_reallyUpdateTerrainAltitude(void)
if (coord.isValid() && (qIsNaN(_terrainAltitude) || !qFuzzyCompare(_lastLatTerrainQuery, coord.latitude()) || qFuzzyCompare(_lastLonTerrainQuery, coord.longitude()))) {
_lastLatTerrainQuery = coord.latitude();
_lastLonTerrainQuery = coord.longitude();
ElevationProvider* terrain = new ElevationProvider(this);
connect(terrain, &ElevationProvider::terrainData, this, &VisualMissionItem::_terrainDataReceived);
TerrainAtCoordinateQuery* terrain = new TerrainAtCoordinateQuery(this);
connect(terrain, &TerrainAtCoordinateQuery::terrainData, this, &VisualMissionItem::_terrainDataReceived);
QList<QGeoCoordinate> rgCoord;
rgCoord.append(coordinate());
terrain->queryTerrainData(rgCoord);
terrain->requestData(rgCoord);
}
}
void VisualMissionItem::_terrainDataReceived(bool success, QList<float> altitudes)
void VisualMissionItem::_terrainDataReceived(bool success, QList<double> heights)
{
if (success) {
_terrainAltitude = altitudes[0];
_terrainAltitude = heights[0];
emit terrainAltitudeChanged(_terrainAltitude);
sender()->deleteLater();
}
......
......@@ -130,6 +130,11 @@ public:
virtual void setSequenceNumber (int sequenceNumber) = 0;
virtual int lastSequenceNumber (void) const = 0;
/// Specifies whether the item has all the data it needs such that it can be saved. Currently the only
/// case where this returns false is if it has not determined terrain values yet.
/// @return true: Ready to save, false: Still waiting on information
virtual bool readyForSave(void) const { return true; }
/// Save the item(s) in Json format
/// @param missionItems Current set of mission items, new items should be appended to the end
virtual void save(QJsonArray& missionItems) = 0;
......@@ -206,9 +211,11 @@ protected:
private slots:
void _updateTerrainAltitude (void);
void _reallyUpdateTerrainAltitude (void);
void _terrainDataReceived (bool success, QList<float> altitudes);
void _terrainDataReceived (bool success, QList<double> heights);
private:
void _commonInit(void);
QTimer _updateTerrainTimer;
double _lastLatTerrainQuery;
double _lastLonTerrainQuery;
......
......@@ -56,6 +56,11 @@ Rectangle {
anchors.right: parent.right
spacing: _margin
QGCLabel {
text: "WIP: Careful!"
color: qgcPal.warningText
}
QGCLabel {
anchors.left: parent.left
anchors.right: parent.right
......@@ -126,6 +131,59 @@ Rectangle {
onClicked: missionItem.rotateEntryPoint()
}
SectionHeader {
id: terrainHeader
text: qsTr("Terrain")
checked: missionItem.followTerrain
}
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
spacing: _margin
visible: terrainHeader.checked
QGCCheckBox {
id: followsTerrainCheckBox
text: qsTr("Vehicle follows terrain")
checked: missionItem.followTerrain
onClicked: missionItem.followTerrain = checked
}
GridLayout {
anchors.left: parent.left
anchors.right: parent.right
columnSpacing: _margin
rowSpacing: _margin
columns: 2
visible: followsTerrainCheckBox.checked
QGCLabel {
text: "WIP: Careful!"
color: qgcPal.warningText
Layout.columnSpan: 2
}
QGCLabel { text: qsTr("Tolerance") }
FactTextField {
fact: missionItem.terrainAdjustTolerance
Layout.fillWidth: true
}
QGCLabel { text: qsTr("Max Climb Rate") }
FactTextField {
fact: missionItem.terrainAdjustMaxClimbRate
Layout.fillWidth: true
}
QGCLabel { text: qsTr("Max Descent Rate") }
FactTextField {
fact: missionItem.terrainAdjustMaxDescentRate
Layout.fillWidth: true
}
}
}
SectionHeader {
id: statsHeader
text: qsTr("Statistics")
......
......@@ -119,7 +119,7 @@ Item {
MapPolyline {
line.color: "white"
line.width: 2
path: _missionItem.transectPoints
path: _missionItem.visualTransectPoints
}
}
}
......@@ -100,7 +100,7 @@ QGCView {
property bool _firstLoadComplete: false
MapFitFunctions {
id: mapFitFunctions
id: mapFitFunctions // The name for this id cannot be changed without breaking references outside of this code. Beware!
map: editorMap
usePlannedHomePosition: true
planMasterController: _planMasterController
......@@ -196,7 +196,15 @@ QGCView {
_missionController.setCurrentPlanViewIndex(0, true)
}
function waitingOnDataMessage() {
_qgcView.showMessage(qsTr("Unable to Save/Upload"), qsTr("Plan is waiting on terrain data from server for correct altitude values."), StandardButton.Ok)
}
function upload() {
if (!readyForSaveSend()) {
waitingOnDataMessage()
return
}
if (_activeVehicle && _activeVehicle.armed && _activeVehicle.flightMode === _activeVehicle.missionFlightMode) {
_qgcView.showDialog(activeMissionUploadDialogComponent, qsTr("Plan Upload"), _qgcView.showDialogDefaultWidth, StandardButton.Cancel)
} else {
......@@ -208,14 +216,22 @@ QGCView {
fileDialog.title = qsTr("Select Plan File")
fileDialog.selectExisting = true
fileDialog.nameFilters = masterController.loadNameFilters
fileDialog.fileExtension = QGroundControl.settingsManager.appSettings.planFileExtension
fileDialog.fileExtension2 = QGroundControl.settingsManager.appSettings.missionFileExtension
fileDialog.openForLoad()
}
function saveToSelectedFile() {
if (!readyForSaveSend()) {
waitingOnDataMessage()
return
}
fileDialog.title = qsTr("Save Plan")
fileDialog.plan = true
fileDialog.selectExisting = false
fileDialog.nameFilters = masterController.saveNameFilters
fileDialog.fileExtension = QGroundControl.settingsManager.appSettings.planFileExtension
fileDialog.fileExtension2 = QGroundControl.settingsManager.appSettings.missionFileExtension
fileDialog.openForSave()
}
......@@ -224,10 +240,16 @@ QGCView {
}
function saveKmlToSelectedFile() {
if (!readyForSaveSend()) {
waitingOnDataMessage()
return
}
fileDialog.title = qsTr("Save KML")
fileDialog.plan = false
fileDialog.selectExisting = false
fileDialog.nameFilters = masterController.saveKmlFilters
fileDialog.fileExtension = QGroundControl.settingsManager.appSettings.kmlFileExtension
fileDialog.fileExtension2 = ""
fileDialog.openForSave()
}
}
......@@ -274,8 +296,6 @@ QGCView {
qgcView: _qgcView
property bool plan: true
folder: QGroundControl.settingsManager.appSettings.missionSavePath
fileExtension: QGroundControl.settingsManager.appSettings.planFileExtension
fileExtension2: QGroundControl.settingsManager.appSettings.missionFileExtension
onAcceptedForSave: {
plan ? masterController.saveToFile(file) : masterController.saveToKml(file)
......
......@@ -17,6 +17,9 @@ Rectangle {
color: qgcPal.windowShadeDark
radius: _radius
property bool _specifiesAltitude: missionItem.specifiesAltitude
property bool _altModeIsTerrain: missionItem.altitudeMode === 2
Column {
id: valuesColumn
anchors.margins: _margin
......@@ -68,9 +71,27 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
flow: GridLayout.TopToBottom
rows: missionItem.textFieldFacts.count + missionItem.nanFacts.count + (missionItem.speedSection.available ? 1 : 0)
rows: missionItem.textFieldFacts.count +
missionItem.nanFacts.count +
(missionItem.speedSection.available ? 1 : 0) +
(_specifiesAltitude ? 1 : 0) +
(_altModeIsTerrain ? 1 : 0)
columns: 2
QGCComboBox {
id: altCombo
model: [ qsTr("Alt (Rel)"), qsTr("AMSL"), qsTr("Above Terrain") ]
currentIndex: missionItem.altitudeMode
Layout.fillWidth: true
onActivated: missionItem.altitudeMode = index
visible: _specifiesAltitude
}
QGCLabel {
text: qsTr("Actual AMSL Alt")
visible: _altModeIsTerrain
}
Repeater {
model: missionItem.textFieldFacts
......@@ -95,6 +116,19 @@ Rectangle {
visible: missionItem.speedSection.available
}
FactTextField {
showUnits: true
fact: missionItem.altitude
Layout.fillWidth: true
visible: _specifiesAltitude
}
FactLabel {
fact: missionItem.amslAltAboveTerrain
visible: _altModeIsTerrain
}
Repeater {
model: missionItem.textFieldFacts
......@@ -102,6 +136,7 @@ Rectangle {
showUnits: true
fact: object
Layout.fillWidth: true
enabled: !object.readOnly
}
}
......@@ -124,15 +159,6 @@ Rectangle {
}
}
Repeater {
model: missionItem.checkboxFacts
FactCheckBox {
text: object.name
fact: object
}
}
CameraSection {
checked: missionItem.cameraSection.settingsSpecified
visible: missionItem.cameraSection.available
......
......@@ -63,6 +63,7 @@ Rectangle {
}
}
}
recalcFromCameraValues()
}
function recalcFromCameraValues() {
......@@ -439,16 +440,28 @@ Rectangle {
Layout.fillWidth: true
}
ToolButton {
id: windRoseButton
anchors.verticalCenter: angleText.verticalCenter
iconSource: qgcPal.globalTheme === QGCPalette.Light ? "/res/wind-roseBlack.svg" : "/res/wind-rose.svg"
visible: _vehicle ? _vehicle.fixedWing : false
Rectangle {
id: windRoseButton
width: ScreenTools.implicitTextFieldHeight
height: width
color: qgcPal.button
visible: _vehicle ? _vehicle.fixedWing : false
QGCColoredImage {
anchors.fill: parent
source: "/res/wind-rose.svg"
smooth: true
color: qgcPal.buttonText
}
onClicked: {
windRosePie.angle = Number(gridAngleText.text)
var cords = windRoseButton.mapToItem(_root, 0, 0)
windRosePie.popup(cords.x + windRoseButton.width / 2, cords.y + windRoseButton.height / 2)
QGCMouseArea {
fillItem: parent
onClicked: {
windRosePie.angle = Number(gridAngleText.text)
var cords = windRoseButton.mapToItem(_root, 0, 0)
windRosePie.popup(cords.x + windRoseButton.width / 2, cords.y + windRoseButton.height / 2)
}
}
}
}
......@@ -542,16 +555,28 @@ Rectangle {
Layout.fillWidth: true
}
ToolButton {
id: manualWindRoseButton
anchors.verticalCenter: manualAngleText.verticalCenter
Layout.columnSpan: 1
iconSource: qgcPal.globalTheme === QGCPalette.Light ? "/res/wind-roseBlack.svg" : "/res/wind-rose.svg"
visible: _vehicle ? _vehicle.fixedWing : false
Rectangle {
id: manualWindRoseButton
width: ScreenTools.implicitTextFieldHeight
height: width
color: qgcPal.button
visible: _vehicle ? _vehicle.fixedWing : false
onClicked: {
var cords = manualWindRoseButton.mapToItem(_root, 0, 0)
windRosePie.popup(cords.x + manualWindRoseButton.width / 2, cords.y + manualWindRoseButton.height / 2)
QGCColoredImage {
anchors.fill: parent
source: "/res/wind-rose.svg"
smooth: true
color: qgcPal.buttonText
}
QGCMouseArea {
fillItem: parent
onClicked: {
windRosePie.angle = Number(gridAngleText.text)
var cords = manualWindRoseButton.mapToItem(_root, 0, 0)
windRosePie.popup(cords.x + manualWindRoseButton.width / 2, cords.y + manualWindRoseButton.height / 2)
}
}
}
}
......
......@@ -172,11 +172,42 @@ void QmlObjectListModel::insert(int i, QObject* object)
setDirty(true);
}
void QmlObjectListModel::insert(int i, QList<QObject*> objects)
{
if (i < 0 || i > _objectList.count()) {
qWarning() << "Invalid index index:count" << i << _objectList.count();
}
int j = i;
foreach (QObject* object, objects) {
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
// Look for a dirtyChanged signal on the object
if (object->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) {
if (!_skipDirtyFirstItem || j != 0) {
QObject::connect(object, SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool)));
}
}
j++;
_objectList.insert(i, object);
}
insertRows(i, objects.count());
setDirty(true);
}
void QmlObjectListModel::append(QObject* object)
{
insert(_objectList.count(), object);
}
void QmlObjectListModel::append(QList<QObject*> objects)
{
insert(_objectList.count(), objects);
}
QObjectList QmlObjectListModel::swapObjectList(const QObjectList& newlist)
{
QObjectList oldlist(_objectList);
......
......@@ -31,22 +31,23 @@ public:
// Property accessors
int count (void) const;
bool dirty (void) const { return _dirty; }
void setDirty (bool dirty);
void append (QObject* object);
QObjectList swapObjectList (const QObjectList& newlist);
void clear (void);
QObject* removeAt (int i);
QObject* removeOne (QObject* object) { return removeAt(indexOf(object)); }
void insert (int i, QObject* object);
QObject* operator[] (int i);
const QObject* operator[] (int i) const;
bool contains (QObject* object) { return _objectList.indexOf(object) != -1; }
int indexOf (QObject* object) { return _objectList.indexOf(object); }
QList<QObject*>* objectList () { return &_objectList; }
int count(void) const;
bool dirty(void) const { return _dirty; }
void setDirty(bool dirty);
void append(QObject* object);
void append(QList<QObject*> objects);
QObjectList swapObjectList(const QObjectList& newlist);
void clear(void);
QObject* removeAt(int i);
QObject* removeOne(QObject* object) { return removeAt(indexOf(object)); }
void insert(int i, QObject* object);
void insert(int i, QList<QObject*> objects);
QObject* operator[](int i);
const QObject* operator[](int i) const;
bool contains(QObject* object) { return _objectList.indexOf(object) != -1; }
int indexOf(QObject* object) { return _objectList.indexOf(object); }
template<class T> T value(int index) { return qobject_cast<T>(_objectList[index]); }
/// Calls deleteLater on all items and this itself.
......@@ -67,12 +68,12 @@ private slots:
private:
// Overrides from QAbstractListModel
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
virtual QHash<int, QByteArray> roleNames(void) const;
virtual bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex());
virtual bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames(void) const override;
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
private:
QList<QObject*> _objectList;
......
/****************************************************************************
*
* (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 "Terrain.h"
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QTimer>
QGC_LOGGING_CATEGORY(ElevationProviderLog, "ElevationProviderLog")
Q_GLOBAL_STATIC(TerrainBatchManager, _terrainBatchManager)
TerrainBatchManager::TerrainBatchManager(void)
{
_batchTimer.setSingleShot(true);
_batchTimer.setInterval(_batchTimeout);
connect(&_batchTimer, &QTimer::timeout, this, &TerrainBatchManager::_sendNextBatch);
}
void TerrainBatchManager::addQuery(ElevationProvider* elevationProvider, const QList<QGeoCoordinate>& coordinates)
{
if (coordinates.length() > 0) {
qCDebug(ElevationProviderLog) << "addQuery: elevationProvider:coordinates.count" << elevationProvider << coordinates.count();
connect(elevationProvider, &ElevationProvider::destroyed, this, &TerrainBatchManager::_elevationProviderDestroyed);
QueuedRequestInfo_t queuedRequestInfo = { elevationProvider, coordinates };
_requestQueue.append(queuedRequestInfo);
if (!_batchTimer.isActive()) {
_batchTimer.start();
}
}
}
void TerrainBatchManager::_sendNextBatch(void)
{
qCDebug(ElevationProviderLog) << "_sendNextBatch _state:_requestQueue.count:_sentRequests.count" << _stateToString(_state) << _requestQueue.count() << _sentRequests.count();
if (_state != State::Idle) {
// Waiting for last download the complete, wait some more
_batchTimer.start();
return;
}
if (_requestQueue.count() == 0) {
return;
}
_sentRequests.clear();
// Convert coordinates to point strings for json query
QString points;
foreach (const QueuedRequestInfo_t& requestInfo, _requestQueue) {
SentRequestInfo_t sentRequestInfo = { requestInfo.elevationProvider, false, requestInfo.coordinates.count() };
qCDebug(ElevationProviderLog) << "Building request: coordinate count" << requestInfo.coordinates.count();
_sentRequests.append(sentRequestInfo);
foreach (const QGeoCoordinate& coord, requestInfo.coordinates) {
points += QString::number(coord.latitude(), 'f', 10) + ","
+ QString::number(coord.longitude(), 'f', 10) + ",";
}
}
points = points.mid(0, points.length() - 1); // remove the last ',' from string
_requestQueue.clear();
QUrlQuery query;
query.addQueryItem(QStringLiteral("points"), points);
QUrl url(QStringLiteral("https://api.airmap.com/elevation/stage/srtm1/ele"));
url.setQuery(query);
QNetworkRequest request(url);
QNetworkProxy tProxy;
tProxy.setType(QNetworkProxy::DefaultProxy);
_networkManager.setProxy(tProxy);
QNetworkReply* networkReply = _networkManager.get(request);
if (!networkReply) {
_batchFailed();
return;
}
connect(networkReply, &QNetworkReply::finished, this, &TerrainBatchManager::_requestFinished);
_state = State::Downloading;
}
void TerrainBatchManager::_batchFailed(void)
{
QList<float> noAltitudes;
foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
if (!sentRequestInfo.providerDestroyed) {
disconnect(sentRequestInfo.elevationProvider, &ElevationProvider::destroyed, this, &TerrainBatchManager::_elevationProviderDestroyed);
sentRequestInfo.elevationProvider->_signalTerrainData(false, noAltitudes);
}
}
_sentRequests.clear();
}
void TerrainBatchManager::_requestFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(QObject::sender());
_state = State::Idle;
// When an error occurs we still end up here
if (reply->error() != QNetworkReply::NoError) {
qCDebug(ElevationProviderLog) << "_requestFinished error:" << reply->error();
_batchFailed();
reply->deleteLater();
return;
}
QByteArray responseBytes = reply->readAll();
QJsonParseError parseError;
QJsonDocument responseJson = QJsonDocument::fromJson(responseBytes, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qCDebug(ElevationProviderLog) << "_requestFinished unable to parse json:" << parseError.errorString();
_batchFailed();
reply->deleteLater();
return;
}
QJsonObject rootObject = responseJson.object();
QString status = rootObject["status"].toString();
if (status != "success") {
qCDebug(ElevationProviderLog) << "_requestFinished status != success:" << status;
_batchFailed();
reply->deleteLater();
return;
}
QList<float> altitudes;
const QJsonArray& dataArray = rootObject["data"].toArray();
for (int i = 0; i < dataArray.count(); i++) {
altitudes.push_back(dataArray[i].toDouble());
}
int currentIndex = 0;
foreach (const SentRequestInfo_t& sentRequestInfo, _sentRequests) {
if (!sentRequestInfo.providerDestroyed) {
disconnect(sentRequestInfo.elevationProvider, &ElevationProvider::destroyed, this, &TerrainBatchManager::_elevationProviderDestroyed);
QList<float> requestAltitudes = altitudes.mid(currentIndex, sentRequestInfo.cCoord);
sentRequestInfo.elevationProvider->_signalTerrainData(true, requestAltitudes);
currentIndex += sentRequestInfo.cCoord;
}
}
_sentRequests.clear();
reply->deleteLater();
}
void TerrainBatchManager::_elevationProviderDestroyed(QObject* elevationProvider)
{
// Remove/Mark deleted objects queries from queues
qCDebug(ElevationProviderLog) << "_elevationProviderDestroyed elevationProvider" << elevationProvider;
int i = 0;
while (i < _requestQueue.count()) {
const QueuedRequestInfo_t& requestInfo = _requestQueue[i];
if (requestInfo.elevationProvider == elevationProvider) {
qCDebug(ElevationProviderLog) << "Removing deleted provider from _requestQueue index:elevationProvider" << i << requestInfo.elevationProvider;
_requestQueue.removeAt(i);
} else {
i++;
}
}
for (int i=0; i<_sentRequests.count(); i++) {
SentRequestInfo_t& sentRequestInfo = _sentRequests[i];
if (sentRequestInfo.elevationProvider == elevationProvider) {
qCDebug(ElevationProviderLog) << "Zombieing deleted provider from _sentRequests index:elevatationProvider" << sentRequestInfo.elevationProvider;
sentRequestInfo.providerDestroyed = true;
}
}
}
QString TerrainBatchManager::_stateToString(State state)
{
switch (state) {
case State::Idle:
return QStringLiteral("Idle");
case State::Downloading:
return QStringLiteral("Downloading");
}
return QStringLiteral("State unknown");
}
ElevationProvider::ElevationProvider(QObject* parent)
: QObject(parent)
{
}
void ElevationProvider::queryTerrainData(const QList<QGeoCoordinate>& coordinates)
{
if (coordinates.length() == 0) {
return;
}
_terrainBatchManager->addQuery(this, coordinates);
}
void ElevationProvider::_signalTerrainData(bool success, QList<float>& altitudes)
{
emit terrainData(success, altitudes);
}
/****************************************************************************
*
* (c) 2017 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.
*
****************************************************************************/
#pragma once
#include "QGCLoggingCategory.h"
#include <QObject>
#include <QGeoCoordinate>
#include <QNetworkAccessManager>
#include <QTimer>
Q_DECLARE_LOGGING_CATEGORY(ElevationProviderLog)
class ElevationProvider;
/// Used internally by ElevationProvider to batch requests together
class TerrainBatchManager : public QObject {
Q_OBJECT
public:
TerrainBatchManager(void);
void addQuery(ElevationProvider* elevationProvider, const QList<QGeoCoordinate>& coordinates);
private slots:
void _sendNextBatch (void);
void _requestFinished (void);
void _elevationProviderDestroyed (QObject* elevationProvider);
private:
typedef struct {
ElevationProvider* elevationProvider;
QList<QGeoCoordinate> coordinates;
} QueuedRequestInfo_t;
typedef struct {
ElevationProvider* elevationProvider;
bool providerDestroyed;
int cCoord;
} SentRequestInfo_t;
enum class State {
Idle,
Downloading,
};
void _batchFailed(void);
QString _stateToString(State state);
QList<QueuedRequestInfo_t> _requestQueue;
QList<SentRequestInfo_t> _sentRequests;
State _state = State::Idle;
QNetworkAccessManager _networkManager;
const int _batchTimeout = 500;
QTimer _batchTimer;
};
/// NOTE: ElevationProvider is not thread safe. All instances/calls to ElevationProvider must be on main thread.
class ElevationProvider : public QObject
{
Q_OBJECT
public:
ElevationProvider(QObject* parent = NULL);
/// Async elevation query for a list of lon,lat coordinates. When the query is done, the terrainData() signal
/// is emitted.
/// @param coordinates to query
void queryTerrainData(const QList<QGeoCoordinate>& coordinates);
// Internal method
void _signalTerrainData(bool success, QList<float>& altitudes);
signals:
void terrainData(bool success, QList<float> altitudes);
};
This diff is collapsed.
/****************************************************************************
*
* (c) 2017 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.
*
****************************************************************************/
#pragma once
#include "QGCLoggingCategory.h"
#include <QObject>
#include <QGeoCoordinate>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
Q_DECLARE_LOGGING_CATEGORY(TerrainQueryLog)
class TerrainAtCoordinateQuery;
/// Base class for offline/online terrain queries
class TerrainQueryInterface : public QObject
{
Q_OBJECT
public:
TerrainQueryInterface(QObject* parent) : QObject(parent) { }
/// Request terrain heights for specified coodinates.
/// Signals: coordinateHeights when data is available
virtual void requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) = 0;
/// Requests terrain heights along the path specified by the two coordinates.
/// Signals: pathHeights
/// @param coordinates to query
virtual void requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) = 0;
/// Request terrain heights for the rectangular area specified.
/// Signals: carpetHeights when data is available
/// @param swCoord South-West bound of rectangular area to query
/// @param neCoord North-East bound of rectangular area to query
/// @param statsOnly true: Return only stats, no carpet data
virtual void requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) = 0;
signals:
void coordinateHeights(bool success, QList<double> heights);
void pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights);
void carpetHeights(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet);
};
/// AirMap online implementation of terrain queries
class TerrainAirMapQuery : public TerrainQueryInterface {
Q_OBJECT
public:
TerrainAirMapQuery(QObject* parent = NULL);
// Overrides from TerrainQueryInterface
void requestCoordinateHeights(const QList<QGeoCoordinate>& coordinates) final;
void requestPathHeights(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord) final;
void requestCarpetHeights(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly) final;
private slots:
void _requestFinished(void);
private:
void _sendQuery (const QString& path, const QUrlQuery& urlQuery);
void _requestFailed (void);
void _parseCoordinateData (const QJsonValue& coordinateJson);
void _parsePathData (const QJsonValue& pathJson);
void _parseCarpetData (const QJsonValue& carpetJson);
enum QueryMode {
QueryModeCoordinates,
QueryModePath,
QueryModeCarpet
};
QNetworkAccessManager _networkManager;
QueryMode _queryMode;
bool _carpetStatsOnly;
};
/// Used internally by TerrainAtCoordinateQuery to batch coordinate requests together
class TerrainAtCoordinateBatchManager : public QObject {
Q_OBJECT
public:
TerrainAtCoordinateBatchManager(void);
void addQuery(TerrainAtCoordinateQuery* terrainAtCoordinateQuery, const QList<QGeoCoordinate>& coordinates);
private slots:
void _sendNextBatch (void);
void _queryObjectDestroyed (QObject* elevationProvider);
void _coordinateHeights (bool success, QList<double> heights);
private:
typedef struct {
TerrainAtCoordinateQuery* terrainAtCoordinateQuery;
QList<QGeoCoordinate> coordinates;
} QueuedRequestInfo_t;
typedef struct {
TerrainAtCoordinateQuery* terrainAtCoordinateQuery;
bool queryObjectDestroyed;
int cCoord;
} SentRequestInfo_t;
enum class State {
Idle,
Downloading,
};
void _batchFailed(void);
QString _stateToString(State state);
QList<QueuedRequestInfo_t> _requestQueue;
QList<SentRequestInfo_t> _sentRequests;
State _state = State::Idle;
const int _batchTimeout = 500;
QTimer _batchTimer;
TerrainAirMapQuery _terrainQuery;
};
/// NOTE: TerrainAtCoordinateQuery is not thread safe. All instances/calls to ElevationProvider must be on main thread.
class TerrainAtCoordinateQuery : public QObject
{
Q_OBJECT
public:
TerrainAtCoordinateQuery(QObject* parent = NULL);
/// Async terrain query for a list of lon,lat coordinates. When the query is done, the terrainData() signal
/// is emitted.
/// @param coordinates to query
void requestData(const QList<QGeoCoordinate>& coordinates);
// Internal method
void _signalTerrainData(bool success, QList<double>& heights);
signals:
void terrainData(bool success, QList<double> heights);
};
class TerrainPathQuery : public QObject
{
Q_OBJECT
public:
TerrainPathQuery(QObject* parent = NULL);
/// Async terrain query for terrain heights between two lat/lon coordinates. When the query is done, the terrainData() signal
/// is emitted.
/// @param coordinates to query
void requestData(const QGeoCoordinate& fromCoord, const QGeoCoordinate& toCoord);
typedef struct {
double latStep; ///< Amount of latitudinal distance between each returned height
double lonStep; ///< Amount of longitudinal distance between each returned height
QList<double> heights; ///< Terrain heights along path
} PathHeightInfo_t;
signals:
/// Signalled when terrain data comes back from server
void terrainData(bool success, const PathHeightInfo_t& pathHeightInfo);
private slots:
void _pathHeights(bool success, double latStep, double lonStep, const QList<double>& heights);
private:
TerrainAirMapQuery _terrainQuery;
};
Q_DECLARE_METATYPE(TerrainPathQuery::PathHeightInfo_t)
class TerrainPolyPathQuery : public QObject
{
Q_OBJECT
public:
TerrainPolyPathQuery(QObject* parent = NULL);
/// Async terrain query for terrain heights for the paths between each specified QGeoCoordinate.
/// When the query is done, the terrainData() signal is emitted.
/// @param polyPath List of QGeoCoordinate
void requestData(const QVariantList& polyPath);
void requestData(const QList<QGeoCoordinate>& polyPath);
signals:
/// Signalled when terrain data comes back from server
void terrainData(bool success, const QList<TerrainPathQuery::PathHeightInfo_t>& rgPathHeightInfo);
private slots:
void _terrainDataReceived(bool success, const TerrainPathQuery::PathHeightInfo_t& pathHeightInfo);
private:
int _curIndex;
QList<QGeoCoordinate> _rgCoords;
QList<TerrainPathQuery::PathHeightInfo_t> _rgPathHeightInfo;
TerrainPathQuery _pathQuery;
};
class TerrainCarpetQuery : public QObject
{
Q_OBJECT
public:
TerrainCarpetQuery(QObject* parent = NULL);
/// Async terrain query for terrain information bounded by the specifed corners.
/// When the query is done, the terrainData() signal is emitted.
/// @param swCoord South-West bound of rectangular area to query
/// @param neCoord North-East bound of rectangular area to query
/// @param statsOnly true: Return only stats, no carpet data
void requestData(const QGeoCoordinate& swCoord, const QGeoCoordinate& neCoord, bool statsOnly);
signals:
/// Signalled when terrain data comes back from server
void terrainData(bool success, double minHeight, double maxHeight, const QList<QList<double>>& carpet);
private:
TerrainAirMapQuery _terrainQuery;
};
......@@ -530,6 +530,22 @@ SetupPage {
}
}
Row {
width: parent.width
spacing: ScreenTools.defaultFontPixelWidth
visible: advancedSettings.checked
QGCCheckBox {
id: joystickCircleCorrection
checked: _activeVehicle.joystickMode != 0
text: qsTr("Enable circle correction")
Component.onCompleted: checked = _activeJoystick.circleCorrection
onClicked: {
_activeJoystick.circleCorrection = checked
}
}
}
Row {
width: parent.width
spacing: ScreenTools.defaultFontPixelWidth
......
......@@ -40,6 +40,9 @@ list=$(apt-cache --names-only search ^gstreamer1.0-* | awk '{ print $1 }' | grep
```
sudo apt-get install $list
```
```
sudo apt-get install libgstreamer-plugins-base1.0-dev
```
The build system is setup to use pkgconfig and it will find the necessary headers and libraries automatically.
......
......@@ -181,24 +181,7 @@ VideoEnabled {
} else {
LinuxBuild|MacBuild|iOSBuild|WindowsBuild|AndroidBuild {
message("Skipping support for video streaming (GStreamer libraries not installed)")
MacBuild {
message(" You can download it from http://gstreamer.freedesktop.org/data/pkg/osx/")
message(" Select the devel package and install it (gstreamer-1.0-devel-1.x.x-x86_64.pkg)")
message(" It will be installed in /Libraries/Frameworks")
}
LinuxBuild {
message(" You can install it using apt-get")
message(" sudo apt-get install gstreamer1.0*")
}
WindowsBuild {
message(" You can download it from http://gstreamer.freedesktop.org/data/pkg/windows/")
message(" Select the devel AND runtime packages and install them (x86, not the 64-Bit)")
message(" It will be installed in C:/gstreamer. You need to update you PATH to point to the bin directory.")
}
AndroidBuild {
message(" You can download it from http://gstreamer.freedesktop.org/data/pkg/android/")
message(" Uncompress the archive into the qgc root source directory (same directory where qgroundcontrol.pro is found.")
}
message("Installation instructions here: https://github.com/mavlink/qgroundcontrol/blob/master/src/VideoStreaming/README.md")
} else {
message("Skipping support for video streaming (Unsupported platform)")
}
......
......@@ -35,9 +35,15 @@ QGC_LOGGING_CATEGORY(MockLinkVerboseLog, "MockLinkVerboseLog")
// Vehicle position is set close to default Gazebo vehicle location. This allows for multi-vehicle
// testing of a gazebo vehicle and a mocklink vehicle
double MockLink::_defaultVehicleLatitude = 47.397f;
double MockLink::_defaultVehicleLongitude = 8.5455f;
double MockLink::_defaultVehicleAltitude = 488.056f;
#if 1
double MockLink::_defaultVehicleLatitude = 47.397;
double MockLink::_defaultVehicleLongitude = 8.5455;
double MockLink::_defaultVehicleAltitude = 488.056;
#else
double MockLink::_defaultVehicleLatitude = 47.6333022928789;
double MockLink::_defaultVehicleLongitude = -122.08833157994995;
double MockLink::_defaultVehicleAltitude = 19.0;
#endif
int MockLink::_nextVehicleSystemId = 128;
const char* MockLink::_failParam = "COM_FLTMODE6";
......
......@@ -43,7 +43,7 @@ QGCMapRCToParamDialog::QGCMapRCToParamDialog(QString param_id, UASInterface *mav
ui->paramIdLabel->setText(param_id);
connect(paramFact, &Fact::valueChanged, this, &QGCMapRCToParamDialog::_parameterUpdated);
connect(paramFact, &Fact::vehicleUpdated, this, &QGCMapRCToParamDialog::_parameterUpdated);
vehicle->parameterManager()->refreshParameter(FactSystem::defaultComponentId, param_id);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment