1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-21 23:55:13 -05:00

Merge pull request #1025 from srcejon/adsb_flight_info

ADS-B: Add support for departure and arrival airport and times
This commit is contained in:
Edouard Griffiths 2021-10-29 17:21:30 +02:00 committed by GitHub
commit 58fb3782de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 825 additions and 152 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 152 KiB

View File

@ -23,7 +23,7 @@
ADSBDemodDisplayDialog::ADSBDemodDisplayDialog( ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(
int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize, int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats, bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
bool autoResizeTableColumns, QWidget* parent) : bool autoResizeTableColumns, const QString& apiKey, QWidget* parent) :
QDialog(parent), QDialog(parent),
m_fontName(fontName), m_fontName(fontName),
m_fontSize(fontSize), m_fontSize(fontSize),
@ -37,6 +37,7 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(
ui->units->setCurrentIndex((int)siUnits); ui->units->setCurrentIndex((int)siUnits);
ui->displayStats->setChecked(displayDemodStats); ui->displayStats->setChecked(displayDemodStats);
ui->autoResizeTableColumns->setChecked(autoResizeTableColumns); ui->autoResizeTableColumns->setChecked(autoResizeTableColumns);
ui->apiKey->setText(apiKey);
} }
ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog() ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
@ -53,6 +54,7 @@ void ADSBDemodDisplayDialog::accept()
m_siUnits = ui->units->currentIndex() == 0 ? false : true; m_siUnits = ui->units->currentIndex() == 0 ? false : true;
m_displayDemodStats = ui->displayStats->isChecked(); m_displayDemodStats = ui->displayStats->isChecked();
m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked(); m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked();
m_apiKey = ui->apiKey->text();
QDialog::accept(); QDialog::accept();
} }

View File

