Merge pull request #704 from srcejon/adsb_improvements2

ADS-B improvements
This commit is contained in:
Edouard Griffiths 2020-11-12 18:13:20 +01:00 committed by GitHub
commit 835aa26902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 661 additions and 247 deletions

View File

@ -20,9 +20,10 @@
#include "adsbdemoddisplaydialog.h" #include "adsbdemoddisplaydialog.h"
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, QWidget* parent) : bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
bool autoResizeTableColumns, QWidget* parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::ADSBDemodDisplayDialog), ui(new Ui::ADSBDemodDisplayDialog),
m_fontName(fontName), m_fontName(fontName),
@ -35,6 +36,7 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog
ui->heliports->setChecked(displayHeliports); ui->heliports->setChecked(displayHeliports);
ui->units->setCurrentIndex((int)siUnits); ui->units->setCurrentIndex((int)siUnits);
ui->displayStats->setChecked(displayDemodStats); ui->displayStats->setChecked(displayDemodStats);
ui->autoResizeTableColumns->setChecked(autoResizeTableColumns);
} }
ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog() ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
@ -50,6 +52,7 @@ void ADSBDemodDisplayDialog::accept()
m_displayHeliports = ui->heliports->isChecked(); m_displayHeliports = ui->heliports->isChecked();
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();
QDialog::accept(); QDialog::accept();
} }

View File

