@ -82,7 +82,7 @@ for:
|
||||
qttools5-dev qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins libqt5websockets5-dev \
|
||||
libqt5quick5 \
|
||||
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \
|
||||
qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-layouts \
|
||||
qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtgraphicaleffects \
|
||||
libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \
|
||||
libqt5charts5-dev libqt5texttospeech5-dev libfaad-dev zlib1g-dev \
|
||||
libusb-1.0-0-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \
|
||||
|
1
debian/control
vendored
@ -22,6 +22,7 @@ Build-Depends: debhelper (>= 9),
|
||||
qml-module-qtquick-controls,
|
||||
qml-module-qtquick-controls2,
|
||||
qml-module-qtquick-layouts,
|
||||
qml-module-qtgraphicaleffects,
|
||||
libqt5serialport5-dev,
|
||||
libqt5charts5-dev,
|
||||
libqt5texttospeech5-dev,
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 826 KiB |
BIN
doc/img/ADSBDemod_plugin_displaysettings.png
Normal file
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 603 KiB After Width: | Height: | Size: 490 KiB |
BIN
doc/img/ADSBDemod_plugin_map2.png
Normal file
After Width: | Height: | Size: 570 KiB |
BIN
doc/img/ADSBDemod_plugin_settings.png
Normal file
After Width: | Height: | Size: 24 KiB |
@ -42,6 +42,7 @@ if(NOT SERVER_MODE)
|
||||
adsbdemoddisplaydialog.ui
|
||||
adsbdemodnotificationdialog.cpp
|
||||
adsbdemodnotificationdialog.ui
|
||||
adsbosmtemplateserver.cpp
|
||||
airlinelogos.qrc
|
||||
flags.qrc
|
||||
map.qrc
|
||||
@ -53,6 +54,7 @@ if(NOT SERVER_MODE)
|
||||
adsbdemodfeeddialog.h
|
||||
adsbdemoddisplaydialog.h
|
||||
adsbdemodnotificationdialog.h
|
||||
adsbosmtemplateserver.h
|
||||
ourairports.h
|
||||
osndb.h
|
||||
)
|
||||
@ -87,3 +89,8 @@ target_link_libraries(${TARGET_NAME}
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
||||
|
||||
if(WIN32)
|
||||
# Run deployqt for QtGraphicalEffects, which isn't used in other plugins
|
||||
include(DeployQt)
|
||||
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/map)
|
||||
endif()
|
||||
|
@ -23,7 +23,8 @@
|
||||
ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(
|
||||
int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
|
||||
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
|
||||
bool autoResizeTableColumns, const QString& apiKey, QWidget* parent) :
|
||||
bool autoResizeTableColumns, const QString& apiKey, QStringList airspaces, float airspaceRange,
|
||||
ADSBDemodSettings::MapType mapType, bool displayNavAids, bool displayPhotos, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
m_fontName(fontName),
|
||||
m_fontSize(fontSize),
|
||||
@ -38,6 +39,17 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(
|
||||
ui->displayStats->setChecked(displayDemodStats);
|
||||
ui->autoResizeTableColumns->setChecked(autoResizeTableColumns);
|
||||
ui->apiKey->setText(apiKey);
|
||||
for (const auto& airspace: airspaces)
|
||||
{
|
||||
QList<QListWidgetItem *> items = ui->airspaces->findItems(airspace, Qt::MatchExactly);
|
||||
for (const auto& item: items) {
|
||||
item->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
ui->airspaceRange->setValue(airspaceRange);
|
||||
ui->mapType->setCurrentIndex((int)mapType);
|
||||
ui->navAids->setChecked(displayNavAids);
|
||||
ui->photos->setChecked(displayPhotos);
|
||||
}
|
||||
|
||||
ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
|
||||
@ -55,6 +67,18 @@ void ADSBDemodDisplayDialog::accept()
|
||||
m_displayDemodStats = ui->displayStats->isChecked();
|
||||
m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked();
|
||||
m_apiKey = ui->apiKey->text();
|
||||
m_airspaces = QStringList();
|
||||
for (int i = 0; i < ui->airspaces->count(); i++)
|
||||
{
|
||||
QListWidgetItem *item = ui->airspaces->item(i);
|
||||
if (item->checkState() == Qt::Checked) {
|
||||
m_airspaces.append(item->text());
|
||||
}
|
||||
}
|
||||
m_airspaceRange = ui->airspaceRange->value();
|
||||
m_mapType = (ADSBDemodSettings::MapType)ui->mapType->currentIndex();
|
||||
m_displayNavAids = ui->navAids->isChecked();
|
||||
m_displayPhotos = ui->photos->isChecked();
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,8 @@ class ADSBDemodDisplayDialog : public QDialog {
|
||||
public:
|
||||
explicit ADSBDemodDisplayDialog(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
|
||||
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
|
||||
bool autoResizeTableColumns, const QString& apiKey, QWidget* parent = 0);
|
||||
bool autoResizeTableColumns, const QString& apiKey, QStringList airspaces, float airspaceRange,
|
||||
ADSBDemodSettings::MapType mapType, bool displayNavAids, bool displayPhotos, QWidget* parent = 0);
|
||||
~ADSBDemodDisplayDialog();
|
||||
|
||||
int m_removeTimeout;
|
||||
@ -40,6 +41,11 @@ public:
|
||||
bool m_displayDemodStats;
|
||||
bool m_autoResizeTableColumns;
|
||||
QString m_apiKey;
|
||||
QStringList m_airspaces;
|
||||
float m_airspaceRange;
|
||||
ADSBDemodSettings::MapType m_mapType;
|
||||
bool m_displayNavAids;
|
||||
bool m_displayPhotos;
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>417</width>
|
||||
<height>287</height>
|
||||
<height>638</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -23,38 +23,14 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="airportSizeLabel">
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="displayStatsLabel">
|
||||
<property name="text">
|
||||
<string>Display airports with size</string>
|
||||
<string>Display demodulator statistics</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="units">
|
||||
<property name="toolTip">
|
||||
<string>The units to use for altitude, speed and climb rate</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ft, kn, ft/min</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>m, kph, m/s</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="timeoutLabel">
|
||||
<property name="text">
|
||||
<string>Aircraft timeout (s)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="airportRange">
|
||||
<property name="toolTip">
|
||||
<string>Displays airports within the specified distance in kilometres from My Position</string>
|
||||
@ -64,7 +40,219 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="timeout">
|
||||
<property name="toolTip">
|
||||
<string>How long in seconds after not receiving any frames will an aircraft be removed from the table and map</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="mapTypeLabel">
|
||||
<property name="text">
|
||||
<string>Map type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="airspacesLabel">
|
||||
<property name="text">
|
||||
<string>Airspaces to display</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QLineEdit" name="apiKey">
|
||||
<property name="toolTip">
|
||||
<string>aviationstack.com API key for accessing flight information</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QListWidget" name="airspaces">
|
||||
<property name="toolTip">
|
||||
<string>Airspace categories to display</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>A</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IFR only</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>B</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IFR and VFR with ATC clearance</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>C</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IFR and VFR with ATC clearance</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>D</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IFR and VFR with ATC clearance</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>E</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IFR with clearance and VFR</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>G</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Uncontrolled</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>FIR</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Flight Information Region</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>CTR</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Controlled Traffic Region</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>TMZ</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Transponder Mandatory Zone</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RMZ</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Radio Mandatory Zone</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RESTRICTED</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>GLIDING</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>DANGER</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>PROHIBITED</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>WAVE</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QCheckBox" name="autoResizeTableColumns">
|
||||
<property name="toolTip">
|
||||
<string>Resize the columns in the table after an aircraft is added to it</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="displayNavAids">
|
||||
<property name="text">
|
||||
<string>Display NAVAIDs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="autoResizeTableColumnsLabel">
|
||||
<property name="text">
|
||||
<string>Resize columns after adding aircraft</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="airportSize">
|
||||
<property name="toolTip">
|
||||
<string>Sets the minimum airport size that will be displayed on the map</string>
|
||||
@ -86,95 +274,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="unitsLabel">
|
||||
<property name="text">
|
||||
<string>Units</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QPushButton" name="font">
|
||||
<property name="toolTip">
|
||||
<string>Select a font for the table</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="timeout">
|
||||
<property name="toolTip">
|
||||
<string>How long in seconds after not receiving any frames will an aircraft be removed from the table and map</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="airportRangeLabel">
|
||||
<widget class="QLabel" name="heliportsLabel">
|
||||
<property name="text">
|
||||
<string>Airport display distance (km)</string>
|
||||
<string>Display heliports</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="fontLabel">
|
||||
<property name="text">
|
||||
<string>Table font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="apiKeyLabel">
|
||||
<property name="text">
|
||||
<string>avaitionstack API key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="autoResizeTableColumns">
|
||||
<property name="toolTip">
|
||||
<string>Resize the columns in the table after an aircraft is added to it</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="autoResizeTableColumnsLabel">
|
||||
<property name="text">
|
||||
<string>Resize columns after adding aircraft</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="heliports">
|
||||
<property name="toolTip">
|
||||
<string>When checked, heliports are displayed on the map</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="14" column="1">
|
||||
<widget class="QCheckBox" name="displayStats">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@ -191,23 +298,152 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="heliportsLabel">
|
||||
<widget class="QLabel" name="airportSizeLabel">
|
||||
<property name="text">
|
||||
<string>Display heliports</string>
|
||||
<string>Display airports with size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="displayStatsLabel">
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="apiKeyLabel">
|
||||
<property name="text">
|
||||
<string>Display demodulator statistics</string>
|
||||
<string>avaitionstack API key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="timeoutLabel">
|
||||
<property name="text">
|
||||
<string>Aircraft timeout (s)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QSpinBox" name="airspaceRange">
|
||||
<property name="toolTip">
|
||||
<string>Displays airspace within the specified distance in kilometres from My Position</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>20000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="units">
|
||||
<property name="toolTip">
|
||||
<string>The units to use for altitude, speed and climb rate</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ft, kn, ft/min</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>m, kph, m/s</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="unitsLabel">
|
||||
<property name="text">
|
||||
<string>Units</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QLineEdit" name="apiKey">
|
||||
<widget class="QPushButton" name="font">
|
||||
<property name="toolTip">
|
||||
<string>aviationstack.com API key for accessing flight information</string>
|
||||
<string>Select a font for the table</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="airportRangeLabel">
|
||||
<property name="text">
|
||||
<string>Airport display distance (km)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="mapType">
|
||||
<property name="toolTip">
|
||||
<string>Type of map to display</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Aviation</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Aviation (Dark)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Street</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Satellite</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="airspaceRangeLabel">
|
||||
<property name="text">
|
||||
<string>Airspace display distance (km)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="heliports">
|
||||
<property name="toolTip">
|
||||
<string>When checked, heliports are displayed on the map</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="fontLabel">
|
||||
<property name="text">
|
||||
<string>Table font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="navAids">
|
||||
<property name="toolTip">
|
||||
<string>Display NAVAIDs such as VORs and NDBs</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="photosLabel">
|
||||
<property name="text">
|
||||
<string>Display aircraft photos</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="photos">
|
||||
<property name="toolTip">
|
||||
<string>Download and display photos of highlighted aircraft</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -226,6 +462,22 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>units</tabstop>
|
||||
<tabstop>mapType</tabstop>
|
||||
<tabstop>airportSize</tabstop>
|
||||
<tabstop>heliports</tabstop>
|
||||
<tabstop>airportRange</tabstop>
|
||||
<tabstop>airspaces</tabstop>
|
||||
<tabstop>airspaceRange</tabstop>
|
||||
<tabstop>navAids</tabstop>
|
||||
<tabstop>photos</tabstop>
|
||||
<tabstop>timeout</tabstop>
|
||||
<tabstop>font</tabstop>
|
||||
<tabstop>autoResizeTableColumns</tabstop>
|
||||
<tabstop>displayStats</tabstop>
|
||||
<tabstop>apiKey</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QFileDialog>
|
||||
#include <QQmlProperty>
|
||||
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
@ -39,9 +40,11 @@
|
||||
#include "util/simpleserializer.h"
|
||||
#include "util/db.h"
|
||||
#include "util/units.h"
|
||||
#include "util/morse.h"
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
#include "gui/devicestreamselectiondialog.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "gui/clickablelabel.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
@ -52,6 +55,7 @@
|
||||
#include "adsbdemoddisplaydialog.h"
|
||||
#include "adsbdemodnotificationdialog.h"
|
||||
#include "adsb.h"
|
||||
#include "adsbosmtemplateserver.h"
|
||||
|
||||
const char *Aircraft::m_speedTypeNames[] = {
|
||||
"GS", "TAS", "IAS"
|
||||
@ -446,6 +450,155 @@ bool AirportModel::setData(const QModelIndex &index, const QVariant& value, int
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant AirspaceModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int row = index.row();
|
||||
if ((row < 0) || (row >= m_airspaces.count())) {
|
||||
return QVariant();
|
||||
}
|
||||
if (role == AirspaceModel::nameRole)
|
||||
{
|
||||
// Airspace name
|
||||
return QVariant::fromValue(m_airspaces[row]->m_name);
|
||||
}
|
||||
else if (role == AirspaceModel::detailsRole)
|
||||
{
|
||||
// Airspace name and altitudes
|
||||
QString details;
|
||||
details.append(m_airspaces[row]->m_name);
|
||||
details.append(QString("\n%1 - %2")
|
||||
.arg(m_airspaces[row]->getAlt(&m_airspaces[row]->m_bottom))
|
||||
.arg(m_airspaces[row]->getAlt(&m_airspaces[row]->m_top)));
|
||||
return QVariant::fromValue(details);
|
||||
}
|
||||
else if (role == AirspaceModel::positionRole)
|
||||
{
|
||||
// Coordinates to display the airspace name at
|
||||
QGeoCoordinate coords;
|
||||
coords.setLatitude(m_airspaces[row]->m_position.y());
|
||||
coords.setLongitude(m_airspaces[row]->m_position.x());
|
||||
coords.setAltitude(m_airspaces[row]->topHeightInMetres());
|
||||
return QVariant::fromValue(coords);
|
||||
}
|
||||
else if (role == AirspaceModel::airspaceBorderColorRole)
|
||||
{
|
||||
if (m_airspaces[row]->m_category == "D")
|
||||
{
|
||||
return QVariant::fromValue(QColor("blue"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant::fromValue(QColor("red"));
|
||||
}
|
||||
}
|
||||
else if (role == AirspaceModel::airspaceFillColorRole)
|
||||
{
|
||||
if (m_airspaces[row]->m_category == "D")
|
||||
{
|
||||
return QVariant::fromValue(QColor(0x00, 0x00, 0xff, 0x10));
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant::fromValue(QColor(0xff, 0x00, 0x00, 0x10));
|
||||
}
|
||||
}
|
||||
else if (role == AirspaceModel::airspacePolygonRole)
|
||||
{
|
||||
return m_polygons[row];
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool AirspaceModel::setData(const QModelIndex &index, const QVariant& value, int role)
|
||||
{
|
||||
(void) value;
|
||||
(void) role;
|
||||
|
||||
int row = index.row();
|
||||
if ((row < 0) || (row >= m_airspaces.count())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant NavAidModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int row = index.row();
|
||||
if ((row < 0) || (row >= m_navAids.count())) {
|
||||
return QVariant();
|
||||
}
|
||||
if (role == NavAidModel::positionRole)
|
||||
{
|
||||
// Coordinates to display the VOR icon at
|
||||
QGeoCoordinate coords;
|
||||
coords.setLatitude(m_navAids[row]->m_latitude);
|
||||
coords.setLongitude(m_navAids[row]->m_longitude);
|
||||
coords.setAltitude(Units::feetToMetres(m_navAids[row]->m_elevation));
|
||||
return QVariant::fromValue(coords);
|
||||
}
|
||||
else if (role == NavAidModel::navAidDataRole)
|
||||
{
|
||||
// Create the text to go in the bubble next to the VOR
|
||||
if (m_selected[row])
|
||||
{
|
||||
QStringList list;
|
||||
list.append(QString("Name: %1").arg(m_navAids[row]->m_name));
|
||||
if (m_navAids[row]->m_type == "NDB") {
|
||||
list.append(QString("Frequency: %1 kHz").arg(m_navAids[row]->m_frequencykHz, 0, 'f', 1));
|
||||
} else {
|
||||
list.append(QString("Frequency: %1 MHz").arg(m_navAids[row]->m_frequencykHz / 1000.0f, 0, 'f', 2));
|
||||
}
|
||||
if (m_navAids[row]->m_channel != "") {
|
||||
list.append(QString("Channel: %1").arg(m_navAids[row]->m_channel));
|
||||
}
|
||||
list.append(QString("Ident: %1 %2").arg(m_navAids[row]->m_ident).arg(Morse::toSpacedUnicodeMorse(m_navAids[row]->m_ident)));
|
||||
list.append(QString("Range: %1 nm").arg(m_navAids[row]->m_range));
|
||||
if (m_navAids[row]->m_alignedTrueNorth) {
|
||||
list.append(QString("Magnetic declination: Aligned to true North"));
|
||||
} else if (m_navAids[row]->m_magneticDeclination != 0.0f) {
|
||||
list.append(QString("Magnetic declination: %1%2").arg(std::round(m_navAids[row]->m_magneticDeclination)).arg(QChar(0x00b0)));
|
||||
}
|
||||
QString data = list.join("\n");
|
||||
return QVariant::fromValue(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant::fromValue(m_navAids[row]->m_name);
|
||||
}
|
||||
}
|
||||
else if (role == NavAidModel::navAidImageRole)
|
||||
{
|
||||
// Select an image to use for the NavAid
|
||||
return QVariant::fromValue(QString("%1.png").arg(m_navAids[row]->m_type));
|
||||
}
|
||||
else if (role == NavAidModel::bubbleColourRole)
|
||||
{
|
||||
// Select a background colour for the text bubble next to the NavAid
|
||||
return QVariant::fromValue(QColor("lightgreen"));
|
||||
}
|
||||
else if (role == NavAidModel::selectedRole)
|
||||
{
|
||||
return QVariant::fromValue(m_selected[row]);
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool NavAidModel::setData(const QModelIndex &index, const QVariant& value, int role)
|
||||
{
|
||||
int row = index.row();
|
||||
if ((row < 0) || (row >= m_navAids.count())) {
|
||||
return false;
|
||||
}
|
||||
if (role == NavAidModel::selectedRole)
|
||||
{
|
||||
bool selected = value.toBool();
|
||||
m_selected[row] = selected;
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set selected device to the given centre frequency (used to tune to ATC selected from airports on map)
|
||||
bool ADSBDemodGUI::setFrequency(float targetFrequencyHz)
|
||||
{
|
||||
@ -632,6 +785,9 @@ void ADSBDemodGUI::handleADSB(
|
||||
"Reserved"
|
||||
};
|
||||
|
||||
bool newAircraft = false;
|
||||
bool updatedCallsign = false;
|
||||
|
||||
int df = (data[0] >> 3) & ADS_B_DF_MASK; // Downlink format
|
||||
int ca = data[0] & 0x7; // Capability
|
||||
unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); // ICAO aircraft address
|
||||
@ -646,6 +802,7 @@ void ADSBDemodGUI::handleADSB(
|
||||
else
|
||||
{
|
||||
// Add new aircraft
|
||||
newAircraft = true;
|
||||
aircraft = new Aircraft(this);
|
||||
aircraft->m_icao = icao;
|
||||
m_aircraft.insert(icao, aircraft);
|
||||
@ -819,8 +976,11 @@ void ADSBDemodGUI::handleADSB(
|
||||
for (int i = 0; i < 8; i++)
|
||||
callsign[i] = idMap[c[i]];
|
||||
callsign[8] = '\0';
|
||||
QString callsignTrimmed = QString(callsign).trimmed();
|
||||
|
||||
aircraft->m_callsign = QString(callsign).trimmed();
|
||||
updatedCallsign = aircraft->m_callsign != callsignTrimmed;
|
||||
|
||||
aircraft->m_callsign = callsignTrimmed;
|
||||
aircraft->m_callsignItem->setText(aircraft->m_callsign);
|
||||
|
||||
// Attempt to map callsign to flight number
|
||||
@ -1193,6 +1353,11 @@ void ADSBDemodGUI::handleADSB(
|
||||
|
||||
// Check to see if we need to emit a notification about this aircraft
|
||||
checkDynamicNotification(aircraft);
|
||||
|
||||
// Update text below photo if it's likely to have changed
|
||||
if ((aircraft == m_highlightAircraft) && (newAircraft || updatedCallsign)) {
|
||||
updatePhotoText(aircraft);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::checkStaticNotification(Aircraft *aircraft)
|
||||
@ -1527,9 +1692,10 @@ void ADSBDemodGUI::on_adsbData_cellClicked(int row, int column)
|
||||
(void) column;
|
||||
// Get ICAO of aircraft in row clicked
|
||||
int icao = ui->adsbData->item(row, 0)->text().toInt(nullptr, 16);
|
||||
if (m_aircraft.contains(icao))
|
||||
if (m_aircraft.contains(icao)) {
|
||||
highlightAircraft(m_aircraft.value(icao));
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::on_adsbData_cellDoubleClicked(int row, int column)
|
||||
{
|
||||
@ -1675,6 +1841,18 @@ void ADSBDemodGUI::on_getAirportDB_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::on_getAirspacesDB_clicked()
|
||||
{
|
||||
// Don't try to download while already in progress
|
||||
if (m_progressDialog == nullptr)
|
||||
{
|
||||
m_progressDialog = new QProgressDialog(this);
|
||||
m_progressDialog->setMaximum(OpenAIP::m_countryCodes.size());
|
||||
m_progressDialog->setCancelButton(nullptr);
|
||||
m_openAIP.downloadAirspaces();
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::on_flightPaths_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_flightPaths = checked;
|
||||
@ -1960,6 +2138,46 @@ void ADSBDemodGUI::updateAirports()
|
||||
m_currentDisplayHeliports = m_settings.m_displayHeliports;
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::updateAirspaces()
|
||||
{
|
||||
AzEl azEl = m_azEl;
|
||||
m_airspaceModel.removeAllAirspaces();
|
||||
for (const auto& airspace: m_airspaces)
|
||||
{
|
||||
if (m_settings.m_airspaces.contains(airspace->m_category))
|
||||
{
|
||||
// Calculate distance to airspace from My Position
|
||||
azEl.setTarget(airspace->m_center.y(), airspace->m_center.x(), 0);
|
||||
azEl.calculate();
|
||||
|
||||
// Only display airport if in range
|
||||
if (azEl.getDistance() <= m_settings.m_airspaceRange*1000.0f) {
|
||||
m_airspaceModel.addAirspace(airspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::updateNavAids()
|
||||
{
|
||||
AzEl azEl = m_azEl;
|
||||
m_navAidModel.removeAllNavAids();
|
||||
if (m_settings.m_displayNavAids)
|
||||
{
|
||||
for (const auto& navAid: m_navAids)
|
||||
{
|
||||
// Calculate distance to NavAid from My Position
|
||||
azEl.setTarget(navAid->m_latitude, navAid->m_longitude, Units::feetToMetres(navAid->m_elevation));
|
||||
azEl.calculate();
|
||||
|
||||
// Only display NavAid if in range
|
||||
if (azEl.getDistance() <= m_settings.m_airspaceRange*1000.0f) {
|
||||
m_navAidModel.addNavAid(navAid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set a static target, such as an airport
|
||||
void ADSBDemodGUI::target(const QString& name, float az, float el, float range)
|
||||
{
|
||||
@ -1993,10 +2211,102 @@ void ADSBDemodGUI::targetAircraft(Aircraft *aircraft)
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::updatePhotoText(Aircraft *aircraft)
|
||||
{
|
||||
if (m_settings.m_displayPhotos)
|
||||
{
|
||||
QString callsign = aircraft->m_callsignItem->text().trimmed();
|
||||
QString reg = aircraft->m_registrationItem->text().trimmed();
|
||||
if (!callsign.isEmpty() && !reg.isEmpty()) {
|
||||
ui->photoHeader->setText(QString("%1 - %2").arg(callsign).arg(reg));
|
||||
} else if (!callsign.isEmpty()) {
|
||||
ui->photoHeader->setText(QString("%1").arg(callsign));
|
||||
} else if (!reg.isEmpty()) {
|
||||
ui->photoHeader->setText(QString("%1").arg(reg));
|
||||
}
|
||||
|
||||
QIcon icon = aircraft->m_countryItem->icon();
|
||||
QList<QSize> sizes = icon.availableSizes();
|
||||
if (sizes.size() > 0) {
|
||||
ui->photoFlag->setPixmap(icon.pixmap(sizes[0]));
|
||||
}
|
||||
|
||||
updatePhotoFlightInformation(aircraft);
|
||||
|
||||
QString aircraftDetails = "<table width=200>"; // Note, Qt seems to make the table bigger than this so text is cropped, not wrapped
|
||||
QString manufacturer = aircraft->m_manufacturerNameItem->text();
|
||||
if (!manufacturer.isEmpty()) {
|
||||
aircraftDetails.append(QString("<tr><th align=left>Manufacturer:<td>%1").arg(manufacturer));
|
||||
}
|
||||
QString model = aircraft->m_modelItem->text();
|
||||
if (!model.isEmpty()) {
|
||||
aircraftDetails.append(QString("<tr><th align=left>Aircraft:<td>%1").arg(model));
|
||||
}
|
||||
QString owner = aircraft->m_ownerItem->text();
|
||||
if (!owner.isEmpty()) {
|
||||
aircraftDetails.append(QString("<tr><th align=left>Owner:<td>%1").arg(owner));
|
||||
}
|
||||
QString operatorICAO = aircraft->m_operatorICAOItem->text();
|
||||
if (!operatorICAO.isEmpty()) {
|
||||
aircraftDetails.append(QString("<tr><th align=left>Operator:<td>%1").arg(operatorICAO));
|
||||
}
|
||||
QString registered = aircraft->m_registeredItem->text();
|
||||
if (!registered.isEmpty()) {
|
||||
aircraftDetails.append(QString("<tr><th align=left>Registered:<td>%1").arg(registered));
|
||||
}
|
||||
aircraftDetails.append("</table>");
|
||||
ui->aircraftDetails->setText(aircraftDetails);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::updatePhotoFlightInformation(Aircraft *aircraft)
|
||||
{
|
||||
if (m_settings.m_displayPhotos)
|
||||
{
|
||||
QString dep = aircraft->m_depItem->text();
|
||||
QString arr = aircraft->m_arrItem->text();
|
||||
QString std = aircraft->m_stdItem->text();
|
||||
QString etd = aircraft->m_etdItem->text();
|
||||
QString atd = aircraft->m_atdItem->text();
|
||||
QString sta = aircraft->m_staItem->text();
|
||||
QString eta = aircraft->m_etaItem->text();
|
||||
QString ata = aircraft->m_ataItem->text();
|
||||
QString flightDetails;
|
||||
if (!dep.isEmpty() && !arr.isEmpty())
|
||||
{
|
||||
flightDetails = QString("<center><table width=200><tr><th colspan=4>%1 - %2").arg(dep).arg(arr);
|
||||
if (!std.isEmpty() && !sta.isEmpty()) {
|
||||
flightDetails.append(QString("<tr><td>STD<td>%1<td>STA<td>%2").arg(std).arg(sta));
|
||||
}
|
||||
if ((!atd.isEmpty() || !etd.isEmpty()) && (!ata.isEmpty() || !eta.isEmpty()))
|
||||
{
|
||||
if (!atd.isEmpty()) {
|
||||
flightDetails.append(QString("<tr><td>Actual<td>%1").arg(atd));
|
||||
} else if (!etd.isEmpty()) {
|
||||
flightDetails.append(QString("<tr><td>Estimated<td>%1").arg(etd));
|
||||
}
|
||||
if (!ata.isEmpty()) {
|
||||
flightDetails.append(QString("<td>Actual<td>%1").arg(ata));
|
||||
} else if (!eta.isEmpty()) {
|
||||
flightDetails.append(QString("<td>Estimated<td>%1").arg(eta));
|
||||
}
|
||||
}
|
||||
flightDetails.append("</center>");
|
||||
}
|
||||
ui->flightDetails->setText(flightDetails);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::highlightAircraft(Aircraft *aircraft)
|
||||
{
|
||||
if (aircraft != m_highlightAircraft)
|
||||
{
|
||||
// Hide photo of old aircraft
|
||||
ui->photoHeader->setVisible(false);
|
||||
ui->photoFlag->setVisible(false);
|
||||
ui->photo->setVisible(false);
|
||||
ui->flightDetails->setVisible(false);
|
||||
ui->aircraftDetails->setVisible(false);
|
||||
if (m_highlightAircraft)
|
||||
{
|
||||
// Restore colour
|
||||
@ -2005,13 +2315,29 @@ void ADSBDemodGUI::highlightAircraft(Aircraft *aircraft)
|
||||
}
|
||||
// Highlight this aircraft
|
||||
m_highlightAircraft = aircraft;
|
||||
if (aircraft)
|
||||
{
|
||||
aircraft->m_isHighlighted = true;
|
||||
m_aircraftModel.aircraftUpdated(aircraft);
|
||||
if (m_settings.m_displayPhotos)
|
||||
{
|
||||
// Download photo
|
||||
updatePhotoText(aircraft);
|
||||
m_planeSpotters.getAircraftPhoto(QString::number(aircraft->m_icao, 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aircraft)
|
||||
{
|
||||
// Highlight the row in the table - always do this, as it can become
|
||||
// unselected
|
||||
ui->adsbData->selectRow(aircraft->m_icaoItem->row());
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->adsbData->clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
// Show feed dialog
|
||||
void ADSBDemodGUI::feedSelect()
|
||||
@ -2033,7 +2359,8 @@ void ADSBDemodGUI::on_displaySettings_clicked()
|
||||
m_settings.m_displayHeliports, m_settings.m_siUnits,
|
||||
m_settings.m_tableFontName, m_settings.m_tableFontSize,
|
||||
m_settings.m_displayDemodStats, m_settings.m_autoResizeTableColumns,
|
||||
m_settings.m_apiKey);
|
||||
m_settings.m_apiKey, m_settings.m_airspaces, m_settings.m_airspaceRange,
|
||||
m_settings.m_mapType, m_settings.m_displayNavAids, m_settings.m_displayPhotos);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
bool unitsChanged = m_settings.m_siUnits != dialog.m_siUnits;
|
||||
@ -2048,14 +2375,116 @@ void ADSBDemodGUI::on_displaySettings_clicked()
|
||||
m_settings.m_displayDemodStats = dialog.m_displayDemodStats;
|
||||
m_settings.m_autoResizeTableColumns = dialog.m_autoResizeTableColumns;
|
||||
m_settings.m_apiKey = dialog.m_apiKey;
|
||||
m_settings.m_airspaces = dialog.m_airspaces;
|
||||
m_settings.m_airspaceRange = dialog.m_airspaceRange;
|
||||
m_settings.m_mapType = dialog.m_mapType;
|
||||
m_settings.m_displayNavAids = dialog.m_displayNavAids;
|
||||
m_settings.m_displayPhotos = dialog.m_displayPhotos;
|
||||
|
||||
if (unitsChanged)
|
||||
if (unitsChanged) {
|
||||
m_aircraftModel.allAircraftUpdated();
|
||||
}
|
||||
displaySettings();
|
||||
applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::applyMapSettings()
|
||||
{
|
||||
QQuickItem *item = ui->map->rootObject();
|
||||
|
||||
// Save existing position of map
|
||||
QObject *object = item->findChild<QObject*>("map");
|
||||
QGeoCoordinate coords;
|
||||
double zoom;
|
||||
if (object != nullptr)
|
||||
{
|
||||
coords = object->property("center").value<QGeoCoordinate>();
|
||||
zoom = object->property("zoomLevel").value<double>();
|
||||
}
|
||||
|
||||
// Create the map using the specified provider
|
||||
QQmlProperty::write(item, "mapProvider", "osm");
|
||||
QVariantMap parameters;
|
||||
// Use our repo, so we can append API key and redefine transmit maps
|
||||
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
|
||||
// Use ADS-B specific cache, as we use different transmit maps
|
||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/QtLocation/5.8/tiles/osm/sdrangel_adsb";
|
||||
parameters["osm.mapping.cache.directory"] = cachePath;
|
||||
// On Linux, we need to create the directory
|
||||
QDir dir(cachePath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(cachePath);
|
||||
}
|
||||
|
||||
QString mapType;
|
||||
switch (m_settings.m_mapType)
|
||||
{
|
||||
case ADSBDemodSettings::AVIATION_LIGHT:
|
||||
mapType = "Transit Map";
|
||||
break;
|
||||
case ADSBDemodSettings::AVIATION_DARK:
|
||||
mapType = "Night Transit Map";
|
||||
break;
|
||||
case ADSBDemodSettings::STREET:
|
||||
mapType = "Street Map";
|
||||
break;
|
||||
case ADSBDemodSettings::SATELLITE:
|
||||
mapType = "Satellite Map";
|
||||
break;
|
||||
}
|
||||
|
||||
QVariant retVal;
|
||||
if (!QMetaObject::invokeMethod(item, "createMap", Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QVariant, retVal),
|
||||
Q_ARG(QVariant, QVariant::fromValue(parameters)),
|
||||
Q_ARG(QVariant, mapType),
|
||||
Q_ARG(QVariant, QVariant::fromValue(this))))
|
||||
{
|
||||
qCritical() << "ADSBDemodGUI::applyMapSettings - Failed to invoke createMap";
|
||||
}
|
||||
QObject *newMap = retVal.value<QObject *>();
|
||||
|
||||
// Restore position of map
|
||||
if (newMap != nullptr)
|
||||
{
|
||||
if (coords.isValid())
|
||||
{
|
||||
newMap->setProperty("zoomLevel", QVariant::fromValue(zoom));
|
||||
newMap->setProperty("center", QVariant::fromValue(coords));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "ADSBDemodGUI::applyMapSettings - createMap returned a nullptr";
|
||||
}
|
||||
|
||||
// Move antenna icon to My Position
|
||||
QObject *stationObject = newMap->findChild<QObject*>("station");
|
||||
if(stationObject != NULL)
|
||||
{
|
||||
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
|
||||
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
|
||||
Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
|
||||
QGeoCoordinate coords = stationObject->property("coordinate").value<QGeoCoordinate>();
|
||||
coords.setLatitude(stationLatitude);
|
||||
coords.setLongitude(stationLongitude);
|
||||
coords.setAltitude(stationAltitude);
|
||||
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
|
||||
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "ADSBDemodGUI::applyMapSettings - Couldn't find station";
|
||||
}
|
||||
}
|
||||
|
||||
// Called from QML when empty space clicked
|
||||
void ADSBDemodGUI::clearHighlighted()
|
||||
{
|
||||
highlightAircraft(nullptr);
|
||||
}
|
||||
|
||||
ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
|
||||
ChannelGUI(parent),
|
||||
ui(new Ui::ADSBDemodGUI),
|
||||
@ -2067,14 +2496,20 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
m_tickCount(0),
|
||||
m_aircraftInfo(nullptr),
|
||||
m_airportModel(this),
|
||||
m_airspaceModel(this),
|
||||
m_trackAircraft(nullptr),
|
||||
m_highlightAircraft(nullptr),
|
||||
m_progressDialog(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
m_osmPort = 0; // Pick a free port
|
||||
m_templateServer = new ADSBOSMTemplateServer("q2RVNAe3eFKCH4XsrE3r", m_osmPort);
|
||||
|
||||
ui->map->rootContext()->setContextProperty("aircraftModel", &m_aircraftModel);
|
||||
ui->map->rootContext()->setContextProperty("airportModel", &m_airportModel);
|
||||
ui->map->rootContext()->setContextProperty("airspaceModel", &m_airspaceModel);
|
||||
ui->map->rootContext()->setContextProperty("navAidModel", &m_navAidModel);
|
||||
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
@ -2136,6 +2571,12 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
connect(ui->adsbData->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(adsbData_sectionMoved(int, int, int)));
|
||||
connect(ui->adsbData->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(adsbData_sectionResized(int, int, int)));
|
||||
|
||||
ui->photoHeader->setVisible(false);
|
||||
ui->photoFlag->setVisible(false);
|
||||
ui->photo->setVisible(false);
|
||||
ui->flightDetails->setVisible(false);
|
||||
ui->aircraftDetails->setVisible(false);
|
||||
|
||||
// Read aircraft information database, if it has previously been downloaded
|
||||
if (!readFastDB(getFastDBFilename()))
|
||||
{
|
||||
@ -2151,6 +2592,16 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
// Read operator air force to military map
|
||||
m_militaryMap = CSV::hash(":/flags/militarymap.csv");
|
||||
|
||||
connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
|
||||
connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
|
||||
connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
|
||||
connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
|
||||
|
||||
// Read airspaces
|
||||
m_airspaces = OpenAIP::readAirspaces();
|
||||
// Read NavAids
|
||||
m_navAids = OpenAIP::readNavAids();
|
||||
|
||||
// Get station position
|
||||
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
|
||||
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
|
||||
@ -2167,26 +2618,21 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
coords.setLongitude(stationLongitude);
|
||||
object->setProperty("center", QVariant::fromValue(coords));
|
||||
}
|
||||
// Move antenna icon to My Position
|
||||
QObject *stationObject = item->findChild<QObject*>("station");
|
||||
if(stationObject != NULL)
|
||||
{
|
||||
QGeoCoordinate coords = stationObject->property("coordinate").value<QGeoCoordinate>();
|
||||
coords.setLatitude(stationLatitude);
|
||||
coords.setLongitude(stationLongitude);
|
||||
coords.setAltitude(stationAltitude);
|
||||
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
|
||||
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
|
||||
}
|
||||
// Add airports within range of My Position
|
||||
if (m_airportInfo != nullptr)
|
||||
if (m_airportInfo != nullptr) {
|
||||
updateAirports();
|
||||
}
|
||||
updateAirspaces();
|
||||
updateNavAids();
|
||||
|
||||
// Initialise text to speech engine
|
||||
m_speech = new QTextToSpeech(this);
|
||||
|
||||
m_flightInformation = nullptr;
|
||||
|
||||
connect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
|
||||
connect(ui->photo, &ClickableLabel::clicked, this, &ADSBDemodGUI::photoClicked);
|
||||
|
||||
updateDeviceSetList();
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
@ -2194,6 +2640,16 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
|
||||
ADSBDemodGUI::~ADSBDemodGUI()
|
||||
{
|
||||
if (m_templateServer)
|
||||
{
|
||||
m_templateServer->close();
|
||||
delete m_templateServer;
|
||||
}
|
||||
disconnect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
|
||||
disconnect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
|
||||
disconnect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
|
||||
disconnect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
|
||||
disconnect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
|
||||
delete ui;
|
||||
qDeleteAll(m_aircraft);
|
||||
if (m_airportInfo) {
|
||||
@ -2209,6 +2665,8 @@ ADSBDemodGUI::~ADSBDemodGUI()
|
||||
disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
|
||||
delete m_flightInformation;
|
||||
}
|
||||
qDeleteAll(m_airspaces);
|
||||
qDeleteAll(m_navAids);
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::applySettings(bool force)
|
||||
@ -2308,11 +2766,16 @@ void ADSBDemodGUI::displaySettings()
|
||||
|| (m_settings.m_displayHeliports != m_currentDisplayHeliports)))
|
||||
updateAirports();
|
||||
|
||||
updateAirspaces();
|
||||
updateNavAids();
|
||||
|
||||
if (!m_settings.m_displayDemodStats)
|
||||
ui->stats->setText("");
|
||||
|
||||
initFlightInformation();
|
||||
|
||||
applyMapSettings();
|
||||
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
@ -2525,6 +2988,7 @@ void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& fli
|
||||
if (aircraft->m_positionValid) {
|
||||
m_aircraftModel.aircraftUpdated(aircraft);
|
||||
}
|
||||
updatePhotoFlightInformation(aircraft);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2532,6 +2996,40 @@ void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& fli
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::aircraftPhoto(const PlaneSpottersPhoto *photo)
|
||||
{
|
||||
// Make sure the photo is for the currently highlighted aircraft, as it may
|
||||
// have taken a while to download
|
||||
if (!photo->m_pixmap.isNull() && m_highlightAircraft && (m_highlightAircraft->m_icaoItem->text() == photo->m_icao))
|
||||
{
|
||||
ui->photo->setPixmap(photo->m_pixmap);
|
||||
ui->photo->setToolTip(QString("Photographer: %1").arg(photo->m_photographer)); // Required by terms of use
|
||||
ui->photoHeader->setVisible(true);
|
||||
ui->photoFlag->setVisible(true);
|
||||
ui->photo->setVisible(true);
|
||||
ui->flightDetails->setVisible(true);
|
||||
ui->aircraftDetails->setVisible(true);
|
||||
m_photoLink = photo->m_link;
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::photoClicked()
|
||||
{
|
||||
// Photo needs to link back to PlaneSpotters, as per terms of use
|
||||
if (m_highlightAircraft)
|
||||
{
|
||||
if (m_photoLink.isEmpty())
|
||||
{
|
||||
QString icaoUpper = QString("%1").arg(m_highlightAircraft->m_icao, 1, 16).toUpper();
|
||||
QDesktopServices::openUrl(QUrl(QString("https://www.planespotters.net/hex/%1").arg(icaoUpper)));
|
||||
}
|
||||
else
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl(m_photoLink));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::on_logEnable_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_logEnabled = checked;
|
||||
@ -2616,3 +3114,48 @@ void ADSBDemodGUI::on_logOpen_clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::downloadingURL(const QString& url)
|
||||
{
|
||||
if (m_progressDialog)
|
||||
{
|
||||
m_progressDialog->setLabelText(QString("Downloading %1.").arg(url));
|
||||
m_progressDialog->setValue(m_progressDialog->value() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::downloadError(const QString& error)
|
||||
{
|
||||
QMessageBox::critical(this, "ADS-B", error);
|
||||
if (m_progressDialog)
|
||||
{
|
||||
m_progressDialog->close();
|
||||
delete m_progressDialog;
|
||||
m_progressDialog = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::downloadAirspaceFinished()
|
||||
{
|
||||
if (m_progressDialog) {
|
||||
m_progressDialog->setLabelText("Reading airspaces.");
|
||||
}
|
||||
m_airspaces = OpenAIP::readAirspaces();
|
||||
updateAirspaces();
|
||||
m_openAIP.downloadNavAids();
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::downloadNavAidsFinished()
|
||||
{
|
||||
if (m_progressDialog) {
|
||||
m_progressDialog->setLabelText("Reading NAVAIDs.");
|
||||
}
|
||||
m_navAids = OpenAIP::readNavAids();
|
||||
updateNavAids();
|
||||
if (m_progressDialog)
|
||||
{
|
||||
m_progressDialog->close();
|
||||
delete m_progressDialog;
|
||||
m_progressDialog = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include "util/movingaverage.h"
|
||||
#include "util/httpdownloadmanager.h"
|
||||
#include "util/flightinformation.h"
|
||||
#include "util/openaip.h"
|
||||
#include "util/planespotters.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "adsbdemodsettings.h"
|
||||
@ -49,6 +51,7 @@ class ADSBDemod;
|
||||
class WebAPIAdapterInterface;
|
||||
class HttpDownloadManager;
|
||||
class ADSBDemodGUI;
|
||||
class ADSBOSMTemplateServer;
|
||||
|
||||
namespace Ui {
|
||||
class ADSBDemodGUI;
|
||||
@ -484,6 +487,155 @@ private:
|
||||
QList<float> m_range;
|
||||
};
|
||||
|
||||
// Airspace data model used by QML map item
|
||||
class AirspaceModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
enum MarkerRoles {
|
||||
nameRole = Qt::UserRole + 1,
|
||||
detailsRole = Qt::UserRole + 2,
|
||||
positionRole = Qt::UserRole + 3,
|
||||
airspaceBorderColorRole = Qt::UserRole + 4,
|
||||
airspaceFillColorRole = Qt::UserRole + 5,
|
||||
airspacePolygonRole = Qt::UserRole + 6
|
||||
};
|
||||
|
||||
Q_INVOKABLE void addAirspace(Airspace *airspace) {
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_airspaces.append(airspace);
|
||||
// Convert QPointF to QVariantList of QGeoCoordinates
|
||||
QVariantList polygon;
|
||||
for (const auto p : airspace->m_polygon)
|
||||
{
|
||||
QGeoCoordinate coord(p.y(), p.x(), airspace->topHeightInMetres());
|
||||
polygon.push_back(QVariant::fromValue(coord));
|
||||
}
|
||||
m_polygons.append(polygon);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
Q_UNUSED(parent)
|
||||
return m_airspaces.count();
|
||||
}
|
||||
|
||||
void removeAllAirspaces() {
|
||||
if (m_airspaces.count() > 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), 0, m_airspaces.count() - 1);
|
||||
m_airspaces.clear();
|
||||
m_polygons.clear();
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
(void) index;
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[nameRole] = "name";
|
||||
roles[detailsRole] = "details";
|
||||
roles[positionRole] = "position";
|
||||
roles[airspaceBorderColorRole] = "airspaceBorderColor";
|
||||
roles[airspaceFillColorRole] = "airspaceFillColor";
|
||||
roles[airspacePolygonRole] = "airspacePolygon";
|
||||
return roles;
|
||||
}
|
||||
|
||||
private:
|
||||
QList<Airspace *> m_airspaces;
|
||||
QList<QVariantList> m_polygons;
|
||||
};
|
||||
|
||||
// NavAid model used for each NavAid on the map
|
||||
class NavAidModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
enum MarkerRoles{
|
||||
positionRole = Qt::UserRole + 1,
|
||||
navAidDataRole = Qt::UserRole + 2,
|
||||
navAidImageRole = Qt::UserRole + 3,
|
||||
bubbleColourRole = Qt::UserRole + 4,
|
||||
selectedRole = Qt::UserRole + 5
|
||||
};
|
||||
|
||||
Q_INVOKABLE void addNavAid(NavAid *vor) {
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_navAids.append(vor);
|
||||
m_selected.append(false);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
Q_UNUSED(parent)
|
||||
return m_navAids.count();
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override {
|
||||
(void) index;
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
void allNavAidsUpdated() {
|
||||
for (int i = 0; i < m_navAids.count(); i++)
|
||||
{
|
||||
QModelIndex idx = index(i);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
void removeNavAid(NavAid *vor) {
|
||||
int row = m_navAids.indexOf(vor);
|
||||
if (row >= 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_navAids.removeAt(row);
|
||||
m_selected.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void removeAllNavAids() {
|
||||
if (m_navAids.count() > 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), 0, m_navAids.count() - 1);
|
||||
m_navAids.clear();
|
||||
m_selected.clear();
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[positionRole] = "position";
|
||||
roles[navAidDataRole] = "navAidData";
|
||||
roles[navAidImageRole] = "navAidImage";
|
||||
roles[bubbleColourRole] = "bubbleColour";
|
||||
roles[selectedRole] = "selected";
|
||||
return roles;
|
||||
}
|
||||
|
||||
private:
|
||||
QList<NavAid *> m_navAids;
|
||||
QList<bool> m_selected;
|
||||
};
|
||||
|
||||
class ADSBDemodGUI : public ChannelGUI {
|
||||
Q_OBJECT
|
||||
|
||||
@ -500,11 +652,13 @@ public:
|
||||
void target(const QString& name, float az, float el, float range);
|
||||
bool setFrequency(float frequency);
|
||||
bool useSIUints() { return m_settings.m_siUnits; }
|
||||
Q_INVOKABLE void clearHighlighted();
|
||||
|
||||
public slots:
|
||||
void channelMarkerChangedByCursor();
|
||||
void channelMarkerHighlightedByCursor();
|
||||
void flightInformationUpdated(const FlightInformation::Flight& flight);
|
||||
void aircraftPhoto(const PlaneSpottersPhoto *photo);
|
||||
|
||||
private:
|
||||
Ui::ADSBDemodGUI* ui;
|
||||
@ -524,10 +678,14 @@ private:
|
||||
QHash<int, AirportInformation *> *m_airportInfo; // Hashed on id
|
||||
AircraftModel m_aircraftModel;
|
||||
AirportModel m_airportModel;
|
||||
AirspaceModel m_airspaceModel;
|
||||
NavAidModel m_navAidModel;
|
||||
QHash<QString, QIcon *> m_airlineIcons; // Hashed on airline ICAO
|
||||
QHash<QString, QIcon *> m_flagIcons; // Hashed on country
|
||||
QHash<QString, QString> *m_prefixMap; // Registration to country (flag name)
|
||||
QHash<QString, QString> *m_militaryMap; // Operator airforce to military (flag name)
|
||||
QList<Airspace *> m_airspaces;
|
||||
QList<NavAid *> m_navAids;
|
||||
|
||||
AzEl m_azEl; // Position of station
|
||||
Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report
|
||||
@ -542,9 +700,14 @@ private:
|
||||
QTextToSpeech *m_speech;
|
||||
QMenu *menu; // Column select context menu
|
||||
FlightInformation *m_flightInformation;
|
||||
PlaneSpotters m_planeSpotters;
|
||||
QString m_photoLink;
|
||||
WebAPIAdapterInterface *m_webAPIAdapterInterface;
|
||||
HttpDownloadManager m_dlm;
|
||||
QProgressDialog *m_progressDialog;
|
||||
quint16 m_osmPort;
|
||||
OpenAIP m_openAIP;
|
||||
ADSBOSMTemplateServer *m_templateServer;
|
||||
|
||||
explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
|
||||
virtual ~ADSBDemodGUI();
|
||||
@ -579,6 +742,8 @@ private:
|
||||
bool readOSNDB(const QString& filename);
|
||||
bool readFastDB(const QString& filename);
|
||||
void updateAirports();
|
||||
void updateAirspaces();
|
||||
void updateNavAids();
|
||||
QIcon *getAirlineIcon(const QString &operatorICAO);
|
||||
QIcon *getFlagIcon(const QString &country);
|
||||
void updateDeviceSetList();
|
||||
@ -586,6 +751,9 @@ private:
|
||||
Aircraft* findAircraftByFlight(const QString& flight);
|
||||
QString dataTimeToShortString(QDateTime dt);
|
||||
void initFlightInformation();
|
||||
void applyMapSettings();
|
||||
void updatePhotoText(Aircraft *aircraft);
|
||||
void updatePhotoFlightInformation(Aircraft *aircraft);
|
||||
|
||||
void leaveEvent(QEvent*);
|
||||
void enterEvent(QEvent*);
|
||||
@ -610,6 +778,7 @@ private slots:
|
||||
void on_flightInfo_clicked();
|
||||
void on_getOSNDB_clicked();
|
||||
void on_getAirportDB_clicked();
|
||||
void on_getAirspacesDB_clicked();
|
||||
void on_flightPaths_clicked(bool checked);
|
||||
void on_allFlightPaths_clicked(bool checked);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
@ -625,6 +794,12 @@ private slots:
|
||||
void on_logEnable_clicked(bool checked=false);
|
||||
void on_logFilename_clicked();
|
||||
void on_logOpen_clicked();
|
||||
void downloadingURL(const QString& url);
|
||||
void downloadError(const QString& error);
|
||||
void downloadAirspaceFinished();
|
||||
void downloadNavAidsFinished();
|
||||
void photoClicked();
|
||||
|
||||
signals:
|
||||
void homePositionChanged();
|
||||
};
|
||||
|
@ -518,6 +518,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="getAirspacesDB">
|
||||
<property name="toolTip">
|
||||
<string>Download airspaces and NAVAIDs from OpenAIP (40MB)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/icons/vor.png</normaloff>:/icons/vor.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="displaySettings">
|
||||
<property name="toolTip">
|
||||
@ -572,6 +586,23 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="flightInfo">
|
||||
<property name="toolTip">
|
||||
<string>Download flight information for selected aircraft</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/info.png</normaloff>:/info.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="feed">
|
||||
<property name="toolTip">
|
||||
@ -606,23 +637,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="flightInfo">
|
||||
<property name="toolTip">
|
||||
<string>Download flight information for selected aircraft</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/info.png</normaloff>:/info.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="logEnable">
|
||||
<property name="maximumSize">
|
||||
@ -738,7 +752,7 @@
|
||||
<x>0</x>
|
||||
<y>140</y>
|
||||
<width>600</width>
|
||||
<height>291</height>
|
||||
<height>270</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -750,13 +764,13 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>0</height>
|
||||
<height>270</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>ADS-B Data</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayoutTable">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
@ -774,6 +788,12 @@
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="adsbData">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
@ -1048,6 +1068,125 @@
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="photoLayout">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="photoHeaderLayout">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="photoHeader">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="photoFlag">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ClickableLabel" name="photo">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="flightDetails">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="aircraftDetails">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="photoVerticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mapContainer" native="true">
|
||||
@ -1132,6 +1271,16 @@
|
||||
<header>gui/rollupwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ClickableLabel</class>
|
||||
<extends>QLabel</extends>
|
||||
<header>gui/clickablelabel.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDialZ</class>
|
||||
<extends>QWidget</extends>
|
||||
@ -1144,11 +1293,6 @@
|
||||
<header>gui/levelmeter.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>deltaFrequency</tabstop>
|
||||
@ -1161,9 +1305,16 @@
|
||||
<tabstop>threshold</tabstop>
|
||||
<tabstop>getOSNDB</tabstop>
|
||||
<tabstop>getAirportDB</tabstop>
|
||||
<tabstop>getAirspacesDB</tabstop>
|
||||
<tabstop>displaySettings</tabstop>
|
||||
<tabstop>flightPaths</tabstop>
|
||||
<tabstop>allFlightPaths</tabstop>
|
||||
<tabstop>flightInfo</tabstop>
|
||||
<tabstop>feed</tabstop>
|
||||
<tabstop>notifications</tabstop>
|
||||
<tabstop>logEnable</tabstop>
|
||||
<tabstop>logFilename</tabstop>
|
||||
<tabstop>logOpen</tabstop>
|
||||
<tabstop>devicesRefresh</tabstop>
|
||||
<tabstop>device</tabstop>
|
||||
<tabstop>adsbData</tabstop>
|
||||
|
@ -51,7 +51,7 @@ void ADSBDemodSettings::resetToDefaults()
|
||||
m_reverseAPIPort = 8888;
|
||||
m_reverseAPIDeviceIndex = 0;
|
||||
m_reverseAPIChannelIndex = 0;
|
||||
m_airportRange = 100;
|
||||
m_airportRange = 100.0f;
|
||||
m_airportMinimumSize = AirportType::Medium;
|
||||
m_displayHeliports = false;
|
||||
m_flightPaths = true;
|
||||
@ -74,6 +74,11 @@ void ADSBDemodSettings::resetToDefaults()
|
||||
}
|
||||
m_logFilename = "adsb_log.csv";
|
||||
m_logEnabled = false;
|
||||
m_airspaces = QStringList({"A", "D", "TMZ"});
|
||||
m_airspaceRange = 500.0f;
|
||||
m_mapType = AVIATION_LIGHT;
|
||||
m_displayNavAids = true;
|
||||
m_displayPhotos = true;
|
||||
}
|
||||
|
||||
QByteArray ADSBDemodSettings::serialize() const
|
||||
@ -123,10 +128,18 @@ QByteArray ADSBDemodSettings::serialize() const
|
||||
s.writeString(36, m_logFilename);
|
||||
s.writeBool(37, m_logEnabled);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
|
||||
s.writeString(38, m_airspaces.join(" "));
|
||||
s.writeFloat(39, m_airspaceRange);
|
||||
s.writeS32(40, (int)m_mapType);
|
||||
s.writeBool(41, m_displayNavAids);
|
||||
s.writeBool(42, m_displayPhotos);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
|
||||
s.writeS32(100 + i, m_columnIndexes[i]);
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
|
||||
}
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
|
||||
s.writeS32(200 + i, m_columnSizes[i]);
|
||||
}
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -147,6 +160,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
qint32 tmp;
|
||||
uint32_t utmp;
|
||||
QByteArray blob;
|
||||
QString string;
|
||||
|
||||
if (m_channelMarker)
|
||||
{
|
||||
@ -187,7 +201,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
|
||||
d.readS32(17, &m_streamIndex, 0);
|
||||
|
||||
d.readFloat(18, &m_airportRange, 100);
|
||||
d.readFloat(18, &m_airportRange, 100.0f);
|
||||
d.readS32(19, (int *)&m_airportMinimumSize, AirportType::Medium);
|
||||
d.readBool(20, &m_displayHeliports, false);
|
||||
d.readBool(21, &m_flightPaths, true);
|
||||
@ -211,10 +225,19 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
d.readString(36, &m_logFilename, "adsb_log.csv");
|
||||
d.readBool(37, &m_logEnabled, false);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
|
||||
d.readString(38, &string, "A D TMZ");
|
||||
m_airspaces = string.split(" ");
|
||||
d.readFloat(39, &m_airspaceRange, 500.0f);
|
||||
d.readS32(40, (int *)&m_mapType, (int)AVIATION_LIGHT);
|
||||
d.readBool(41, &m_displayNavAids, true);
|
||||
d.readBool(42, &m_displayPhotos, true);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
|
||||
d.readS32(100 + i, &m_columnIndexes[i], i);
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
|
||||
}
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
|
||||
d.readS32(200 + i, &m_columnSizes[i], -1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -134,6 +134,17 @@ struct ADSBDemodSettings
|
||||
QString m_logFilename;
|
||||
bool m_logEnabled;
|
||||
|
||||
QStringList m_airspaces; //!< Airspace names to display
|
||||
float m_airspaceRange; //!< How far away we display airspace (mkm)
|
||||
enum MapType {
|
||||
AVIATION_LIGHT, //!< White map with no place names
|
||||
AVIATION_DARK,
|
||||
STREET,
|
||||
SATELLITE
|
||||
} m_mapType;
|
||||
bool m_displayNavAids;
|
||||
bool m_displayPhotos;
|
||||
|
||||
ADSBDemodSettings();
|
||||
void resetToDefaults();
|
||||
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
|
||||
|
18
plugins/channelrx/demodadsb/adsbosmtemplateserver.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "adsbosmtemplateserver.h"
|
155
plugins/channelrx/demodadsb/adsbosmtemplateserver.h
Normal file
@ -0,0 +1,155 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_ADSBOSMTEMPLATE_SERVER_H_
|
||||
#define INCLUDE_ADSBOSMTEMPLATE_SERVER_H_
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QDebug>
|
||||
|
||||
// Redirect OSM maps so we can support Street, Satellite and an Aviation map (CartoDB position)
|
||||
class ADSBOSMTemplateServer : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QString m_thunderforestAPIKey;
|
||||
QString m_maptilerAPIKey;
|
||||
|
||||
public:
|
||||
// port - port to listen on / is listening on. Use 0 for any free port.
|
||||
ADSBOSMTemplateServer(const QString &maptilerAPIKey, quint16 &port, QObject* parent = 0) :
|
||||
QTcpServer(parent),
|
||||
m_maptilerAPIKey(maptilerAPIKey)
|
||||
{
|
||||
listen(QHostAddress::Any, port);
|
||||
port = serverPort();
|
||||
}
|
||||
|
||||
void incomingConnection(qintptr socket) override
|
||||
{
|
||||
QTcpSocket* s = new QTcpSocket(this);
|
||||
connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
|
||||
connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
|
||||
s->setSocketDescriptor(socket);
|
||||
//addPendingConnection(socket);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void readClient()
|
||||
{
|
||||
QTcpSocket* socket = (QTcpSocket*)sender();
|
||||
if (socket->canReadLine())
|
||||
{
|
||||
QString line = socket->readLine();
|
||||
QStringList tokens = QString(line).split(QRegExp("[ \r\n][ \r\n]*"));
|
||||
if (tokens[0] == "GET")
|
||||
{
|
||||
bool hires = tokens[1].contains("hires");
|
||||
QString hiresURL = hires ? "@2x" : "";
|
||||
QString xml;
|
||||
if ((tokens[1] == "/street") || (tokens[1] == "/street-hires"))
|
||||
{
|
||||
xml = QString("\
|
||||
{\
|
||||
\"UrlTemplate\" : \"https://maps.wikimedia.org/osm-intl/%z/%x/%y%1.png\",\
|
||||
\"ImageFormat\" : \"png\",\
|
||||
\"QImageFormat\" : \"Indexed8\",\
|
||||
\"ID\" : \"wmf-intl-%2x\",\
|
||||
\"MaximumZoomLevel\" : 18,\
|
||||
\"MapCopyRight\" : \"<a href='https://wikimediafoundation.org/wiki/Terms_of_Use'>WikiMedia Foundation</a>\",\
|
||||
\"DataCopyRight\" : \"<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors\"\
|
||||
}").arg(hiresURL).arg(hires ? 1 : 2);
|
||||
}
|
||||
else if (tokens[1] == "/satellite")
|
||||
{
|
||||
xml = QString("\
|
||||
{\
|
||||
\"Enabled\" : true,\
|
||||
\"UrlTemplate\" : \"https://api.maptiler.com/tiles/satellite/%z/%x/%y%1.jpg?key=%2\",\
|
||||
\"ImageFormat\" : \"jpg\",\
|
||||
\"QImageFormat\" : \"RGB888\",\
|
||||
\"ID\" : \"usgs-l7\",\
|
||||
\"MaximumZoomLevel\" : 20,\
|
||||
\"MapCopyRight\" : \"<a href='http://maptiler.com'>Maptiler</a>\",\
|
||||
\"DataCopyRight\" : \"<a href='http://maptiler.com'>Maptiler</a>\"\
|
||||
}").arg(hiresURL).arg(m_maptilerAPIKey);
|
||||
}
|
||||
else if (tokens[1].contains("transit"))
|
||||
{
|
||||
QStringList map({"/night-transit", "/night-transit-hires", "/transit", "/transit-hires"});
|
||||
QStringList mapId({"thf-nighttransit", "thf-nighttransit-hires", "thf-transit", "thf-transit-hires"});
|
||||
QStringList mapUrl({"dark_nolabels", "dark_nolabels", "light_nolabels", "light_nolabels"});
|
||||
|
||||
// Use CartoDB maps without labels for aviation maps
|
||||
int idx = map.indexOf(tokens[1]);
|
||||
xml = QString("\
|
||||
{\
|
||||
\"UrlTemplate\" : \"http://1.basemaps.cartocdn.com/%2/%z/%x/%y.png%1\",\
|
||||
\"ImageFormat\" : \"png\",\
|
||||
\"QImageFormat\" : \"Indexed8\",\
|
||||
\"ID\" : \"%3\",\
|
||||
\"MaximumZoomLevel\" : 20,\
|
||||
\"MapCopyRight\" : \"<a href='https://carto.com'>CartoDB</a>\",\
|
||||
\"DataCopyRight\" : \"<a href='https://carto.com'>CartoDB</a>\"\
|
||||
}").arg(hiresURL).arg(mapUrl[idx]).arg(mapId[idx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList map({"/cycle", "/cycle-hires", "/hiking", "/hiking-hires", "/night-transit", "/night-transit-hires", "/terrain", "/terrain-hires", "/transit", "/transit-hires"});
|
||||
QStringList mapId({"thf-cycle", "thf-cycle-hires", "thf-hike", "thf-hike-hires", "thf-nighttransit", "thf-nighttransit-hires", "thf-landsc", "thf-landsc-hires", "thf-transit", "thf-transit-hires"});
|
||||
QStringList mapUrl({"cycle", "cycle", "outdoors", "outdoors", "transport-dark", "transport-dark", "landscape", "landscape", "transport", "transport"});
|
||||
|
||||
int idx = map.indexOf(tokens[1]);
|
||||
if (idx != -1)
|
||||
{
|
||||
xml = QString("\
|
||||
{\
|
||||
\"UrlTemplate\" : \"http://a.tile.thunderforest.com/%1/%z/%x/%y%4.png?apikey=%2\",\
|
||||
\"ImageFormat\" : \"png\",\
|
||||
\"QImageFormat\" : \"Indexed8\",\
|
||||
\"ID\" : \"%3\",\
|
||||
\"MaximumZoomLevel\" : 20,\
|
||||
\"MapCopyRight\" : \"<a href='http://www.thunderforest.com/'>Thunderforest</a>\",\
|
||||
\"DataCopyRight\" : \"<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors\"\
|
||||
}").arg(mapUrl[idx]).arg("3e1f614f78a345459931ba3c898e975e").arg(mapId[idx]).arg(hiresURL);
|
||||
}
|
||||
}
|
||||
QTextStream os(socket);
|
||||
os.setAutoDetectUnicode(true);
|
||||
os << "HTTP/1.0 200 Ok\r\n"
|
||||
"Content-Type: text/html; charset=\"utf-8\"\r\n"
|
||||
"\r\n"
|
||||
<< xml << "\n";
|
||||
socket->close();
|
||||
|
||||
if (socket->state() == QTcpSocket::UnconnectedState) {
|
||||
delete socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void discardClient()
|
||||
{
|
||||
QTcpSocket* socket = (QTcpSocket*)sender();
|
||||
socket->deleteLater();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -3,5 +3,6 @@
|
||||
<file>icons/aircraft.png</file>
|
||||
<file>icons/controltower.png</file>
|
||||
<file>icons/allflightpaths.png</file>
|
||||
<file>icons/vor.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
plugins/channelrx/demodadsb/icons/vor.png
Normal file
After Width: | Height: | Size: 365 B |
@ -16,5 +16,10 @@
|
||||
<file>map/heliport.png</file>
|
||||
<file>map/antenna.png</file>
|
||||
<file>map/truck.png</file>
|
||||
<file>map/VOR.png</file>
|
||||
<file>map/VOR-DME.png</file>
|
||||
<file>map/VORTAC.png</file>
|
||||
<file>map/NDB.png</file>
|
||||
<file>map/DME.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
plugins/channelrx/demodadsb/map/DME.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
plugins/channelrx/demodadsb/map/NDB.png
Normal file
After Width: | Height: | Size: 674 B |
BIN
plugins/channelrx/demodadsb/map/VOR-DME.png
Normal file
After Width: | Height: | Size: 652 B |
BIN
plugins/channelrx/demodadsb/map/VOR.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
plugins/channelrx/demodadsb/map/VORTAC.png
Normal file
After Width: | Height: | Size: 847 B |
@ -2,30 +2,90 @@ import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtLocation 5.12
|
||||
import QtPositioning 5.12
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
Item {
|
||||
id: qmlMap
|
||||
property int aircraftZoomLevel: 11
|
||||
property int airportZoomLevel: 11
|
||||
property string mapProvider: "osm"
|
||||
property variant mapPtr
|
||||
property string requestedMapType
|
||||
property bool lightIcons
|
||||
property variant guiPtr
|
||||
|
||||
Plugin {
|
||||
id: mapPlugin
|
||||
name: "osm"
|
||||
function createMap(pluginParameters, requestedMap, gui) {
|
||||
requestedMapType = requestedMap
|
||||
guiPtr = gui
|
||||
|
||||
var paramString = ""
|
||||
for (var prop in pluginParameters) {
|
||||
var parameter = 'PluginParameter { name: "' + prop + '"; value: "' + pluginParameters[prop] + '"}'
|
||||
paramString = paramString + parameter
|
||||
}
|
||||
var pluginString = 'import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; ' + paramString + '}'
|
||||
var plugin = Qt.createQmlObject (pluginString, qmlMap)
|
||||
|
||||
if (mapPtr) {
|
||||
// Objects aren't destroyed immediately, so don't call findChild("map")
|
||||
mapPtr.destroy()
|
||||
mapPtr = null
|
||||
}
|
||||
mapPtr = actualMapComponent.createObject(page)
|
||||
mapPtr.plugin = plugin
|
||||
mapPtr.forceActiveFocus()
|
||||
return mapPtr
|
||||
}
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: actualMapComponent
|
||||
|
||||
Map {
|
||||
id: map
|
||||
objectName: "map"
|
||||
anchors.fill: parent
|
||||
plugin: mapPlugin
|
||||
center: QtPositioning.coordinate(51.5, 0.125) // London
|
||||
zoomLevel: 10
|
||||
|
||||
// Needs to come first, otherwise MouseAreas in the MapItemViews don't get clicked event first
|
||||
// Setting z doesn't seem to work
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// Unhighlight current aircraft
|
||||
guiPtr.clearHighlighted()
|
||||
}
|
||||
}
|
||||
|
||||
MapStation {
|
||||
id: station
|
||||
objectName: "station"
|
||||
stationName: "Home"
|
||||
coordinate: QtPositioning.coordinate(51.5, 0.125)
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: airspaceModel
|
||||
delegate: airspaceComponent
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: navAidModel
|
||||
delegate: navAidComponent
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: airspaceModel
|
||||
delegate: airspaceNameComponent
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: airportModel
|
||||
delegate: airportComponent
|
||||
}
|
||||
|
||||
// This needs to be before aircraftComponent MapItemView, so it's drawn underneath
|
||||
@ -34,11 +94,6 @@ Item {
|
||||
delegate: aircraftPathComponent
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: airportModel
|
||||
delegate: airportComponent
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: aircraftModel
|
||||
delegate: aircraftComponent
|
||||
@ -56,6 +111,108 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
onSupportedMapTypesChanged : {
|
||||
for (var i = 0; i < supportedMapTypes.length; i++) {
|
||||
if (requestedMapType == supportedMapTypes[i].name) {
|
||||
activeMapType = supportedMapTypes[i]
|
||||
}
|
||||
}
|
||||
lightIcons = requestedMapType == "Night Transit Map"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: navAidComponent
|
||||
MapQuickItem {
|
||||
id: navAid
|
||||
anchorPoint.x: image.width/2
|
||||
anchorPoint.y: image.height/2
|
||||
coordinate: position
|
||||
zoomLevel: airportZoomLevel
|
||||
|
||||
sourceItem: Grid {
|
||||
columns: 1
|
||||
Grid {
|
||||
horizontalItemAlignment: Grid.AlignHCenter
|
||||
columnSpacing: 5
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
Image {
|
||||
id: image
|
||||
source: navAidImage
|
||||
visible: !lightIcons
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: (mouse) => {
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
}
|
||||
ColorOverlay {
|
||||
cached: true
|
||||
width: image.width
|
||||
height: image.height
|
||||
source: image
|
||||
color: "#c0ffffff"
|
||||
visible: lightIcons
|
||||
}
|
||||
Rectangle {
|
||||
id: bubble
|
||||
color: bubbleColour
|
||||
border.width: 1
|
||||
width: text.width + 5
|
||||
height: text.height + 5
|
||||
radius: 5
|
||||
Text {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
text: navAidData
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: (mouse) => {
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: airspaceComponent
|
||||
MapPolygon {
|
||||
border.width: 1
|
||||
border.color: airspaceBorderColor
|
||||
color: airspaceFillColor
|
||||
path: airspacePolygon
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: airspaceNameComponent
|
||||
MapQuickItem {
|
||||
coordinate: position
|
||||
anchorPoint.x: airspaceText.width/2
|
||||
anchorPoint.y: airspaceText.height/2
|
||||
zoomLevel: airportZoomLevel
|
||||
sourceItem: Grid {
|
||||
columns: 1
|
||||
Grid {
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
horizontalItemAlignment: Grid.AlignHCenter
|
||||
Text {
|
||||
id: airspaceText
|
||||
text: details
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
@ -86,9 +243,27 @@ Item {
|
||||
id: image
|
||||
rotation: heading
|
||||
source: aircraftImage
|
||||
visible: !lightIcons
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
highlighted = true
|
||||
}
|
||||
onDoubleClicked: {
|
||||
target = true
|
||||
}
|
||||
}
|
||||
}
|
||||
ColorOverlay {
|
||||
cached: true
|
||||
width: image.width
|
||||
height: image.height
|
||||
rotation: heading
|
||||
source: image
|
||||
color: "#c0ffffff"
|
||||
visible: lightIcons
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
highlighted = true
|
||||
}
|
||||
@ -111,7 +286,6 @@ Item {
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
showAll = !showAll
|
||||
}
|
||||
@ -140,6 +314,15 @@ Item {
|
||||
Image {
|
||||
id: image
|
||||
source: airportImage
|
||||
visible: !lightIcons
|
||||
}
|
||||
ColorOverlay {
|
||||
cached: true
|
||||
width: image.width
|
||||
height: image.height
|
||||
source: image
|
||||
color: "#c0ffffff"
|
||||
visible: lightIcons
|
||||
}
|
||||
Rectangle {
|
||||
id: bubble
|
||||
@ -155,7 +338,6 @@ Item {
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: (mouse) => {
|
||||
if (showFreq) {
|
||||
var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
|
||||
|
@ -31,8 +31,8 @@
|
||||
#include "util/csv.h"
|
||||
#include "adsbdemodsettings.h"
|
||||
|
||||
#define AIRPORTS_URL "https://ourairports.com/data/airports.csv"
|
||||
#define AIRPORT_FREQUENCIES_URL "https://ourairports.com/data/airport-frequencies.csv"
|
||||
#define AIRPORTS_URL "https://davidmegginson.github.io/ourairports-data/airports.csv"
|
||||
#define AIRPORT_FREQUENCIES_URL "https://davidmegginson.github.io/ourairports-data/airport-frequencies.csv"
|
||||
|
||||
struct AirportInformation {
|
||||
|
||||
|
@ -4,10 +4,14 @@
|
||||
|
||||
The ADS-B demodulator plugin can be used to receive and display ADS-B aircraft information. This is information about an aircraft, such as position, altitude, heading and speed, broadcast by aircraft on 1090MHz, in the 1090ES (Extended Squitter) format. 1090ES frames have a chip rate of 2Mchip/s, so the baseband sample rate should be set to be greater than 2MSa/s.
|
||||
|
||||
<h2>Interface</h2>
|
||||
As well as displaying information received via ADS-B, the plugin can also combine information from a number of databases to display more information about the aircraft and flight.
|
||||
|
||||
![ADS-B Demodulator plugin GUI](../../../doc/img/ADSBDemod_plugin.png)
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||
![ADS-B Demodulator plugin settings](../../../doc/img/ADSBDemod_plugin_settings.png)
|
||||
|
||||
<h3>1: Frequency shift from center frequency of reception value</h3>
|
||||
|
||||
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit.
|
||||
@ -48,20 +52,29 @@ This sets the correlation threshold in dB between the received signal and expect
|
||||
|
||||
<h3>9: Download Opensky-Network Aircraft Database</h3>
|
||||
|
||||
Clicking this will download the Opensky-Network (https://opensky-network.org/) aircraft database. This database contains information about aircrafts, such as registration, aircraft model and owner details, that is not broadcast via ADS-B. Once downloaded, this additional information will be displayed in the table alongside the ADS-B data. The database should only need to be downloaded once, as it is saved to disk, and it is recommended to download it before enabling the demodulator.
|
||||
Clicking this will download the [Opensky-Network](https://opensky-network.org/) aircraft database. This database contains information about aircrafts, such as registration, aircraft model and owner details, that is not broadcast via ADS-B. Once downloaded, this additional information will be displayed in the table alongside the ADS-B data. The database should only need to be downloaded once, as it is saved to disk, and it is recommended to download it before enabling the demodulator.
|
||||
|
||||
<h3>10: Download OurAirports Airport Databases</h3>
|
||||
|
||||
Clicking this will download the OurAirports (https://ourairports.com/) airport databases. These contains names and locations for airports allowing them to be drawn on the map, as well as their corresponding ATC frequencies, which can also be displayed next to the airport on the map, by clicking the airport name. The size of airports that will be displayed on the map, and whether heliports are displayed, can be set in the Display Settings dialog.
|
||||
Clicking this will download the [OurAirports](https://ourairports.com/) airport databases. These contains names and locations for airports allowing them to be drawn on the map, as well as their corresponding ATC frequencies, which can also be displayed next to the airport on the map, by clicking the airport name. The size of airports that will be displayed on the map, and whether heliports are displayed, can be set in the Display Settings dialog.
|
||||
|
||||
<h3>11: Display Settings</h3>
|
||||
<h3>11: Download OpenAIP Airspace and NAVAID Databases</h3>
|
||||
|
||||
Clicking this will download the [OpenAIP](https://www.openaip.net/) airspace and NAVAID databases. These can be displayed on the map. The airspace categories to be displayed can individually be selected in the Display Settings dialog.
|
||||
|
||||
<h3>12: Display Settings</h3>
|
||||
|
||||
Clicking the Display Settings button will open the Display Settings dialog, which allows you to choose:
|
||||
|
||||
* The units for altitude, speed and vertical climb rate. These can be either ft (feet), kn (knots) and ft/min (feet per minute), or m (metres), kph (kilometers per hour) and m/s (metres per second).
|
||||
* The type of map that will be displayed. This can either be a light or dark aviation map (with no place names to reduce clutter), a street map or satellite imagery.
|
||||
* The minimum size airport that will be displayed on the map: small, medium or large. Use small to display GA airfields, medium for regional airports and large for international airports.
|
||||
* Whether or not to display heliports.
|
||||
* The distance (in kilometres), from the location set under Preferences > My Position, at which airports will be displayed on the map.
|
||||
* The distance (in kilometres), from the location set under Preferences > My Position, at which airports will be displayed on the map. Displaying too many airports will slow down drawing of the map.
|
||||
* What category of airspaces should be displayed.
|
||||
* The distance (in kilometres), from the location set under Preferences > My Position, at which airspaces will be displayed on the map. Displaying too many airspaces will slow down drawing of the map.
|
||||
* Whether NAVAIDs, such as VORs, are displayed on the map.
|
||||
* Whether aircraft photos are displayed for the highlighted aircraft.
|
||||
* The timeout, in seconds, after which an aircraft will be removed from the table and map, if an ADS-B frame has not been received from it.
|
||||
* The font used for the table.
|
||||
* Whether demodulator statistics are displayed (primarily an option for developers).
|
||||
@ -69,11 +82,25 @@ Clicking the Display Settings button will open the Display Settings dialog, whic
|
||||
|
||||
You can also enter an [avaiationstack](https://aviationstack.com/product) API key, needed to download flight information (such as departure and arrival airports and times).
|
||||
|
||||
<h3>12: Display Flight Paths</h3>
|
||||
![ADS-B Demodulator display settings](../../../doc/img/ADSBDemod_plugin_displaysettings.png)
|
||||
|
||||
Checking this button draws a line on the map showing aircraft's flight paths, as determined from received ADS-B frames.
|
||||
<h3>13: Display Flight Path</h3>
|
||||
|
||||
<h3>13: Feed</h3>
|
||||
Checking this button draws a line on the map showing the highlighted aircraft's flight paths, as determined from received ADS-B frames.
|
||||
|
||||
<h3>14: Display Flight All Paths</h3>
|
||||
|
||||
Checking this button draws flight paths for all aircraft.
|
||||
|
||||
<h3>15: Download flight information for selected flight</h3>
|
||||
|
||||
When clicked, flight information (departure and arrival airport and times) is downloaded for the aircraft highlighted in the ADS-B data table using the aviationstack.com API.
|
||||
To be able to use this, a callsign for the highlighted aircraft must have been received. Also, the callsign must be mappable to a flight number, which is not always possible (this is typically
|
||||
the case for callsigns that end in two characters, as for these, some digits from the flight number will have been omitted).
|
||||
|
||||
To use this feature, an [aviationstack](https://aviationstack.com) API Key must be entered in the Display Settings dialog (12). A free key giving 500 API calls per month is [available](https://aviationstack.com/product).
|
||||
|
||||
<h3>16: Feed</h3>
|
||||
|
||||
Checking Feed enables feeding received ADS-B frames to aggregators such as ADS-B Exchange: https://www.adsbexchange.com or ADSBHub
|
||||
: https://www.adsbhub.org. Right clicking on the Feed button opens the Feed Settings dialog.
|
||||
@ -85,7 +112,7 @@ The server hostname and port to send the frames to should be entered in the Serv
|
||||
|
||||
The Beast binary and Hex formats are as detailed here: https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats
|
||||
|
||||
<h3>Open Notifications Dialog</h3>
|
||||
<h3>17: Open Notifications Dialog</h3>
|
||||
|
||||
When clicked, opens the Notifications Dialog, which allows speech notifications or programs/scripts to be run when aircraft matching user-defined rules are seen.
|
||||
|
||||
@ -159,31 +186,19 @@ In the Speech and Command strings, variables can be used to substitute in data f
|
||||
* ${eta}
|
||||
* ${ata}
|
||||
|
||||
<h3>Download flight information for selected flight</h3>
|
||||
|
||||
When clicked, flight information (departure and arrival airport and times) is downloaded for the aircraft highlighted in the ADS-B data table using the aviationstack.com API.
|
||||
To be able to use this, a callsign for the highlighted aircraft must have been received. Also, the callsign must be mappable to a flight number, which is not always possible (this is typically
|
||||
the case for callsigns that end in two characters, as for these, some digits from the flight number will have been omitted).
|
||||
|
||||
To use this feature, an (aviationstack)[aviationstack.com] API Key must be entered in the Display Settings dialog (11). A free key giving 500 API calls per month is (available)[https://aviationstack.com/product].
|
||||
|
||||
<h3>Start/stop Logging ADS-B frames to .csv File</h3>
|
||||
<h3>18: Start/stop Logging ADS-B frames to .csv File</h3>
|
||||
|
||||
When checked, writes all received ADS-B frames to a .csv file.
|
||||
|
||||
<h3>.csv Log Filename</h3>
|
||||
<h3>19: .csv Log Filename</h3>
|
||||
|
||||
Click to specify the name of the .csv file which received ADS-B frames are logged to.
|
||||
|
||||
<h3>Read Data from .csv File</h3>
|
||||
<h3>20: Read Data from .csv File</h3>
|
||||
|
||||
Click to specify a previously written ADS-B .csv log file, which is read and used to update the ADS-B data table and map.
|
||||
|
||||
<h3>14: Refresh list of devices</h3>
|
||||
|
||||
Use this button to refresh the list of devices.
|
||||
|
||||
<h3>15: Select device set</h3>
|
||||
<h3>21: Select device set</h3>
|
||||
|
||||
Specify the SDRangel device set that will be have its centre frequency set when an airport ATC frequency is clicked on the map. Typically, this device set would be a second SDR (as ATC frequencies are around 120MHz, so they can not be received simultaneously with 1090MHz for ADS-B) and have an AM Demodulator channel plugin.
|
||||
|
||||
@ -240,10 +255,12 @@ If an ADS-B frame has not been received from an aircraft for 60 seconds, the air
|
||||
|
||||
<h2>Map</h2>
|
||||
|
||||
The map displays aircraft locations and data geographically.
|
||||
The map displays aircraft locations and data geographically. Four types of map can be chosen from in the Display Settings dialog: Aviation, Avation (Dark), Street and Satellite.
|
||||
|
||||
![ADS-B Demodulator Map](../../../doc/img/ADSBDemod_plugin_map.png)
|
||||
|
||||
![ADS-B Demodulator Map](../../../doc/img/ADSBDemod_plugin_map2.png)
|
||||
|
||||
The initial antenna location is placed according to My Position set under the Preferences > My Position menu. The position is only updated when the ADS-B demodulator plugin is first opened.
|
||||
|
||||
Aircraft are only placed upon the map when a position can be calculated, which can require several frames to be received.
|
||||
@ -259,3 +276,5 @@ Aircraft are only placed upon the map when a position can be calculated, which c
|
||||
Airline logos and flags are by Steve Hibberd from https://radarspotting.com
|
||||
|
||||
Map icons are by Alice Design, Alex Ahineev, Botho Willer, Verry Obito, Sean Maldjia, Tinashe Mugayi, Georgiana Ionescu, Andreas Vögele, Tom Fricker, Will Sullivan, Tim Tores, BGBOXXX Design, and Angriawan Ditya Zulkarnain from the Noun Project https://thenounproject.com/
|
||||
|
||||
NDB icon is by Inductiveload from WikiMedia.
|
||||
|
@ -81,9 +81,13 @@ When clicked, opens the Map Display Settings dialog, which allows setting:
|
||||
* Which data the Map will display.
|
||||
* The colour of the taken and predicted tracks.
|
||||
* Which Map provider will be used to source the map image.
|
||||
* API keys, required to access maps from different providers.
|
||||
|
||||
In order to display Mapbox maps, you will need to enter an API Key. A key can be obtained by registering at: http://www.mapbox.com/
|
||||
Note that it is not currently possible to support entering an API Key for Open Street Maps, in order to remove the watermarks.
|
||||
Free API keys are available by signing up for an accounts with:
|
||||
|
||||
* [Thunderforest](https://www.thunderforest.com/)
|
||||
* [Maptiler](https://www.maptiler.com/)
|
||||
* [Mapbox](https://www.mapbox.com/)
|
||||
|
||||
<h3>Map</h3>
|
||||
|
||||
|
@ -199,6 +199,8 @@ set(sdrbase_SOURCES
|
||||
util/message.cpp
|
||||
util/messagequeue.cpp
|
||||
util/morse.cpp
|
||||
util/openaip.cpp
|
||||
util/planespotters.cpp
|
||||
util/png.cpp
|
||||
util/prettyprint.cpp
|
||||
util/rtpsink.cpp
|
||||
@ -410,6 +412,8 @@ set(sdrbase_HEADERS
|
||||
util/messagequeue.h
|
||||
util/morse.h
|
||||
util/movingaverage.h
|
||||
util/openaip.h
|
||||
util/planespotters.h
|
||||
util/png.h
|
||||
util/prettyprint.h
|
||||
util/rtpsink.h
|
||||
|
@ -72,11 +72,6 @@ void AviationStack::getFlightInformation(const QString& flight)
|
||||
url.setQuery(query);
|
||||
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
/*QFile file("flight.json");
|
||||
if (file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
parseJson(file.readAll());
|
||||
}*/
|
||||
}
|
||||
|
||||
void AviationStack::handleReply(QNetworkReply* reply)
|
||||
|
422
sdrbase/util/openaip.cpp
Normal file
@ -0,0 +1,422 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "openaip.h"
|
||||
|
||||
const QStringList OpenAIP::m_countryCodes = {
|
||||
"ad",
|
||||
"ae",
|
||||
"af",
|
||||
"ag",
|
||||
"ai",
|
||||
"al",
|
||||
"am",
|
||||
"an",
|
||||
"ao",
|
||||
"aq",
|
||||
"ar",
|
||||
"as",
|
||||
"at",
|
||||
"au",
|
||||
"aw",
|
||||
"ax",
|
||||
"az",
|
||||
"ba",
|
||||
"bb",
|
||||
"bd",
|
||||
"be",
|
||||
"bf",
|
||||
"bg",
|
||||
"bh",
|
||||
"bi",
|
||||
"bj",
|
||||
"bl",
|
||||
"bm",
|
||||
"bn",
|
||||
"bo",
|
||||
"bq",
|
||||
"br",
|
||||
"bs",
|
||||
"bt",
|
||||
"bv",
|
||||
"bw",
|
||||
"by",
|
||||
"bz",
|
||||
"ca",
|
||||
"cc",
|
||||
"cd",
|
||||
"cf",
|
||||
"cg",
|
||||
"ch",
|
||||
"ci",
|
||||
"ck",
|
||||
"cl",
|
||||
"cm",
|
||||
"cn",
|
||||
"co",
|
||||
"cr",
|
||||
"cu",
|
||||
"cv",
|
||||
"cw",
|
||||
"cx",
|
||||
"cy",
|
||||
"cz",
|
||||
"de",
|
||||
"dj",
|
||||
"dk",
|
||||
"dm",
|
||||
"do",
|
||||
"dz",
|
||||
"ec",
|
||||
"ee",
|
||||
"eg",
|
||||
"eh",
|
||||
"er",
|
||||
"es",
|
||||
"et",
|
||||
"fi",
|
||||
"fj",
|
||||
"fk",
|
||||
"fm",
|
||||
"fo",
|
||||
"fr",
|
||||
"ga",
|
||||
"gb",
|
||||
"ge",
|
||||
"gf",
|
||||
"gg",
|
||||
"gh",
|
||||
"gi",
|
||||
"gl",
|
||||
"gm",
|
||||
"gn",
|
||||
"gp",
|
||||
"gq",
|
||||
"gr",
|
||||
"gs",
|
||||
"gt",
|
||||
"gu",
|
||||
"gw",
|
||||
"gy",
|
||||
"hk",
|
||||
"hm",
|
||||
"hn",
|
||||
"hr",
|
||||
"hu",
|
||||
"id",
|
||||
"ie",
|
||||
"il",
|
||||
"im",
|
||||
"in",
|
||||
"io",
|
||||
"iq",
|
||||
"ir",
|
||||
"is",
|
||||
"it",
|
||||
"je",
|
||||
"jm",
|
||||
"jo",
|
||||
"jp",
|
||||
"ke",
|
||||
"kg",
|
||||
"kh",
|
||||
"ki",
|
||||
"km",
|
||||
"kn",
|
||||
"kp",
|
||||
"kr",
|
||||
"kw",
|
||||
"ky",
|
||||
"kz",
|
||||
"la",
|
||||
"lb",
|
||||
"lc",
|
||||
"li",
|
||||
"lk",
|
||||
"lr",
|
||||
"ls",
|
||||
"lt",
|
||||
"lu",
|
||||
"lv",
|
||||
"ly",
|
||||
"ma",
|
||||
"mc",
|
||||
"md",
|
||||
"me",
|
||||
"mf",
|
||||
"mg",
|
||||
"mh",
|
||||
"mk",
|
||||
"ml",
|
||||
"mm",
|
||||
"mn",
|
||||
"mo",
|
||||
"mp",
|
||||
"mq",
|
||||
"mr",
|
||||
"ms",
|
||||
"mt",
|
||||
"mu",
|
||||
"mv",
|
||||
"mw",
|
||||
"mx",
|
||||
"my",
|
||||
"mz",
|
||||
"na",
|
||||
"nc",
|
||||
"ne",
|
||||
"nf",
|
||||
"ng",
|
||||
"ni",
|
||||
"nl",
|
||||
"no",
|
||||
"np",
|
||||
"nr",
|
||||
"nu",
|
||||
"nz",
|
||||
"om",
|
||||
"pa",
|
||||
"pe",
|
||||
"pf",
|
||||
"pg",
|
||||
"ph",
|
||||
"pk",
|
||||
"pl",
|
||||
"pm",
|
||||
"pn",
|
||||
"pr",
|
||||
"ps",
|
||||
"pt",
|
||||
"pw",
|
||||
"py",
|
||||
"qa",
|
||||
"re",
|
||||
"ro",
|
||||
"rs",
|
||||
"ru",
|
||||
"rw",
|
||||
"sa",
|
||||
"sb",
|
||||
"sc",
|
||||
"sd",
|
||||
"se",
|
||||
"sg",
|
||||
"sh",
|
||||
"si",
|
||||
"sj",
|
||||
"sk",
|
||||
"sl",
|
||||
"sm",
|
||||
"sn",
|
||||
"so",
|
||||
"sr",
|
||||
"ss",
|
||||
"st",
|
||||
"sv",
|
||||
"sx",
|
||||
"sy",
|
||||
"sz",
|
||||
"tc",
|
||||
"td",
|
||||
"tf",
|
||||
"tg",
|
||||
"th",
|
||||
"tj",
|
||||
"tk",
|
||||
"tl",
|
||||
"tm",
|
||||
"tn",
|
||||
"to",
|
||||
"tr",
|
||||
"tt",
|
||||
"tv",
|
||||
"tw",
|
||||
"tz",
|
||||
"ua",
|
||||
"ug",
|
||||
"um",
|
||||
"us",
|
||||
"uy",
|
||||
"uz",
|
||||
"va",
|
||||
"vc",
|
||||
"ve",
|
||||
"vg",
|
||||
"vi",
|
||||
"vn",
|
||||
"vu",
|
||||
"wf",
|
||||
"ws",
|
||||
"ye",
|
||||
"yt",
|
||||
"za",
|
||||
"zm",
|
||||
"zw"
|
||||
};
|
||||
|
||||
OpenAIP::OpenAIP(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished);
|
||||
}
|
||||
|
||||
OpenAIP::~OpenAIP()
|
||||
{
|
||||
disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished);
|
||||
}
|
||||
|
||||
QString OpenAIP::getDataDir()
|
||||
{
|
||||
// Get directory to store app data in
|
||||
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
// First dir is writable
|
||||
return locations[0];
|
||||
}
|
||||
|
||||
QString OpenAIP::getAirspaceFilename(int i)
|
||||
{
|
||||
return getAirspaceFilename(m_countryCodes[i]);
|
||||
}
|
||||
|
||||
QString OpenAIP::getAirspaceFilename(const QString& countryCode)
|
||||
{
|
||||
return getDataDir() + "/" + countryCode + "_asp.xml";
|
||||
}
|
||||
|
||||
QString OpenAIP::getAirspaceURL(int i)
|
||||
{
|
||||
if (i < m_countryCodes.size()) {
|
||||
return QString(OPENAIP_AIRSPACE_URL).arg(m_countryCodes[i]);
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenAIP::downloadAirspaces()
|
||||
{
|
||||
m_countryIndex = 0;
|
||||
downloadAirspace();
|
||||
}
|
||||
|
||||
void OpenAIP::downloadAirspace()
|
||||
{
|
||||
QString filename = getAirspaceFilename(m_countryIndex);
|
||||
QString urlString = getAirspaceURL(m_countryIndex);
|
||||
QUrl dbURL(urlString);
|
||||
qDebug() << "OpenAIP::downloadAirspace: Downloading " << urlString;
|
||||
emit downloadingURL(urlString);
|
||||
m_dlm.download(dbURL, filename);
|
||||
}
|
||||
|
||||
QString OpenAIP::getNavAidsFilename(int i)
|
||||
{
|
||||
return getNavAidsFilename(m_countryCodes[i]);
|
||||
}
|
||||
|
||||
QString OpenAIP::getNavAidsFilename(const QString& countryCode)
|
||||
{
|
||||
return getDataDir() + "/" + countryCode + "_nav.xml";
|
||||
}
|
||||
|
||||
QString OpenAIP::getNavAidsURL(int i)
|
||||
{
|
||||
if (i < m_countryCodes.size()) {
|
||||
return QString(OPENAIP_NAVAIDS_URL).arg(m_countryCodes[i]);
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenAIP::downloadNavAids()
|
||||
{
|
||||
m_countryIndex = 0;
|
||||
downloadNavAid();
|
||||
}
|
||||
|
||||
void OpenAIP::downloadNavAid()
|
||||
{
|
||||
QString filename = getNavAidsFilename(m_countryIndex);
|
||||
QString urlString = getNavAidsURL(m_countryIndex);
|
||||
QUrl dbURL(urlString);
|
||||
qDebug() << "OpenAIP::downloadNavAid: Downloading " << urlString;
|
||||
emit downloadingURL(urlString);
|
||||
m_dlm.download(dbURL, filename);
|
||||
}
|
||||
|
||||
void OpenAIP::downloadFinished(const QString& filename, bool success)
|
||||
{
|
||||
// Not all countries have corresponding files, so we should expect some errors
|
||||
if (!success) {
|
||||
qDebug() << "OpenAIP::downloadFinished: Failed: " << filename;
|
||||
}
|
||||
|
||||
if (filename == getNavAidsFilename(m_countryIndex))
|
||||
{
|
||||
m_countryIndex++;
|
||||
if (m_countryIndex < m_countryCodes.size()) {
|
||||
downloadNavAid();
|
||||
} else {
|
||||
emit downloadNavAidsFinished();
|
||||
}
|
||||
}
|
||||
else if (filename == getAirspaceFilename(m_countryIndex))
|
||||
{
|
||||
m_countryIndex++;
|
||||
if (m_countryIndex < m_countryCodes.size()) {
|
||||
downloadAirspace();
|
||||
} else {
|
||||
emit downloadAirspaceFinished();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "OpenAIP::downloadFinished: Unexpected filename: " << filename;
|
||||
emit downloadError(QString("Unexpected filename: %1").arg(filename));
|
||||
}
|
||||
}
|
||||
|
||||
// Read airspaces for all countries
|
||||
QList<Airspace *> OpenAIP::readAirspaces()
|
||||
{
|
||||
QList<Airspace *> airspaces;
|
||||
for (const auto& countryCode : m_countryCodes) {
|
||||
airspaces.append(readAirspaces(countryCode));
|
||||
}
|
||||
return airspaces;
|
||||
}
|
||||
|
||||
// Read airspaces for a single country
|
||||
QList<Airspace *> OpenAIP::readAirspaces(const QString& countryCode)
|
||||
{
|
||||
return Airspace::readXML(getAirspaceFilename(countryCode));
|
||||
}
|
||||
|
||||
// Read NavAids for all countries
|
||||
QList<NavAid *> OpenAIP::readNavAids()
|
||||
{
|
||||
QList<NavAid *> navAids;
|
||||
for (const auto& countryCode : m_countryCodes) {
|
||||
navAids.append(readNavAids(countryCode));
|
||||
}
|
||||
return navAids;
|
||||
}
|
||||
|
||||
// Read NavAids for a single country
|
||||
QList<NavAid *> OpenAIP::readNavAids(const QString& countryCode)
|
||||
{
|
||||
return NavAid::readXML(getNavAidsFilename(countryCode));
|
||||
}
|
433
sdrbase/util/openaip.h
Normal file
@ -0,0 +1,433 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_OPENAIP_H
|
||||
#define INCLUDE_OPENAIP_H
|
||||
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QDebug>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QPointF>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include "util/units.h"
|
||||
#include "util/httpdownloadmanager.h"
|
||||
|
||||
// Server is moving, use new URL and .xml extension instead of .aip
|
||||
//#define OPENAIP_AIRSPACE_URL "https://www.openaip.net/customer_export_akfshb9237tgwiuvb4tgiwbf/%1_asp.aip"
|
||||
//#define OPENAIP_NAVAIDS_URL "https://www.openaip.net/customer_export_akfshb9237tgwiuvb4tgiwbf/%1_nav.aip"
|
||||
#define OPENAIP_AIRSPACE_URL "https://storage.googleapis.com/29f98e10-a489-4c82-ae5e-489dbcd4912f/%1_asp.xml"
|
||||
#define OPENAIP_NAVAIDS_URL "https://storage.googleapis.com/29f98e10-a489-4c82-ae5e-489dbcd4912f/%1_nav.xml"
|
||||
|
||||
struct SDRBASE_API Airspace {
|
||||
|
||||
struct AltLimit {
|
||||
QString m_reference; // STD, MSL
|
||||
int m_alt; // Altitude
|
||||
QString m_altUnit; // FL (Flight level) or F (Feet)
|
||||
};
|
||||
|
||||
QString m_category; // A-G, GLIDING, DANGER, PROHIBITED, TMZ
|
||||
int m_id;
|
||||
QString m_country; // GB
|
||||
QString m_name; // BIGGIN HILL ATZ 129.405 - TODO: Extract frequency so we can tune to it
|
||||
AltLimit m_top; // Top of airspace
|
||||
AltLimit m_bottom; // Bottom of airspace
|
||||
QVector<QPointF> m_polygon;
|
||||
QPointF m_center; // Center of polygon
|
||||
QPointF m_position; // Position for text (not at the center, otherwise it will clash with airport)
|
||||
|
||||
void calculatePosition()
|
||||
{
|
||||
qreal minX, maxX;
|
||||
qreal minY, maxY;
|
||||
if (m_polygon.size() > 0)
|
||||
{
|
||||
minX = maxX = m_polygon[0].x();
|
||||
minY = maxY = m_polygon[0].y();
|
||||
for (int i = 1; i < m_polygon.size(); i++)
|
||||
{
|
||||
qreal x = m_polygon[i].x();
|
||||
qreal y = m_polygon[i].y();
|
||||
minX = std::min(minX, x);
|
||||
maxX = std::max(maxX, x);
|
||||
minY = std::min(minY, y);
|
||||
maxY = std::max(maxY, y);
|
||||
}
|
||||
m_center.setX(minX + (maxX - minX) / 2.0);
|
||||
m_center.setY(minY + (maxY - minY) * 2.0);
|
||||
m_position.setX(minX + (maxX - minX) / 2.0);
|
||||
m_position.setY(minY + (maxY - minY) * 3.0 / 4.0);
|
||||
}
|
||||
}
|
||||
|
||||
QString getAlt(const AltLimit *altlimit) const
|
||||
{
|
||||
// Format on UK charts
|
||||
if (altlimit->m_alt == 0) {
|
||||
return "SFC"; // Surface
|
||||
} if (altlimit->m_altUnit == "FL") {
|
||||
return QString("FL%1").arg(altlimit->m_alt);
|
||||
} else if (altlimit->m_altUnit == "F") {
|
||||
return QString("%1'").arg(altlimit->m_alt);
|
||||
} else {
|
||||
return QString("%1 %2").arg(altlimit->m_alt).arg(altlimit->m_altUnit);
|
||||
}
|
||||
}
|
||||
|
||||
double heightInMetres(const AltLimit *altlimit) const
|
||||
{
|
||||
if (altlimit->m_altUnit == "FL") {
|
||||
return Units::feetToMetres(altlimit->m_alt * 100);
|
||||
} else if (altlimit->m_altUnit == "F") {
|
||||
return Units::feetToMetres(altlimit->m_alt);
|
||||
} else {
|
||||
return altlimit->m_alt;
|
||||
}
|
||||
}
|
||||
|
||||
double topHeightInMetres() const
|
||||
{
|
||||
return heightInMetres(&m_top);
|
||||
}
|
||||
|
||||
double bottomHeightInMetres() const
|
||||
{
|
||||
return heightInMetres(&m_bottom);
|
||||
}
|
||||
|
||||
// Read OpenAIP XML file
|
||||
static QList<Airspace *> readXML(const QString &filename)
|
||||
{
|
||||
QList<Airspace *> airspaces;
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QXmlStreamReader xmlReader(&file);
|
||||
|
||||
while(!xmlReader.atEnd() && !xmlReader.hasError())
|
||||
{
|
||||
if (xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == "ASP")
|
||||
{
|
||||
Airspace *airspace = new Airspace();
|
||||
|
||||
airspace->m_category = xmlReader.attributes().value("CATEGORY").toString();
|
||||
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == QLatin1String("ID"))
|
||||
{
|
||||
airspace->m_id = xmlReader.readElementText().toInt();
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("COUNTRY"))
|
||||
{
|
||||
airspace->m_country = xmlReader.readElementText();
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("NAME"))
|
||||
{
|
||||
airspace->m_name = xmlReader.readElementText();
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("ALTLIMIT_TOP"))
|
||||
{
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
airspace->m_top.m_reference = xmlReader.attributes().value("REFERENCE").toString();
|
||||
airspace->m_top.m_altUnit = xmlReader.attributes().value("UNIT").toString();
|
||||
if (xmlReader.name() == QLatin1String("ALT"))
|
||||
{
|
||||
airspace->m_top.m_alt = xmlReader.readElementText().toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("ALTLIMIT_BOTTOM"))
|
||||
{
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
airspace->m_bottom.m_reference = xmlReader.attributes().value("REFERENCE").toString();
|
||||
airspace->m_bottom.m_altUnit = xmlReader.attributes().value("UNIT").toString();
|
||||
if (xmlReader.name() == QLatin1String("ALT"))
|
||||
{
|
||||
airspace->m_bottom.m_alt = xmlReader.readElementText().toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("GEOMETRY"))
|
||||
{
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == QLatin1String("POLYGON"))
|
||||
{
|
||||
QString pointsString = xmlReader.readElementText();
|
||||
QStringList points = pointsString.split(",");
|
||||
for (const auto& ps : points)
|
||||
{
|
||||
QStringList split = ps.trimmed().split(" ");
|
||||
if (split.size() == 2)
|
||||
{
|
||||
QPointF pf(split[0].toDouble(), split[1].toDouble());
|
||||
airspace->m_polygon.append(pf);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Airspace::readXML - Unexpected polygon point format: " << ps;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
airspace->calculatePosition();
|
||||
//qDebug() << "Adding airspace: " << airspace->m_name << " " << airspace->m_category;
|
||||
airspaces.append(airspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't warn, as many countries don't have files
|
||||
//qDebug() << "Airspace::readXML: Could not open " << filename << " for reading.";
|
||||
}
|
||||
return airspaces;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct SDRBASE_API NavAid {
|
||||
|
||||
QString m_ident; // 2 or 3 character ident
|
||||
QString m_type; // NDB, VOR, VOR-DME or VORTAC
|
||||
QString m_name;
|
||||
float m_latitude;
|
||||
float m_longitude;
|
||||
float m_elevation;
|
||||
float m_frequencykHz;
|
||||
QString m_channel;
|
||||
int m_range; // Nautical miles
|
||||
float m_magneticDeclination;
|
||||
bool m_alignedTrueNorth; // Is the VOR aligned to true North, rather than magnetic (may be the case at high latitudes)
|
||||
|
||||
int getRangeMetres() const
|
||||
{
|
||||
return Units::nauticalMilesToIntegerMetres((float)m_range);
|
||||
}
|
||||
|
||||
// OpenAIP XML file
|
||||
static QList<NavAid *> readXML(const QString &filename)
|
||||
{
|
||||
QList<NavAid *> navAidInfo;
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QXmlStreamReader xmlReader(&file);
|
||||
|
||||
while(!xmlReader.atEnd() && !xmlReader.hasError())
|
||||
{
|
||||
if (xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == "NAVAID")
|
||||
{
|
||||
QStringRef typeRef = xmlReader.attributes().value("TYPE");
|
||||
if ((typeRef == QLatin1String("NDB"))
|
||||
|| (typeRef == QLatin1String("DME"))
|
||||
|| (typeRef == QLatin1String("VOR"))
|
||||
|| (typeRef== QLatin1String("VOR-DME"))
|
||||
|| (typeRef == QLatin1String("VORTAC")))
|
||||
{
|
||||
QString type = typeRef.toString();
|
||||
QString name;
|
||||
QString id;
|
||||
float lat = 0.0f;
|
||||
float lon = 0.0f;
|
||||
float elevation = 0.0f;
|
||||
float frequency = 0.0f;
|
||||
QString channel;
|
||||
int range = 25;
|
||||
float declination = 0.0f;
|
||||
bool alignedTrueNorth = false;
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == QLatin1String("NAME"))
|
||||
{
|
||||
name = xmlReader.readElementText();
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("ID"))
|
||||
{
|
||||
id = xmlReader.readElementText();
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("GEOLOCATION"))
|
||||
{
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == QLatin1String("LAT")) {
|
||||
lat = xmlReader.readElementText().toFloat();
|
||||
} else if (xmlReader.name() == QLatin1String("LON")) {
|
||||
lon = xmlReader.readElementText().toFloat();
|
||||
} else if (xmlReader.name() == QLatin1String("ELEV")) {
|
||||
elevation = xmlReader.readElementText().toFloat();
|
||||
} else {
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("RADIO"))
|
||||
{
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == QLatin1String("FREQUENCY"))
|
||||
{
|
||||
if (type == "NDB") {
|
||||
frequency = xmlReader.readElementText().toFloat();
|
||||
} else {
|
||||
frequency = xmlReader.readElementText().toFloat() * 1000.0;
|
||||
}
|
||||
} else if (xmlReader.name() == QLatin1String("CHANNEL")) {
|
||||
channel = xmlReader.readElementText();
|
||||
} else {
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (xmlReader.name() == QLatin1String("PARAMS"))
|
||||
{
|
||||
while(xmlReader.readNextStartElement())
|
||||
{
|
||||
if (xmlReader.name() == QLatin1String("RANGE")) {
|
||||
range = xmlReader.readElementText().toInt();
|
||||
} else if (xmlReader.name() == QLatin1String("DECLINATION")) {
|
||||
declination = xmlReader.readElementText().toFloat();
|
||||
} else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH")) {
|
||||
alignedTrueNorth = xmlReader.readElementText() == "TRUE";
|
||||
} else {
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlReader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
NavAid *navAid = new NavAid();
|
||||
navAid->m_ident = id;
|
||||
// Check idents conform to our filtering rules
|
||||
if (navAid->m_ident.size() < 2) {
|
||||
qDebug() << "NavAid::readXML: Ident less than 2 characters: " << navAid->m_ident;
|
||||
} else if (navAid->m_ident.size() > 3) {
|
||||
qDebug() << "NavAid::readXML: Ident greater than 3 characters: " << navAid->m_ident;
|
||||
}
|
||||
navAid->m_type = type;
|
||||
navAid->m_name = name;
|
||||
navAid->m_frequencykHz = frequency;
|
||||
navAid->m_channel = channel;
|
||||
navAid->m_latitude = lat;
|
||||
navAid->m_longitude = lon;
|
||||
navAid->m_elevation = elevation;
|
||||
navAid->m_range = range;
|
||||
navAid->m_magneticDeclination = declination;
|
||||
navAid->m_alignedTrueNorth = alignedTrueNorth;
|
||||
navAidInfo.append(navAid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't warn, as many countries don't have files
|
||||
//qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading.";
|
||||
}
|
||||
return navAidInfo;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class SDRBASE_API OpenAIP : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenAIP(QObject* parent = nullptr);
|
||||
~OpenAIP();
|
||||
|
||||
static const QStringList m_countryCodes;
|
||||
|
||||
static QList<Airspace *> readAirspaces();
|
||||
static QList<Airspace *> readAirspaces(const QString& countryCode);
|
||||
static QList<NavAid *> readNavAids();
|
||||
static QList<NavAid *> readNavAids(const QString& countryCode);
|
||||
|
||||
void downloadAirspaces();
|
||||
void downloadNavAids();
|
||||
|
||||
private:
|
||||
HttpDownloadManager m_dlm;
|
||||
int m_countryIndex;
|
||||
|
||||
static QString getDataDir();
|
||||
static QString getAirspaceFilename(int i);
|
||||
static QString getAirspaceFilename(const QString& countryCode);
|
||||
static QString getAirspaceURL(int i);
|
||||
static QString getNavAidsFilename(int i);
|
||||
static QString getNavAidsFilename(const QString& countryCode);
|
||||
static QString getNavAidsURL(int i);
|
||||
|
||||
void downloadAirspace();
|
||||
void downloadNavAid();
|
||||
|
||||
public slots:
|
||||
void downloadFinished(const QString& filename, bool success);
|
||||
|
||||
signals:
|
||||
void downloadingURL(const QString& url);
|
||||
void downloadError(const QString& error);
|
||||
void downloadAirspaceFinished();
|
||||
void downloadNavAidsFinished();
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_OPENAIP_H
|
145
sdrbase/util/planespotters.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "planespotters.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
PlaneSpotters::PlaneSpotters()
|
||||
{
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleReply(QNetworkReply*)));
|
||||
}
|
||||
|
||||
PlaneSpotters::~PlaneSpotters()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleReply(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
void PlaneSpotters::getAircraftPhoto(const QString& icao)
|
||||
{
|
||||
if (m_photos.contains(icao))
|
||||
{
|
||||
emit aircraftPhoto(m_photos[icao]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new photo hash table entry
|
||||
PlaneSpottersPhoto *photo = new PlaneSpottersPhoto();
|
||||
photo->m_icao = icao;
|
||||
m_photos.insert(icao, photo);
|
||||
|
||||
// Fetch from network
|
||||
QUrl url(QString("https://api.planespotters.net/pub/photos/hex/%1").arg(icao));
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("User-Agent", "SDRangel/1.0"); // Get 403 error without this
|
||||
request.setOriginatingObject(photo);
|
||||
m_networkManager->get(request);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaneSpotters::handleReply(QNetworkReply* reply)
|
||||
{
|
||||
if (reply)
|
||||
{
|
||||
if (!reply->error())
|
||||
{
|
||||
if (reply->url().path().startsWith("/pub/photos/hex")) {
|
||||
parseJson((PlaneSpottersPhoto *)reply->request().originatingObject(), reply->readAll());
|
||||
} else {
|
||||
parsePhoto((PlaneSpottersPhoto *)reply->request().originatingObject(), reply->readAll());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "PlaneSpotters::handleReply: error: " << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "PlaneSpotters::handleReply: reply is null";
|
||||
}
|
||||
}
|
||||
|
||||
void PlaneSpotters::parsePhoto(PlaneSpottersPhoto *photo, QByteArray bytes)
|
||||
{
|
||||
photo->m_pixmap.loadFromData(bytes);
|
||||
emit aircraftPhoto(photo);
|
||||
}
|
||||
|
||||
void PlaneSpotters::parseJson(PlaneSpottersPhoto *photo, QByteArray bytes)
|
||||
{
|
||||
QJsonDocument document = QJsonDocument::fromJson(bytes);
|
||||
if (document.isObject())
|
||||
{
|
||||
QJsonObject obj = document.object();
|
||||
if (obj.contains(QStringLiteral("photos")))
|
||||
{
|
||||
QJsonArray photos = obj.value(QStringLiteral("photos")).toArray();
|
||||
if (photos.size() > 0)
|
||||
{
|
||||
QJsonObject photoObj = photos[0].toObject();
|
||||
|
||||
if (photoObj.contains(QStringLiteral("id"))) {
|
||||
photo->m_link = photoObj.value(QStringLiteral("id")).toString();
|
||||
}
|
||||
if (photoObj.contains(QStringLiteral("thumbnail")))
|
||||
{
|
||||
QJsonObject thumbnailObj = photoObj.value(QStringLiteral("thumbnail")).toObject();
|
||||
photo->m_thumbnail.m_src = thumbnailObj.value(QStringLiteral("src")).toString();
|
||||
QJsonObject sizeObj = thumbnailObj.value(QStringLiteral("size")).toObject();
|
||||
photo->m_thumbnail.m_width = sizeObj.value(QStringLiteral("width")).toInt();
|
||||
photo->m_thumbnail.m_height = sizeObj.value(QStringLiteral("width")).toInt();
|
||||
}
|
||||
if (photoObj.contains(QStringLiteral("link"))) {
|
||||
photo->m_link = photoObj.value(QStringLiteral("link")).toString();
|
||||
}
|
||||
if (photoObj.contains(QStringLiteral("photographer"))) {
|
||||
photo->m_photographer = photoObj.value(QStringLiteral("photographer")).toString();
|
||||
}
|
||||
|
||||
if (!photo->m_thumbnail.m_src.isEmpty())
|
||||
{
|
||||
QUrl url(photo->m_thumbnail.m_src);
|
||||
QNetworkRequest request(url);
|
||||
request.setOriginatingObject(photo);
|
||||
m_networkManager->get(request);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "PlaneSpotters::handleReply: data array is empty";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "PlaneSpotters::handleReply: Object doesn't contain data: " << obj;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "PlaneSpotters::handleReply: Document is not an object: " << document;
|
||||
}
|
||||
}
|
80
sdrbase/util/planespotters.h
Normal file
@ -0,0 +1,80 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_PLANESPOTTERS_H
|
||||
#define INCLUDE_PLANESPOTTERS_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QDateTime>
|
||||
#include <QPixmap>
|
||||
#include <QHash>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class SDRBASE_API PlaneSpottersPhoto : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
struct Thumbnail {
|
||||
QString m_src;
|
||||
int m_width;
|
||||
int m_height;
|
||||
};
|
||||
|
||||
public:
|
||||
QString m_icao;
|
||||
QString m_id;
|
||||
Thumbnail m_thumbnail;
|
||||
Thumbnail m_largeThumbnail;
|
||||
QString m_link;
|
||||
QString m_photographer
|
||||
;
|
||||
QPixmap m_pixmap;
|
||||
};
|
||||
|
||||
// PlaneSpotters API wrapper
|
||||
// Allows downloading of images of aircraft from https://www.planespotters.net/
|
||||
// Note that API terms of use require us not to cache images on disk
|
||||
class SDRBASE_API PlaneSpotters : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PlaneSpotters();
|
||||
~PlaneSpotters();
|
||||
|
||||
|
||||
void getAircraftPhoto(const QString& icao);
|
||||
|
||||
signals:
|
||||
void aircraftPhoto(const PlaneSpottersPhoto *photo); // Called when photo is available.
|
||||
|
||||
private:
|
||||
void parseJson(PlaneSpottersPhoto *photo, QByteArray bytes);
|
||||
void parsePhoto(PlaneSpottersPhoto *photo, QByteArray bytes);
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QHash<QString, PlaneSpottersPhoto *> m_photos;
|
||||
|
||||
public slots:
|
||||
void handleReply(QNetworkReply* reply);
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_PLANESPOTTERS_H */
|