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()
{
ADSBDemodFeedDialog dialog(&m_settings);
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);
if (dialog.exec() == QDialog::Accepted)
{
bool unitsChanged = m_settings.m_siUnits != oldSiUnits;
if (unitsChanged) {
m_aircraftModel.allAircraftUpdated();
}
displaySettings();
applySettings();
}
}
void ADSBDemodGUI::applyMapSettings()
{
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
QQuickItem *item = ui->map->rootObject();
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;
}
// Create the map using the specified provider
QQmlProperty::write(item, "mapProvider", "osm");
QVariantMap parameters;
// Use our repo, so we can append API key and redefine transmit maps
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
// Use ADS-B specific cache, as we use different transmit maps
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/QtLocation/5.8/tiles/osm/sdrangel_adsb";
parameters["osm.mapping.cache.directory"] = cachePath;
// On Linux, we need to create the directory
QDir dir(cachePath);
if (!dir.exists()) {
dir.mkpath(cachePath);
}
QString mapType;
switch (m_settings.m_mapType)
{
case ADSBDemodSettings::AVIATION_LIGHT:
mapType = "Transit Map";
break;
case ADSBDemodSettings::AVIATION_DARK:
mapType = "Night Transit Map";
break;
case ADSBDemodSettings::STREET:
mapType = "Street Map";
break;
case ADSBDemodSettings::SATELLITE:
mapType = "Satellite Map";
break;
}
QVariant retVal;
if (!QMetaObject::invokeMethod(item, "createMap", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, retVal),
Q_ARG(QVariant, QVariant::fromValue(parameters)),
Q_ARG(QVariant, mapType),
Q_ARG(QVariant, QVariant::fromValue(this))))
{
qCritical() << "ADSBDemodGUI::applyMapSettings - Failed to invoke createMap";
}
QObject *newMap = retVal.value();
// Restore position of map
if (newMap != nullptr)
{
if (coords.isValid())
{
newMap->setProperty("zoomLevel", QVariant::fromValue(zoom));
newMap->setProperty("center", QVariant::fromValue(coords));
}
}
else
{
qDebug() << "ADSBDemodGUI::applyMapSettings - createMap returned a nullptr";
}
// Move antenna icon to My Position
QObject *stationObject = newMap->findChild("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";
}
}
// 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_progressDialog(nullptr)
{
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)));
m_osmPort = 0; // Pick a free port
m_templateServer = new ADSBOSMTemplateServer("q2RVNAe3eFKCH4XsrE3r", m_osmPort);
ui->map->rootContext()->setContextProperty("aircraftModel", &m_aircraftModel);
ui->map->rootContext()->setContextProperty("airportModel", &m_airportModel);
ui->map->rootContext()->setContextProperty("airspaceModel", &m_airspaceModel);
ui->map->rootContext()->setContextProperty("navAidModel", &m_navAidModel);
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &ADSBDemodGUI::downloadFinished);
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()));
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)));
ui->photoHeader->setVisible(false);
ui->photoFlag->setVisible(false);
ui->photo->setVisible(false);
ui->flightDetails->setVisible(false);
ui->aircraftDetails->setVisible(false);
// Read aircraft information database, if it has previously been downloaded
if (!readFastDB(getFastDBFilename()))
{
if (readOSNDB(getOSNDBFilename()))
AircraftInformation::writeFastDB(getFastDBFilename(), m_aircraftInfo);
}
// Read airport information database, if it has previously been downloaded
m_airportInfo = AirportInformation::readAirportsDB(getAirportDBFilename());
if (m_airportInfo != nullptr)
AirportInformation::readFrequenciesDB(getAirportFrequenciesDBFilename(), m_airportInfo);
// Read registration prefix to country map
m_prefixMap = CSV::hash(":/flags/regprefixmap.csv");
// Read operator air force to military map
m_militaryMap = CSV::hash(":/flags/militarymap.csv");
connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
// Read airspaces
m_airspaces = OpenAIP::readAirspaces();
// Read NavAids
m_navAids = OpenAIP::readNavAids();
// Get station position
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
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.012423) && (stationLongitude == 8.418125)) {
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);
// Add airports within range of My Position
if (m_airportInfo != nullptr) {
updateAirports();
}
updateAirspaces();
updateNavAids();
update3DModels();
// Initialise text to speech engine
m_speech = new QTextToSpeech(this);
m_flightInformation = nullptr;
connect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
connect(ui->photo, &ClickableLabel::clicked, this, &ADSBDemodGUI::photoClicked);
// Update device list when devices are added or removed
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &ADSBDemodGUI::updateDeviceSetList);
connect(MainCore::instance(), &MainCore::deviceSetRemoved, this, &ADSBDemodGUI::updateDeviceSetList);
updateDeviceSetList();
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);
ui->map->installEventFilter(this);
}
ADSBDemodGUI::~ADSBDemodGUI()
{
if (m_templateServer)
{
m_templateServer->close();
delete m_templateServer;
}
disconnect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
disconnect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
disconnect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
disconnect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
disconnect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap);
m_redrawMapTimer.stop();
delete ui;
qDeleteAll(m_aircraft);
if (m_airportInfo) {
qDeleteAll(*m_airportInfo);
}
if (m_aircraftInfo) {
qDeleteAll(*m_aircraftInfo);
}
qDeleteAll(m_airlineIcons);
qDeleteAll(m_flagIcons);
if (m_flightInformation)
{
disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
delete m_flightInformation;
}
qDeleteAll(m_airspaces);
qDeleteAll(m_navAids);
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);
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_SPEED)->setText("Spd (kph)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (m/s)");
}
else
{
ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (ft)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_SPEED)->setText("Spd (kn)");
ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (ft/m)");
}
// 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();
applyMapSettings();
applyImportSettings();
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
}
void ADSBDemodGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void ADSBDemodGUI::enterEvent(QEvent* 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 minute 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
QList mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16)));
swgMapItem->setImage(new QString(""));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem);
messageQueue->push(msg);
}
// And finally free its memory
delete aircraft;
}
else
{
++i;
}
}
}
}
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_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_SPEED, new QTableWidgetItem("Spd (kn)"));
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->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_apiKey.isEmpty())
{
m_flightInformation = FlightInformation::create(m_settings.m_apiKey);
if (m_flightInformation) {
connect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
}
}
}
void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& flight)
{
Aircraft* aircraft = findAircraftByFlight(flight.m_flightICAO);
if (aircraft)
{
aircraft->m_flightStatusItem->setText(flight.m_flightStatus);
aircraft->m_depItem->setText(flight.m_departureICAO);
aircraft->m_arrItem->setText(flight.m_arrivalICAO);
aircraft->m_stdItem->setText(dataTimeToShortString(flight.m_departureScheduled));
aircraft->m_etdItem->setText(dataTimeToShortString(flight.m_departureEstimated));
aircraft->m_atdItem->setText(dataTimeToShortString(flight.m_departureActual));
aircraft->m_staItem->setText(dataTimeToShortString(flight.m_arrivalScheduled));
aircraft->m_etaItem->setText(dataTimeToShortString(flight.m_arrivalEstimated));
aircraft->m_ataItem->setText(dataTimeToShortString(flight.m_arrivalActual));
if (aircraft->m_positionValid) {
m_aircraftModel.aircraftUpdated(aircraft);
}
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_icao))
{
ui->photo->setPixmap(photo->m_pixmap);
ui->photo->setToolTip(QString("Photographer: %1").arg(photo->m_photographer)); // Required by terms of use
ui->photoHeader->setVisible(true);
ui->photoFlag->setVisible(true);
ui->photo->setVisible(true);
ui->flightDetails->setVisible(true);
ui->aircraftDetails->setVisible(true);
m_photoLink = photo->m_link;
}
}
void ADSBDemodGUI::photoClicked()
{
// Photo needs to link back to PlaneSpotters, as per terms of use
if (m_highlightAircraft)
{
if (m_photoLink.isEmpty())
{
QString icaoUpper = QString("%1").arg(m_highlightAircraft->m_icao, 1, 16).toUpper();
QDesktopServices::openUrl(QUrl(QString("https://www.planespotters.net/hex/%1").arg(icaoUpper)));
}
else
{
QDesktopServices::openUrl(QUrl(m_photoLink));
}
}
}
void ADSBDemodGUI::on_logEnable_clicked(bool checked)
{
m_settings.m_logEnabled = checked;
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))
{
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;
bool cancelled = false;
QStringList cols;
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();
handleADSB(bytes, dateTime, correlation, correlation, false);
if ((count > 0) && (count % 100000 == 0))
{
dialog.setText(QString("Reading ADS-B data\n%1").arg(count));
QApplication::processEvents();
if (dialog.clickedButton()) {
cancelled = true;
}
}
count++;
}
}
m_aircraftModel.allAircraftUpdated();
dialog.close();
}
else
{
QMessageBox::critical(this, "ADS-B", error);
}
}
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::downloadError(const QString& error)
{
QMessageBox::critical(this, "ADS-B", error);
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void ADSBDemodGUI::downloadAirspaceFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading airspaces.");
}
m_airspaces = OpenAIP::readAirspaces();
updateAirspaces();
m_openAIP.downloadNavAids();
}
void ADSBDemodGUI::downloadNavAidsFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading NAVAIDs.");
}
m_navAids = OpenAIP::readNavAids();
updateNavAids();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
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()
{
// 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));
}
}
}
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);
}
}
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 false;
}
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);
}
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);
}
aircraft->m_positionDateTime = timePosition;
}
aircraft->m_onSurface = state[8].toBool(false);
if (!state[9].isNull())
{
aircraft->m_speed = (int)state[9].toDouble();
aircraft->m_speedItem->setData(Qt::DisplayRole, aircraft->m_speed);
aircraft->m_speedValid = true;
aircraft->m_speedType = Aircraft::GS;
}
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::preferenceChanged(int elementType)
{
Preferences::ElementType pref = (Preferences::ElementType)elementType;
if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude))
{
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
if ( (stationLatitude != m_azEl.getLocationSpherical().m_latitude)
|| (stationLongitude != m_azEl.getLocationSpherical().m_longitude)
|| (stationAltitude != m_azEl.getLocationSpherical().m_altitude))
{
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
// Update distances and what is visible
updateAirports();
updateAirspaces();
updateNavAids();
// Update icon position on Map
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild("map");
if (map != nullptr)
{
QObject *stationObject = map->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));
}
}
}
}
if (pref == Preferences::StationName)
{
// Update icon label on Map
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild("map");
if (map != nullptr)
{
QObject *stationObject = map->findChild("station");
if(stationObject != NULL) {
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
}
}
}
}
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->device, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_device_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);
}
|