@ -26,8 +26,8 @@ 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,
, QWidget* parent = 0); bool autoResizeTableColumns, QWidget* parent = 0);
~ADSBDemodDisplayDialog(); ~ADSBDemodDisplayDialog();
int m_removeTimeout; int m_removeTimeout;
@ -38,6 +38,7 @@ public:
QString m_fontName; QString m_fontName;
int m_fontSize; int m_fontSize;
bool m_displayDemodStats; bool m_displayDemodStats;
bool m_autoResizeTableColumns;
private slots: private slots:
void accept(); void accept();

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>351</width> <width>351</width>
<height>275</height> <height>289</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -22,29 +22,52 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<layout class="QFormLayout" name="formLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="5" column="1">
<widget class="QLabel" name="unitsLabel"> <widget class="QPushButton" name="font">
<property name="toolTip">
<string>Select a font for the table</string>
</property>
<property name="text"> <property name="text">
<string>Units</string> <string>Select...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="4" column="0">
<widget class="QComboBox" name="units"> <widget class="QLabel" name="timeoutLabel">
<property name="text">
<string>Aircraft timeout (s)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="airportRange">
<property name="toolTip"> <property name="toolTip">
<string>The units to use for altitude, speed and climb rate</string> <string>Displays airports within the specified distance in kilometres from My Position</string>
</property> </property>
<item> <property name="maximum">
<property name="text"> <number>20000</number>
<string>ft, kn, ft/min</string>
</property> </property>
</widget>
</item> </item>
<item> <item row="2" column="0">
<property name="text"> <widget class="QCheckBox" name="heliports">
<string>m, kph, m/s</string> <property name="toolTip">
<string>When checked, heliports are displayed on the map</string>
</property> </property>
<property name="text">
<string>Display heliports</string>
</property>
</widget>
</item> </item>
<item row="4" column="1">
<widget class="QSpinBox" name="timeout">
<property name="toolTip">
<string>How long in seconds after not receving any frames will an aircraft be removed from the table and map</string>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
@ -54,6 +77,43 @@
</property> </property>
</widget> </widget>
</item> </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">
@ -76,75 +136,31 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="heliports"> <widget class="QCheckBox" name="autoResizeTableColumns">
<property name="toolTip"> <property name="toolTip">
<string>When checked, heliports are displayed on the map</string> <string>Resize the columns in the table after an aircraft is added to it</string>
</property> </property>
<property name="text"> <property name="text">
<string>Display heliports</string> <string>Resize columns after adding aircraft</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="0" column="1">
<widget class="QLabel" name="airportRangeLabel"> <widget class="QComboBox" name="units">
<property name="text">
<string>Airport display range (nm)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="airportRange">
<property name="toolTip"> <property name="toolTip">
<string>Displays airports within the specified range in nautical miles from My Position</string> <string>The units to use for altitude, speed and climb rate</string>
</property> </property>
<property name="maximum"> <item>
<number>20000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="text"> <property name="text">
<string>Aircraft timeout (s)</string> <string>ft, kn, ft/min</string>
</property> </property>
</widget>
</item> </item>
<item row="4" column="1"> <item>
<widget class="QSpinBox" name="timeout">
<property name="toolTip">
<string>How long in seconds after not receving 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="6" column="0">
<widget class="QLabel" name="fontLabel">
<property name="text"> <property name="text">
<string>Table font</string> <string>m, kph, m/s</string>
</property> </property>
</widget>
</item> </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="7" column="0">
<widget class="QCheckBox" name="displayStats">
<property name="toolTip">
<string>Display demodulator statistics</string>
</property>
<property name="text">
<string>Display demodulator statistics</string>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -26,6 +26,7 @@
#include <QQmlContext> #include <QQmlContext>
#include <QDesktopServices> #include <QDesktopServices>
#include <QUrl> #include <QUrl>
#include <QMessageBox>
#include <QDebug> #include <QDebug>
#include "ui_adsbdemodgui.h" #include "ui_adsbdemodgui.h"
@ -61,16 +62,17 @@
#define ADSB_COL_LONGITUDE 11 #define ADSB_COL_LONGITUDE 11
#define ADSB_COL_CATEGORY 12 #define ADSB_COL_CATEGORY 12
#define ADSB_COL_STATUS 13 #define ADSB_COL_STATUS 13
#define ADSB_COL_REGISTRATION 14 #define ADSB_COL_SQUAWK 14
#define ADSB_COL_COUNTRY 15 #define ADSB_COL_REGISTRATION 15
#define ADSB_COL_REGISTERED 16 #define ADSB_COL_COUNTRY 16
#define ADSB_COL_MANUFACTURER 17 #define ADSB_COL_REGISTERED 17
#define ADSB_COL_OWNER 18 #define ADSB_COL_MANUFACTURER 18
#define ADSB_COL_OPERATOR_ICAO 19 #define ADSB_COL_OWNER 19
#define ADSB_COL_TIME 20 #define ADSB_COL_OPERATOR_ICAO 20
#define ADSB_COL_FRAMECOUNT 21 #define ADSB_COL_TIME 21
#define ADSB_COL_CORRELATION 22 #define ADSB_COL_FRAMECOUNT 22
#define ADSB_COL_RSSI 23 #define ADSB_COL_CORRELATION 23
#define ADSB_COL_RSSI 24
const char *Aircraft::m_speedTypeNames[] = { const char *Aircraft::m_speedTypeNames[] = {
"GS", "TAS", "IAS" "GS", "TAS", "IAS"
@ -431,8 +433,12 @@ bool AirportModel::setData(const QModelIndex &index, const QVariant& value, int
int idx = value.toInt(); int idx = value.toInt();
if ((idx >= 0) && (idx < m_airports[row]->m_frequencies.size())) if ((idx >= 0) && (idx < m_airports[row]->m_frequencies.size()))
m_gui->setFrequency(m_airports[row]->m_frequencies[idx]->m_frequency * 1000000); m_gui->setFrequency(m_airports[row]->m_frequencies[idx]->m_frequency * 1000000);
else else if (idx == m_airports[row]->m_frequencies.size())
qDebug() << "AirportModel::setData unexpected idx " << idx << " frequencies.size() " << m_airports[row]->m_frequencies.size(); {
// Set airport as target
m_gui->target(m_azimuth[row], m_elevation[row]);
emit dataChanged(index, index);
}
return true; return true;
} }
return true; return true;
@ -468,6 +474,26 @@ void ADSBDemodGUI::updatePosition(Aircraft *aircraft)
m_adsbDemod->setTarget(aircraft->m_azimuth, aircraft->m_elevation); m_adsbDemod->setTarget(aircraft->m_azimuth, aircraft->m_elevation);
} }
// Called when we have lat & long from local decode and we need to check if it is in a valid range (<180nm/333km)
// or 1/4 of that for surface positions
bool ADSBDemodGUI::updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition)
{
// Calculate range to aircraft from station
m_azEl.setTarget(latitude, longitude, feetToMetres(aircraft->m_altitude));
m_azEl.calculate();
// Don't use the full 333km, as there may be some error in station position
if (m_azEl.getDistance() < (surfacePosition ? 80000 : 320000))
{
aircraft->m_latitude = latitude;
aircraft->m_longitude = longitude;
updatePosition(aircraft);
return true;
}
else
return false;
}
// Try to find an airline logo based on ICAO // Try to find an airline logo based on ICAO
QIcon *ADSBDemodGUI::getAirlineIcon(const QString &operatorICAO) QIcon *ADSBDemodGUI::getAirlineIcon(const QString &operatorICAO)
{ {
@ -617,6 +643,7 @@ void ADSBDemodGUI::handleADSB(
ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, aircraft->m_longitudeItem); ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, aircraft->m_longitudeItem);
ui->adsbData->setItem(row, ADSB_COL_CATEGORY, aircraft->m_emitterCategoryItem); ui->adsbData->setItem(row, ADSB_COL_CATEGORY, aircraft->m_emitterCategoryItem);
ui->adsbData->setItem(row, ADSB_COL_STATUS, aircraft->m_statusItem); ui->adsbData->setItem(row, ADSB_COL_STATUS, aircraft->m_statusItem);
ui->adsbData->setItem(row, ADSB_COL_SQUAWK, aircraft->m_squawkItem);
ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, aircraft->m_registrationItem); ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, aircraft->m_registrationItem);
ui->adsbData->setItem(row, ADSB_COL_COUNTRY, aircraft->m_countryItem); ui->adsbData->setItem(row, ADSB_COL_COUNTRY, aircraft->m_countryItem);
ui->adsbData->setItem(row, ADSB_COL_REGISTERED, aircraft->m_registeredItem); ui->adsbData->setItem(row, ADSB_COL_REGISTERED, aircraft->m_registeredItem);
@ -696,6 +723,8 @@ void ADSBDemodGUI::handleADSB(
} }
} }
} }
if (m_settings.m_autoResizeTableColumns)
ui->adsbData->resizeColumnsToContents();
ui->adsbData->setSortingEnabled(true); ui->adsbData->setSortingEnabled(true);
} }
aircraft->m_time = dateTime; aircraft->m_time = dateTime;
@ -718,6 +747,7 @@ void ADSBDemodGUI::handleADSB(
m_correlationOnesAvg(correlationOnes); m_correlationOnesAvg(correlationOnes);
aircraft->m_rssiItem->setText(QString("%1") aircraft->m_rssiItem->setText(QString("%1")
.arg(CalcDb::dbPower(m_correlationOnesAvg.instantAverage()), 3, 'f', 1)); .arg(CalcDb::dbPower(m_correlationOnesAvg.instantAverage()), 3, 'f', 1));
// ADS-B, non-transponder ADS-B or TIS-B rebroadcast of ADS-B (ADS-R) // ADS-B, non-transponder ADS-B or TIS-B rebroadcast of ADS-B (ADS-R)
if ((df == 17) || ((df == 18) && ((ca == 0) || (ca == 1) || (ca == 6)))) if ((df == 17) || ((df == 18) && ((ca == 0) || (ca == 1) || (ca == 6))))
{ {
@ -754,15 +784,96 @@ void ADSBDemodGUI::handleADSB(
aircraft->m_flight = QString(callsign); aircraft->m_flight = QString(callsign);
aircraft->m_flightItem->setText(aircraft->m_flight); aircraft->m_flightItem->setText(aircraft->m_flight);
} }
else if ((tc >= 5) && (tc <= 8)) else if (((tc >= 5) && (tc <= 18)) || ((tc >= 20) && (tc <= 22)))
{
bool surfacePosition = (tc >= 5) && (tc <= 8);
if (surfacePosition)
{ {
// Surface position // Surface position
// Set altitude to 0, if we're on the surface
// Actual altitude may of course depend on airport elevation
aircraft->m_altitudeValid = true;
aircraft->m_altitudeItem->setData(Qt::DisplayRole, 0);
int movement = ((data[4] & 0x7) << 4) | ((data[5] >> 4) & 0xf);
if (movement == 0)
{
// No information available
aircraft->m_speedValid = false;
aircraft->m_speedItem->setData(Qt::DisplayRole, "");
}
else if (movement == 1)
{
// Aircraft stopped
aircraft->m_speedValid = true;
aircraft->m_speedItem->setData(Qt::DisplayRole, 0);
}
else if ((movement >= 2) && (movement <= 123))
{
float base, step; // In knts
int adjust;
if ((movement >= 2) && (movement <= 8))
{
base = 0.125f;
step = 0.125f;
adjust = 2;
}
else if ((movement >= 9) && (movement <= 12))
{
base = 1.0f;
step = 0.25f;
adjust = 9;
}
else if ((movement >= 13) && (movement <= 38))
{
base = 2.0f;
step = 0.5f;
adjust = 13;
}
else if ((movement >= 39) && (movement <= 93))
{
base = 15.0f;
step = 1.0f;
adjust = 39;
}
else if ((movement >= 94) && (movement <= 108))
{
base = 70.0f;
step = 2.0f;
adjust = 94;
}
else
{
base = 100.0f;
step = 5.0f;
adjust = 109;
}
float speed = base + (movement - adjust) * step;
aircraft->m_speedType = Aircraft::GS;
aircraft->m_speedValid = true;
aircraft->m_speedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? knotsToKPHInt(aircraft->m_speed) : (int)std::round(aircraft->m_speed));
}
else if (movement == 124)
{
aircraft->m_speedValid = true;
aircraft->m_speedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? 324 : 175); // Actually greater than this
}
int groundTrackStatus = (data[5] >> 3) & 1;
int groundTrackValue = ((data[5] & 0x7) << 4) | ((data[6] >> 4) & 0xf);
if (groundTrackStatus)
{
aircraft->m_heading = std::round((groundTrackValue * 360.0/128.0));
aircraft->m_headingValid = true;
aircraft->m_headingItem->setData(Qt::DisplayRole, aircraft->m_heading);
}
} }
else if (((tc >= 9) && (tc <= 18)) || ((tc >= 20) && (tc <= 22))) else if (((tc >= 9) && (tc <= 18)) || ((tc >= 20) && (tc <= 22)))
{ {
// Airbourne position (9-18 baro, 20-22 GNSS) // Airbourne position (9-18 baro, 20-22 GNSS)
int ss = (data[4] >> 1) & 0x3; // Surveillance status int ss = (data[4] >> 1) & 0x3; // Surveillance status
int nicsb = data[4] & 1; // NIC supplement-B int nicsb = data[4] & 1; // NIC supplement-B - Or single antenna flag
int alt = ((data[5] & 0xff) << 4) | ((data[6] >> 4) & 0xf); // Altitude int alt = ((data[5] & 0xff) << 4) | ((data[6] >> 4) & 0xf); // Altitude
int n = ((alt >> 1) & 0x7f0) | (alt & 0xf); int n = ((alt >> 1) & 0x7f0) | (alt & 0xf);
int alt_ft = n * ((alt & 0x10) ? 25 : 100) - 1000; int alt_ft = n * ((alt & 0x10) ? 25 : 100) - 1000;
@ -771,6 +882,7 @@ void ADSBDemodGUI::handleADSB(
aircraft->m_altitudeValid = true; aircraft->m_altitudeValid = true;
// setData rather than setText so it sorts numerically // setData rather than setText so it sorts numerically
aircraft->m_altitudeItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? feetToMetresInt(aircraft->m_altitude) : aircraft->m_altitude); aircraft->m_altitudeItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? feetToMetresInt(aircraft->m_altitude) : aircraft->m_altitude);
}
int t = (data[6] >> 3) & 1; // Time synchronisation to UTC int t = (data[6] >> 3) & 1; // Time synchronisation to UTC
int f = (data[6] >> 2) & 1; // CPR odd/even frame - should alternate every 0.2s int f = (data[6] >> 2) & 1; // CPR odd/even frame - should alternate every 0.2s
@ -789,7 +901,8 @@ void ADSBDemodGUI::handleADSB(
// We also need to check that both frames aren't greater than 10s apart in time (C.2.6.7), otherwise position may be out by ~10deg // We also need to check that both frames aren't greater than 10s apart in time (C.2.6.7), otherwise position may be out by ~10deg
// We could compare global + local methods to see if the positions are sensible // We could compare global + local methods to see if the positions are sensible
if (aircraft->m_cprValid[0] && aircraft->m_cprValid[1] if (aircraft->m_cprValid[0] && aircraft->m_cprValid[1]
&& (std::abs(aircraft->m_cprTime[0].toSecsSinceEpoch() - aircraft->m_cprTime[1].toSecsSinceEpoch()) < 10)) && (std::abs(aircraft->m_cprTime[0].toSecsSinceEpoch() - aircraft->m_cprTime[1].toSecsSinceEpoch()) < 10)
&& !surfacePosition)
{ {
// Global decode using odd and even frames (C.2.6) // Global decode using odd and even frames (C.2.6)
@ -801,21 +914,19 @@ void ADSBDemodGUI::handleADSB(
int ni, m; int ni, m;
int j = std::floor(59.0f*aircraft->m_cprLat[0] - 60.0f*aircraft->m_cprLat[1] + 0.5); int j = std::floor(59.0f*aircraft->m_cprLat[0] - 60.0f*aircraft->m_cprLat[1] + 0.5);
latEven = dLatEven * ((j % 60) + aircraft->m_cprLat[0]); latEven = dLatEven * (modulus(j, 60) + aircraft->m_cprLat[0]);
// Southern hemisphere is in range 270-360, so adjust to -90-0
if (latEven >= 270.0f) if (latEven >= 270.0f)
latEven -= 360.0f; latEven -= 360.0f;
else if (latEven <= -270.0f) latOdd = dLatOdd * (modulus(j, 59) + aircraft->m_cprLat[1]);
latEven += 360.0f;
latOdd = dLatOdd * ((j % 59) + aircraft->m_cprLat[1]);
if (latOdd >= 270.0f) if (latOdd >= 270.0f)
latOdd -= 360.0f; latOdd -= 360.0f;
else if (latOdd <= -270.0f)
latOdd += 360.0f;
if (aircraft->m_cprTime[0] >= aircraft->m_cprTime[1]) if (aircraft->m_cprTime[0] >= aircraft->m_cprTime[1])
latitude = latEven; latitude = latEven;
else else
latitude = latOdd; latitude = latOdd;
if ((latitude <= 90.0) && (latitude >= -90.0))
{
// Check if both frames in same latitude zone // Check if both frames in same latitude zone
int latEvenNL = cprNL(latEven); int latEvenNL = cprNL(latEven);
int latOddNL = cprNL(latOdd); int latOddNL = cprNL(latOdd);
@ -845,40 +956,57 @@ void ADSBDemodGUI::handleADSB(
} }
} }
else else
{
qDebug() << "ADSBDemodGUI::handleADSB: Invalid latitude " << latitude << " for " << QString("%1").arg(aircraft->m_icao, 1, 16)
<< " m_cprLat[0] " << aircraft->m_cprLat[0]
<< " m_cprLat[1] " << aircraft->m_cprLat[1];
aircraft->m_cprValid[0] = false;
aircraft->m_cprValid[1] = false;
}
}
else
{ {
// Local decode using a single aircraft position + location of receiver // Local decode using a single aircraft position + location of receiver
// Only valid if within 180nm (C.2.6.4) // Only valid if airbourne within 180nm/333km (C.2.6.4) or 45nm for surface
// Caclulate latitude // Caclulate latitude
const double dLatEven = 360.0/60.0; const double maxDeg = surfacePosition ? 90.0 : 360.0;
const double dLatOdd = 360.0/59.0; const double dLatEven = maxDeg/60.0;
const double dLatOdd = maxDeg/59.0;
double dLat = f ? dLatOdd : dLatEven; double dLat = f ? dLatOdd : dLatEven;
double latitude, longitude;
int j = std::floor(m_azEl.getLocationSpherical().m_latitude/dLat) + std::floor(modulus(m_azEl.getLocationSpherical().m_latitude, dLat)/dLat - aircraft->m_cprLat[f] + 0.5); int j = std::floor(m_azEl.getLocationSpherical().m_latitude/dLat) + std::floor(modulus(m_azEl.getLocationSpherical().m_latitude, dLat)/dLat - aircraft->m_cprLat[f] + 0.5);
aircraft->m_latitude = dLat * (j + aircraft->m_cprLat[f]); latitude = dLat * (j + aircraft->m_cprLat[f]);
aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude);
// Caclulate longitude // Caclulate longitude
double dLong; double dLong;
int latNL = cprNL(aircraft->m_latitude); int latNL = cprNL(latitude);
if (f == 0) if (f == 0)
{ {
if (latNL > 0) if (latNL > 0)
dLong = 360.0 / latNL; dLong = maxDeg / latNL;
else else
dLong = 360.0; dLong = maxDeg;
} }
else else
{ {
if ((latNL - 1) > 0) if ((latNL - 1) > 0)
dLong = 360.0 / (latNL - 1); dLong = maxDeg / (latNL - 1);
else else
dLong = 360.0; dLong = maxDeg;
} }
int m = std::floor(m_azEl.getLocationSpherical().m_longitude/dLong) + std::floor(modulus(m_azEl.getLocationSpherical().m_longitude, dLong)/dLong - aircraft->m_cprLong[f] + 0.5); int m = std::floor(m_azEl.getLocationSpherical().m_longitude/dLong) + std::floor(modulus(m_azEl.getLocationSpherical().m_longitude, dLong)/dLong - aircraft->m_cprLong[f] + 0.5);
aircraft->m_longitude = dLong * (m + aircraft->m_cprLong[f]); longitude = dLong * (m + aircraft->m_cprLong[f]);
if (updateLocalPosition(aircraft, latitude, longitude, surfacePosition))
{
aircraft->m_latitude = latitude;
aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude);
aircraft->m_longitude = longitude;
aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude); aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude);
aircraft->m_coordinates.push_back(QVariant::fromValue(*new QGeoCoordinate(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude))); aircraft->m_coordinates.push_back(QVariant::fromValue(*new QGeoCoordinate(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude)));
updatePosition(aircraft); }
} }
} }
else if (tc == 19) else if (tc == 19)
@ -957,12 +1085,27 @@ void ADSBDemodGUI::handleADSB(
{ {
// Aircraft status // Aircraft status
int st = data[4] & 0x7; // Subtype int st = data[4] & 0x7; // Subtype
int es = (data[5] >> 5) & 0x7; // Emergency state
if (st == 1) if (st == 1)
{
int es = (data[5] >> 5) & 0x7; // Emergency state
int modeA = ((data[5] << 8) & 0x1f00) | (data[6] & 0xff); // Mode-A code (squawk)
aircraft->m_status = emergencyStatus[es]; aircraft->m_status = emergencyStatus[es];
else
aircraft->m_status = QString("");
aircraft->m_statusItem->setText(aircraft->m_status); aircraft->m_statusItem->setText(aircraft->m_status);
int a, b, c, d;
c = ((modeA >> 12) & 1) | ((modeA >> (10-1)) & 0x2) | ((modeA >> (8-2)) & 0x4);
a = ((modeA >> 11) & 1) | ((modeA >> (9-1)) & 0x2) | ((modeA >> (7-2)) & 0x4);
b = ((modeA >> 5) & 1) | ((modeA >> (3-1)) & 0x2) | ((modeA << (1)) & 0x4);
d = ((modeA >> 4) & 1) | ((modeA >> (2-1)) & 0x2) | ((modeA << (2)) & 0x4);
aircraft->m_squawk = a*1000 + b*100 + c*10 + d;
if (modeA & 0x40)
aircraft->m_squawkItem->setText(QString("%1 IDENT").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0')));
else
aircraft->m_squawkItem->setText(QString("%1").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0')));
}
else if (st == 2)
{
// TCAS/ACAS RA Broadcast
}
} }
else if (tc == 29) else if (tc == 29)
{ {
@ -1071,6 +1214,21 @@ void ADSBDemodGUI::on_threshold_valueChanged(int value)
applySettings(); applySettings();
} }
void ADSBDemodGUI::on_phaseSteps_valueChanged(int value)
{
ui->phaseStepsText->setText(QString("%1").arg(value));
m_settings.m_interpolatorPhaseSteps = value;
applySettings();
}
void ADSBDemodGUI::on_tapsPerPhase_valueChanged(int value)
{
Real tapsPerPhase = ((Real)value)/10.0f;
ui->tapsPerPhaseText->setText(QString("%1").arg(tapsPerPhase, 0, 'f', 1));
m_settings.m_interpolatorTapsPerPhase = tapsPerPhase;
applySettings();
}
void ADSBDemodGUI::on_feed_clicked(bool checked) void ADSBDemodGUI::on_feed_clicked(bool checked)
{ {
m_settings.m_feedEnabled = checked; m_settings.m_feedEnabled = checked;
@ -1193,6 +1351,9 @@ void ADSBDemodGUI::on_getOSNDB_clicked(bool checked)
{ {
// Don't try to download while already in progress // Don't try to download while already in progress
if (m_progressDialog == nullptr) if (m_progressDialog == nullptr)
{
QString osnDBFilename = getOSNDBFilename();
if (confirmDownload(osnDBFilename))
{ {
// Download Opensky network database to a file // Download Opensky network database to a file
QUrl dbURL(QString(OSNDB_URL)); QUrl dbURL(QString(OSNDB_URL));
@ -1200,15 +1361,19 @@ void ADSBDemodGUI::on_getOSNDB_clicked(bool checked)
m_progressDialog->setAttribute(Qt::WA_DeleteOnClose); m_progressDialog->setAttribute(Qt::WA_DeleteOnClose);
m_progressDialog->setCancelButton(nullptr); m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(OSNDB_URL)); m_progressDialog->setLabelText(QString("Downloading %1.").arg(OSNDB_URL));
QNetworkReply *reply = m_dlm.download(dbURL, getOSNDBFilename()); QNetworkReply *reply = m_dlm.download(dbURL, osnDBFilename);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64))); connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
} }
} }
}
void ADSBDemodGUI::on_getAirportDB_clicked(bool checked) void ADSBDemodGUI::on_getAirportDB_clicked(bool checked)
{ {
// Don't try to download while already in progress // Don't try to download while already in progress
if (m_progressDialog == nullptr) if (m_progressDialog == nullptr)
{
QString airportDBFile = getAirportDBFilename();
if (confirmDownload(airportDBFile))
{ {
// Download Opensky network database to a file // Download Opensky network database to a file
QUrl dbURL(QString(AIRPORTS_URL)); QUrl dbURL(QString(AIRPORTS_URL));
@ -1216,10 +1381,11 @@ void ADSBDemodGUI::on_getAirportDB_clicked(bool checked)
m_progressDialog->setAttribute(Qt::WA_DeleteOnClose); m_progressDialog->setAttribute(Qt::WA_DeleteOnClose);
m_progressDialog->setCancelButton(nullptr); m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(AIRPORTS_URL)); m_progressDialog->setLabelText(QString("Downloading %1.").arg(AIRPORTS_URL));
QNetworkReply *reply = m_dlm.download(dbURL, getAirportDBFilename()); QNetworkReply *reply = m_dlm.download(dbURL, airportDBFile);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64))); connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
} }
} }
}
void ADSBDemodGUI::on_flightPaths_clicked(bool checked) void ADSBDemodGUI::on_flightPaths_clicked(bool checked)
{ {
@ -1255,6 +1421,38 @@ QString ADSBDemodGUI::getFastDBFilename()
return getDataDir() + "/aircraftDatabaseFast.csv"; return getDataDir() + "/aircraftDatabaseFast.csv";
} }
qint64 ADSBDemodGUI::fileAgeInDays(QString filename)
{
QFile file(filename);
if (file.exists())
{
QDateTime modified = file.fileTime(QFileDevice::FileModificationTime);
if (modified.isValid())
return modified.daysTo(QDateTime::currentDateTime());
else
return -1;
}
return -1;
}
bool ADSBDemodGUI::confirmDownload(QString filename)
{
qint64 age = fileAgeInDays(filename);
if ((age == -1) || (age > 100))
return true;
else
{
QMessageBox::StandardButton reply;
if (age == 0)
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded today. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
else if (age == 1)
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded yesterday. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
else
reply = QMessageBox::question(this, "Confirm download", QString("This file was last downloaded %1 days ago. Are you sure you wish to redownload this file?").arg(age), QMessageBox::Yes|QMessageBox::No);
return reply == QMessageBox::Yes;
}
}
bool ADSBDemodGUI::readOSNDB(const QString& filename) bool ADSBDemodGUI::readOSNDB(const QString& filename)
{ {
m_aircraftInfo = AircraftInformation::readOSNDB(filename); m_aircraftInfo = AircraftInformation::readOSNDB(filename);
@ -1439,15 +1637,18 @@ void ADSBDemodGUI::updateAirports()
{ {
m_airportModel.removeAllAirports(); m_airportModel.removeAllAirports();
QHash<int, AirportInformation *>::iterator i = m_airportInfo->begin(); QHash<int, AirportInformation *>::iterator i = m_airportInfo->begin();
AzEl azEl = m_azEl;
while (i != m_airportInfo->end()) while (i != m_airportInfo->end())
{ {
AirportInformation *airportInfo = i.value(); AirportInformation *airportInfo = i.value();
// Calculate range to airport from My Position - One degree = 60 nautical miles
float latDiff = std::fabs(airportInfo->m_latitude - m_azEl.getLocationSpherical().m_latitude) * 60.0f; // Calculate distance and az/el to airport from My Position
float longDiff = std::fabs(airportInfo->m_longitude - m_azEl.getLocationSpherical().m_longitude) * 60.0f; azEl.setTarget(airportInfo->m_latitude, airportInfo->m_longitude, feetToMetres(airportInfo->m_elevation));
float range = sqrt(latDiff*latDiff+longDiff*longDiff); azEl.calculate();
// Only display airport if in range // Only display airport if in range
if (range <= m_settings.m_airportRange) if (azEl.getDistance() <= m_settings.m_airportRange*1000.0f)
{ {
// Only display the airport if it's large enough // Only display the airport if it's large enough
if (airportInfo->m_type >= m_settings.m_airportMinimumSize) if (airportInfo->m_type >= m_settings.m_airportMinimumSize)
@ -1455,7 +1656,7 @@ void ADSBDemodGUI::updateAirports()
// Only display heliports if enabled // Only display heliports if enabled
if (m_settings.m_displayHeliports || (airportInfo->m_type != ADSBDemodSettings::AirportType::Heliport)) if (m_settings.m_displayHeliports || (airportInfo->m_type != ADSBDemodSettings::AirportType::Heliport))
{ {
m_airportModel.addAirport(airportInfo); m_airportModel.addAirport(airportInfo, azEl.getAzimuth(), azEl.getElevation(), azEl.getDistance());
} }
} }
} }
@ -1467,6 +1668,19 @@ void ADSBDemodGUI::updateAirports()
m_currentDisplayHeliports = m_settings.m_displayHeliports; m_currentDisplayHeliports = m_settings.m_displayHeliports;
} }
// Set a static target, such as an airport
void ADSBDemodGUI::target(float az, float el)
{
if (m_trackAircraft)
{
// Restore colour of current target
m_trackAircraft->m_isTarget = false;
m_aircraftModel.aircraftUpdated(m_trackAircraft);
m_trackAircraft = nullptr;
}
m_adsbDemod->setTarget(az, el);
}
void ADSBDemodGUI::targetAircraft(Aircraft *aircraft) void ADSBDemodGUI::targetAircraft(Aircraft *aircraft)
{ {
if (aircraft != m_trackAircraft) if (aircraft != m_trackAircraft)
@ -1526,7 +1740,7 @@ void ADSBDemodGUI::on_displaySettings_clicked(bool checked)
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_displayDemodStats, m_settings.m_autoResizeTableColumns);
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;
@ -1539,6 +1753,7 @@ void ADSBDemodGUI::on_displaySettings_clicked(bool checked)
m_settings.m_tableFontName = dialog.m_fontName; m_settings.m_tableFontName = dialog.m_fontName;
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;
if (unitsChanged) if (unitsChanged)
m_aircraftModel.allAircraftUpdated(); m_aircraftModel.allAircraftUpdated();
@ -1727,6 +1942,18 @@ void ADSBDemodGUI::displaySettings()
ui->thresholdText->setText(QString("%1").arg(m_settings.m_correlationThreshold, 0, 'f', 1)); ui->thresholdText->setText(QString("%1").arg(m_settings.m_correlationThreshold, 0, 'f', 1));
ui->threshold->setValue((int)(m_settings.m_correlationThreshold*10.0f)); ui->threshold->setValue((int)(m_settings.m_correlationThreshold*10.0f));
ui->phaseStepsText->setText(QString("%1").arg(m_settings.m_interpolatorPhaseSteps));
ui->phaseSteps->setValue(m_settings.m_interpolatorPhaseSteps);
ui->tapsPerPhaseText->setText(QString("%1").arg(m_settings.m_interpolatorTapsPerPhase, 0, 'f', 1));
ui->tapsPerPhase->setValue((int)(m_settings.m_interpolatorTapsPerPhase*10.0f));
// Enable these controls only for developers
if (1)
{
ui->phaseStepsText->setVisible(false);
ui->phaseSteps->setVisible(false);
ui->tapsPerPhaseText->setVisible(false);
ui->tapsPerPhase->setVisible(false);
}
ui->feed->setChecked(m_settings.m_feedEnabled); ui->feed->setChecked(m_settings.m_feedEnabled);
ui->flightPaths->setChecked(m_settings.m_flightPaths); ui->flightPaths->setChecked(m_settings.m_flightPaths);
@ -1869,9 +2096,10 @@ void ADSBDemodGUI::resizeTable()
ui->adsbData->setItem(row, ADSB_COL_RANGE, new QTableWidgetItem("D (km)")); ui->adsbData->setItem(row, ADSB_COL_RANGE, new QTableWidgetItem("D (km)"));
ui->adsbData->setItem(row, ADSB_COL_AZEL, new QTableWidgetItem("Az/El (o)")); ui->adsbData->setItem(row, ADSB_COL_AZEL, new QTableWidgetItem("Az/El (o)"));
ui->adsbData->setItem(row, ADSB_COL_LATITUDE, new QTableWidgetItem("-90.00000")); ui->adsbData->setItem(row, ADSB_COL_LATITUDE, new QTableWidgetItem("-90.00000"));
ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, new QTableWidgetItem("-180.00000")); ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, new QTableWidgetItem("-180.000000"));
ui->adsbData->setItem(row, ADSB_COL_CATEGORY, new QTableWidgetItem("Heavy")); ui->adsbData->setItem(row, ADSB_COL_CATEGORY, new QTableWidgetItem("Heavy"));
ui->adsbData->setItem(row, ADSB_COL_STATUS, new QTableWidgetItem("No emergency")); ui->adsbData->setItem(row, ADSB_COL_STATUS, new QTableWidgetItem("No emergency"));
ui->adsbData->setItem(row, ADSB_COL_SQUAWK, new QTableWidgetItem("Squawk"));
ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, new QTableWidgetItem("G-12345")); ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, new QTableWidgetItem("G-12345"));
ui->adsbData->setItem(row, ADSB_COL_COUNTRY, new QTableWidgetItem("Country")); ui->adsbData->setItem(row, ADSB_COL_COUNTRY, new QTableWidgetItem("Country"));
ui->adsbData->setItem(row, ADSB_COL_REGISTERED, new QTableWidgetItem("Registered")); ui->adsbData->setItem(row, ADSB_COL_REGISTERED, new QTableWidgetItem("Registered"));
@ -1897,6 +2125,7 @@ void ADSBDemodGUI::resizeTable()
ui->adsbData->removeCellWidget(row, ADSB_COL_LONGITUDE); ui->adsbData->removeCellWidget(row, ADSB_COL_LONGITUDE);
ui->adsbData->removeCellWidget(row, ADSB_COL_CATEGORY); ui->adsbData->removeCellWidget(row, ADSB_COL_CATEGORY);
ui->adsbData->removeCellWidget(row, ADSB_COL_STATUS); 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_REGISTRATION);
ui->adsbData->removeCellWidget(row, ADSB_COL_COUNTRY); ui->adsbData->removeCellWidget(row, ADSB_COL_COUNTRY);
ui->adsbData->removeCellWidget(row, ADSB_COL_REGISTERED); ui->adsbData->removeCellWidget(row, ADSB_COL_REGISTERED);

