Estimated | %1").arg(etd));
}
if (!ata.isEmpty()) {
flightDetails.append(QString(" | Actual | %1").arg(ata));
} else if (!eta.isEmpty()) {
flightDetails.append(QString(" | Estimated | %1").arg(eta));
}
}
flightDetails.append("");
}
ui->flightDetails->setText(flightDetails);
}
}
void ADSBDemodGUI::highlightAircraft(Aircraft *aircraft)
{
if (aircraft != m_highlightAircraft)
{
// Hide photo of old aircraft
ui->photoHeader->setVisible(false);
ui->photoFlag->setVisible(false);
ui->photo->setVisible(false);
ui->flightDetails->setVisible(false);
ui->aircraftDetails->setVisible(false);
if (m_highlightAircraft)
{
// Restore colour
m_highlightAircraft->m_isHighlighted = false;
m_aircraftModel.aircraftUpdated(m_highlightAircraft);
}
// Highlight this aircraft
m_highlightAircraft = aircraft;
if (aircraft)
{
aircraft->m_isHighlighted = true;
m_aircraftModel.aircraftUpdated(aircraft);
if (m_settings.m_displayPhotos)
{
// Download photo
updatePhotoText(aircraft);
m_planeSpotters.getAircraftPhoto(aircraft->m_icaoHex);
}
}
}
if (aircraft)
{
// Highlight the row in the table - always do this, as it can become
// unselected
ui->adsbData->selectRow(aircraft->m_icaoItem->row());
}
else
{
ui->adsbData->clearSelection();
}
}
// Show feed dialog
void ADSBDemodGUI::feedSelect(const QPoint& p)
{
ADSBDemodFeedDialog dialog(&m_settings);
dialog.move(p);
new DialogPositioner(&dialog, false);
if (dialog.exec() == QDialog::Accepted)
{
applySettings();
applyImportSettings();
}
}
// Show display settings dialog
void ADSBDemodGUI::on_displaySettings_clicked()
{
bool oldSiUnits = m_settings.m_siUnits;
ADSBDemodDisplayDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted)
{
bool unitsChanged = m_settings.m_siUnits != oldSiUnits;
if (unitsChanged) {
m_aircraftModel.allAircraftUpdated();
}
displaySettings();
applySettings();
}
}
void ADSBDemodGUI::applyMapSettings()
{
#ifdef QT_LOCATION_FOUND
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
QQuickItem *item = ui->map->rootObject();
if (!item)
{
qCritical("ADSBDemodGUI::applyMapSettings: Map not found. Are all required Qt plugins installed?");
return;
}
QObject *object = item->findChild("map");
QGeoCoordinate coords;
double zoom;
if (object != nullptr)
{
// Save existing position of map
coords = object->property("center").value();
zoom = object->property("zoomLevel").value();
}
else
{
// Center on my location when map is first opened
coords.setLatitude(stationLatitude);
coords.setLongitude(stationLongitude);
coords.setAltitude(stationAltitude);
zoom = 10.0;
}
// Check requested map provider is available - if not, try the other
QString mapProvider = m_settings.m_mapProvider;
QStringList mapProviders = QGeoServiceProvider::availableServiceProviders();
if ((mapProvider == "osm") && (!mapProviders.contains(mapProvider))) {
mapProvider = "mapboxgl";
}
if ((mapProvider == "mapboxgl") && (!mapProviders.contains(mapProvider))) {
mapProvider = "osm";
}
// Create the map using the specified provider
QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing());
QQmlProperty::write(item, "aircraftMinZoomLevel", m_settings.m_aircraftMinZoom);
QQmlProperty::write(item, "mapProvider", mapProvider);
QVariantMap parameters;
QString mapType;
if (mapProvider == "osm")
{
#ifdef __EMSCRIPTEN__
// Default is http://maps-redirect.qt.io/osm/5.8/ and Emscripten needs https
parameters["osm.mapping.providersrepository.address"] = QString("https://sdrangel.beniston.com/sdrangel/maps/");
#else
// Use our repo, so we can append API key and redefine transmit maps
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
#endif
// Use ADS-B specific cache, as we use different transmit maps
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/QtLocation/5.8/tiles/osm/sdrangel_adsb";
parameters["osm.mapping.cache.directory"] = cachePath;
// On Linux, we need to create the directory
QDir dir(cachePath);
if (!dir.exists()) {
dir.mkpath(cachePath);
}
switch (m_settings.m_mapType)
{
case ADSBDemodSettings::AVIATION_LIGHT:
mapType = "Transit Map";
break;
case ADSBDemodSettings::AVIATION_DARK:
mapType = "Night Transit Map";
break;
case ADSBDemodSettings::STREET:
mapType = "Street Map";
break;
case ADSBDemodSettings::SATELLITE:
mapType = "Satellite Map";
break;
}
}
else if (mapProvider == "mapboxgl")
{
switch (m_settings.m_mapType)
{
case ADSBDemodSettings::AVIATION_LIGHT:
mapType = "mapbox://styles/mapbox/light-v9";
break;
case ADSBDemodSettings::AVIATION_DARK:
mapType = "mapbox://styles/mapbox/dark-v9";
break;
case ADSBDemodSettings::STREET:
mapType = "mapbox://styles/mapbox/streets-v10";
break;
case ADSBDemodSettings::SATELLITE:
mapType = "mapbox://styles/mapbox/satellite-v9";
break;
}
}
QVariant retVal;
if (!QMetaObject::invokeMethod(item, "createMap", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, retVal),
Q_ARG(QVariant, QVariant::fromValue(parameters)),
Q_ARG(QVariant, mapType),
Q_ARG(QVariant, QVariant::fromValue(this))))
{
qCritical() << "ADSBDemodGUI::applyMapSettings - Failed to invoke createMap";
}
QObject *newMap = retVal.value();
// Restore position of map
if (newMap != nullptr)
{
// Move antenna icon to My Position
QObject *stationObject = newMap->findChild("station");
if(stationObject != NULL)
{
QGeoCoordinate coords = stationObject->property("coordinate").value();
coords.setLatitude(stationLatitude);
coords.setLongitude(stationLongitude);
coords.setAltitude(stationAltitude);
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
}
else
{
qDebug() << "ADSBDemodGUI::applyMapSettings - Couldn't find station";
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
newMap = newMap->findChild("map");
#endif
if (coords.isValid())
{
newMap->setProperty("zoomLevel", QVariant::fromValue(zoom));
newMap->setProperty("center", QVariant::fromValue(coords));
}
}
else
{
qDebug() << "ADSBDemodGUI::applyMapSettings - createMap returned a nullptr";
}
#endif // QT_LOCATION_FOUND
}
// Called from QML when empty space clicked
void ADSBDemodGUI::clearHighlighted()
{
highlightAircraft(nullptr);
}
ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::ADSBDemodGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_basicSettingsShown(false),
m_doApplySettings(true),
m_tickCount(0),
m_aircraftInfo(nullptr),
m_airportModel(this),
m_airspaceModel(this),
m_trackAircraft(nullptr),
m_highlightAircraft(nullptr),
m_speech(nullptr),
m_progressDialog(nullptr),
m_loadingData(false)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodadsb/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
// Enable MSAA antialiasing on 2D map
// This can be much faster than using layer.smooth in the QML, when there are many items
// However, only seems to work when set to 16, and doesn't seem to be supported on all graphics cards
int multisamples = MainCore::instance()->getSettings().getMapMultisampling();
if (multisamples > 0)
{
QSurfaceFormat format;
format.setSamples(multisamples);
#ifdef QT_LOCATION_FOUND
ui->map->setFormat(format);
#endif
}
m_osmPort = 0; // Pick a free port
m_templateServer = new ADSBOSMTemplateServer("q2RVNAe3eFKCH4XsrE3r", m_osmPort);
#ifdef QT_LOCATION_FOUND
ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true);
ui->map->rootContext()->setContextProperty("aircraftModel", &m_aircraftModel);
ui->map->rootContext()->setContextProperty("airportModel", &m_airportModel);
ui->map->rootContext()->setContextProperty("airspaceModel", &m_airspaceModel);
ui->map->rootContext()->setContextProperty("navAidModel", &m_navAidModel);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
#elif defined(__EMSCRIPTEN__)
// No Qt5Compat.GraphicalEffects
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map_6_strict.qml")));
#else
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map_6_strict.qml")));
#endif
ui->map->installEventFilter(this);
#else
ui->map->hide();
#endif
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_adsbDemod = reinterpret_cast(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI);
m_adsbDemod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
CRightClickEnabler *feedRightClickEnabler = new CRightClickEnabler(ui->feed);
connect(feedRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(feedSelect(const QPoint &)));
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
ui->warning->setVisible(false);
ui->warning->setStyleSheet("QLabel { background-color: red; }");
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::red);
m_channelMarker.setBandwidth(5000);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle("ADS-B Demodulator");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_settings.setChannelMarker(&m_channelMarker);
m_settings.setRollupState(&m_rollupState);
m_deviceUISet->addChannelMarker(&m_channelMarker);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
// Set size of airline icons
ui->adsbData->setIconSize(QSize(85, 20));
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
ui->adsbData->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->adsbData->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
menu = new QMenu(ui->adsbData);
for (int i = 0; i < ui->adsbData->horizontalHeader()->count(); i++)
{
QString text = ui->adsbData->horizontalHeaderItem(i)->text();
menu->addAction(createCheckableItem(text, i, true));
}
ui->adsbData->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->adsbData->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->adsbData->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(adsbData_sectionMoved(int, int, int)));
connect(ui->adsbData->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(adsbData_sectionResized(int, int, int)));
// Context menu
ui->adsbData->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->adsbData, SIGNAL(customContextMenuRequested(QPoint)), SLOT(adsbData_customContextMenuRequested(QPoint)));
TableTapAndHold *tableTapAndHold = new TableTapAndHold(ui->adsbData);
connect(tableTapAndHold, &TableTapAndHold::tapAndHold, this, &ADSBDemodGUI::adsbData_customContextMenuRequested);
ui->photoHeader->setVisible(false);
ui->photoFlag->setVisible(false);
ui->photo->setVisible(false);
ui->flightDetails->setVisible(false);
ui->aircraftDetails->setVisible(false);
// Read aircraft information database, if it has previously been downloaded
AircraftInformation::init();
connect(&m_osnDB, &OsnDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
connect(&m_osnDB, &OsnDB::downloadError, this, &ADSBDemodGUI::downloadError);
connect(&m_osnDB, &OsnDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress);
connect(&m_osnDB, &OsnDB::downloadAircraftInformationFinished, this, &ADSBDemodGUI::downloadAircraftInformationFinished);
m_aircraftInfo = OsnDB::getAircraftInformation();
// Read airport information database, if it has previously been downloaded
connect(&m_ourAirportsDB, &OurAirportsDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadError, this, &ADSBDemodGUI::downloadError);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadAirportInformationFinished, this, &ADSBDemodGUI::downloadAirportInformationFinished);
m_airportInfo = OurAirportsDB::getAirportsById();
// Read airspaces and NAVAIDs
connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
m_airspaces = OpenAIP::getAirspaces();
m_navAids = OpenAIP::getNavAids();
// Get station position
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
// These are the default values in sdrbase/settings/preferences.cpp
if ((stationLatitude == 49.012423f) && (stationLongitude == 8.418125f)) {
ui->warning->setText("Please set your antenna location under Preferences > My Position");
}
// Get updated when position changes
connect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &ADSBDemodGUI::preferenceChanged);
if (m_deviceUISet->m_deviceAPI->getSampleSource()) {
connect(m_deviceUISet->m_deviceAPI->getSampleSource(), &DeviceSampleSource::positionChanged, this, &ADSBDemodGUI::devicePositionChanged);
}
// Get airport weather when requested
connect(&m_airportModel, &AirportModel::requestMetar, this, &ADSBDemodGUI::requestMetar);
// Add airports within range of My Position
updateAirports();
updateAirspaces();
updateNavAids();
update3DModels();
m_flightInformation = nullptr;
m_aviationWeather = nullptr;
connect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
connect(ui->photo, &ClickableLabel::clicked, this, &ADSBDemodGUI::photoClicked);
// Update demod list when channels are added or removed
connect(MainCore::instance(), &MainCore::channelAdded, this, &ADSBDemodGUI::updateChannelList);
connect(MainCore::instance(), &MainCore::channelRemoved, this, &ADSBDemodGUI::updateChannelList);
updateChannelList();
displaySettings();
makeUIConnections();
applySettings(true);
connect(&m_importTimer, &QTimer::timeout, this, &ADSBDemodGUI::import);
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&ADSBDemodGUI::handleImportReply
);
applyImportSettings();
connect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap);
m_redrawMapTimer.setSingleShot(true);
DialPopup::addPopupsToChildDials(this);
m_resizer.enableChildMouseTracking();
}
ADSBDemodGUI::~ADSBDemodGUI()
{
disconnect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &ADSBDemodGUI::preferenceChanged);
if (m_templateServer)
{
m_templateServer->close();
delete m_templateServer;
}
disconnect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
disconnect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
disconnect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
disconnect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
disconnect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap);
disconnect(&MainCore::instance()->getMasterTimer(), &QTimer::timeout, this, &ADSBDemodGUI::tick);
m_redrawMapTimer.stop();
// Remove aircraft from Map feature
QHash::iterator i = m_aircraft.begin();
while (i != m_aircraft.end())
{
Aircraft *aircraft = i.value();
clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16));
++i;
}
delete ui;
qDeleteAll(m_aircraft);
if (m_flightInformation)
{
disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
delete m_flightInformation;
}
delete m_aviationWeather;
qDeleteAll(m_3DModelMatch);
delete m_networkManager;
}
void ADSBDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
qDebug() << "ADSBDemodGUI::applySettings";
ADSBDemod::MsgConfigureADSBDemod* message = ADSBDemod::MsgConfigureADSBDemod::create(m_settings, force);
m_adsbDemod->getInputMessageQueue()->push(message);
}
}
void ADSBDemodGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor);
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
setTitle(m_channelMarker.getTitle());
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->rfBWText->setText(QString("%1M").arg(m_settings.m_rfBandwidth / 1000000.0, 0, 'f', 1));
ui->rfBW->setValue((int)m_settings.m_rfBandwidth);
ui->spb->setCurrentIndex(m_settings.m_samplesPerBit/2-1);
ui->correlateFullPreamble->setChecked(m_settings.m_correlateFullPreamble);
ui->demodModeS->setChecked(m_settings.m_demodModeS);
ui->thresholdText->setText(QString("%1").arg(m_settings.m_correlationThreshold, 0, 'f', 1));
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->flightPaths->setChecked(m_settings.m_flightPaths);
m_aircraftModel.setFlightPaths(m_settings.m_flightPaths);
ui->allFlightPaths->setChecked(m_settings.m_allFlightPaths);
m_aircraftModel.setAllFlightPaths(m_settings.m_allFlightPaths);
m_aircraftModel.setSettings(&m_settings);
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled);
updateIndexLabel();
QFont font(m_settings.m_tableFontName, m_settings.m_tableFontSize);
ui->adsbData->setFont(font);
// Set units in column headers
if (m_settings.m_siUnits)
{
ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (m)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (m/s)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_SEL_ALTITUDE)->setText("Sel Alt (m)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_GROUND_SPEED)->setText("GS (kph)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_TRUE_AIRSPEED)->setText("TAS (kph)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_INDICATED_AIRSPEED)->setText("IAS (kph)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_HEADWIND)->setText("H Wnd (kph)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_WIND_SPEED)->setText("Wnd (kph)");
}
else
{
ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (ft)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (ft/m)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_SEL_ALTITUDE)->setText("Sel Alt (ft)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_GROUND_SPEED)->setText("GS (kn)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_TRUE_AIRSPEED)->setText("TAS (kn)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_INDICATED_AIRSPEED)->setText("IAS (kn)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_HEADWIND)->setText("H Wnd (kn)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_WIND_SPEED)->setText("Wnd (kn)");
}
// Order and size columns
QHeaderView *header = ui->adsbData->horizontalHeader();
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
{
bool hidden = m_settings.m_columnSizes[i] == 0;
header->setSectionHidden(i, hidden);
menu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_columnSizes[i] > 0)
ui->adsbData->setColumnWidth(i, m_settings.m_columnSizes[i]);
header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
}
// Only update airports on map if settings have changed
if ((m_airportInfo != nullptr)
&& ((m_settings.m_airportRange != m_currentAirportRange)
|| (m_settings.m_airportMinimumSize != m_currentAirportMinimumSize)
|| (m_settings.m_displayHeliports != m_currentDisplayHeliports)))
updateAirports();
updateAirspaces();
updateNavAids();
if (!m_settings.m_displayDemodStats)
ui->stats->setText("");
initFlightInformation();
initAviationWeather();
applyImportSettings();
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
enableSpeechIfNeeded();
#ifdef __EMSCRIPTEN__
// FIXME: If we don't have this delay, tile server requests get deleted
QTimer::singleShot(250, [this] {
applyMapSettings();
});
#else
applyMapSettings();
#endif
}
void ADSBDemodGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void ADSBDemodGUI::enterEvent(EnterEventType* event)
{
m_channelMarker.setHighlighted(true);
ChannelGUI::enterEvent(event);
}
void ADSBDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void ADSBDemodGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_adsbDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(100.0f + powDbAvg) / 100.0f,
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(tr("%1 dB").arg(powDbAvg, 0, 'f', 1));
}
m_tickCount++;
// Tick is called 20x a second - lets check this every 10 seconds
if (m_tickCount % (20*10) == 0)
{
// Remove aircraft that haven't been heard of for a user-defined time, as probably out of range
QDateTime now = QDateTime::currentDateTime();
qint64 nowSecs = now.toSecsSinceEpoch();
QHash::iterator i = m_aircraft.begin();
while (i != m_aircraft.end())
{
Aircraft *aircraft = i.value();
qint64 secondsSinceLastFrame = nowSecs - aircraft->m_time.toSecsSinceEpoch();
if (secondsSinceLastFrame >= m_settings.m_removeTimeout)
{
// Don't try to track it anymore
if (m_trackAircraft == aircraft)
{
m_adsbDemod->clearTarget();
m_trackAircraft = nullptr;
}
// Remove map model
m_aircraftModel.removeAircraft(aircraft);
// Remove row from table
ui->adsbData->removeRow(aircraft->m_icaoItem->row());
// Remove aircraft from hash
i = m_aircraft.erase(i);
// Remove from map feature
clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16));
// And finally free its memory
delete aircraft;
}
else
{
++i;
}
}
}
// Create and send aircraft report every second for WebAPI
if (m_tickCount % (20*1) == 0) {
sendAircraftReport();
}
}
void ADSBDemodGUI::sendAircraftReport()
{
ADSBDemod::MsgAircraftReport* message = ADSBDemod::MsgAircraftReport::create();
QList& report = message->getReport();
report.reserve(m_aircraft.size());
QHash::iterator i = m_aircraft.begin();
while (i != m_aircraft.end())
{
Aircraft *aircraft = i.value();
ADSBDemod::MsgAircraftReport::AircraftReport aircraftReport {
aircraft->m_icaoHex,
aircraft->m_callsign,
aircraft->m_latitude,
aircraft->m_longitude,
aircraft->m_altitude,
aircraft->m_groundspeed
};
report.append(aircraftReport);
++i;
}
m_adsbDemod->getInputMessageQueue()->push(message);
}
void ADSBDemodGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = ui->adsbData->rowCount();
ui->adsbData->setRowCount(row + 1);
ui->adsbData->setItem(row, ADSB_COL_ICAO, new QTableWidgetItem("ICAO ID"));
ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, new QTableWidgetItem("Callsign--"));
ui->adsbData->setItem(row, ADSB_COL_ATC_CALLSIGN, new QTableWidgetItem("ATC Callsign-"));
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_ALTITUDE, new QTableWidgetItem("Alt (ft)"));
ui->adsbData->setItem(row, ADSB_COL_HEADING, new QTableWidgetItem("Hd (o)"));
ui->adsbData->setItem(row, ADSB_COL_VERTICALRATE, new QTableWidgetItem("VR (ft/m)"));
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_LATITUDE, new QTableWidgetItem("-90.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_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_COUNTRY, new QTableWidgetItem("Country"));
ui->adsbData->setItem(row, ADSB_COL_REGISTERED, new QTableWidgetItem("Registered"));
ui->adsbData->setItem(row, ADSB_COL_MANUFACTURER, new QTableWidgetItem("The Boeing Company"));
ui->adsbData->setItem(row, ADSB_COL_OWNER, new QTableWidgetItem("British Airways"));
ui->adsbData->setItem(row, ADSB_COL_OPERATOR_ICAO, new QTableWidgetItem("Operator"));
ui->adsbData->setItem(row, ADSB_COL_TIME, new QTableWidgetItem("99:99:99"));
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_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->setItem(row, ADSB_COL_SEL_ALTITUDE, new QTableWidgetItem("Sel Alt (ft)"));
ui->adsbData->setItem(row, ADSB_COL_SEL_HEADING, new QTableWidgetItem("Sel Hd (o)"));
ui->adsbData->setItem(row, ADSB_COL_BARO, new QTableWidgetItem("Baro (mb)"));
ui->adsbData->setItem(row, ADSB_COL_AP, new QTableWidgetItem("AP"));
ui->adsbData->setItem(row, ADSB_COL_V_MODE, new QTableWidgetItem("V Mode"));
ui->adsbData->setItem(row, ADSB_COL_L_MODE, new QTableWidgetItem("L Mode"));
ui->adsbData->setItem(row, ADSB_COL_TRUE_AIRSPEED, new QTableWidgetItem("TAS (kn)"));
ui->adsbData->setItem(row, ADSB_COL_INDICATED_AIRSPEED, new QTableWidgetItem("IAS (kn)"));
ui->adsbData->setItem(row, ADSB_COL_MACH, new QTableWidgetItem("0.999"));
ui->adsbData->setItem(row, ADSB_COL_HEADWIND, new QTableWidgetItem("H Wnd (kn)"));
ui->adsbData->setItem(row, ADSB_COL_EST_AIR_TEMP, new QTableWidgetItem("OAT (C)"));
ui->adsbData->setItem(row, ADSB_COL_WIND_SPEED, new QTableWidgetItem("Wnd (kn)"));
ui->adsbData->setItem(row, ADSB_COL_WIND_DIR, new QTableWidgetItem("Wnd (o)"));
ui->adsbData->setItem(row, ADSB_COL_STATIC_PRESSURE, new QTableWidgetItem("P (hPa)"));
ui->adsbData->setItem(row, ADSB_COL_STATIC_AIR_TEMP, new QTableWidgetItem("T (C)"));
ui->adsbData->setItem(row, ADSB_COL_HUMIDITY, new QTableWidgetItem("U (%)"));
ui->adsbData->setItem(row, ADSB_COL_TIS_B, new QTableWidgetItem("TIS-B"));
ui->adsbData->resizeColumnsToContents();
ui->adsbData->setRowCount(row);
}
Aircraft* ADSBDemodGUI::findAircraftByFlight(const QString& flight)
{
QHash::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_aviationstackAPIKey.isEmpty())
{
m_flightInformation = FlightInformation::create(m_settings.m_aviationstackAPIKey);
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);
}
updatePhotoFlightInformation(aircraft);
}
else
{
qDebug() << "ADSBDemodGUI::flightInformationUpdated - Flight not found in ADS-B table: " << flight.m_flightICAO;
}
}
void ADSBDemodGUI::aircraftPhoto(const PlaneSpottersPhoto *photo)
{
// Make sure the photo is for the currently highlighted aircraft, as it may
// have taken a while to download
if (!photo->m_pixmap.isNull() && m_highlightAircraft && (m_highlightAircraft->m_icaoItem->text() == photo->m_id))
{
ui->photo->setPixmap(photo->m_pixmap);
ui->photo->setToolTip(QString("Photographer: %1").arg(photo->m_photographer)); // Required by terms of use
ui->photoHeader->setVisible(true);
ui->photoFlag->setVisible(true);
ui->photo->setVisible(true);
ui->flightDetails->setVisible(true);
ui->aircraftDetails->setVisible(true);
m_photoLink = photo->m_link;
}
}
void ADSBDemodGUI::photoClicked()
{
// Photo needs to link back to PlaneSpotters, as per terms of use
if (m_highlightAircraft)
{
if (m_photoLink.isEmpty())
{
QString icaoUpper = QString("%1").arg(m_highlightAircraft->m_icao, 1, 16).toUpper();
QDesktopServices::openUrl(QUrl(QString("https://www.planespotters.net/hex/%1").arg(icaoUpper)));
}
else
{
QDesktopServices::openUrl(QUrl(m_photoLink));
}
}
}
void ADSBDemodGUI::on_logEnable_clicked(bool checked)
{
m_settings.m_logEnabled = checked;
applySettings();
}
void ADSBDemodGUI::on_logFilename_clicked()
{
// Get filename to save to
QFileDialog fileDialog(nullptr, "Select file to log received frames to", "", "*.csv");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
m_settings.m_logFilename = fileNames[0];
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
applySettings();
}
}
}
// Read .csv log and process as received frames
void ADSBDemodGUI::on_logOpen_clicked()
{
QFileDialog fileDialog(nullptr, "Select .csv log file to read", "", "*.csv");
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
QFile file(fileNames[0]);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QDateTime startTime = QDateTime::currentDateTime();
m_loadingData = true;
ui->adsbData->blockSignals(true);
QTextStream in(&file);
QString error;
QHash colIndexes = CSV::readHeader(in, {"Data", "Correlation"}, error);
if (error.isEmpty())
{
int dataCol = colIndexes.value("Data");
int correlationCol = colIndexes.value("Correlation");
int maxCol = std::max(dataCol, correlationCol);
QMessageBox dialog(this);
dialog.setText("Reading ADS-B data");
dialog.addButton(QMessageBox::Cancel);
dialog.show();
QApplication::processEvents();
int count = 0;
int countOtherDF = 0;
bool cancelled = false;
QStringList cols;
crcadsb crc;
while (!cancelled && CSV::readRow(in, &cols))
{
if (cols.size() > maxCol)
{
QDateTime dateTime = QDateTime::currentDateTime(); // So they aren't removed immediately as too old
QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1());
float correlation = cols[correlationCol].toFloat();
int df = (bytes[0] >> 3) & ADS_B_DF_MASK; // Downlink format
if ((df == 4) || (df == 5) || (df == 17) || (df == 18) || (df == 20) || (df == 21))
{
int crcCalc = 0;
if ((df == 4) || (df == 5) || (df == 20) || (df == 21)) // handleADSB requires calculated CRC for Mode-S frames
{
crc.init();
crc.calculate((const uint8_t *)bytes.data(), bytes.size()-3);
crcCalc = crc.get();
}
//qDebug() << "bytes.size " << bytes.size() << " crc " << Qt::hex << crcCalc;
handleADSB(bytes, dateTime, correlation, correlation, crcCalc, false);
if ((count > 0) && (count % 100000 == 0))
{
dialog.setText(QString("Reading ADS-B data\n%1 (Skipped %2)").arg(count).arg(countOtherDF));
QApplication::processEvents();
if (dialog.clickedButton()) {
cancelled = true;
}
}
count++;
}
else
{
countOtherDF++;
}
}
}
m_aircraftModel.allAircraftUpdated();
dialog.close();
}
else
{
QMessageBox::critical(this, "ADS-B", error);
}
ui->adsbData->blockSignals(false);
m_loadingData = false;
if (m_settings.m_autoResizeTableColumns)
ui->adsbData->resizeColumnsToContents();
ui->adsbData->setSortingEnabled(true);
QDateTime finishTime = QDateTime::currentDateTime();
qDebug() << "Read CSV in " << startTime.secsTo(finishTime);
}
else
{
QMessageBox::critical(this, "ADS-B", QString("Failed to open file %1").arg(fileNames[0]));
}
}
}
}
void ADSBDemodGUI::downloadingURL(const QString& url)
{
if (m_progressDialog)
{
m_progressDialog->setLabelText(QString("Downloading %1.").arg(url));
m_progressDialog->setValue(m_progressDialog->value() + 1);
}
}
void ADSBDemodGUI::downloadProgress(qint64 bytesRead, qint64 totalBytes)
{
if (m_progressDialog)
{
m_progressDialog->setMaximum(totalBytes);
m_progressDialog->setValue(bytesRead);
}
}
void ADSBDemodGUI::downloadError(const QString& error)
{
QMessageBox::critical(this, "ADS-B", error);
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void ADSBDemodGUI::downloadAirspaceFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading airspaces.");
}
m_airspaces = OpenAIP::getAirspaces();
updateAirspaces();
m_openAIP.downloadNavAids();
}
void ADSBDemodGUI::downloadNavAidsFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading NAVAIDs.");
}
m_navAids = OpenAIP::getNavAids();
updateNavAids();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void ADSBDemodGUI::downloadAircraftInformationFinished()
{
if (m_progressDialog)
{
delete m_progressDialog;
m_progressDialog = new QProgressDialog("Reading Aircraft Information.", "", 0, 1, this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->show();
QApplication::processEvents();
}
m_aircraftInfo = OsnDB::getAircraftInformation();
m_aircraftModel.updateAircraftInformation(m_aircraftInfo);
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void ADSBDemodGUI::downloadAirportInformationFinished()
{
if (m_progressDialog)
{
delete m_progressDialog;
m_progressDialog = new QProgressDialog("Reading Airport Information.", "", 0, 1, this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->show();
QApplication::processEvents();
}
m_airportInfo = OurAirportsDB::getAirportsById();
updateAirports();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
int ADSBDemodGUI::squawkDecode(int modeA) const
{
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);
return a*1000 + b*100 + c*10 + d;
}
// https://en.wikipedia.org/wiki/Gillham_code
int ADSBDemodGUI::gillhamToFeet(int n) const
{
int c1 = (n >> 10) & 1;
int a1 = (n >> 9) & 1;
int c2 = (n >> 8) & 1;
int a2 = (n >> 7) & 1;
int c4 = (n >> 6) & 1;
int a4 = (n >> 5) & 1;
int b1 = (n >> 4) & 1;
int b2 = (n >> 3) & 1;
int d2 = (n >> 2) & 1;
int b4 = (n >> 1) & 1;
int d4 = n & 1;
int n500 = grayToBinary((d2 << 7) | (d4 << 6) | (a1 << 5) | (a2 << 4) | (a4 << 3) | (b1 << 2) | (b2 << 1) | b4, 4);
int n100 = grayToBinary((c1 << 2) | (c2 << 1) | c4, 3) - 1;
if (n100 == 6) {
n100 = 4;
}
if (n500 %2 != 0) {
n100 = 4 - n100;
}
return -1200 + n500*500 + n100*100;
}
int ADSBDemodGUI::grayToBinary(int gray, int bits) const
{
int binary = 0;
for (int i = bits - 1; i >= 0; i--) {
binary = binary | ((((1 << (i+1)) & binary) >> 1) ^ ((1 << i) & gray));
}
return binary;
}
void ADSBDemodGUI::redrawMap()
{
#ifdef QT_LOCATION_FOUND
// An awful workaround for https://bugreports.qt.io/browse/QTBUG-100333
// Also used in Map feature
QQuickItem *item = ui->map->rootObject();
if (item)
{
QObject *object = item->findChild("map");
if (object)
{
double zoom = object->property("zoomLevel").value();
object->setProperty("zoomLevel", QVariant::fromValue(zoom+1));
object->setProperty("zoomLevel", QVariant::fromValue(zoom));
}
}
#endif
}
void ADSBDemodGUI::showEvent(QShowEvent *event)
{
if (!event->spontaneous())
{
// Workaround for https://bugreports.qt.io/browse/QTBUG-100333
// MapQuickItems can be in wrong position when window is first displayed
m_redrawMapTimer.start(500);
}
ChannelGUI::showEvent(event);
}
bool ADSBDemodGUI::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->map)
{
if (event->type() == QEvent::Resize)
{
// Workaround for https://bugreports.qt.io/browse/QTBUG-100333
// MapQuickItems can be in wrong position after vertical resize
QResizeEvent *resizeEvent = static_cast(event);
QSize oldSize = resizeEvent->oldSize();
QSize size = resizeEvent->size();
if (oldSize.height() != size.height()) {
redrawMap();
}
}
}
return ChannelGUI::eventFilter(obj, event);
}
void ADSBDemodGUI::applyImportSettings()
{
m_importTimer.setInterval(m_settings.m_importPeriod * 1000);
if (m_settings.m_feedEnabled && m_settings.m_importEnabled) {
m_importTimer.start();
} else {
m_importTimer.stop();
}
}
// Import ADS-B data from opensky-network via an API call
void ADSBDemodGUI::import()
{
QString urlString = "https://";
if (!m_settings.m_importUsername.isEmpty() && !m_settings.m_importPassword.isEmpty()) {
urlString = urlString + m_settings.m_importUsername + ":" + m_settings.m_importPassword + "@";
}
urlString = urlString + m_settings.m_importHost + "/api/states/all";
QChar join = '?';
if (!m_settings.m_importParameters.isEmpty())
{
urlString = urlString + join + m_settings.m_importParameters;
join = '&';
}
if (!m_settings.m_importMinLatitude.isEmpty())
{
urlString = urlString + join + "lamin=" + m_settings.m_importMinLatitude;
join = '&';
}
if (!m_settings.m_importMaxLatitude.isEmpty())
{
urlString = urlString + join + "lamax=" + m_settings.m_importMaxLatitude;
join = '&';
}
if (!m_settings.m_importMinLongitude.isEmpty())
{
urlString = urlString + join + "lomin=" + m_settings.m_importMinLongitude;
join = '&';
}
if (!m_settings.m_importMaxLongitude.isEmpty())
{
urlString = urlString + join + "lomax=" + m_settings.m_importMaxLongitude;
join = '&';
}
m_networkManager->get(QNetworkRequest(QUrl(urlString)));
}
// Handle opensky-network API call reply
void ADSBDemodGUI::handleImportReply(QNetworkReply* reply)
{
if (reply)
{
if (!reply->error())
{
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if (document.isObject())
{
QJsonObject obj = document.object();
if (obj.contains("time") && obj.contains("states"))
{
int seconds = obj.value("time").toInt();
QDateTime dateTime = QDateTime::fromSecsSinceEpoch(seconds);
QJsonArray states = obj.value("states").toArray();
for (int i = 0; i < states.size(); i++)
{
QJsonArray state = states[i].toArray();
int icao = state[0].toString().toInt(nullptr, 16);
bool newAircraft;
Aircraft *aircraft = getAircraft(icao, newAircraft);
QString callsign = state[1].toString().trimmed();
if (!callsign.isEmpty())
{
aircraft->m_callsign = callsign;
aircraft->m_callsignItem->setText(aircraft->m_callsign);
atcCallsign(aircraft);
}
QDateTime timePosition = dateTime;
if (state[3].isNull()) {
timePosition = dateTime.addSecs(-15); // At least 15 seconds old
} else {
timePosition = QDateTime::fromSecsSinceEpoch(state[3].toInt());
}
aircraft->m_time = QDateTime::fromSecsSinceEpoch(state[4].toInt());
QTime time = aircraft->m_time.time();
aircraft->m_timeItem->setText(QString("%1:%2:%3").arg(time.hour(), 2, 10, QLatin1Char('0')).arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
aircraft->m_adsbFrameCount++;
aircraft->m_adsbFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsbFrameCount);
if (timePosition > aircraft->m_positionDateTime)
{
if (!state[5].isNull() && !state[6].isNull())
{
aircraft->m_longitude = state[5].toDouble();
aircraft->m_latitude = state[6].toDouble();
aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude);
aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude);
updatePosition(aircraft);
aircraft->m_cprValid[0] = false;
aircraft->m_cprValid[1] = false;
}
if (!state[7].isNull())
{
aircraft->m_altitude = (int)Units::metresToFeet(state[7].toDouble());
aircraft->m_altitudeValid = true;
aircraft->m_altitudeGNSS = false;
aircraft->m_altitudeItem->setData(Qt::DisplayRole, aircraft->m_altitude);
}
if (!state[5].isNull() && !state[6].isNull() && !state[7].isNull())
{
QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude);
aircraft->m_coordinates.push_back(QVariant::fromValue(coord));
aircraft->m_coordinateDateTimes.push_back(dateTime);
}
aircraft->m_positionDateTime = timePosition;
}
aircraft->m_onSurface = state[8].toBool(false);
if (!state[9].isNull())
{
aircraft->m_groundspeed = (int)state[9].toDouble();
aircraft->m_groundspeedItem->setData(Qt::DisplayRole, aircraft->m_groundspeed);
aircraft->m_groundspeedValid = true;
}
if (!state[10].isNull())
{
aircraft->m_heading = (float)state[10].toDouble();
aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading));
aircraft->m_headingValid = true;
aircraft->m_headingDateTime = aircraft->m_time;
}
if (!state[11].isNull())
{
aircraft->m_verticalRate = (int)state[10].toDouble();
aircraft->m_verticalRateItem->setData(Qt::DisplayRole, aircraft->m_verticalRate);
aircraft->m_verticalRateValid = true;
}
if (!state[14].isNull())
{
aircraft->m_squawk = state[14].toString().toInt();
aircraft->m_squawkItem->setText(QString("%1").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0')));
}
// Update aircraft in map
if (aircraft->m_positionValid)
{
// Check to see if we need to start any animations
QList *animations = animate(dateTime, aircraft);
// Update map displayed in channel
m_aircraftModel.aircraftUpdated(aircraft);
// Send to Map feature
sendToMap(aircraft, animations);
}
// Check to see if we need to emit a notification about this aircraft
checkDynamicNotification(aircraft);
}
}
else
{
qDebug() << "ADSBDemodGUI::handleImportReply: Document object does not contain time and states: " << document;
}
}
else
{
qDebug() << "ADSBDemodGUI::handleImportReply: Document is not an object: " << document;
}
}
else
{
qDebug() << "ADSBDemodGUI::handleImportReply: error " << reply->error();
}
reply->deleteLater();
}
}
void ADSBDemodGUI::updatePosition(float latitude, float longitude, float altitude)
{
// Use device postion in preference to My Position
ChannelWebAPIUtils::getDevicePosition(getDeviceSetIndex(), latitude, longitude, altitude);
QGeoCoordinate stationPosition(latitude, longitude, altitude);
QGeoCoordinate previousPosition(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, m_azEl.getLocationSpherical().m_altitude);
if (stationPosition != previousPosition)
{
m_azEl.setLocation(latitude, longitude, altitude);
// Update distances and what is visible, but only do it if position has changed significantly
if (!m_lastFullUpdatePosition.isValid() || (stationPosition.distanceTo(m_lastFullUpdatePosition) >= 1000))
{
updateAirports();
updateAirspaces();
updateNavAids();
m_lastFullUpdatePosition = stationPosition;
}
#ifdef QT_LOCATION_FOUND
// Update icon position on Map
QQuickItem *item = ui->map->rootObject();
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QObject *map = item->findChild("map");
#else
QObject *map = item->findChild("mapView");
#endif
if (map != nullptr)
{
QObject *stationObject = map->findChild("station");
if(stationObject != NULL)
{
QGeoCoordinate coords = stationObject->property("coordinate").value();
coords.setLatitude(latitude);
coords.setLongitude(longitude);
coords.setAltitude(altitude);
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
}
}
#endif
}
}
void ADSBDemodGUI::devicePositionChanged(float latitude, float longitude, float altitude)
{
updatePosition(latitude, longitude, altitude);
}
void ADSBDemodGUI::preferenceChanged(int elementType)
{
Preferences::ElementType pref = (Preferences::ElementType)elementType;
if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude))
{
Real myLatitude = MainCore::instance()->getSettings().getLatitude();
Real myLongitude = MainCore::instance()->getSettings().getLongitude();
Real myAltitude = MainCore::instance()->getSettings().getAltitude();
updatePosition(myLatitude, myLongitude, myAltitude);
}
else if (pref == Preferences::StationName)
{
#ifdef QT_LOCATION_FOUND
// Update icon label on Map
QQuickItem *item = ui->map->rootObject();
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QObject *map = item->findChild("map");
#else
QObject *map = item->findChild("mapView");
#endif
if (map != nullptr)
{
QObject *stationObject = map->findChild("station");
if(stationObject != NULL) {
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
}
}
#endif
}
else if (pref == Preferences::MapSmoothing)
{
#ifdef QT_LOCATION_FOUND
QQuickItem *item = ui->map->rootObject();
QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing());
#endif
}
}
void ADSBDemodGUI::initAviationWeather()
{
if (m_aviationWeather)
{
disconnect(m_aviationWeather, &AviationWeather::weatherUpdated, this, &ADSBDemodGUI::weatherUpdated);
delete m_aviationWeather;
m_aviationWeather = nullptr;
}
if (!m_settings.m_checkWXAPIKey.isEmpty())
{
m_aviationWeather = AviationWeather::create(m_settings.m_checkWXAPIKey);
if (m_aviationWeather) {
connect(m_aviationWeather, &AviationWeather::weatherUpdated, this, &ADSBDemodGUI::weatherUpdated);
}
}
}
void ADSBDemodGUI::requestMetar(const QString& icao)
{
if (m_aviationWeather) {
m_aviationWeather->getWeather(icao);
}
}
void ADSBDemodGUI::weatherUpdated(const AviationWeather::METAR &metar)
{
m_airportModel.updateWeather(metar.m_icao, metar.m_text, metar.decoded());
}
void ADSBDemodGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ADSBDemodGUI::on_deltaFrequency_changed);
QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &ADSBDemodGUI::on_rfBW_valueChanged);
QObject::connect(ui->threshold, &QDial::valueChanged, this, &ADSBDemodGUI::on_threshold_valueChanged);
QObject::connect(ui->phaseSteps, &QDial::valueChanged, this, &ADSBDemodGUI::on_phaseSteps_valueChanged);
QObject::connect(ui->tapsPerPhase, &QDial::valueChanged, this, &ADSBDemodGUI::on_tapsPerPhase_valueChanged);
QObject::connect(ui->adsbData, &QTableWidget::cellClicked, this, &ADSBDemodGUI::on_adsbData_cellClicked);
QObject::connect(ui->adsbData, &QTableWidget::cellDoubleClicked, this, &ADSBDemodGUI::on_adsbData_cellDoubleClicked);
QObject::connect(ui->spb, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_spb_currentIndexChanged);
QObject::connect(ui->correlateFullPreamble, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_correlateFullPreamble_clicked);
QObject::connect(ui->demodModeS, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_demodModeS_clicked);
QObject::connect(ui->feed, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_feed_clicked);
QObject::connect(ui->notifications, &QToolButton::clicked, this, &ADSBDemodGUI::on_notifications_clicked);
QObject::connect(ui->flightInfo, &QToolButton::clicked, this, &ADSBDemodGUI::on_flightInfo_clicked);
QObject::connect(ui->findOnMapFeature, &QToolButton::clicked, this, &ADSBDemodGUI::on_findOnMapFeature_clicked);
QObject::connect(ui->getOSNDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getOSNDB_clicked);
QObject::connect(ui->getAirportDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirportDB_clicked);
QObject::connect(ui->getAirspacesDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirspacesDB_clicked);
QObject::connect(ui->flightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_flightPaths_clicked);
QObject::connect(ui->allFlightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_allFlightPaths_clicked);
QObject::connect(ui->atcLabels, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_atcLabels_clicked);
QObject::connect(ui->amDemod, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_amDemod_currentIndexChanged);
QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &ADSBDemodGUI::on_displaySettings_clicked);
QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_logEnable_clicked);
QObject::connect(ui->logFilename, &QToolButton::clicked, this, &ADSBDemodGUI::on_logFilename_clicked);
QObject::connect(ui->logOpen, &QToolButton::clicked, this, &ADSBDemodGUI::on_logOpen_clicked);
}
void ADSBDemodGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}
|