@ -27,7 +27,7 @@ class ADSBDemodDisplayDialog : public QDialog {
public: public:
explicit ADSBDemodDisplayDialog(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize, explicit ADSBDemodDisplayDialog(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats, bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
bool autoResizeTableColumns, QWidget* parent = 0); bool autoResizeTableColumns, const QString& apiKey, QWidget* parent = 0);
~ADSBDemodDisplayDialog(); ~ADSBDemodDisplayDialog();
int m_removeTimeout; int m_removeTimeout;
@ -39,6 +39,7 @@ public:
int m_fontSize; int m_fontSize;
bool m_displayDemodStats; bool m_displayDemodStats;
bool m_autoResizeTableColumns; bool m_autoResizeTableColumns;
QString m_apiKey;
private slots: private slots:
void accept(); void accept();

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>351</width> <width>417</width>
<height>289</height> <height>287</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -23,17 +23,31 @@
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="5" column="1"> <item row="1" column="0">
<widget class="QPushButton" name="font"> <widget class="QLabel" name="airportSizeLabel">
<property name="toolTip">
<string>Select a font for the table</string>
</property>
<property name="text"> <property name="text">
<string>Select...</string> <string>Display airports with size</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <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"> <widget class="QLabel" name="timeoutLabel">
<property name="text"> <property name="text">
<string>Aircraft timeout (s)</string> <string>Aircraft timeout (s)</string>
@ -50,70 +64,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QCheckBox" name="heliports">
<property name="toolTip">
<string>When checked, heliports are displayed on the map</string>
</property>
<property name="text">
<string>Display heliports</string>
</property>
</widget>
</item>
<item row="4" 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="airportSizeLabel">
<property name="text">
<string>Display airports with size</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="airportRangeLabel">
<property name="text">
<string>Airport display distance (km)</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="fontLabel">
<property name="text">
<string>Table font</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="displayStats">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Display demodulator statistics</string>
</property>
<property name="text">
<string>Display demodulator statistics</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="unitsLabel">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="airportSize"> <widget class="QComboBox" name="airportSize">
<property name="toolTip"> <property name="toolTip">
@ -136,31 +86,129 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2"> <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">
<property name="text">
<string>Airport display distance (km)</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"> <widget class="QCheckBox" name="autoResizeTableColumns">
<property name="toolTip"> <property name="toolTip">
<string>Resize the columns in the table after an aircraft is added to it</string> <string>Resize the columns in the table after an aircraft is added to it</string>
</property> </property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="autoResizeTableColumnsLabel">
<property name="text"> <property name="text">
<string>Resize columns after adding aircraft</string> <string>Resize columns after adding aircraft</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="units"> <widget class="QCheckBox" name="heliports">
<property name="toolTip"> <property name="toolTip">
<string>The units to use for altitude, speed and climb rate</string> <string>When checked, heliports are displayed on the map</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="displayStats">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Display demodulator statistics</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="heliportsLabel">
<property name="text">
<string>Display heliports</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="displayStatsLabel">
<property name="text">
<string>Display demodulator statistics</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="apiKey">
<property name="toolTip">
<string>aviationstack.com API key for accessing flight information</string>
</property> </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> </widget>
</item> </item>
</layout> </layout>

View File

@ -160,9 +160,9 @@ QString Aircraft::getImage()
QString Aircraft::getText(bool all) QString Aircraft::getText(bool all)
{ {
QStringList list; QStringList list;
if (m_flight.length() > 0) if (m_callsign.length() > 0)
{ {
list.append(QString("Flight: %1").arg(m_flight)); list.append(QString("Callsign: %1").arg(m_callsign));
} }
else else
{ {
@ -215,10 +215,54 @@ QString Aircraft::getText(bool all)
desc = QString("Descending: %1 (%2)").arg(rate).arg(units); desc = QString("Descending: %1 (%2)").arg(rate).arg(units);
list.append(QString(desc)); list.append(QString(desc));
} }
if ((m_status.length() > 0) && m_status.compare("No emergency")) if ((m_status.length() > 0) && m_status.compare("No emergency")) {
{
list.append(m_status); list.append(m_status);
} }
QString flightStatus = m_flightStatusItem->text();
if (!flightStatus.isEmpty()) {
list.append(QString("Flight status: %1").arg(flightStatus));
}
QString dep = m_depItem->text();
if (!dep.isEmpty()) {
list.append(QString("Departed: %1").arg(dep));
}
QString std = m_stdItem->text();
if (!std.isEmpty()) {
list.append(QString("STD: %1").arg(std));
}
QString atd = m_atdItem->text();
if (!atd.isEmpty())
{
list.append(QString("ATD: %1").arg(atd));
}
else
{
QString etd = m_etdItem->text();
if (!etd.isEmpty()) {
list.append(QString("ETD: %1").arg(etd));
}
}
QString arr = m_arrItem->text();
if (!arr.isEmpty()) {
list.append(QString("Arrival: %1").arg(arr));
}
QString sta = m_staItem->text();
if (!sta.isEmpty()) {
list.append(QString("STA: %1").arg(sta));
}
QString ata = m_ataItem->text();
if (!ata.isEmpty())
{
list.append(QString("ATA: %1").arg(ata));
}
else
{
QString eta = m_etaItem->text();
if (!eta.isEmpty()) {
list.append(QString("ETA: %1").arg(eta));
}
}
} }
return list.join("\n"); return list.join("\n");
} }
@ -501,6 +545,8 @@ QIcon *ADSBDemodGUI::getAirlineIcon(const QString &operatorICAO)
icon = new QIcon(":" + endPath); icon = new QIcon(":" + endPath);
m_airlineIcons.insert(operatorICAO, icon); m_airlineIcons.insert(operatorICAO, icon);
} }
else
qDebug() << "ADSBDemodGUI: No airline logo for " << operatorICAO;
} }
return icon; return icon;
} }
@ -611,7 +657,7 @@ void ADSBDemodGUI::handleADSB(
int row = ui->adsbData->rowCount(); int row = ui->adsbData->rowCount();
ui->adsbData->setRowCount(row + 1); ui->adsbData->setRowCount(row + 1);
ui->adsbData->setItem(row, ADSB_COL_ICAO, aircraft->m_icaoItem); ui->adsbData->setItem(row, ADSB_COL_ICAO, aircraft->m_icaoItem);
ui->adsbData->setItem(row, ADSB_COL_FLIGHT, aircraft->m_flightItem); ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, aircraft->m_callsignItem);
ui->adsbData->setItem(row, ADSB_COL_MODEL, aircraft->m_modelItem); ui->adsbData->setItem(row, ADSB_COL_MODEL, aircraft->m_modelItem);
ui->adsbData->setItem(row, ADSB_COL_AIRLINE, aircraft->m_airlineItem); ui->adsbData->setItem(row, ADSB_COL_AIRLINE, aircraft->m_airlineItem);
ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, aircraft->m_altitudeItem); ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, aircraft->m_altitudeItem);
@ -635,6 +681,15 @@ void ADSBDemodGUI::handleADSB(
ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, aircraft->m_adsbFrameCountItem); ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, aircraft->m_adsbFrameCountItem);
ui->adsbData->setItem(row, ADSB_COL_CORRELATION, aircraft->m_correlationItem); ui->adsbData->setItem(row, ADSB_COL_CORRELATION, aircraft->m_correlationItem);
ui->adsbData->setItem(row, ADSB_COL_RSSI, aircraft->m_rssiItem); ui->adsbData->setItem(row, ADSB_COL_RSSI, aircraft->m_rssiItem);
ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, aircraft->m_flightStatusItem);
ui->adsbData->setItem(row, ADSB_COL_DEP, aircraft->m_depItem);
ui->adsbData->setItem(row, ADSB_COL_ARR, aircraft->m_arrItem);
ui->adsbData->setItem(row, ADSB_COL_STD, aircraft->m_stdItem);
ui->adsbData->setItem(row, ADSB_COL_ETD, aircraft->m_etdItem);
ui->adsbData->setItem(row, ADSB_COL_ATD, aircraft->m_atdItem);
ui->adsbData->setItem(row, ADSB_COL_STA, aircraft->m_staItem);
ui->adsbData->setItem(row, ADSB_COL_ETA, aircraft->m_etaItem);
ui->adsbData->setItem(row, ADSB_COL_ATA, aircraft->m_ataItem);
// Look aircraft up in database // Look aircraft up in database
if (m_aircraftInfo != nullptr) if (m_aircraftInfo != nullptr)
{ {
@ -764,8 +819,39 @@ void ADSBDemodGUI::handleADSB(
callsign[i] = idMap[c[i]]; callsign[i] = idMap[c[i]];
callsign[8] = '\0'; callsign[8] = '\0';
aircraft->m_flight = QString(callsign); aircraft->m_callsign = QString(callsign).trimmed();
aircraft->m_flightItem->setText(aircraft->m_flight); aircraft->m_callsignItem->setText(aircraft->m_callsign);
// Attempt to map callsign to flight number
if (!aircraft->m_callsign.isEmpty())
{
QRegularExpression flightNoExp("^[A-Z]{2,3}[0-9]{1,4}$");
// Airlines line BA add a single charater suffix that can be stripped
// If the suffix is two characters, then it typically means a digit
// has been replaced which I don't know how to guess
// E.g Easyjet might use callsign EZY67JQ for flight EZY6267
// BA use BAW90BG for BA890
QRegularExpression suffixedFlightNoExp("^([A-Z]{2,3})([0-9]{1,4})[A-Z]?$");
QRegularExpressionMatch suffixMatch;
if (flightNoExp.match(aircraft->m_callsign).hasMatch())
{
aircraft->m_flight = aircraft->m_callsign;
}
else if ((suffixMatch = suffixedFlightNoExp.match(aircraft->m_callsign)).hasMatch())
{
aircraft->m_flight = QString("%1%2").arg(suffixMatch.captured(1)).arg(suffixMatch.captured(2));
}
else
{
// Don't guess, to save wasting API calls
aircraft->m_flight = "";
}
}
else
{
aircraft->m_flight = "";
}
} }
else if (((tc >= 5) && (tc <= 18)) || ((tc >= 20) && (tc <= 22))) else if (((tc >= 5) && (tc <= 18)) || ((tc >= 20) && (tc <= 22)))
{ {
@ -1136,11 +1222,11 @@ void ADSBDemodGUI::checkStaticNotification(Aircraft *aircraft)
} }
if (!match.isEmpty()) if (!match.isEmpty())
{ {
//QRegularExpression regExp(m_settings.m_notificationSettings[i]->m_regExp);
if (m_settings.m_notificationSettings[i]->m_regularExpression.isValid()) if (m_settings.m_notificationSettings[i]->m_regularExpression.isValid())
{ {
if (m_settings.m_notificationSettings[i]->m_regularExpression.match(match).hasMatch()) if (m_settings.m_notificationSettings[i]->m_regularExpression.match(match).hasMatch())
{ {
highlightAircraft(aircraft);
if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty()) { if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty()) {
speechNotification(aircraft, m_settings.m_notificationSettings[i]->m_speech); speechNotification(aircraft, m_settings.m_notificationSettings[i]->m_speech);
} }
@ -1160,7 +1246,7 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft)
{ {
for (int i = 0; i < m_settings.m_notificationSettings.size(); i++) for (int i = 0; i < m_settings.m_notificationSettings.size(); i++)
{ {
if ( (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_FLIGHT) if ( (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_CALLSIGN)
|| (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_ALTITUDE) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_ALTITUDE)
|| (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_SPEED) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_SPEED)
|| (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_RANGE) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_RANGE)
@ -1172,8 +1258,8 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft)
QString match; QString match;
switch (m_settings.m_notificationSettings[i]->m_matchColumn) switch (m_settings.m_notificationSettings[i]->m_matchColumn)
{ {
case ADSB_COL_FLIGHT: case ADSB_COL_CALLSIGN:
match = aircraft->m_flightItem->data(Qt::DisplayRole).toString(); match = aircraft->m_callsignItem->data(Qt::DisplayRole).toString();
break; break;
case ADSB_COL_ALTITUDE: case ADSB_COL_ALTITUDE:
match = aircraft->m_altitudeItem->data(Qt::DisplayRole).toString(); match = aircraft->m_altitudeItem->data(Qt::DisplayRole).toString();
@ -1202,6 +1288,7 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft)
{ {
if (m_settings.m_notificationSettings[i]->m_regularExpression.match(match).hasMatch()) if (m_settings.m_notificationSettings[i]->m_regularExpression.match(match).hasMatch())
{ {
highlightAircraft(aircraft);
if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty()) { if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty()) {
speechNotification(aircraft, m_settings.m_notificationSettings[i]->m_speech); speechNotification(aircraft, m_settings.m_notificationSettings[i]->m_speech);
} }
@ -1231,7 +1318,7 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin
{ {
QString s = string; QString s = string;
s = s.replace("${icao}", aircraft->m_icaoItem->data(Qt::DisplayRole).toString()); s = s.replace("${icao}", aircraft->m_icaoItem->data(Qt::DisplayRole).toString());
s = s.replace("${flight}", aircraft->m_flightItem->data(Qt::DisplayRole).toString()); s = s.replace("${callsign}", aircraft->m_callsignItem->data(Qt::DisplayRole).toString());
s = s.replace("${aircraft}", aircraft->m_modelItem->data(Qt::DisplayRole).toString()); s = s.replace("${aircraft}", aircraft->m_modelItem->data(Qt::DisplayRole).toString());
s = s.replace("${latitude}", aircraft->m_latitudeItem->data(Qt::DisplayRole).toString()); s = s.replace("${latitude}", aircraft->m_latitudeItem->data(Qt::DisplayRole).toString());
s = s.replace("${longitude}", aircraft->m_longitudeItem->data(Qt::DisplayRole).toString()); s = s.replace("${longitude}", aircraft->m_longitudeItem->data(Qt::DisplayRole).toString());
@ -1246,6 +1333,15 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin
s = s.replace("${manufacturer}", aircraft->m_manufacturerNameItem->data(Qt::DisplayRole).toString()); s = s.replace("${manufacturer}", aircraft->m_manufacturerNameItem->data(Qt::DisplayRole).toString());
s = s.replace("${owner}", aircraft->m_ownerItem->data(Qt::DisplayRole).toString()); s = s.replace("${owner}", aircraft->m_ownerItem->data(Qt::DisplayRole).toString());
s = s.replace("${operator}", aircraft->m_operatorICAOItem->data(Qt::DisplayRole).toString()); s = s.replace("${operator}", aircraft->m_operatorICAOItem->data(Qt::DisplayRole).toString());
s = s.replace("${flightstatus}", aircraft->m_flightStatusItem->data(Qt::DisplayRole).toString());
s = s.replace("${departure}", aircraft->m_depItem->data(Qt::DisplayRole).toString());
s = s.replace("${arrival}", aircraft->m_arrItem->data(Qt::DisplayRole).toString());
s = s.replace("${std}", aircraft->m_stdItem->data(Qt::DisplayRole).toString());
s = s.replace("${etd}", aircraft->m_etdItem->data(Qt::DisplayRole).toString());
s = s.replace("${atd}", aircraft->m_atdItem->data(Qt::DisplayRole).toString());
s = s.replace("${sta}", aircraft->m_staItem->data(Qt::DisplayRole).toString());
s = s.replace("${eta}", aircraft->m_etaItem->data(Qt::DisplayRole).toString());
s = s.replace("${ata}", aircraft->m_ataItem->data(Qt::DisplayRole).toString());
return s; return s;
} }
@ -1364,6 +1460,45 @@ void ADSBDemodGUI::on_notifications_clicked()
} }
} }
void ADSBDemodGUI::on_flightInfo_clicked()
{
if (m_flightInformation)
{
// Selection mode is single, so only a single row should be returned
QModelIndexList indexList = ui->adsbData->selectionModel()->selectedRows();
if (!indexList.isEmpty())
{
int row = indexList.at(0).row();
int icao = ui->adsbData->item(row, 0)->text().toInt(nullptr, 16);
if (m_aircraft.contains(icao))
{
Aircraft *aircraft = m_aircraft.value(icao);
if (!aircraft->m_flight.isEmpty())
{
// Download flight information
m_flightInformation->getFlightInformation(aircraft->m_flight);
}
else
{
qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No flight number for selected aircraft";
}
}
else
{
qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No aircraft with icao " << icao;
}
}
else
{
qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No aircraft selected";
}
}
else
{
qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No flight information service - have you set an API key?";
}
}
void ADSBDemodGUI::on_adsbData_cellClicked(int row, int column) void ADSBDemodGUI::on_adsbData_cellClicked(int row, int column)
{ {
(void) column; (void) column;
@ -1387,12 +1522,12 @@ void ADSBDemodGUI::on_adsbData_cellDoubleClicked(int row, int column)
{ {
Aircraft *aircraft = m_aircraft.value(icao); Aircraft *aircraft = m_aircraft.value(icao);
if (column == ADSB_COL_FLIGHT) if (column == ADSB_COL_CALLSIGN)
{ {
if (aircraft->m_flight.length() > 0) if (!aircraft->m_callsign.isEmpty())
{ {
// Search for flight on flightradar24 // Search for flight on flightradar24
QDesktopServices::openUrl(QUrl(QString("https://www.flightradar24.com/%1").arg(aircraft->m_flight.trimmed()))); QDesktopServices::openUrl(QUrl(QString("https://www.flightradar24.com/%1").arg(aircraft->m_callsign)));
} }
} }
else else
@ -1876,7 +2011,8 @@ void ADSBDemodGUI::on_displaySettings_clicked()
ADSBDemodDisplayDialog dialog(m_settings.m_removeTimeout, m_settings.m_airportRange, m_settings.m_airportMinimumSize, ADSBDemodDisplayDialog dialog(m_settings.m_removeTimeout, m_settings.m_airportRange, m_settings.m_airportMinimumSize,
m_settings.m_displayHeliports, m_settings.m_siUnits, m_settings.m_displayHeliports, m_settings.m_siUnits,
m_settings.m_tableFontName, m_settings.m_tableFontSize, m_settings.m_tableFontName, m_settings.m_tableFontSize,
m_settings.m_displayDemodStats, m_settings.m_autoResizeTableColumns); m_settings.m_displayDemodStats, m_settings.m_autoResizeTableColumns,
m_settings.m_apiKey);
if (dialog.exec() == QDialog::Accepted) if (dialog.exec() == QDialog::Accepted)
{ {
bool unitsChanged = m_settings.m_siUnits != dialog.m_siUnits; bool unitsChanged = m_settings.m_siUnits != dialog.m_siUnits;
@ -1890,6 +2026,7 @@ void ADSBDemodGUI::on_displaySettings_clicked()
m_settings.m_tableFontSize = dialog.m_fontSize; m_settings.m_tableFontSize = dialog.m_fontSize;
m_settings.m_displayDemodStats = dialog.m_displayDemodStats; m_settings.m_displayDemodStats = dialog.m_displayDemodStats;
m_settings.m_autoResizeTableColumns = dialog.m_autoResizeTableColumns; m_settings.m_autoResizeTableColumns = dialog.m_autoResizeTableColumns;
m_settings.m_apiKey = dialog.m_apiKey;
if (unitsChanged) if (unitsChanged)
m_aircraftModel.allAircraftUpdated(); m_aircraftModel.allAircraftUpdated();
@ -2027,6 +2164,8 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
// Initialise text to speech engine // Initialise text to speech engine
m_speech = new QTextToSpeech(this); m_speech = new QTextToSpeech(this);
m_flightInformation = nullptr;
updateDeviceSetList(); updateDeviceSetList();
displaySettings(); displaySettings();
applySettings(true); applySettings(true);
@ -2042,6 +2181,11 @@ ADSBDemodGUI::~ADSBDemodGUI()
delete a; delete a;
++i; ++i;
} }
if (m_flightInformation)
{
disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
delete m_flightInformation;
}
} }
void ADSBDemodGUI::applySettings(bool force) void ADSBDemodGUI::applySettings(bool force)
@ -2141,6 +2285,8 @@ void ADSBDemodGUI::displaySettings()
if (!m_settings.m_displayDemodStats) if (!m_settings.m_displayDemodStats)
ui->stats->setText(""); ui->stats->setText("");
initFlightInformation();
blockApplySettings(false); blockApplySettings(false);
} }
@ -2242,7 +2388,7 @@ void ADSBDemodGUI::resizeTable()
int row = ui->adsbData->rowCount(); int row = ui->adsbData->rowCount();
ui->adsbData->setRowCount(row + 1); ui->adsbData->setRowCount(row + 1);
ui->adsbData->setItem(row, ADSB_COL_ICAO, new QTableWidgetItem("ICAO ID")); ui->adsbData->setItem(row, ADSB_COL_ICAO, new QTableWidgetItem("ICAO ID"));
ui->adsbData->setItem(row, ADSB_COL_FLIGHT, new QTableWidgetItem("Flight No.")); ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, new QTableWidgetItem("Callsign"));
ui->adsbData->setItem(row, ADSB_COL_MODEL, new QTableWidgetItem("Aircraft12345")); ui->adsbData->setItem(row, ADSB_COL_MODEL, new QTableWidgetItem("Aircraft12345"));
ui->adsbData->setItem(row, ADSB_COL_AIRLINE, new QTableWidgetItem("airbrigdecargo1")); ui->adsbData->setItem(row, ADSB_COL_AIRLINE, new QTableWidgetItem("airbrigdecargo1"));
ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, new QTableWidgetItem("Alt (ft)")); ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, new QTableWidgetItem("Alt (ft)"));
@ -2266,31 +2412,96 @@ void ADSBDemodGUI::resizeTable()
ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, new QTableWidgetItem("Frames")); ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, new QTableWidgetItem("Frames"));
ui->adsbData->setItem(row, ADSB_COL_CORRELATION, new QTableWidgetItem("0.001/0.001/0.001")); ui->adsbData->setItem(row, ADSB_COL_CORRELATION, new QTableWidgetItem("0.001/0.001/0.001"));
ui->adsbData->setItem(row, ADSB_COL_RSSI, new QTableWidgetItem("-100.0")); ui->adsbData->setItem(row, ADSB_COL_RSSI, new QTableWidgetItem("-100.0"));
ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, new QTableWidgetItem("scheduled"));
ui->adsbData->setItem(row, ADSB_COL_DEP, new QTableWidgetItem("WWWW"));
ui->adsbData->setItem(row, ADSB_COL_ARR, new QTableWidgetItem("WWWW"));
ui->adsbData->setItem(row, ADSB_COL_STD, new QTableWidgetItem("12:00 -1"));
ui->adsbData->setItem(row, ADSB_COL_ETD, new QTableWidgetItem("12:00 -1"));
ui->adsbData->setItem(row, ADSB_COL_ATD, new QTableWidgetItem("12:00 -1"));
ui->adsbData->setItem(row, ADSB_COL_STA, new QTableWidgetItem("12:00 +1"));
ui->adsbData->setItem(row, ADSB_COL_ETA, new QTableWidgetItem("12:00 +1"));
ui->adsbData->setItem(row, ADSB_COL_ATA, new QTableWidgetItem("12:00 +1"));
ui->adsbData->resizeColumnsToContents(); ui->adsbData->resizeColumnsToContents();
ui->adsbData->removeCellWidget(row, ADSB_COL_ICAO);
ui->adsbData->removeCellWidget(row, ADSB_COL_FLIGHT);
ui->adsbData->removeCellWidget(row, ADSB_COL_MODEL);
ui->adsbData->removeCellWidget(row, ADSB_COL_AIRLINE);
ui->adsbData->removeCellWidget(row, ADSB_COL_ALTITUDE);
ui->adsbData->removeCellWidget(row, ADSB_COL_SPEED);
ui->adsbData->removeCellWidget(row, ADSB_COL_HEADING);
ui->adsbData->removeCellWidget(row, ADSB_COL_VERTICALRATE);
ui->adsbData->removeCellWidget(row, ADSB_COL_RANGE);
ui->adsbData->removeCellWidget(row, ADSB_COL_AZEL);
ui->adsbData->removeCellWidget(row, ADSB_COL_LATITUDE);
ui->adsbData->removeCellWidget(row, ADSB_COL_LONGITUDE);
ui->adsbData->removeCellWidget(row, ADSB_COL_CATEGORY);
ui->adsbData->removeCellWidget(row, ADSB_COL_STATUS);
ui->adsbData->removeCellWidget(row, ADSB_COL_SQUAWK);
ui->adsbData->removeCellWidget(row, ADSB_COL_REGISTRATION);
ui->adsbData->removeCellWidget(row, ADSB_COL_COUNTRY);
ui->adsbData->removeCellWidget(row, ADSB_COL_REGISTERED);
ui->adsbData->removeCellWidget(row, ADSB_COL_MANUFACTURER);
ui->adsbData->removeCellWidget(row, ADSB_COL_OWNER);
ui->adsbData->removeCellWidget(row, ADSB_COL_OPERATOR_ICAO);
ui->adsbData->removeCellWidget(row, ADSB_COL_TIME);
ui->adsbData->removeCellWidget(row, ADSB_COL_FRAMECOUNT);
ui->adsbData->removeCellWidget(row, ADSB_COL_CORRELATION);
ui->adsbData->removeCellWidget(row, ADSB_COL_RSSI);
ui->adsbData->setRowCount(row); ui->adsbData->setRowCount(row);
} }
Aircraft* ADSBDemodGUI::findAircraftByFlight(const QString& flight)
{
QHash<int, Aircraft *>::iterator i = m_aircraft.begin();
while (i != m_aircraft.end())
{
Aircraft *aircraft = i.value();
if (aircraft->m_flight == flight) {
return aircraft;
}
++i;
}
return nullptr;
}
// Convert to hh:mm (+/-days)
QString ADSBDemodGUI::dataTimeToShortString(QDateTime dt)
{
if (dt.isValid())
{
QDate currentDate = QDateTime::currentDateTimeUtc().date();
if (dt.date() == currentDate)
{
return dt.time().toString("hh:mm");
}
else
{
int days = currentDate.daysTo(dt.date());
if (days >= 0) {
return QString("%1 +%2").arg(dt.time().toString("hh:mm")).arg(days);
} else {
return QString("%1 %2").arg(dt.time().toString("hh:mm")).arg(days);
}
}
}
else
{
return "";
}
}
void ADSBDemodGUI::initFlightInformation()
{
if (m_flightInformation)
{
disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
delete m_flightInformation;
m_flightInformation = nullptr;
}
if (!m_settings.m_apiKey.isEmpty())
{
m_flightInformation = FlightInformation::create(m_settings.m_apiKey);
if (m_flightInformation) {
connect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
}
}
}
void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& flight)
{
Aircraft* aircraft = findAircraftByFlight(flight.m_flightICAO);
if (aircraft)
{
aircraft->m_flightStatusItem->setText(flight.m_flightStatus);
aircraft->m_depItem->setText(flight.m_departureICAO);
aircraft->m_arrItem->setText(flight.m_arrivalICAO);
aircraft->m_stdItem->setText(dataTimeToShortString(flight.m_departureScheduled));
aircraft->m_etdItem->setText(dataTimeToShortString(flight.m_departureEstimated));
aircraft->m_atdItem->setText(dataTimeToShortString(flight.m_departureActual));
aircraft->m_staItem->setText(dataTimeToShortString(flight.m_arrivalScheduled));
aircraft->m_etaItem->setText(dataTimeToShortString(flight.m_arrivalEstimated));
aircraft->m_ataItem->setText(dataTimeToShortString(flight.m_arrivalActual));
if (aircraft->m_positionValid) {
m_aircraftModel.aircraftUpdated(aircraft);
}
}
else
{
qDebug() << "ADSBDemodGUI::flightInformationUpdated - Flight not found in ADS-B table: " << flight.m_flightICAO;
}
}