View File

@ -91,6 +91,7 @@ struct Aircraft {
int m_verticalRate; // Vertical climb rate in ft/min int m_verticalRate; // Vertical climb rate in ft/min
QString m_emitterCategory; // Aircraft type QString m_emitterCategory; // Aircraft type
QString m_status; // Aircraft status QString m_status; // Aircraft status
int m_squawk; // Mode-A code
Real m_range; // Distance from station to aircraft Real m_range; // Distance from station to aircraft
Real m_azimuth; // Azimuth from station to aircraft Real m_azimuth; // Azimuth from station to aircraft
Real m_elevation; // Elevation from station to aicraft; Real m_elevation; // Elevation from station to aicraft;
@ -138,6 +139,7 @@ struct Aircraft {
QTableWidgetItem *m_azElItem; QTableWidgetItem *m_azElItem;
QTableWidgetItem *m_emitterCategoryItem; QTableWidgetItem *m_emitterCategoryItem;
QTableWidgetItem *m_statusItem; QTableWidgetItem *m_statusItem;
QTableWidgetItem *m_squawkItem;
QTableWidgetItem *m_registrationItem; QTableWidgetItem *m_registrationItem;
QTableWidgetItem *m_countryItem; QTableWidgetItem *m_countryItem;
QTableWidgetItem *m_registeredItem; QTableWidgetItem *m_registeredItem;
@ -193,6 +195,7 @@ struct Aircraft {
m_longitudeItem = new QTableWidgetItem(); m_longitudeItem = new QTableWidgetItem();
m_emitterCategoryItem = new QTableWidgetItem(); m_emitterCategoryItem = new QTableWidgetItem();
m_statusItem = new QTableWidgetItem(); m_statusItem = new QTableWidgetItem();
m_squawkItem = new QTableWidgetItem();
m_registrationItem = new QTableWidgetItem(); m_registrationItem = new QTableWidgetItem();
m_countryItem = new QTableWidgetItem(); m_countryItem = new QTableWidgetItem();
m_registeredItem = new QTableWidgetItem(); m_registeredItem = new QTableWidgetItem();
@ -321,16 +324,18 @@ public:
{ {
} }
Q_INVOKABLE void addAirport(AirportInformation *airport) { Q_INVOKABLE void addAirport(AirportInformation *airport, float az, float el, float distance) {
QString text; QString text;
int rows; int rows;
beginInsertRows(QModelIndex(), rowCount(), rowCount()); beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_airports.append(airport); m_airports.append(airport);
airportFreq(airport, text, rows); airportFreq(airport, az, el, distance, text, rows);
m_airportDataFreq.append(text); m_airportDataFreq.append(text);
m_airportDataFreqRows.append(rows); m_airportDataFreqRows.append(rows);
m_showFreq.append(false); m_showFreq.append(false);
m_azimuth.append(az);
m_elevation.append(el);
endInsertRows(); endInsertRows();
} }
@ -343,6 +348,8 @@ public:
m_airportDataFreq.removeAt(row); m_airportDataFreq.removeAt(row);
m_airportDataFreqRows.removeAt(row); m_airportDataFreqRows.removeAt(row);
m_showFreq.removeAt(row); m_showFreq.removeAt(row);
m_azimuth.removeAt(row);
m_elevation.removeAt(row);
endRemoveRows(); endRemoveRows();
} }
} }
@ -353,6 +360,8 @@ public:
m_airportDataFreq.clear(); m_airportDataFreq.clear();
m_airportDataFreqRows.clear(); m_airportDataFreqRows.clear();
m_showFreq.clear(); m_showFreq.clear();
m_azimuth.clear();
m_elevation.clear();
endRemoveRows(); endRemoveRows();
} }
@ -369,7 +378,7 @@ public:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
} }
void airportFreq(AirportInformation *airport, QString& text, int& rows) { void airportFreq(AirportInformation *airport, float az, float el, float distance, QString& text, int& rows) {
// Create the text to go in the bubble next to the airport // Create the text to go in the bubble next to the airport
// Display name and frequencies // Display name and frequencies
QStringList list; QStringList list;
@ -382,6 +391,9 @@ public:
list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency)); list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency));
rows++; rows++;
} }
list.append(QString("Az/El: %1/%2").arg((int)std::round(az)).arg((int)std::round(el)));
list.append(QString("Distance: %1 km").arg(distance/1000.0f, 0, 'f', 1));
rows += 2;
text = list.join("\n"); text = list.join("\n");
} }
@ -412,6 +424,8 @@ private:
QList<QString> m_airportDataFreq; QList<QString> m_airportDataFreq;
QList<int> m_airportDataFreqRows; QList<int> m_airportDataFreqRows;
QList<bool> m_showFreq; QList<bool> m_showFreq;
QList<float> m_azimuth;
QList<float> m_elevation;
}; };
class ADSBDemodGUI : public ChannelGUI { class ADSBDemodGUI : public ChannelGUI {
@ -427,6 +441,7 @@ public:
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void highlightAircraft(Aircraft *aircraft); void highlightAircraft(Aircraft *aircraft);
void targetAircraft(Aircraft *aircraft); void targetAircraft(Aircraft *aircraft);
void target(float az, float el);
bool setFrequency(float frequency); bool setFrequency(float frequency);
bool useSIUints() { return m_settings.m_siUnits; } bool useSIUints() { return m_settings.m_siUnits; }
@ -482,6 +497,7 @@ private:
void displayStreamIndex(); void displayStreamIndex();
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
void updatePosition(Aircraft *aircraft); void updatePosition(Aircraft *aircraft);
bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition);
void handleADSB( void handleADSB(
const QByteArray data, const QByteArray data,
const QDateTime dateTime, const QDateTime dateTime,
@ -493,6 +509,8 @@ private:
QString getAirportFrequenciesDBFilename(); QString getAirportFrequenciesDBFilename();
QString getOSNDBFilename(); QString getOSNDBFilename();
QString getFastDBFilename(); QString getFastDBFilename();
qint64 fileAgeInDays(QString filename);
bool confirmDownload(QString filename);
void readAirportDB(const QString& filename); void readAirportDB(const QString& filename);
void readAirportFrequenciesDB(const QString& filename); void readAirportFrequenciesDB(const QString& filename);
bool readOSNDB(const QString& filename); bool readOSNDB(const QString& filename);
@ -510,6 +528,8 @@ private slots:
void on_deltaFrequency_changed(qint64 value); void on_deltaFrequency_changed(qint64 value);
void on_rfBW_valueChanged(int value); void on_rfBW_valueChanged(int value);
void on_threshold_valueChanged(int value); void on_threshold_valueChanged(int value);
void on_phaseSteps_valueChanged(int value);
void on_tapsPerPhase_valueChanged(int value);
void on_adsbData_cellClicked(int row, int column); void on_adsbData_cellClicked(int row, int column);
void on_adsbData_cellDoubleClicked(int row, int column); void on_adsbData_cellDoubleClicked(int row, int column);
void adsbData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); void adsbData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>384</width> <width>435</width>
<height>1046</height> <height>1046</height>
</rect> </rect>
</property> </property>
@ -36,7 +36,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>381</width> <width>431</width>
<height>141</height> <height>141</height>
</rect> </rect>
</property> </property>
@ -167,6 +167,13 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="levelMeterLayout"> <layout class="QHBoxLayout" name="levelMeterLayout">
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="channelPowerMeterUnits"> <widget class="QLabel" name="channelPowerMeterUnits">
<property name="text"> <property name="text">
@ -203,10 +210,17 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="rfBWLayout"> <layout class="QHBoxLayout" name="rfBWLayout">
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="rfBWLabel"> <widget class="QLabel" name="rfBWLabel">
<property name="text"> <property name="text">
<string>RFBW</string> <string>BW</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -262,6 +276,85 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QDial" name="phaseSteps">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Interpolator phase steps</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>16</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>16</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="phaseStepsText">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>12</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="tapsPerPhase">
<property name="enabled">
<bool>true</bool>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Interpolator taps per phase</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>60</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>45</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tapsPerPhaseText">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>4.5</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="spbrLabel"> <widget class="QLabel" name="spbrLabel">
<property name="text"> <property name="text">
@ -547,7 +640,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>140</y> <y>140</y>
<width>381</width> <width>431</width>
<height>291</height> <height>291</height>
</rect> </rect>
</property> </property>
@ -693,6 +786,14 @@
<string>Aircraft emergency status</string> <string>Aircraft emergency status</string>
</property> </property>
</column> </column>
<column>
<property name="text">
<string>Squawk</string>
</property>
<property name="toolTip">
<string>Mode-A transponder code</string>
</property>
</column>
<column> <column>
<property name="text"> <property name="text">
<string>Reg</string> <string>Reg</string>
@ -779,7 +880,7 @@
<rect> <rect>
<x>10</x> <x>10</x>
<y>450</y> <y>450</y>
<width>361</width> <width>421</width>
<height>581</height> <height>581</height>
</rect> </rect>
</property> </property>
@ -844,6 +945,11 @@
<extends>QWidget</extends> <extends>QWidget</extends>
<header location="global">QtQuickWidgets/QQuickWidget</header> <header location="global">QtQuickWidgets/QQuickWidget</header>
</customwidget> </customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget> <customwidget>
<class>RollupWidget</class> <class>RollupWidget</class>
<extends>QWidget</extends> <extends>QWidget</extends>
@ -862,17 +968,25 @@
<header>gui/valuedialz.h</header> <header>gui/valuedialz.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>deltaFrequency</tabstop> <tabstop>deltaFrequency</tabstop>
<tabstop>rfBW</tabstop> <tabstop>rfBW</tabstop>
<tabstop>phaseSteps</tabstop>
<tabstop>tapsPerPhase</tabstop>
<tabstop>spb</tabstop> <tabstop>spb</tabstop>
<tabstop>demodModeS</tabstop>
<tabstop>correlateFullPreamble</tabstop>
<tabstop>threshold</tabstop> <tabstop>threshold</tabstop>
<tabstop>getOSNDB</tabstop>
<tabstop>getAirportDB</tabstop>
<tabstop>displaySettings</tabstop>
<tabstop>flightPaths</tabstop>
<tabstop>feed</tabstop>
<tabstop>devicesRefresh</tabstop>
<tabstop>device</tabstop>
<tabstop>adsbData</tabstop>
<tabstop>map</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="../../../sdrgui/resources/res.qrc"/> <include location="../../../sdrgui/resources/res.qrc"/>

View File

@ -60,6 +60,9 @@ void ADSBDemodSettings::resetToDefaults()
m_correlateFullPreamble = true; m_correlateFullPreamble = true;
m_demodModeS = false; m_demodModeS = false;
m_deviceIndex = -1; m_deviceIndex = -1;
m_autoResizeTableColumns = false;
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
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
{ {
m_columnIndexes[i] = i; m_columnIndexes[i] = i;
@ -103,6 +106,9 @@ QByteArray ADSBDemodSettings::serialize() const
s.writeBool(27, m_displayDemodStats); s.writeBool(27, m_displayDemodStats);
s.writeBool(28, m_correlateFullPreamble); s.writeBool(28, m_correlateFullPreamble);
s.writeBool(29, m_demodModeS); s.writeBool(29, m_demodModeS);
s.writeBool(30, m_autoResizeTableColumns);
s.writeS32(31, m_interpolatorPhaseSteps);
s.writeFloat(32, m_interpolatorTapsPerPhase);
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]);
@ -179,6 +185,9 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
d.readBool(27, &m_displayDemodStats, false); d.readBool(27, &m_displayDemodStats, false);
d.readBool(28, &m_correlateFullPreamble, true); d.readBool(28, &m_correlateFullPreamble, true);
d.readBool(29, &m_demodModeS, false); d.readBool(29, &m_demodModeS, false);
d.readBool(30, &m_autoResizeTableColumns, false);
d.readS32(31, &m_interpolatorPhaseSteps, 4);
d.readFloat(32, &m_interpolatorTapsPerPhase, 3.5f);
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);

View File

@ -57,7 +57,7 @@ struct ADSBDemodSettings
Serializable *m_channelMarker; Serializable *m_channelMarker;
float m_airportRange; //!< How far away should we display airports (nm) float m_airportRange; //!< How far away should we display airports (km)
enum AirportType { enum AirportType {
Small, Small,
Medium, Medium,
@ -73,6 +73,9 @@ struct ADSBDemodSettings
bool m_correlateFullPreamble; bool m_correlateFullPreamble;
bool m_demodModeS; //!< Demodulate all Mode-S frames, not just ADS-B bool m_demodModeS; //!< Demodulate all Mode-S frames, not just ADS-B
int m_deviceIndex; //!< Device to set to ATC frequencies int m_deviceIndex; //!< Device to set to ATC frequencies
bool m_autoResizeTableColumns;
int m_interpolatorPhaseSteps;
float m_interpolatorTapsPerPhase;
ADSBDemodSettings(); ADSBDemodSettings();
void resetToDefaults(); void resetToDefaults();

View File

@ -83,28 +83,37 @@ void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const Sample
processOneSample(magsq); processOneSample(magsq);
} }
} }
else else if (m_interpolatorDistance == 1.0f) // just apply offset
{ {
for (SampleVector::const_iterator it = begin; it != end; ++it) for (SampleVector::const_iterator it = begin; it != end; ++it)
{ {
Complex c(it->real(), it->imag()); Complex c(it->real(), it->imag());
Complex ci; Complex ci;
c *= m_nco.nextIQ(); c *= m_nco.nextIQ();
if (m_interpolatorDistance == 1.0f)
{
processOneSample(complexMagSq(c)); processOneSample(complexMagSq(c));
} }
}
else if (m_interpolatorDistance < 1.0f) // interpolate else if (m_interpolatorDistance < 1.0f) // interpolate
{ {
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
Complex ci;
c *= m_nco.nextIQ();
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{ {
processOneSample(complexMagSq(ci)); processOneSample(complexMagSq(ci));
m_interpolatorDistanceRemain += m_interpolatorDistance; m_interpolatorDistanceRemain += m_interpolatorDistance;
} }
} }
}
else // decimate else // decimate
{ {
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
Complex ci;
c *= m_nco.nextIQ();
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{ {
processOneSample(complexMagSq(ci)); processOneSample(complexMagSq(ci));
@ -112,7 +121,6 @@ void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const Sample
} }
} }
} }
}
// Calculate number of seconds in this function // Calculate number of seconds in this function
boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - m_startPoint; boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - m_startPoint;
@ -228,7 +236,7 @@ void ADSBDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequ
if ((channelSampleRate != m_channelSampleRate) || force) if ((channelSampleRate != m_channelSampleRate) || force)
{ {
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); m_interpolator.create(m_settings.m_interpolatorPhaseSteps, channelSampleRate, m_settings.m_rfBandwidth / 2.2, m_settings.m_interpolatorTapsPerPhase);
m_interpolatorDistanceRemain = 0; m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) (ADS_B_BITS_PER_SECOND * m_settings.m_samplesPerBit); m_interpolatorDistance = (Real) channelSampleRate / (Real) (ADS_B_BITS_PER_SECOND * m_settings.m_samplesPerBit);
} }
@ -249,9 +257,12 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, bool force)
<< " force: " << force; << " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|| (settings.m_samplesPerBit != m_settings.m_samplesPerBit) || force) || (settings.m_samplesPerBit != m_settings.m_samplesPerBit)
|| (settings.m_interpolatorPhaseSteps != m_settings.m_interpolatorPhaseSteps)
|| (settings.m_interpolatorTapsPerPhase != m_settings.m_interpolatorTapsPerPhase)
|| force)
{ {
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); m_interpolator.create(m_settings.m_interpolatorPhaseSteps, m_channelSampleRate, settings.m_rfBandwidth / 2.2, m_settings.m_interpolatorTapsPerPhase);
m_interpolatorDistanceRemain = 0; m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) (ADS_B_BITS_PER_SECOND * settings.m_samplesPerBit); m_interpolatorDistance = (Real) m_channelSampleRate / (Real) (ADS_B_BITS_PER_SECOND * settings.m_samplesPerBit);
} }

