mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-08 17:46:03 -05:00
9c7aa8b333
Allow OpenSkyNetwork DB, OpenAIP and OurAirports DB stuctures to be shared by different plugins, to speed up loading. Perform map anti-aliasing on the whole map, rather than just info boxes, to improve rendering speed when there are many items. Add map multisampling as a preference. Add plotting of airspaces, airports, navaids on Map feature. Add support for polylines and polygons to be plotted on Map feature. Add support for images to 2D Map feature. Add distance and name filters to Map feature. Filter map items when zoomed out or if off screen, to improve rendering performance. Add UK DAB, FM and AM transmitters to Map feature. Use labelless maps for 2D transmit maps in Map feature (same as in ADS-B demod).
572 lines
21 KiB
C++
572 lines
21 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2020 Jon Beniston, M7RCE //
|
|
// //
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
// it under the terms of the GNU General Public License as published by //
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
// (at your option) any later version. //
|
|
// //
|
|
// This program is distributed in the hope that it will be useful, //
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
// GNU General Public License V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <QFileInfo>
|
|
#include <QResource>
|
|
|
|
#include <QtGui/private/qzipreader_p.h>
|
|
|
|
#include "util/osndb.h"
|
|
|
|
QHash<QString, QIcon *> AircraftInformation::m_airlineIcons;
|
|
QHash<QString, bool> AircraftInformation::m_airlineMissingIcons;
|
|
QHash<QString, QIcon *> AircraftInformation::m_flagIcons;
|
|
QHash<QString, QString> *AircraftInformation::m_prefixMap;
|
|
QHash<QString, QString> *AircraftInformation::m_militaryMap;
|
|
QMutex AircraftInformation::m_mutex;
|
|
|
|
QSharedPointer<const QHash<int, AircraftInformation *>> OsnDB::m_aircraftInformation;
|
|
QSharedPointer<const QHash<QString, AircraftInformation *>> OsnDB::m_aircraftInformationByReg;
|
|
QDateTime OsnDB::m_modifiedDateTime;
|
|
|
|
|
|
OsnDB::OsnDB(QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OsnDB::downloadFinished);
|
|
}
|
|
|
|
OsnDB::~OsnDB()
|
|
{
|
|
disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OsnDB::downloadFinished);
|
|
}
|
|
|
|
void OsnDB::downloadAircraftInformation()
|
|
{
|
|
QString filename = OsnDB::getOSNDBZipFilename();
|
|
QString urlString = OSNDB_URL;
|
|
QUrl dbURL(urlString);
|
|
qDebug() << "OsnDB::downloadAircraftInformation: Downloading " << urlString;
|
|
emit downloadingURL(urlString);
|
|
QNetworkReply *reply = m_dlm.download(dbURL, filename);
|
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) {
|
|
emit downloadProgress(bytesRead, totalBytes);
|
|
});
|
|
}
|
|
|
|
void OsnDB::downloadFinished(const QString& filename, bool success)
|
|
{
|
|
if (!success)
|
|
{
|
|
qWarning() << "OsnDB::downloadFinished: Failed to download: " << filename;
|
|
emit downloadError(QString("Failed to download: %1").arg(filename));
|
|
}
|
|
else if (filename == OsnDB::getOSNDBZipFilename())
|
|
{
|
|
// Extract .csv file from .zip file
|
|
QZipReader reader(filename);
|
|
QByteArray database = reader.fileData("media/data/samples/metadata/aircraftDatabase.csv");
|
|
if (database.size() > 0)
|
|
{
|
|
QFile file(OsnDB::getOSNDBFilename());
|
|
if (file.open(QIODevice::WriteOnly))
|
|
{
|
|
file.write(database);
|
|
file.close();
|
|
emit downloadAircraftInformationFinished();
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "OsnDB::downloadFinished - Failed to open " << file.fileName() << " for writing";
|
|
emit downloadError(QString("Failed to open %1 for writing").arg(file.fileName()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "OsnDB::downloadFinished - aircraftDatabase.csv not in expected dir. Extracting all.";
|
|
if (reader.extractAll(getDataDir()))
|
|
{
|
|
emit downloadAircraftInformationFinished();
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "OsnDB::downloadFinished - Failed to extract files from " << filename;
|
|
emit downloadError(QString("Failed to extract files from ").arg(filename));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "OsnDB::downloadFinished: Unexpected filename: " << filename;
|
|
emit downloadError(QString("Unexpected filename: %1").arg(filename));
|
|
}
|
|
}
|
|
|
|
QSharedPointer<const QHash<int, AircraftInformation *>> OsnDB::getAircraftInformation()
|
|
{
|
|
QFileInfo fastFileInfo(getFastDBFilename());
|
|
QFileInfo fullFileInfo(getOSNDBFilename());
|
|
QDateTime fastModifiedDateTime = fastFileInfo.lastModified();
|
|
QDateTime fullModifiedDateTime = fullFileInfo.lastModified();
|
|
|
|
// Update fast database, if full database is newer
|
|
if (fullModifiedDateTime > fastModifiedDateTime)
|
|
{
|
|
qDebug() << "AircraftInformation::getAircraftInformation: Creating fast database";
|
|
m_aircraftInformation = QSharedPointer<const QHash<int, AircraftInformation *>>(OsnDB::readOSNDB(getOSNDBFilename()));
|
|
if (m_aircraftInformation)
|
|
{
|
|
OsnDB::writeFastDB(OsnDB::getFastDBFilename(), m_aircraftInformation.get());
|
|
fastModifiedDateTime = fastFileInfo.lastModified();
|
|
m_modifiedDateTime = fastModifiedDateTime;
|
|
m_aircraftInformationByReg = QSharedPointer<const QHash<QString, AircraftInformation *>> (OsnDB::registrationHash(m_aircraftInformation.get()));
|
|
}
|
|
}
|
|
|
|
if (!m_aircraftInformation || (fastModifiedDateTime > m_modifiedDateTime))
|
|
{
|
|
m_aircraftInformation = QSharedPointer<const QHash<int, AircraftInformation *>>(OsnDB::readFastDB(getFastDBFilename()));
|
|
if (m_aircraftInformation)
|
|
{
|
|
m_modifiedDateTime = fastModifiedDateTime;
|
|
m_aircraftInformationByReg = QSharedPointer<const QHash<QString, AircraftInformation *>> (OsnDB::registrationHash(m_aircraftInformation.get()));
|
|
}
|
|
}
|
|
|
|
return m_aircraftInformation;
|
|
}
|
|
|
|
QSharedPointer<const QHash<QString, AircraftInformation *>> OsnDB::getAircraftInformationByReg()
|
|
{
|
|
getAircraftInformation();
|
|
return m_aircraftInformationByReg;
|
|
}
|
|
|
|
QHash<int, AircraftInformation *> *OsnDB::readOSNDB(const QString &filename)
|
|
{
|
|
int cnt = 0;
|
|
QHash<int, AircraftInformation *> *aircraftInfo = nullptr;
|
|
|
|
// Column numbers used for the data as of 2020/10/28
|
|
int icaoCol = 0;
|
|
int registrationCol = 1;
|
|
int manufacturerNameCol = 3;
|
|
int modelCol = 4;
|
|
int ownerCol = 13;
|
|
int operatorCol = 9;
|
|
int operatorICAOCol = 11;
|
|
int registeredCol = 15;
|
|
|
|
qDebug() << "AircraftInformation::readOSNDB: " << filename;
|
|
|
|
FILE *file;
|
|
QByteArray utfFilename = filename.toUtf8();
|
|
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
|
|
{
|
|
char row[2048];
|
|
|
|
if (fgets(row, sizeof(row), file))
|
|
{
|
|
aircraftInfo = new QHash<int, AircraftInformation *>();
|
|
aircraftInfo->reserve(500000);
|
|
// Read header
|
|
int idx = 0;
|
|
char *p = strtok(row, ",");
|
|
while (p != NULL)
|
|
{
|
|
if (!strcmp(p, "icao24"))
|
|
icaoCol = idx;
|
|
else if (!strcmp(p, "registration"))
|
|
registrationCol = idx;
|
|
else if (!strcmp(p, "manufacturername"))
|
|
manufacturerNameCol = idx;
|
|
else if (!strcmp(p, "model"))
|
|
modelCol = idx;
|
|
else if (!strcmp(p, "owner"))
|
|
ownerCol = idx;
|
|
else if (!strcmp(p, "operator"))
|
|
operatorCol = idx;
|
|
else if (!strcmp(p, "operatoricao"))
|
|
operatorICAOCol = idx;
|
|
else if (!strcmp(p, "registered"))
|
|
registeredCol = idx;
|
|
p = strtok(NULL, ",");
|
|
idx++;
|
|
}
|
|
// Read data
|
|
while (fgets(row, sizeof(row), file))
|
|
{
|
|
int icao = 0;
|
|
char *icaoString = NULL;
|
|
char *registration = NULL;
|
|
size_t registrationLen = 0;
|
|
char *manufacturerName = NULL;
|
|
size_t manufacturerNameLen = 0;
|
|
char *model = NULL;
|
|
size_t modelLen = 0;
|
|
char *owner = NULL;
|
|
size_t ownerLen = 0;
|
|
char *operatorName = NULL;
|
|
size_t operatorNameLen = 0;
|
|
char *operatorICAO = NULL;
|
|
size_t operatorICAOLen = 0;
|
|
char *registered = NULL;
|
|
size_t registeredLen = 0;
|
|
|
|
p = strtok(row, ",");
|
|
idx = 0;
|
|
while (p != NULL)
|
|
{
|
|
// Read strings, stripping quotes
|
|
if (idx == icaoCol)
|
|
{
|
|
icaoString = p+1;
|
|
icaoString[strlen(icaoString)-1] = '\0';
|
|
icao = strtol(icaoString, NULL, 16);
|
|
// Ignore entries with uppercase - might be better to try and merge?
|
|
// See: https://opensky-network.org/forum/bug-reports/652-are-the-aircraft-database-dumps-working
|
|
for (int i = 0; icaoString[i] != '\0'; i++)
|
|
{
|
|
char c = icaoString[i];
|
|
if ((c >= 'A') && (c <= 'F'))
|
|
{
|
|
icao = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (idx == registrationCol)
|
|
{
|
|
registration = p+1;
|
|
registrationLen = strlen(registration)-1;
|
|
registration[registrationLen] = '\0';
|
|
}
|
|
else if (idx == manufacturerNameCol)
|
|
{
|
|
manufacturerName = p+1;
|
|
manufacturerNameLen = strlen(manufacturerName)-1;
|
|
manufacturerName[manufacturerNameLen] = '\0';
|
|
}
|
|
else if (idx == modelCol)
|
|
{
|
|
model = p+1;
|
|
modelLen = strlen(model)-1;
|
|
model[modelLen] = '\0';
|
|
}
|
|
else if (idx == ownerCol)
|
|
{
|
|
owner = p+1;
|
|
ownerLen = strlen(owner)-1;
|
|
owner[ownerLen] = '\0';
|
|
}
|
|
else if (idx == operatorCol)
|
|
{
|
|
operatorName = p+1;
|
|
operatorNameLen = strlen(operatorName)-1;
|
|
operatorName[operatorNameLen] = '\0';
|
|
}
|
|
else if (idx == operatorICAOCol)
|
|
{
|
|
operatorICAO = p+1;
|
|
operatorICAOLen = strlen(operatorICAO)-1;
|
|
operatorICAO[operatorICAOLen] = '\0';
|
|
}
|
|
else if (idx == registeredCol)
|
|
{
|
|
registered = p+1;
|
|
registeredLen = strlen(registered)-1;
|
|
registered[registeredLen] = '\0';
|
|
}
|
|
p = strtok(NULL, ",");
|
|
idx++;
|
|
}
|
|
|
|
// Only create the entry if we have some interesting data
|
|
if ((icao != 0) && (registrationLen > 0 || modelLen > 0 || ownerLen > 0 || operatorNameLen > 0 || operatorICAOLen > 0))
|
|
{
|
|
QString modelQ = QString(model);
|
|
// Tidy up the model names
|
|
if (modelQ.endsWith(" (Boeing)"))
|
|
modelQ = modelQ.left(modelQ.size() - 9);
|
|
else if (modelQ.startsWith("BOEING "))
|
|
modelQ = modelQ.right(modelQ.size() - 7);
|
|
else if (modelQ.startsWith("Boeing "))
|
|
modelQ = modelQ.right(modelQ.size() - 7);
|
|
else if (modelQ.startsWith("AIRBUS "))
|
|
modelQ = modelQ.right(modelQ.size() - 7);
|
|
else if (modelQ.startsWith("Airbus "))
|
|
modelQ = modelQ.right(modelQ.size() - 7);
|
|
else if (modelQ.endsWith(" (Cessna)"))
|
|
modelQ = modelQ.left(modelQ.size() - 9);
|
|
|
|
AircraftInformation *aircraft = new AircraftInformation();
|
|
aircraft->m_icao = icao;
|
|
aircraft->m_registration = QString(registration);
|
|
aircraft->m_manufacturerName = QString(manufacturerName);
|
|
aircraft->m_model = modelQ;
|
|
aircraft->m_owner = QString(owner);
|
|
aircraft->m_operator = QString(operatorName);
|
|
aircraft->m_operatorICAO = QString(operatorICAO);
|
|
aircraft->m_registered = QString(registered);
|
|
aircraftInfo->insert(icao, aircraft);
|
|
cnt++;
|
|
}
|
|
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
else
|
|
qDebug() << "AircraftInformation::readOSNDB: Failed to open " << filename;
|
|
|
|
qDebug() << "AircraftInformation::readOSNDB: Read " << cnt << " aircraft";
|
|
|
|
return aircraftInfo;
|
|
}
|
|
|
|
QHash<QString, AircraftInformation *> *OsnDB::registrationHash(const QHash<int, AircraftInformation *> *in)
|
|
{
|
|
QHash<QString, AircraftInformation *> *out = new QHash<QString, AircraftInformation *>();
|
|
QHashIterator<int, AircraftInformation *> i(*in);
|
|
while (i.hasNext())
|
|
{
|
|
i.next();
|
|
AircraftInformation *info = i.value();
|
|
out->insert(info->m_registration, info);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
bool OsnDB::writeFastDB(const QString &filename, const QHash<int, AircraftInformation *> *aircraftInfo)
|
|
{
|
|
QFile file(filename);
|
|
if (file.open(QIODevice::WriteOnly))
|
|
{
|
|
file.write("icao24,registration,manufacturername,model,owner,operator,operatoricao,registered\n");
|
|
QHash<int, AircraftInformation *>::const_iterator i = aircraftInfo->begin();
|
|
while (i != aircraftInfo->end())
|
|
{
|
|
AircraftInformation *info = i.value();
|
|
file.write(QString("%1").arg(info->m_icao, 1, 16).toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_registration.toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_manufacturerName.toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_model.toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_owner.toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_operator.toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_operatorICAO.toUtf8());
|
|
file.write(",");
|
|
file.write(info->m_registered.toUtf8());
|
|
file.write("\n");
|
|
++i;
|
|
}
|
|
file.close();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
qCritical() << "AircraftInformation::writeFastDB failed to open " << filename << " for writing: " << file.errorString();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QHash<int, AircraftInformation *> *OsnDB::readFastDB(const QString &filename)
|
|
{
|
|
int cnt = 0;
|
|
QHash<int, AircraftInformation *> *aircraftInfo = nullptr;
|
|
|
|
qDebug() << "AircraftInformation::readFastDB: " << filename;
|
|
|
|
FILE *file;
|
|
QByteArray utfFilename = filename.toUtf8();
|
|
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
|
|
{
|
|
char row[2048];
|
|
|
|
if (fgets(row, sizeof(row), file))
|
|
{
|
|
// Check header
|
|
if (!strcmp(row, "icao24,registration,manufacturername,model,owner,operator,operatoricao,registered\n"))
|
|
{
|
|
aircraftInfo = new QHash<int, AircraftInformation *>();
|
|
aircraftInfo->reserve(500000);
|
|
// Read data
|
|
while (fgets(row, sizeof(row), file))
|
|
{
|
|
char *p = row;
|
|
AircraftInformation *aircraft = new AircraftInformation();
|
|
char *icaoString = csvNext(&p);
|
|
int icao = strtol(icaoString, NULL, 16);
|
|
aircraft->m_icao = icao;
|
|
aircraft->m_registration = QString(csvNext(&p));
|
|
aircraft->m_manufacturerName = QString(csvNext(&p));
|
|
aircraft->m_model = QString(csvNext(&p));
|
|
aircraft->m_owner = QString(csvNext(&p));
|
|
aircraft->m_operator = QString(csvNext(&p));
|
|
aircraft->m_operatorICAO = QString(csvNext(&p));
|
|
aircraft->m_registered = QString(csvNext(&p));
|
|
aircraftInfo->insert(icao, aircraft);
|
|
cnt++;
|
|
}
|
|
}
|
|
else
|
|
qDebug() << "AircraftInformation::readFastDB: Unexpected header";
|
|
}
|
|
else
|
|
qDebug() << "AircraftInformation::readFastDB: Empty file";
|
|
fclose(file);
|
|
}
|
|
else
|
|
qDebug() << "AircraftInformation::readFastDB: Failed to open " << filename;
|
|
|
|
qDebug() << "AircraftInformation::readFastDB - read " << cnt << " aircraft";
|
|
|
|
return aircraftInfo;
|
|
}
|
|
|
|
QString AircraftInformation::getFlag() const
|
|
{
|
|
QString flag;
|
|
if (m_prefixMap)
|
|
{
|
|
int idx = m_registration.indexOf('-');
|
|
if (idx >= 0)
|
|
{
|
|
QString prefix;
|
|
|
|
// Some countries use AA-A - try these first as first letters are common
|
|
prefix = m_registration.left(idx + 2);
|
|
if (m_prefixMap->contains(prefix))
|
|
{
|
|
flag = m_prefixMap->value(prefix);
|
|
}
|
|
else
|
|
{
|
|
// Try letters before '-'
|
|
prefix = m_registration.left(idx);
|
|
if (m_prefixMap->contains(prefix)) {
|
|
flag = m_prefixMap->value(prefix);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No '-' Could be one of a few countries or military.
|
|
// See: https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes
|
|
if (m_registration.startsWith("N")) {
|
|
flag = m_prefixMap->value("N"); // US
|
|
} else if (m_registration.startsWith("JA")) {
|
|
flag = m_prefixMap->value("JA"); // Japan
|
|
} else if (m_registration.startsWith("HL")) {
|
|
flag = m_prefixMap->value("HL"); // Korea
|
|
} else if (m_registration.startsWith("YV")) {
|
|
flag = m_prefixMap->value("YV"); // Venezuela
|
|
} else if ((m_militaryMap != nullptr) && (m_militaryMap->contains(m_operator))) {
|
|
flag = m_militaryMap->value(m_operator);
|
|
}
|
|
}
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
QString AircraftInformation::getAirlineIconPath(const QString &operatorICAO)
|
|
{
|
|
QString endPath = QString("/airlinelogos/%1.bmp").arg(operatorICAO);
|
|
// Try in user directory first, so they can customise
|
|
QString userIconPath = OsnDB::getDataDir() + endPath;
|
|
QFile file(userIconPath);
|
|
if (file.exists())
|
|
{
|
|
return userIconPath;
|
|
}
|
|
else
|
|
{
|
|
// Try in resources
|
|
QString resourceIconPath = ":" + endPath;
|
|
QResource resource(resourceIconPath);
|
|
if (resource.isValid())
|
|
{
|
|
return resourceIconPath;
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
QIcon *AircraftInformation::getAirlineIcon(const QString &operatorICAO)
|
|
{
|
|
if (m_airlineIcons.contains(operatorICAO))
|
|
{
|
|
return m_airlineIcons.value(operatorICAO);
|
|
}
|
|
else
|
|
{
|
|
QIcon *icon = nullptr;
|
|
QString path = getAirlineIconPath(operatorICAO);
|
|
if (!path.isEmpty())
|
|
{
|
|
icon = new QIcon(path);
|
|
m_airlineIcons.insert(operatorICAO, icon);
|
|
}
|
|
else
|
|
{
|
|
if (!m_airlineMissingIcons.contains(operatorICAO))
|
|
{
|
|
qDebug() << "ADSBDemodGUI: No airline logo for " << operatorICAO;
|
|
m_airlineMissingIcons.insert(operatorICAO, true);
|
|
}
|
|
}
|
|
return icon;
|
|
}
|
|
}
|
|
QString AircraftInformation::getFlagIconPath(const QString &country)
|
|
{
|
|
QString endPath = QString("/flags/%1.bmp").arg(country);
|
|
// Try in user directory first, so they can customise
|
|
QString userIconPath = OsnDB::getDataDir() + endPath;
|
|
QFile file(userIconPath);
|
|
if (file.exists())
|
|
{
|
|
return userIconPath;
|
|
}
|
|
else
|
|
{
|
|
// Try in resources
|
|
QString resourceIconPath = ":" + endPath;
|
|
QResource resource(resourceIconPath);
|
|
if (resource.isValid())
|
|
{
|
|
return resourceIconPath;
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
QIcon *AircraftInformation::getFlagIcon(const QString &country)
|
|
{
|
|
if (m_flagIcons.contains(country))
|
|
{
|
|
return m_flagIcons.value(country);
|
|
}
|
|
else
|
|
{
|
|
QIcon *icon = nullptr;
|
|
QString path = getFlagIconPath(country);
|
|
if (!path.isEmpty())
|
|
{
|
|
icon = new QIcon(path);
|
|
m_flagIcons.insert(country, icon);
|
|
}
|
|
return icon;
|
|
}
|
|
}
|
|
|