1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-26 17:58:43 -05:00
sdrangel/sdrbase/util/ourairportsdb.cpp
Jon Beniston 9c7aa8b333 Map Updates
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).
2023-02-14 14:46:08 +00:00

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;
}