mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-23 18:15:45 -05:00
731 lines
21 KiB
C++
731 lines
21 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
|
// //
|
|
// 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 "openaip.h"
|
|
|
|
QList<Airspace *> Airspace::readXML(const QString &filename)
|
|
{
|
|
QList<Airspace *> airspaces;
|
|
QFile file(filename);
|
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
{
|
|
QXmlStreamReader xmlReader(&file);
|
|
|
|
while(!xmlReader.atEnd() && !xmlReader.hasError())
|
|
{
|
|
if (xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("ASP"))
|
|
{
|
|
Airspace *airspace = new Airspace();
|
|
|
|
airspace->m_category = xmlReader.attributes().value("CATEGORY").toString();
|
|
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("COUNTRY"))
|
|
{
|
|
airspace->m_country = xmlReader.readElementText();
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("NAME"))
|
|
{
|
|
airspace->m_name = xmlReader.readElementText();
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("ALTLIMIT_TOP"))
|
|
{
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
airspace->m_top.m_reference = xmlReader.attributes().value("REFERENCE").toString();
|
|
airspace->m_top.m_altUnit = xmlReader.attributes().value("UNIT").toString();
|
|
if (xmlReader.name() == QLatin1String("ALT"))
|
|
{
|
|
airspace->m_top.m_alt = xmlReader.readElementText().toInt();
|
|
}
|
|
else
|
|
{
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("ALTLIMIT_BOTTOM"))
|
|
{
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
airspace->m_bottom.m_reference = xmlReader.attributes().value("REFERENCE").toString();
|
|
airspace->m_bottom.m_altUnit = xmlReader.attributes().value("UNIT").toString();
|
|
if (xmlReader.name() == QLatin1String("ALT"))
|
|
{
|
|
airspace->m_bottom.m_alt = xmlReader.readElementText().toInt();
|
|
}
|
|
else
|
|
{
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("GEOMETRY"))
|
|
{
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("POLYGON"))
|
|
{
|
|
QString pointsString = xmlReader.readElementText();
|
|
QStringList points = pointsString.split(",");
|
|
for (const auto& ps : points)
|
|
{
|
|
QStringList split = ps.trimmed().split(" ");
|
|
if (split.size() == 2)
|
|
{
|
|
QPointF pf(split[0].toDouble(), split[1].toDouble());
|
|
airspace->m_polygon.append(pf);
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "Airspace::readXML - Unexpected polygon point format: " << ps;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
|
|
airspace->calculatePosition();
|
|
//qDebug() << "Adding airspace: " << airspace->m_name << " " << airspace->m_category;
|
|
airspaces.append(airspace);
|
|
}
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
}
|
|
else
|
|
{
|
|
// Don't warn, as many countries don't have files
|
|
//qDebug() << "Airspace::readXML: Could not open " << filename << " for reading.";
|
|
}
|
|
return airspaces;
|
|
}
|
|
|
|
QList<NavAid *> NavAid::readXML(const QString &filename)
|
|
{
|
|
int uniqueId = 1;
|
|
QList<NavAid *> navAidInfo;
|
|
QFile file(filename);
|
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
{
|
|
QXmlStreamReader xmlReader(&file);
|
|
|
|
while(!xmlReader.atEnd() && !xmlReader.hasError())
|
|
{
|
|
if (xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("NAVAID"))
|
|
{
|
|
QStringView typeRef = xmlReader.attributes().value("TYPE");
|
|
if ((typeRef == QLatin1String("NDB"))
|
|
|| (typeRef == QLatin1String("DME"))
|
|
|| (typeRef == QLatin1String("VOR"))
|
|
|| (typeRef == QLatin1String("VOR-DME"))
|
|
|| (typeRef == QLatin1String("VORTAC"))
|
|
|| (typeRef == QLatin1String("DVOR"))
|
|
|| (typeRef == QLatin1String("DVOR-DME"))
|
|
|| (typeRef == QLatin1String("DVORTAC")))
|
|
{
|
|
QString type = typeRef.toString();
|
|
QString name;
|
|
QString id;
|
|
float lat = 0.0f;
|
|
float lon = 0.0f;
|
|
float elevation = 0.0f;
|
|
float frequency = 0.0f;
|
|
QString channel;
|
|
int range = 25;
|
|
float declination = 0.0f;
|
|
bool alignedTrueNorth = false;
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("NAME"))
|
|
{
|
|
name = xmlReader.readElementText();
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("ID"))
|
|
{
|
|
id = xmlReader.readElementText();
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("GEOLOCATION"))
|
|
{
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("LAT")) {
|
|
lat = xmlReader.readElementText().toFloat();
|
|
} else if (xmlReader.name() == QLatin1String("LON")) {
|
|
lon = xmlReader.readElementText().toFloat();
|
|
} else if (xmlReader.name() == QLatin1String("ELEV")) {
|
|
elevation = xmlReader.readElementText().toFloat();
|
|
} else {
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("RADIO"))
|
|
{
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("FREQUENCY"))
|
|
{
|
|
if (type == "NDB") {
|
|
frequency = xmlReader.readElementText().toFloat();
|
|
} else {
|
|
frequency = xmlReader.readElementText().toFloat() * 1000.0;
|
|
}
|
|
} else if (xmlReader.name() == QLatin1String("CHANNEL")) {
|
|
channel = xmlReader.readElementText();
|
|
} else {
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
else if (xmlReader.name() == QLatin1String("PARAMS"))
|
|
{
|
|
while(xmlReader.readNextStartElement())
|
|
{
|
|
if (xmlReader.name() == QLatin1String("RANGE")) {
|
|
range = xmlReader.readElementText().toInt();
|
|
} else if (xmlReader.name() == QLatin1String("DECLINATION")) {
|
|
declination = xmlReader.readElementText().toFloat();
|
|
} else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH")) {
|
|
alignedTrueNorth = xmlReader.readElementText() == "TRUE";
|
|
} else {
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xmlReader.skipCurrentElement();
|
|
}
|
|
}
|
|
NavAid *navAid = new NavAid();
|
|
navAid->m_id = uniqueId++;
|
|
navAid->m_ident = id;
|
|
// Check idents conform to our filtering rules
|
|
if (navAid->m_ident.size() < 2) {
|
|
qDebug() << "NavAid::readXML: Ident less than 2 characters: " << navAid->m_ident;
|
|
} else if (navAid->m_ident.size() > 3) {
|
|
qDebug() << "NavAid::readXML: Ident greater than 3 characters: " << navAid->m_ident;
|
|
}
|
|
navAid->m_type = type;
|
|
navAid->m_name = name;
|
|
navAid->m_frequencykHz = frequency;
|
|
navAid->m_channel = channel;
|
|
navAid->m_latitude = lat;
|
|
navAid->m_longitude = lon;
|
|
navAid->m_elevation = elevation;
|
|
navAid->m_range = range;
|
|
navAid->m_magneticDeclination = declination;
|
|
navAid->m_alignedTrueNorth = alignedTrueNorth;
|
|
navAidInfo.append(navAid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
}
|
|
else
|
|
{
|
|
// Don't warn, as many countries don't have files
|
|
//qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading.";
|
|
}
|
|
return navAidInfo;
|
|
}
|
|
|
|
const QStringList OpenAIP::m_countryCodes = {
|
|
"ad",
|
|
"ae",
|
|
"af",
|
|
"ag",
|
|
"ai",
|
|
"al",
|
|
"am",
|
|
"an",
|
|
"ao",
|
|
"aq",
|
|
"ar",
|
|
"as",
|
|
"at",
|
|
"au",
|
|
"aw",
|
|
"ax",
|
|
"az",
|
|
"ba",
|
|
"bb",
|
|
"bd",
|
|
"be",
|
|
"bf",
|
|
"bg",
|
|
"bh",
|
|
"bi",
|
|
"bj",
|
|
"bl",
|
|
"bm",
|
|
"bn",
|
|
"bo",
|
|
"bq",
|
|
"br",
|
|
"bs",
|
|
"bt",
|
|
"bv",
|
|
"bw",
|
|
"by",
|
|
"bz",
|
|
"ca",
|
|
"cc",
|
|
"cd",
|
|
"cf",
|
|
"cg",
|
|
"ch",
|
|
"ci",
|
|
"ck",
|
|
"cl",
|
|
"cm",
|
|
"cn",
|
|
"co",
|
|
"cr",
|
|
"cu",
|
|
"cv",
|
|
"cw",
|
|
"cx",
|
|
"cy",
|
|
"cz",
|
|
"de",
|
|
"dj",
|
|
"dk",
|
|
"dm",
|
|
"do",
|
|
"dz",
|
|
"ec",
|
|
"ee",
|
|
"eg",
|
|
"eh",
|
|
"er",
|
|
"es",
|
|
"et",
|
|
"fi",
|
|
"fj",
|
|
"fk",
|
|
"fm",
|
|
"fo",
|
|
"fr",
|
|
"ga",
|
|
"gb",
|
|
"ge",
|
|
"gf",
|
|
"gg",
|
|
"gh",
|
|
"gi",
|
|
"gl",
|
|
"gm",
|
|
"gn",
|
|
"gp",
|
|
"gq",
|
|
"gr",
|
|
"gs",
|
|
"gt",
|
|
"gu",
|
|
"gw",
|
|
"gy",
|
|
"hk",
|
|
"hm",
|
|
"hn",
|
|
"hr",
|
|
"hu",
|
|
"id",
|
|
"ie",
|
|
"il",
|
|
"im",
|
|
"in",
|
|
"io",
|
|
"iq",
|
|
"ir",
|
|
"is",
|
|
"it",
|
|
"je",
|
|
"jm",
|
|
"jo",
|
|
"jp",
|
|
"ke",
|
|
"kg",
|
|
"kh",
|
|
"ki",
|
|
"km",
|
|
"kn",
|
|
"kp",
|
|
"kr",
|
|
"kw",
|
|
"ky",
|
|
"kz",
|
|
"la",
|
|
"lb",
|
|
"lc",
|
|
"li",
|
|
"lk",
|
|
"lr",
|
|
"ls",
|
|
"lt",
|
|
"lu",
|
|
"lv",
|
|
"ly",
|
|
"ma",
|
|
"mc",
|
|
"md",
|
|
"me",
|
|
"mf",
|
|
"mg",
|
|
"mh",
|
|
"mk",
|
|
"ml",
|
|
"mm",
|
|
"mn",
|
|
"mo",
|
|
"mp",
|
|
"mq",
|
|
"mr",
|
|
"ms",
|
|
"mt",
|
|
"mu",
|
|
"mv",
|
|
"mw",
|
|
"mx",
|
|
"my",
|
|
"mz",
|
|
"na",
|
|
"nc",
|
|
"ne",
|
|
"nf",
|
|
"ng",
|
|
"ni",
|
|
"nl",
|
|
"no",
|
|
"np",
|
|
"nr",
|
|
"nu",
|
|
"nz",
|
|
"om",
|
|
"pa",
|
|
"pe",
|
|
"pf",
|
|
"pg",
|
|
"ph",
|
|
"pk",
|
|
"pl",
|
|
"pm",
|
|
"pn",
|
|
"pr",
|
|
"ps",
|
|
"pt",
|
|
"pw",
|
|
"py",
|
|
"qa",
|
|
"re",
|
|
"ro",
|
|
"rs",
|
|
"ru",
|
|
"rw",
|
|
"sa",
|
|
"sb",
|
|
"sc",
|
|
"sd",
|
|
"se",
|
|
"sg",
|
|
"sh",
|
|
"si",
|
|
"sj",
|
|
"sk",
|
|
"sl",
|
|
"sm",
|
|
"sn",
|
|
"so",
|
|
"sr",
|
|
"ss",
|
|
"st",
|
|
"sv",
|
|
"sx",
|
|
"sy",
|
|
"sz",
|
|
"tc",
|
|
"td",
|
|
"tf",
|
|
"tg",
|
|
"th",
|
|
"tj",
|
|
"tk",
|
|
"tl",
|
|
"tm",
|
|
"tn",
|
|
"to",
|
|
"tr",
|
|
"tt",
|
|
"tv",
|
|
"tw",
|
|
"tz",
|
|
"ua",
|
|
"ug",
|
|
"um",
|
|
"us",
|
|
"uy",
|
|
"uz",
|
|
"va",
|
|
"vc",
|
|
"ve",
|
|
"vg",
|
|
"vi",
|
|
"vn",
|
|
"vu",
|
|
"wf",
|
|
"ws",
|
|
"ye",
|
|
"yt",
|
|
"za",
|
|
"zm",
|
|
"zw"
|
|
};
|
|
|
|
QSharedPointer<QList<Airspace *>> OpenAIP::m_airspaces;
|
|
QSharedPointer<QList<NavAid *>> OpenAIP::m_navAids;
|
|
|
|
QDateTime OpenAIP::m_airspacesModifiedDateTime;
|
|
QDateTime OpenAIP::m_navAidsModifiedDateTime;
|
|
|
|
OpenAIP::OpenAIP(QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished);
|
|
}
|
|
|
|
OpenAIP::~OpenAIP()
|
|
{
|
|
disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OpenAIP::downloadFinished);
|
|
}
|
|
|
|
QString OpenAIP::getDataDir()
|
|
{
|
|
// Get directory to store app data in
|
|
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
|
// First dir is writable
|
|
return locations[0];
|
|
}
|
|
|
|
QString OpenAIP::getAirspaceFilename(int i)
|
|
{
|
|
return getAirspaceFilename(m_countryCodes[i]);
|
|
}
|
|
|
|
QString OpenAIP::getAirspaceFilename(const QString& countryCode)
|
|
{
|
|
return getDataDir() + "/" + countryCode + "_asp.xml";
|
|
}
|
|
|
|
QString OpenAIP::getAirspaceURL(int i)
|
|
{
|
|
if (i < m_countryCodes.size()) {
|
|
return QString(OPENAIP_AIRSPACE_URL).arg(m_countryCodes[i]);
|
|
} else {
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
void OpenAIP::downloadAirspaces()
|
|
{
|
|
m_countryIndex = 0;
|
|
downloadAirspace();
|
|
}
|
|
|
|
void OpenAIP::downloadAirspace()
|
|
{
|
|
QString filename = getAirspaceFilename(m_countryIndex);
|
|
QString urlString = getAirspaceURL(m_countryIndex);
|
|
QUrl dbURL(urlString);
|
|
qDebug() << "OpenAIP::downloadAirspace: Downloading " << urlString;
|
|
emit downloadingURL(urlString);
|
|
m_dlm.download(dbURL, filename);
|
|
}
|
|
|
|
QString OpenAIP::getNavAidsFilename(int i)
|
|
{
|
|
return getNavAidsFilename(m_countryCodes[i]);
|
|
}
|
|
|
|
QString OpenAIP::getNavAidsFilename(const QString& countryCode)
|
|
{
|
|
return getDataDir() + "/" + countryCode + "_nav.xml";
|
|
}
|
|
|
|
QString OpenAIP::getNavAidsURL(int i)
|
|
{
|
|
if (i < m_countryCodes.size()) {
|
|
return QString(OPENAIP_NAVAIDS_URL).arg(m_countryCodes[i]);
|
|
} else {
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
void OpenAIP::downloadNavAids()
|
|
{
|
|
m_countryIndex = 0;
|
|
downloadNavAid();
|
|
}
|
|
|
|
void OpenAIP::downloadNavAid()
|
|
{
|
|
QString filename = getNavAidsFilename(m_countryIndex);
|
|
QString urlString = getNavAidsURL(m_countryIndex);
|
|
QUrl dbURL(urlString);
|
|
qDebug() << "OpenAIP::downloadNavAid: Downloading " << urlString;
|
|
emit downloadingURL(urlString);
|
|
m_dlm.download(dbURL, filename);
|
|
}
|
|
|
|
void OpenAIP::downloadFinished(const QString& filename, bool success)
|
|
{
|
|
// Not all countries have corresponding files, so we should expect some errors
|
|
if (!success) {
|
|
qDebug() << "OpenAIP::downloadFinished: Failed: " << filename;
|
|
}
|
|
|
|
if (filename == getNavAidsFilename(m_countryIndex))
|
|
{
|
|
m_countryIndex++;
|
|
if (m_countryIndex < m_countryCodes.size()) {
|
|
downloadNavAid();
|
|
} else {
|
|
emit downloadNavAidsFinished();
|
|
}
|
|
}
|
|
else if (filename == getAirspaceFilename(m_countryIndex))
|
|
{
|
|
m_countryIndex++;
|
|
if (m_countryIndex < m_countryCodes.size()) {
|
|
downloadAirspace();
|
|
} else {
|
|
emit downloadAirspaceFinished();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "OpenAIP::downloadFinished: Unexpected filename: " << filename;
|
|
emit downloadError(QString("Unexpected filename: %1").arg(filename));
|
|
}
|
|
}
|
|
|
|
// Read airspaces for all countries
|
|
QList<Airspace *> *OpenAIP::readAirspaces()
|
|
{
|
|
QList<Airspace *> *airspaces = new QList<Airspace *>();
|
|
for (const auto& countryCode : m_countryCodes) {
|
|
airspaces->append(readAirspaces(countryCode));
|
|
}
|
|
return airspaces;
|
|
}
|
|
|
|
// Read airspaces for a single country
|
|
QList<Airspace *> OpenAIP::readAirspaces(const QString& countryCode)
|
|
{
|
|
return Airspace::readXML(getAirspaceFilename(countryCode));
|
|
}
|
|
|
|
// Read NavAids for all countries
|
|
QList<NavAid *> *OpenAIP::readNavAids()
|
|
{
|
|
QList<NavAid *> *navAids = new QList<NavAid *>();
|
|
for (const auto& countryCode : m_countryCodes) {
|
|
navAids->append(readNavAids(countryCode));
|
|
}
|
|
return navAids;
|
|
}
|
|
|
|
// Read NavAids for a single country
|
|
QList<NavAid *> OpenAIP::readNavAids(const QString& countryCode)
|
|
{
|
|
return NavAid::readXML(getNavAidsFilename(countryCode));
|
|
}
|
|
|
|
QSharedPointer<const QList<Airspace *>> OpenAIP::getAirspaces()
|
|
{
|
|
QDateTime filesDateTime = getAirspacesModifiedDateTime();
|
|
|
|
if (!m_airspaces || (filesDateTime > m_airspacesModifiedDateTime))
|
|
{
|
|
// Using shared pointer, so old object, if it exists, will be deleted, when no longer user
|
|
m_airspaces = QSharedPointer<QList<Airspace *>>(readAirspaces());
|
|
m_airspacesModifiedDateTime = filesDateTime;
|
|
}
|
|
return m_airspaces;
|
|
}
|
|
|
|
QSharedPointer<const QList<NavAid *>> OpenAIP::getNavAids()
|
|
{
|
|
QDateTime filesDateTime = getNavAidsModifiedDateTime();
|
|
|
|
if (!m_navAids || (filesDateTime > m_navAidsModifiedDateTime))
|
|
{
|
|
// Using shared pointer, so old object, if it exists, will be deleted, when no longer user
|
|
m_navAids = QSharedPointer<QList<NavAid *>>(readNavAids());
|
|
m_navAidsModifiedDateTime = filesDateTime;
|
|
}
|
|
return m_navAids;
|
|
}
|
|
|
|
// Gets the date and time the airspaces files were most recently modified
|
|
QDateTime OpenAIP::getAirspacesModifiedDateTime()
|
|
{
|
|
QDateTime dateTime;
|
|
for (const auto& countryCode : m_countryCodes)
|
|
{
|
|
QFileInfo fileInfo(getAirspaceFilename(countryCode));
|
|
QDateTime fileModifiedDateTime = fileInfo.lastModified();
|
|
if (fileModifiedDateTime > dateTime) {
|
|
dateTime = fileModifiedDateTime;
|
|
}
|
|
}
|
|
return dateTime;
|
|
}
|
|
|
|
// Gets the date and time the navaid files were most recently modified
|
|
QDateTime OpenAIP::getNavAidsModifiedDateTime()
|
|
{
|
|
QDateTime dateTime;
|
|
for (const auto& countryCode : m_countryCodes)
|
|
{
|
|
QFileInfo fileInfo(getNavAidsFilename(countryCode));
|
|
QDateTime fileModifiedDateTime = fileInfo.lastModified();
|
|
if (fileModifiedDateTime > dateTime) {
|
|
dateTime = fileModifiedDateTime;
|
|
}
|
|
}
|
|
return dateTime;
|
|
}
|
|
|