View File

@ -35,6 +35,7 @@
#include "util/azel.h" #include "util/azel.h"
#include "util/movingaverage.h" #include "util/movingaverage.h"
#include "util/httpdownloadmanager.h" #include "util/httpdownloadmanager.h"
#include "util/flightinformation.h"
#include "maincore.h" #include "maincore.h"
#include "adsbdemodsettings.h" #include "adsbdemodsettings.h"
@ -78,7 +79,8 @@ public:
// Data about an aircraft extracted from an ADS-B frames // Data about an aircraft extracted from an ADS-B frames
struct Aircraft { struct Aircraft {
int m_icao; // 24-bit ICAO aircraft address int m_icao; // 24-bit ICAO aircraft address
QString m_flight; // Flight callsign QString m_callsign; // Flight callsign
QString m_flight; // Guess at flight number
Real m_latitude; // Latitude in decimal degrees Real m_latitude; // Latitude in decimal degrees
Real m_longitude; // Longitude in decimal degrees Real m_longitude; // Longitude in decimal degrees
int m_altitude; // Altitude in feet int m_altitude; // Altitude in feet
@ -130,7 +132,7 @@ struct Aircraft {
// GUI table items for above data // GUI table items for above data
QTableWidgetItem *m_icaoItem; QTableWidgetItem *m_icaoItem;
QTableWidgetItem *m_flightItem; QTableWidgetItem *m_callsignItem;
QTableWidgetItem *m_modelItem; QTableWidgetItem *m_modelItem;
QTableWidgetItem *m_airlineItem; QTableWidgetItem *m_airlineItem;
QTableWidgetItem *m_latitudeItem; QTableWidgetItem *m_latitudeItem;
@ -154,6 +156,15 @@ struct Aircraft {
QTableWidgetItem *m_adsbFrameCountItem; QTableWidgetItem *m_adsbFrameCountItem;
QTableWidgetItem *m_correlationItem; QTableWidgetItem *m_correlationItem;
QTableWidgetItem *m_rssiItem; QTableWidgetItem *m_rssiItem;
QTableWidgetItem *m_flightStatusItem;
QTableWidgetItem *m_depItem;
QTableWidgetItem *m_arrItem;
QTableWidgetItem *m_stdItem;
QTableWidgetItem *m_etdItem;
QTableWidgetItem *m_atdItem;
QTableWidgetItem *m_staItem;
QTableWidgetItem *m_etaItem;
QTableWidgetItem *m_ataItem;
Aircraft(ADSBDemodGUI *gui) : Aircraft(ADSBDemodGUI *gui) :
m_icao(0), m_icao(0),
@ -187,7 +198,7 @@ struct Aircraft {
} }
// These are deleted by QTableWidget // These are deleted by QTableWidget
m_icaoItem = new QTableWidgetItem(); m_icaoItem = new QTableWidgetItem();
m_flightItem = new QTableWidgetItem(); m_callsignItem = new QTableWidgetItem();
m_modelItem = new QTableWidgetItem(); m_modelItem = new QTableWidgetItem();
m_airlineItem = new QTableWidgetItem(); m_airlineItem = new QTableWidgetItem();
m_altitudeItem = new QTableWidgetItem(); m_altitudeItem = new QTableWidgetItem();
@ -211,6 +222,15 @@ struct Aircraft {
m_adsbFrameCountItem = new QTableWidgetItem(); m_adsbFrameCountItem = new QTableWidgetItem();
m_correlationItem = new QTableWidgetItem(); m_correlationItem = new QTableWidgetItem();
m_rssiItem = new QTableWidgetItem(); m_rssiItem = new QTableWidgetItem();
m_flightStatusItem = new QTableWidgetItem();
m_depItem = new QTableWidgetItem();
m_arrItem = new QTableWidgetItem();
m_stdItem = new QTableWidgetItem();
m_etdItem = new QTableWidgetItem();
m_atdItem = new QTableWidgetItem();
m_staItem = new QTableWidgetItem();
m_etaItem = new QTableWidgetItem();
m_ataItem = new QTableWidgetItem();
} }
QString getImage(); QString getImage();
@ -219,8 +239,8 @@ struct Aircraft {
// Name to use when selected as a target // Name to use when selected as a target
QString targetName() QString targetName()
{ {
if (!m_flight.isEmpty()) if (!m_callsign.isEmpty())
return QString("Flight: %1").arg(m_flight); return QString("Callsign: %1").arg(m_callsign);
else else
return QString("ICAO: %1").arg(m_icao, 0, 16); return QString("ICAO: %1").arg(m_icao, 0, 16);
} }
@ -480,6 +500,7 @@ public:
public slots: public slots:
void channelMarkerChangedByCursor(); void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor(); void channelMarkerHighlightedByCursor();
void flightInformationUpdated(const FlightInformation::Flight& flight);
private: private:
Ui::ADSBDemodGUI* ui; Ui::ADSBDemodGUI* ui;
@ -516,7 +537,7 @@ private:
QTextToSpeech *m_speech; QTextToSpeech *m_speech;
QMenu *menu; // Column select context menu QMenu *menu; // Column select context menu
FlightInformation *m_flightInformation;
WebAPIAdapterInterface *m_webAPIAdapterInterface; WebAPIAdapterInterface *m_webAPIAdapterInterface;
HttpDownloadManager m_dlm; HttpDownloadManager m_dlm;
QProgressDialog *m_progressDialog; QProgressDialog *m_progressDialog;
@ -558,6 +579,9 @@ private:
QIcon *getFlagIcon(const QString &country); QIcon *getFlagIcon(const QString &country);
void updateDeviceSetList(); void updateDeviceSetList();
QAction *createCheckableItem(QString& text, int idx, bool checked); QAction *createCheckableItem(QString& text, int idx, bool checked);
Aircraft* findAircraftByFlight(const QString& flight);
QString dataTimeToShortString(QDateTime dt);
void initFlightInformation();
void leaveEvent(QEvent*); void leaveEvent(QEvent*);
void enterEvent(QEvent*); void enterEvent(QEvent*);
@ -579,6 +603,7 @@ private slots:
void on_demodModeS_clicked(bool checked); void on_demodModeS_clicked(bool checked);
void on_feed_clicked(bool checked); void on_feed_clicked(bool checked);
void on_notifications_clicked(); void on_notifications_clicked();
void on_flightInfo_clicked();
void on_getOSNDB_clicked(); void on_getOSNDB_clicked();
void on_getAirportDB_clicked(); void on_getAirportDB_clicked();
void on_flightPaths_clicked(bool checked); void on_flightPaths_clicked(bool checked);

View File

@ -613,7 +613,24 @@
<normaloff>:/mono.png</normaloff>:/mono.png</iconset> <normaloff>:/mono.png</normaloff>:/mono.png</iconset>
</property> </property>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>false</bool>
</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> </property>
</widget> </widget>
</item> </item>
@ -721,10 +738,10 @@
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>Flight No.</string> <string>Callsign</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Commercial flight number. Links to www.flightradar24.com</string> <string>Callsign. Links to www.flightradar24.com</string>
</property> </property>
</column> </column>
<column> <column>
@ -908,6 +925,78 @@
<string>RSSI</string> <string>RSSI</string>
</property> </property>
</column> </column>
<column>
<property name="text">
<string>Flight Status</string>
</property>
<property name="toolTip">
<string>Status of flight</string>
</property>
</column>
<column>
<property name="text">
<string>Dep</string>
</property>
<property name="toolTip">
<string>Departure airport</string>
</property>
</column>
<column>
<property name="text">
<string>Arr</string>
</property>
<property name="toolTip">
<string>Arrival airport</string>
</property>
</column>
<column>
<property name="text">
<string>STD</string>
</property>
<property name="toolTip">
<string>Scheduled time of departure</string>
</property>
</column>
<column>
<property name="text">
<string>ETD</string>
</property>
<property name="toolTip">
<string>Estimated time of departure</string>
</property>
</column>
<column>
<property name="text">
<string>ATD</string>
</property>
<property name="toolTip">
<string>Actual time of departure</string>
</property>
</column>
<column>
<property name="text">
<string>STA</string>
</property>
<property name="toolTip">
<string>Scheduled time of arrival</string>
</property>
</column>
<column>
<property name="text">
<string>ETA</string>
</property>
<property name="toolTip">
<string>Estimated time of arrival</string>
</property>
</column>
<column>
<property name="text">
<string>ATA</string>
</property>
<property name="toolTip">
<string>Actual time of arrival</string>
</property>
</column>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -25,7 +25,7 @@
// Map main ADS-B table column numbers to combo box indicies // Map main ADS-B table column numbers to combo box indicies
std::vector<int> ADSBDemodNotificationDialog::m_columnMap = { std::vector<int> ADSBDemodNotificationDialog::m_columnMap = {
ADSB_COL_ICAO, ADSB_COL_FLIGHT, ADSB_COL_MODEL, ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL,
ADSB_COL_ALTITUDE, ADSB_COL_SPEED, ADSB_COL_RANGE, ADSB_COL_ALTITUDE, ADSB_COL_SPEED, ADSB_COL_RANGE,
ADSB_COL_CATEGORY, ADSB_COL_STATUS, ADSB_COL_SQUAWK, ADSB_COL_CATEGORY, ADSB_COL_STATUS, ADSB_COL_SQUAWK,
ADSB_COL_REGISTRATION, ADSB_COL_MANUFACTURER, ADSB_COL_OWNER, ADSB_COL_OPERATOR_ICAO ADSB_COL_REGISTRATION, ADSB_COL_MANUFACTURER, ADSB_COL_OWNER, ADSB_COL_OPERATOR_ICAO
@ -111,7 +111,7 @@ void ADSBDemodNotificationDialog::addRow(ADSBDemodSettings::NotificationSettings
matchWidget->setLayout(pLayout); matchWidget->setLayout(pLayout);
match->addItem("ICAO ID"); match->addItem("ICAO ID");
match->addItem("Flight No."); match->addItem("Callsign");
match->addItem("Aircraft"); match->addItem("Aircraft");
match->addItem("Alt (ft)"); match->addItem("Alt (ft)");
match->addItem("Spd (kn)"); match->addItem("Spd (kn)");

View File

@ -66,6 +66,7 @@ void ADSBDemodSettings::resetToDefaults()
m_autoResizeTableColumns = false; m_autoResizeTableColumns = false;
m_interpolatorPhaseSteps = 4; // Higher than these two values will struggle to run in real-time m_interpolatorPhaseSteps = 4; // Higher than these two values will struggle to run in real-time
m_interpolatorTapsPerPhase = 3.5f; // without gaining much improvement in PER m_interpolatorTapsPerPhase = 3.5f; // without gaining much improvement in PER
m_apiKey = "";
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
{ {
m_columnIndexes[i] = i; m_columnIndexes[i] = i;
@ -115,6 +116,7 @@ QByteArray ADSBDemodSettings::serialize() const
s.writeBool(33, m_allFlightPaths); s.writeBool(33, m_allFlightPaths);
s.writeBlob(34, serializeNotificationSettings(m_notificationSettings)); s.writeBlob(34, serializeNotificationSettings(m_notificationSettings));
s.writeString(35, m_apiKey);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
s.writeS32(100 + i, m_columnIndexes[i]); s.writeS32(100 + i, m_columnIndexes[i]);
@ -199,6 +201,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
d.readBlob(34, &blob); d.readBlob(34, &blob);
deserializeNotificationSettings(blob, m_notificationSettings); deserializeNotificationSettings(blob, m_notificationSettings);
d.readString(35, &m_apiKey, "");
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
d.readS32(100 + i, &m_columnIndexes[i], i); d.readS32(100 + i, &m_columnIndexes[i], i);
@ -259,7 +262,7 @@ void ADSBDemodSettings::NotificationSettings::updateRegularExpression()
{ {
m_regularExpression.setPattern(m_regExp); m_regularExpression.setPattern(m_regExp);
m_regularExpression.optimize(); m_regularExpression.optimize();
if (m_regularExpression.isValid()) { if (!m_regularExpression.isValid()) {
qDebug() << "ADSBDemod: Regular expression is not valid: " << m_regExp; qDebug() << "ADSBDemod: Regular expression is not valid: " << m_regExp;
} }
} }

View File

@ -29,11 +29,11 @@
class Serializable; class Serializable;
// Number of columns in the table // Number of columns in the table
#define ADSBDEMOD_COLUMNS 25 #define ADSBDEMOD_COLUMNS 34
// ADS-B table columns // ADS-B table columns
#define ADSB_COL_ICAO 0 #define ADSB_COL_ICAO 0
#define ADSB_COL_FLIGHT 1 #define ADSB_COL_CALLSIGN 1
#define ADSB_COL_MODEL 2 #define ADSB_COL_MODEL 2
#define ADSB_COL_AIRLINE 3 #define ADSB_COL_AIRLINE 3
#define ADSB_COL_ALTITUDE 4 #define ADSB_COL_ALTITUDE 4
@ -57,6 +57,15 @@ class Serializable;
#define ADSB_COL_FRAMECOUNT 22 #define ADSB_COL_FRAMECOUNT 22
#define ADSB_COL_CORRELATION 23 #define ADSB_COL_CORRELATION 23
#define ADSB_COL_RSSI 24 #define ADSB_COL_RSSI 24
#define ADSB_COL_FLIGHT_STATUS 25
#define ADSB_COL_DEP 26
#define ADSB_COL_ARR 27
#define ADSB_COL_STD 28
#define ADSB_COL_ETD 29
#define ADSB_COL_ATD 30
#define ADSB_COL_STA 31
#define ADSB_COL_ETA 32
#define ADSB_COL_ATA 33
struct ADSBDemodSettings struct ADSBDemodSettings
{ {
@ -119,6 +128,7 @@ struct ADSBDemodSettings
float m_interpolatorTapsPerPhase; float m_interpolatorTapsPerPhase;
QList<NotificationSettings *> m_notificationSettings; QList<NotificationSettings *> m_notificationSettings;
QString m_apiKey; //!< aviationstack.com API key
ADSBDemodSettings(); ADSBDemodSettings();
void resetToDefaults(); void resetToDefaults();

View File

@ -67,6 +67,8 @@ Clicking the Display Settings button will open the Display Settings dialog, whic
* Whether demodulator statistics are displayed (primarily an option for developers). * Whether demodulator statistics are displayed (primarily an option for developers).
* Whether the columns in the table are automatically resized after an aircraft is added to it. If unchecked, columns can be resized manually and should be saved with presets. * Whether the columns in the table are automatically resized after an aircraft is added to it. If unchecked, columns can be resized manually and should be saved with presets.
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> <h3>12: Display Flight Paths</h3>
Checking this button draws a line on the map showing aircraft's flight paths, as determined from received ADS-B frames. Checking this button draws a line on the map showing aircraft's flight paths, as determined from received ADS-B frames.
@ -125,10 +127,10 @@ Emergency status are:
* Unlawful interference * Unlawful interference
* Downed aircraft * Downed aircraft
In the Speech and Command strings, variables can be used to substitute in ADS-B data for the aircraft: In the Speech and Command strings, variables can be used to substitute in data from the ADS-B table for the aircraft:
* ${icao}, * ${icao},
* ${flight} * ${callsign}
* ${aircraft} * ${aircraft}
* ${latitude} * ${latitude}
* ${longitude} * ${longitude}
@ -143,6 +145,23 @@ In the Speech and Command strings, variables can be used to substitute in ADS-B
* ${manufacturer} * ${manufacturer}
* ${owner} * ${owner}
* ${operator} * ${operator}
* ${flightstatus}
* ${departure}
* ${arrival}
* ${std}
* ${etd}
* ${atd}
* ${sta}
* ${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>14: Refresh list of devices</h3> <h3>14: Refresh list of devices</h3>
@ -154,12 +173,12 @@ Specify the SDRangel device set that will be have its centre frequency set when
<h3>ADS-B Data</h3> <h3>ADS-B Data</h3>
The table displays the decoded ADS-B data for each aircraft along side data available for the aircraft from the Opensky Network database. The data is not all able to be transmitted in a single ADS-B frame, so the table displays an amalgamation of the latest received data of each type. The table displays the decoded ADS-B data for each aircraft along side data available for the aircraft from the Opensky Network database (DB) and aviationstack (API). The data is not all able to be transmitted in a single ADS-B frame, so the table displays an amalgamation of the latest received data of each type.
![ADS-B Demodulator Data](../../../doc/img/ADSBDemod_plugin_table.png) ![ADS-B Demodulator Data](../../../doc/img/ADSBDemod_plugin_table.png)
* ICAO ID - 24-bit hexidecimal ICAO aircraft address. This is unique for each aircraft. (ADS-B) * ICAO ID - 24-bit hexidecimal ICAO aircraft address. This is unique for each aircraft. (ADS-B)
* Flight No. - Airline flight number or callsign. (ADS-B) * Callsign - Aircraft callsign (which is sometimes also the flight number). (ADS-B)
* Aircraft - The aircraft model. (DB) * Aircraft - The aircraft model. (DB)
* Airline - The logo of the operator of the aircraft (or owner if no operator known). (DB) * Airline - The logo of the operator of the aircraft (or owner if no operator known). (DB)
* Altitude (Alt) - Altitude in feet or metres. (ADS-B) * Altitude (Alt) - Altitude in feet or metres. (ADS-B)
@ -182,6 +201,15 @@ The table displays the decoded ADS-B data for each aircraft along side data avai
* RX Frames - A count of the number of ADS-B frames received from this aircraft. * RX Frames - A count of the number of ADS-B frames received from this aircraft.
* Correlation - Displays the minimun, average and maximum of the preamable correlation in dB for each recevied frame. These values can be used to help select a threshold setting. This correlation value is the ratio between the presence and absence of the signal corresponding to the "ones" and the "zeros" of the sync word adjusted by the bits ratio. It can be interpreted as a SNR estimation. * Correlation - Displays the minimun, average and maximum of the preamable correlation in dB for each recevied frame. These values can be used to help select a threshold setting. This correlation value is the ratio between the presence and absence of the signal corresponding to the "ones" and the "zeros" of the sync word adjusted by the bits ratio. It can be interpreted as a SNR estimation.
* RSSI - This Received Signal Strength Indicator is based on the signal power during correlation estimation. This is the power sum during the expected presence of the signal i.e. the "ones" of the sync word. * RSSI - This Received Signal Strength Indicator is based on the signal power during correlation estimation. This is the power sum during the expected presence of the signal i.e. the "ones" of the sync word.
* Flight status - scheduled, active, landed, cancelled, incident or diverted. (API)
* Dep - Departure airport. (API)
* Arr - Arrival airport. (API)
* STD - Scheduled time of departure. (API)
* ETD - Estimated time of departure. (API)
* ATD - Actual time of departure. (API)
* STA - Scheduled time of arrival. (API)
* ETA - Estimated time of arrival. (API)
* ATA - Actual time of arrival. (API)
If an ADS-B frame has not been received from an aircraft for 60 seconds, the aircraft is removed from the table and map. This timeout can be adjusted in the Display Settings dialog. If an ADS-B frame has not been received from an aircraft for 60 seconds, the aircraft is removed from the table and map. This timeout can be adjusted in the Display Settings dialog.
@ -190,7 +218,7 @@ If an ADS-B frame has not been received from an aircraft for 60 seconds, the air
* To reorder the columns, left click and drag left or right a column header. * To reorder the columns, left click and drag left or right a column header.
* Left click on a header to sort the table by the data in that column. * Left click on a header to sort the table by the data in that column.
* Double clicking in an ICAO ID cell will open a Web browser and search for the corresponding aircraft on https://www.planespotters.net/ * Double clicking in an ICAO ID cell will open a Web browser and search for the corresponding aircraft on https://www.planespotters.net/
* Double clicking in an Flight No cell will open a Web browser and search for the corresponding flight on https://www.flightradar24.com/ * Double clicking in an Callsign cell will open a Web browser and search for the corresponding flight on https://www.flightradar24.com/
* Double clicking in an Az/El cell will set the aircraft as the active target. The azimuth and elevation to the aicraft will be sent to a rotator controller plugin. The aircraft information box will be coloured green, rather than blue, on the map. * Double clicking in an Az/El cell will set the aircraft as the active target. The azimuth and elevation to the aicraft will be sent to a rotator controller plugin. The aircraft information box will be coloured green, rather than blue, on the map.
* Double clicking on any other cell in the table will centre the map on the corresponding aircraft. * Double clicking on any other cell in the table will centre the map on the corresponding aircraft.

View File

@ -190,6 +190,7 @@ set(sdrbase_SOURCES
util/db.cpp util/db.cpp
util/fixedtraits.cpp util/fixedtraits.cpp
util/fits.cpp util/fits.cpp
util/flightinformation.cpp
util/golay2312.cpp util/golay2312.cpp
util/httpdownloadmanager.cpp util/httpdownloadmanager.cpp
util/interpolation.cpp util/interpolation.cpp
@ -397,6 +398,7 @@ set(sdrbase_HEADERS
util/doublebuffermultiple.h util/doublebuffermultiple.h
util/fixedtraits.h util/fixedtraits.h
util/fits.h util/fits.h
util/flightinformation.h
util/golay2312.h util/golay2312.h
util/httpdownloadmanager.h util/httpdownloadmanager.h
util/incrementalarray.h util/incrementalarray.h

View File

@ -0,0 +1,164 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "flightinformation.h"
#include <QDebug>
#include <QFile>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
FlightInformation::FlightInformation()
{
}
FlightInformation* FlightInformation::create(const QString& apiKey, const QString& service)
{
if (service == "aviationstack.com")
{
if (!apiKey.isEmpty())
{
return new AviationStack(apiKey);
}
else
{
qDebug() << "FlightInformation::create: An API key is required for: " << service;
return nullptr;
}
}
else
{
qDebug() << "FlightInformation::create: Unsupported service: " << service;
return nullptr;
}
}
AviationStack::AviationStack(const QString& apiKey) :
m_apiKey(apiKey)
{
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleReply(QNetworkReply*)));
}
AviationStack::~AviationStack()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleReply(QNetworkReply*)));
delete m_networkManager;
}
void AviationStack::getFlightInformation(const QString& flight)
{
QUrl url(QString("http://api.aviationstack.com/v1/flights"));
QUrlQuery query;
query.addQueryItem("flight_icao",flight);
query.addQueryItem("access_key", m_apiKey);
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)
{
if (reply)
{
if (!reply->error())
{
parseJson(reply->readAll());
}
else
{
qDebug() << "AviationStack::handleReply: error: " << reply->error();
}
reply->deleteLater();
}
else
{
qDebug() << "AviationStack::handleReply: reply is null";
}
}
void AviationStack::parseJson(QByteArray bytes)
{
QJsonDocument document = QJsonDocument::fromJson(bytes);
if (document.isObject())
{
QJsonObject obj = document.object();
if (obj.contains(QStringLiteral("data")))
{
QJsonArray data = obj.value(QStringLiteral("data")).toArray();
if (data.size() > 0)
{
QJsonObject flightObj = data[0].toObject();
Flight flight;
if (flightObj.contains(QStringLiteral("flight_status"))) {
flight.m_flightStatus = flightObj.value(QStringLiteral("flight_status")).toString();
}
if (flightObj.contains(QStringLiteral("departure")))
{
QJsonObject departure = flightObj.value(QStringLiteral("departure")).toObject();
flight.m_departureAirport = departure.value(QStringLiteral("airport")).toString();
flight.m_departureICAO = departure.value(QStringLiteral("icao")).toString();
flight.m_departureTerminal = departure.value(QStringLiteral("terminal")).toString();
flight.m_departureGate = departure.value(QStringLiteral("gate")).toString();
flight.m_departureScheduled = QDateTime::fromString(departure.value(QStringLiteral("scheduled")).toString(), Qt::ISODate);
flight.m_departureEstimated = QDateTime::fromString(departure.value(QStringLiteral("estimated")).toString(), Qt::ISODate);
flight.m_departureActual = QDateTime::fromString(departure.value(QStringLiteral("actual")).toString(), Qt::ISODate);
}
if (flightObj.contains(QStringLiteral("arrival")))
{
QJsonObject departure = flightObj.value(QStringLiteral("arrival")).toObject();
flight.m_arrivalAirport = departure.value(QStringLiteral("airport")).toString();
flight.m_arrivalICAO = departure.value(QStringLiteral("icao")).toString();
flight.m_arrivalTerminal = departure.value(QStringLiteral("terminal")).toString();
flight.m_arrivalGate = departure.value(QStringLiteral("gate")).toString();
flight.m_arrivalScheduled = QDateTime::fromString(departure.value(QStringLiteral("scheduled")).toString(), Qt::ISODate);
flight.m_arrivalEstimated = QDateTime::fromString(departure.value(QStringLiteral("estimated")).toString(), Qt::ISODate);
flight.m_arrivalActual = QDateTime::fromString(departure.value(QStringLiteral("actual")).toString(), Qt::ISODate);
}
if (flightObj.contains(QStringLiteral("flight")))
{
QJsonObject flightNo = flightObj.value(QStringLiteral("flight")).toObject();
flight.m_flightICAO = flightNo.value(QStringLiteral("icao")).toString();
flight.m_flightIATA = flightNo.value(QStringLiteral("iata")).toString();
}
emit flightUpdated(flight);
}
else
{
qDebug() << "AviationStack::handleReply: data array is empty";
}
}
else
{
qDebug() << "AviationStack::handleReply: Object doesn't contain data: " << obj;
}
}
else
{
qDebug() << "AviationStack::handleReply: Document is not an object: " << document;
}
}

View File

@ -0,0 +1,90 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_FLIGHTINFORMATION_H
#define INCLUDE_FLIGHTINFORMATION_H
#include <QtCore>
#include <QDateTime>
#include "export.h"
class QNetworkAccessManager;
class QNetworkReply;
// Flight information API wrapper
// Allows searching for departure/arrival airports and status of a flight
// Currently supports aviationstack.com
class SDRBASE_API FlightInformation : public QObject
{
Q_OBJECT
protected:
FlightInformation();
public:
struct Flight {
QString m_flightICAO;
QString m_flightIATA;
QString m_flightStatus; // active, landed...
QString m_departureAirport;
QString m_departureICAO;
QString m_departureTerminal;
QString m_departureGate;
QDateTime m_departureScheduled;
QDateTime m_departureEstimated;
QDateTime m_departureActual;
QString m_arrivalAirport;
QString m_arrivalICAO;
QString m_arrivalTerminal;
QString m_arrivalGate;
QDateTime m_arrivalScheduled;
QDateTime m_arrivalEstimated;
QDateTime m_arrivalActual;
};
static FlightInformation* create(const QString& apiKey, const QString& service="aviationstack.com");
virtual void getFlightInformation(const QString& flight) = 0;
signals:
void flightUpdated(const Flight& flight); // Called when new data available.
private:
};
class SDRBASE_API AviationStack : public FlightInformation {
Q_OBJECT
public:
AviationStack(const QString& apiKey);
~AviationStack();
virtual void getFlightInformation(const QString& flight) override;
private:
void parseJson(QByteArray bytes);
QString m_apiKey;
QNetworkAccessManager *m_networkManager;
public slots:
void handleReply(QNetworkReply* reply);
};
#endif /* INCLUDE_FLIGHTINFORMATION_H */

View File

@ -39,13 +39,13 @@ Weather* Weather::create(const QString& apiKey, const QString& service)
} }
else else
{ {
qDebug() << "Weather::connect: An API key is required for: " << service; qDebug() << "Weather::create: An API key is required for: " << service;
return nullptr; return nullptr;
} }
} }
else else
{ {
qDebug() << "Weather::connect: Unsupported service: " << service; qDebug() << "Weather::create: Unsupported service: " << service;
return nullptr; return nullptr;
} }
} }