View File

@ -161,13 +161,19 @@ Item {
var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows)) var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
if (freqIdx == 0) { if (freqIdx == 0) {
showFreq = false showFreq = false
} else {
selectedFreq = freqIdx - 1
} }
} else { } else {
showFreq = true showFreq = true
} }
} }
onDoubleClicked: (mouse) => {
if (showFreq) {
var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
if (freqIdx != 0) {
selectedFreq = freqIdx - 1
}
}
}
} }
} }
} }

View File

@ -70,7 +70,6 @@ struct AircraftInformation {
if ((file = fopen(utfFilename.constData(), "r")) != NULL) if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{ {
char row[2048]; char row[2048];
int idx;
if (fgets(row, sizeof(row), file)) if (fgets(row, sizeof(row), file))
{ {

View File

@ -82,7 +82,6 @@ struct AirportInformation {
if ((file = fopen(utfFilename.constData(), "r")) != NULL) if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{ {
char row[2048]; char row[2048];
int idx;
if (fgets(row, sizeof(row), file)) if (fgets(row, sizeof(row), file))
{ {

View File

@ -61,9 +61,11 @@ Clicking the Display Settings button will open the Display Settings dialog, whic
* 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 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 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. * 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. * Whether or not to display heliports.
* The range (in nautical miles) from 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.
* 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 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. * The font used for the table.
* 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.
<h3>12: Display Flight Paths</h3> <h3>12: Display Flight Paths</h3>
@ -99,17 +101,18 @@ The table displays the decoded ADS-B data for each aircraft along side data avai
* Flight No. - Airline flight number or callsign. (ADS-B) * Flight No. - Airline flight number or callsign. (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 - Altitude in feet or metres. (ADS-B) * Altitude (Alt) - Altitude in feet or metres. (ADS-B)
* Speed - Speed (either ground speed, indicated airspeed, or true airspeed) in knots or kph. (ADS-B) * Speed (Spd) - Speed (either ground speed, indicated airspeed, or true airspeed) in knots or kph. (ADS-B)
* Heading - The direction the aircraft is heading, in degrees. (ADS-B) * Heading (Hd) - The direction the aircraft is heading, in degrees. (ADS-B)
* VR - The vertical climb rate (or descent rate if negative) in feet/minute or m/s. (ADS-B) * Vertical rate (VR) - The vertical climb rate (or descent rate if negative) in feet/minute or m/s. (ADS-B)
* Range - The range (distance) to the aircraft from the receiving antenna in kilometres. The location of the receiving antenna is set under the Preferences > My Position menu. * Distance (D) - The distance to the aircraft from the receiving antenna in kilometres. The location of the receiving antenna is set under the Preferences > My Position menu.
* Az/El - The azimuth and elevation angles to the aircraft from the receiving antenna in degrees. These values can be sent to a rotator controller Feature plugin to track the aircraft. * Az/El - The azimuth and elevation angles to the aircraft from the receiving antenna in degrees. These values can be sent to a rotator controller Feature plugin to track the aircraft.
* Latitude - Vertical position coordinate, in decimal degrees. (ADS-B) * Latitude (Lat) - Vertical position coordinate, in decimal degrees. (ADS-B)
* Longitude - Horizontal position coordinate, in decimal degrees. (ADS-B) * Longitude (Lon) - Horizontal position coordinate, in decimal degrees. (ADS-B)
* Category - The vehicle category, such as Light, Large, Heavy or Rotorcraft. (ADS-B) * Category (Cat) - The vehicle category, such as Light, Large, Heavy or Rotorcraft. (ADS-B)
* Status - The status of the flight, including if there is an emergency. (ADS-B) * Status - The status of the flight, including if there is an emergency. (ADS-B)
* Registration - The registration number of the aircraft. (DB) * Squawk - The squawk code (Mode-A transponder code). (ADS-B)
* Registration (Reg) - The registration number of the aircraft. (DB)
* Country - The flag of the country the aircraft is registered in. (DB) * Country - The flag of the country the aircraft is registered in. (DB)
* Registered - The date when the aircraft was registered. (DB) * Registered - The date when the aircraft was registered. (DB)
* Manufacturer - The manufacturer of the aircraft. (DB) * Manufacturer - The manufacturer of the aircraft. (DB)
@ -121,14 +124,14 @@ The table displays the decoded ADS-B data for each aircraft along side data avai
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.
Single clicking in a cell will highlight the row and the corresponding aircraft information box on the map will be coloured orange, rather than blue. * Single clicking in a cell will highlight the row and the corresponding aircraft information box on the map will be coloured orange, rather than blue.
Right clicking on the header will open a menu allowing you to select which columns are visible. * Right clicking on the header will open a menu allowing you to select which columns are visible.
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 Flight No 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.
<h3>Map</h3> <h3>Map</h3>
@ -140,10 +143,11 @@ The initial antenna location is placed according to My Position set under the Pr
Aircraft are only placed upon the map when a position can be calculated, which can require several frames to be received. Aircraft are only placed upon the map when a position can be calculated, which can require several frames to be received.
To pan around the map, click the left mouse button and drag. To zoom in or out, use the mouse scroll wheel. * To pan around the map, click the left mouse button and drag. To zoom in or out, use the mouse scroll wheel.
Left clicking on an aicraft will highlight the corresponding row in the table for the aircraft and the information box on the map will be coloured orange, rather than blue. * Left clicking on an aicraft will highlight the corresponding row in the table for the aircraft and the information box on the map will be coloured orange, rather than blue.
Left clicking the information box next to an aircraft will reveal more information. If can be closed by clicking it again. * Double clicking on an aircraft will set it as the active target and the information box will be coloured green.
Left clicking the information box next to an airport will reveal ATC frequencies for the airport (if the OurAirports database has been downloaded.). This information box can be closed by left clicking on the airport identifier. Left clicking on one of the listed frequencies, will set it as the centre frequency on the selected SDRangel device set. * Left clicking the information box next to an aircraft will reveal more information. It can be closed by clicking it again.
* Left clicking the information box next to an airport will reveal ATC frequencies for the airport (if the OurAirports database has been downloaded.). This information box can be closed by left clicking on the airport identifier. Double clicking on one of the listed frequencies, will set it as the centre frequency on the selected SDRangel device set (15). The Az/El row gives the azimuth and elevation of the airport from the location set under Preferences > My Position. Double clicking on this row will set the airport as the active target.
<h3>Attribution</h3> <h3>Attribution</h3>