mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-06 00:41:17 -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).
420 lines
15 KiB
C++
420 lines
15 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 <QFile>
|
|
#include <QFileInfo>
|
|
#include <QByteArray>
|
|
#include <QList>
|
|
#include <QDebug>
|
|
#include <QLocale>
|
|
#include <QStandardPaths>
|
|
|
|
#include "util/ourairportsdb.h"
|
|
|
|
QMutex OurAirportsDB::m_mutex;
|
|
QSharedPointer<QHash<int, AirportInformation *>> OurAirportsDB::m_airportsById;
|
|
QSharedPointer<QHash<QString, AirportInformation *>> OurAirportsDB::m_airportsByIdent;
|
|
QDateTime OurAirportsDB::m_modifiedDateTime;
|
|
|
|
AirportInformation::~AirportInformation()
|
|
{
|
|
qDeleteAll(m_frequencies);
|
|
}
|
|
|
|
QString AirportInformation::getImageName() const
|
|
{
|
|
switch (m_type)
|
|
{
|
|
case AirportType::Large:
|
|
return "airport_large.png";
|
|
case AirportType::Medium:
|
|
return "airport_medium.png";
|
|
case AirportType::Heliport:
|
|
return "heliport.png";
|
|
default:
|
|
return "airport_small.png";
|
|
}
|
|
}
|
|
|
|
OurAirportsDB::OurAirportsDB(QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OurAirportsDB::downloadFinished);
|
|
}
|
|
|
|
OurAirportsDB::~OurAirportsDB()
|
|
{
|
|
disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OurAirportsDB::downloadFinished);
|
|
}
|
|
|
|
void OurAirportsDB::downloadAirportInformation()
|
|
{
|
|
// Download airport database
|
|
QString urlString = AIRPORTS_URL;
|
|
QUrl dbURL(urlString);
|
|
qDebug() << "OurAirportsDB::downloadAirportInformation: Downloading " << urlString;
|
|
emit downloadingURL(urlString);
|
|
QNetworkReply *reply = m_dlm.download(dbURL, OurAirportsDB::getAirportDBFilename());
|
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) {
|
|
emit downloadProgress(bytesRead, totalBytes);
|
|
});
|
|
}
|
|
|
|
void OurAirportsDB::downloadFinished(const QString& filename, bool success)
|
|
{
|
|
if (!success)
|
|
{
|
|
qWarning() << "OurAirportsDB::downloadFinished: Failed to download: " << filename;
|
|
emit downloadError(QString("Failed to download: %1").arg(filename));
|
|
}
|
|
else if (filename == OurAirportsDB::getAirportDBFilename())
|
|
{
|
|
// Now download airport frequencies
|
|
QString urlString = AIRPORT_FREQUENCIES_URL;
|
|
QUrl dbURL(urlString);
|
|
qDebug() << "OurAirportsDB::downloadFinished: Downloading " << urlString;
|
|
emit downloadingURL(urlString);
|
|
QNetworkReply *reply = m_dlm.download(dbURL, OurAirportsDB::getAirportFrequenciesDBFilename());
|
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) {
|
|
emit downloadProgress(bytesRead, totalBytes);
|
|
});
|
|
}
|
|
else if (filename == OurAirportsDB::getAirportFrequenciesDBFilename())
|
|
{
|
|
emit downloadAirportInformationFinished();
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "OurAirportsDB::downloadFinished: Unexpected filename: " << filename;
|
|
emit downloadError(QString("Unexpected filename: %1").arg(filename));
|
|
}
|
|
}
|
|
|
|
QSharedPointer<const QHash<int, AirportInformation *>> OurAirportsDB::getAirportsById()
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
readDB();
|
|
return m_airportsById;
|
|
}
|
|
|
|
QSharedPointer<const QHash<QString, AirportInformation *>> OurAirportsDB::getAirportsByIdent()
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
readDB();
|
|
return m_airportsByIdent;
|
|
}
|
|
|
|
void OurAirportsDB::readDB()
|
|
{
|
|
QFileInfo airportDBFileInfo(getAirportDBFilename());
|
|
QDateTime airportDBModifiedDateTime = airportDBFileInfo.lastModified();
|
|
|
|
if (!m_airportsById || (airportDBModifiedDateTime > m_modifiedDateTime))
|
|
{
|
|
// Using shared pointer, so old object, if it exists, will be deleted, when no longer user
|
|
m_airportsById = QSharedPointer<QHash<int, AirportInformation *>>(OurAirportsDB::readAirportsDB(getAirportDBFilename()));
|
|
if (m_airportsById != nullptr)
|
|
{
|
|
OurAirportsDB::readFrequenciesDB(getAirportFrequenciesDBFilename(), m_airportsById.get());
|
|
m_airportsByIdent = QSharedPointer<QHash<QString, AirportInformation *>>(identHash(m_airportsById.get()));
|
|
}
|
|
|
|
m_modifiedDateTime = airportDBModifiedDateTime;
|
|
}
|
|
}
|
|
|
|
QString OurAirportsDB::getDataDir()
|
|
{
|
|
// Get directory to store app data in
|
|
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
|
// First dir is writable
|
|
return locations[0];
|
|
}
|
|
|
|
QString OurAirportsDB::getAirportDBFilename()
|
|
{
|
|
return getDataDir() + "/airportDatabase.csv";
|
|
}
|
|
|
|
QString OurAirportsDB::getAirportFrequenciesDBFilename()
|
|
{
|
|
return getDataDir() + "/airportFrequenciesDatabase.csv";
|
|
}
|
|
|
|
QString OurAirportsDB::trimQuotes(const QString s)
|
|
{
|
|
if (s.startsWith('\"') && s.endsWith('\"')) {
|
|
return s.mid(1, s.size() - 2);
|
|
} else {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
// Read OurAirport's airport CSV file
|
|
// See comments for readOSNDB
|
|
QHash<int, AirportInformation *> *OurAirportsDB::readAirportsDB(const QString &filename)
|
|
{
|
|
int cnt = 0;
|
|
QHash<int, AirportInformation *> *airportInfo = nullptr;
|
|
|
|
// Column numbers used for the data as of 2020/10/28
|
|
int idCol = 0;
|
|
int identCol = 1;
|
|
int typeCol = 2;
|
|
int nameCol = 3;
|
|
int latitudeCol = 4;
|
|
int longitudeCol = 5;
|
|
int elevationCol = 6;
|
|
|
|
qDebug() << "OurAirportsDB::readAirportsDB: " << filename;
|
|
|
|
FILE *file;
|
|
QByteArray utfFilename = filename.toUtf8();
|
|
QLocale cLocale(QLocale::C);
|
|
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
|
|
{
|
|
char row[2048];
|
|
|
|
if (fgets(row, sizeof(row), file))
|
|
{
|
|
airportInfo = new QHash<int, AirportInformation *>();
|
|
airportInfo->reserve(70000);
|
|
|
|
// Read header
|
|
int idx = 0;
|
|
char *p = strtok(row, ",");
|
|
while (p != NULL)
|
|
{
|
|
if (!strcmp(p, "id"))
|
|
idCol = idx;
|
|
else if (!strcmp(p, "ident"))
|
|
identCol = idx;
|
|
else if (!strcmp(p, "type"))
|
|
typeCol = idx;
|
|
else if (!strcmp(p, "name"))
|
|
nameCol = idx;
|
|
else if (!strcmp(p, "latitude_deg"))
|
|
latitudeCol = idx;
|
|
else if (!strcmp(p, "longitude_deg"))
|
|
longitudeCol = idx;
|
|
else if (!strcmp(p, "elevation_ft"))
|
|
elevationCol = idx;
|
|
p = strtok(NULL, ",");
|
|
idx++;
|
|
}
|
|
// Read data
|
|
while (fgets(row, sizeof(row), file))
|
|
{
|
|
int id = 0;
|
|
char *idString = NULL;
|
|
char *ident = NULL;
|
|
size_t identLen = 0;
|
|
char *type = NULL;
|
|
size_t typeLen = 0;
|
|
char *name = NULL;
|
|
size_t nameLen = 0;
|
|
float latitude = 0.0f;
|
|
char *latitudeString = NULL;
|
|
size_t latitudeLen = 0;
|
|
float longitude = 0.0f;
|
|
char *longitudeString = NULL;
|
|
size_t longitudeLen = 0;
|
|
float elevation = 0.0f;
|
|
char *elevationString = NULL;
|
|
size_t elevationLen = 0;
|
|
|
|
p = strtok(row, ",");
|
|
idx = 0;
|
|
while (p != NULL)
|
|
{
|
|
// Read strings, stripping quotes
|
|
if (idx == idCol)
|
|
{
|
|
idString = p;
|
|
idString[strlen(idString)] = '\0';
|
|
id = strtol(idString, NULL, 10);
|
|
}
|
|
else if (idx == identCol)
|
|
{
|
|
ident = p+1;
|
|
identLen = strlen(ident)-1;
|
|
ident[identLen] = '\0';
|
|
}
|
|
else if (idx == typeCol)
|
|
{
|
|
type = p+1;
|
|
typeLen = strlen(type)-1;
|
|
type[typeLen] = '\0';
|
|
}
|
|
else if (idx == nameCol)
|
|
{
|
|
name = p+1;
|
|
nameLen = strlen(name)-1;
|
|
name[nameLen] = '\0';
|
|
}
|
|
else if (idx == latitudeCol)
|
|
{
|
|
latitudeString = p;
|
|
latitudeLen = strlen(latitudeString)-1;
|
|
latitudeString[latitudeLen] = '\0';
|
|
latitude = cLocale.toFloat(latitudeString);
|
|
}
|
|
else if (idx == longitudeCol)
|
|
{
|
|
longitudeString = p;
|
|
longitudeLen = strlen(longitudeString)-1;
|
|
longitudeString[longitudeLen] = '\0';
|
|
longitude = cLocale.toFloat(longitudeString);
|
|
}
|
|
else if (idx == elevationCol)
|
|
{
|
|
elevationString = p;
|
|
elevationLen = strlen(elevationString)-1;
|
|
elevationString[elevationLen] = '\0';
|
|
elevation = cLocale.toFloat(elevationString);
|
|
}
|
|
p = strtok(NULL, ",");
|
|
idx++;
|
|
}
|
|
|
|
// Only create the entry if we have some interesting data
|
|
if (((latitude != 0.0f) || (longitude != 0.0f)) && (type && strcmp(type, "closed")))
|
|
{
|
|
AirportInformation *airport = new AirportInformation();
|
|
airport->m_id = id;
|
|
airport->m_ident = QString(ident);
|
|
if (!strcmp(type, "small_airport")) {
|
|
airport->m_type = AirportInformation::AirportType::Small;
|
|
} else if (!strcmp(type, "medium_airport")) {
|
|
airport->m_type = AirportInformation::AirportType::Medium;
|
|
} else if (!strcmp(type, "large_airport")) {
|
|
airport->m_type = AirportInformation::AirportType::Large;
|
|
} else if (!strcmp(type, "heliport")) {
|
|
airport->m_type = AirportInformation::AirportType::Heliport;
|
|
}
|
|
airport->m_name = QString(name);
|
|
airport->m_latitude = latitude;
|
|
airport->m_longitude = longitude;
|
|
airport->m_elevation = elevation;
|
|
airportInfo->insert(id, airport);
|
|
cnt++;
|
|
}
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
else
|
|
qDebug() << "OurAirportsDB::readAirportsDB: Failed to open " << filename;
|
|
|
|
qDebug() << "OurAirportsDB::readAirportsDB: Read " << cnt << " airports";
|
|
|
|
return airportInfo;
|
|
}
|
|
|
|
// Create hash table using ICAO identifier as key
|
|
QHash<QString, AirportInformation *> *OurAirportsDB::identHash(QHash<int, AirportInformation *> *in)
|
|
{
|
|
QHash<QString, AirportInformation *> *out = new QHash<QString, AirportInformation *>();
|
|
QHashIterator<int, AirportInformation *> i(*in);
|
|
while (i.hasNext())
|
|
{
|
|
i.next();
|
|
AirportInformation *info = i.value();
|
|
out->insert(info->m_ident, info);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Read OurAirport's airport frequencies CSV file
|
|
bool OurAirportsDB::readFrequenciesDB(const QString &filename, QHash<int, AirportInformation *> *airportInfo)
|
|
{
|
|
int cnt = 0;
|
|
|
|
// Column numbers used for the data as of 2020/10/28
|
|
int airportRefCol = 1;
|
|
int typeCol = 3;
|
|
int descriptionCol = 4;
|
|
int frequencyCol = 5;
|
|
|
|
qDebug() << "OurAirportsDB::readFrequenciesDB: " << filename;
|
|
|
|
QFile file(filename);
|
|
if (file.open(QIODevice::ReadOnly))
|
|
{
|
|
QList<QByteArray> colNames;
|
|
int idx;
|
|
|
|
// Read header
|
|
if (!file.atEnd())
|
|
{
|
|
QByteArray row = file.readLine().trimmed();
|
|
colNames = row.split(',');
|
|
// Work out which columns the data is in, based on the headers
|
|
idx = colNames.indexOf("airport_ref");
|
|
if (idx >= 0)
|
|
airportRefCol = idx;
|
|
idx = colNames.indexOf("type");
|
|
if (idx >= 0)
|
|
typeCol = idx;
|
|
idx = colNames.indexOf("descrption");
|
|
if (idx >= 0)
|
|
descriptionCol = idx;
|
|
idx = colNames.indexOf("frequency_mhz");
|
|
if (idx >= 0)
|
|
frequencyCol = idx;
|
|
}
|
|
// Read data
|
|
while (!file.atEnd())
|
|
{
|
|
QByteArray row = file.readLine();
|
|
QList<QByteArray> cols = row.split(',');
|
|
|
|
bool ok = false;
|
|
int airportRef = cols[airportRefCol].toInt(&ok, 10);
|
|
if (ok)
|
|
{
|
|
if (airportInfo->contains(airportRef))
|
|
{
|
|
QString type = trimQuotes(cols[typeCol]);
|
|
QString description = trimQuotes(cols[descriptionCol]);
|
|
float frequency = cols[frequencyCol].toFloat();
|
|
|
|
AirportInformation::FrequencyInformation *frequencyInfo = new AirportInformation::FrequencyInformation();
|
|
frequencyInfo->m_type = type;
|
|
frequencyInfo->m_description = description;
|
|
frequencyInfo->m_frequency = frequency;
|
|
airportInfo->value(airportRef)->m_frequencies.append(frequencyInfo);
|
|
cnt++;
|
|
}
|
|
}
|
|
}
|
|
file.close();
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "Failed to open " << filename << " " << file.errorString();
|
|
return false;
|
|
}
|
|
|
|
qDebug() << "OurAirportsDB::readFrequenciesDB: - read " << cnt << " airports";
|
|
|
|
return true;
|
|
}
|
|
|