File indexing completed on 2024-05-12 05:17:29

0001 /*
0002   SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-kitinerary.h>
0008 #include "knowledgedb/airportdb.h"
0009 #include <knowledgedb/timezonedb.cpp>
0010 
0011 #include <KItinerary/LocationUtil>
0012 
0013 #include <QDebug>
0014 #include <QObject>
0015 #include <QTest>
0016 #include <QTimeZone>
0017 
0018 #include <cmath>
0019 
0020 Q_DECLARE_METATYPE(KItinerary::KnowledgeDb::IataCode)
0021 
0022 using namespace KItinerary;
0023 using namespace KItinerary::KnowledgeDb;
0024 
0025 #define s(x) QStringLiteral(x)
0026 
0027 namespace KItinerary { namespace KnowledgeDb {
0028 char *toString(const IataCode &code)
0029 {
0030     using QTest::toString;
0031     return toString(code.toString());
0032 }
0033 char *toString(const CountryId &code)
0034 {
0035     using QTest::toString;
0036     return toString(code.toString());
0037 }
0038 }}
0039 
0040 char *toString(const QTimeZone &tz)
0041 {
0042     using QTest::toString;
0043     return toString(tz.id());
0044 }
0045 
0046 class AirportDbTest : public QObject
0047 {
0048     Q_OBJECT
0049 private Q_SLOTS:
0050     void iataCodeTest()
0051     {
0052         const auto txl = KnowledgeDb::IataCode{"TXL"};
0053         QVERIFY(txl.isValid());
0054         const auto invalid = KnowledgeDb::IataCode{};
0055         QVERIFY(!invalid.isValid());
0056         QVERIFY(txl != invalid);
0057         QVERIFY(!(txl == invalid));
0058         QVERIFY(txl == txl);
0059         QCOMPARE(invalid.toString(), QString());
0060 
0061         const auto cdg = KnowledgeDb::IataCode{"CDG"};
0062         QVERIFY(cdg.isValid());
0063         QVERIFY(cdg != txl);
0064         QVERIFY(!(cdg == txl));
0065         QVERIFY(cdg < txl);
0066         QVERIFY(!(txl < cdg));
0067 
0068         QVERIFY(KnowledgeDb::IataCode{"ABC"} < KnowledgeDb::IataCode{"CBA"});
0069         QVERIFY(!(KnowledgeDb::IataCode{"CBA"} < KnowledgeDb::IataCode{"ABC"}));
0070     }
0071 
0072     void coordinateLookupTest()
0073     {
0074         auto coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"TXL"});
0075         QVERIFY(coord.isValid());
0076         QCOMPARE((int)coord.longitude, 13);
0077         QCOMPARE((int)coord.latitude, 52);
0078 
0079         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"XXX"});
0080         QVERIFY(!coord.isValid());
0081         QVERIFY(std::isnan(coord.latitude));
0082         QVERIFY(std::isnan(coord.longitude));
0083 
0084         // test coordinate parsing corner cases
0085         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"LCY"});
0086         QCOMPARE((int)coord.longitude, 0);
0087         QVERIFY(coord.longitude > 0.0f);
0088         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"LHR"});
0089         QCOMPARE((int)coord.longitude, 0);
0090         QVERIFY(coord.longitude < 0.0f);
0091 
0092         // Köln-Bonn is a hybrid civilian/military airport, so that should be included
0093         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"CGN"});
0094         QVERIFY(coord.isValid());
0095         // Frankfurt-Hahn is a former military airport, should be included
0096         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"HHN"});
0097         QVERIFY(coord.isValid());
0098         // Ramstein is a military airport that should not be included
0099         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"RMS"});
0100         QVERIFY(!coord.isValid());
0101 
0102         // IATA codes that changed airports in various ways
0103         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"DEN"}).isValid());
0104         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"MUC"}).isValid());
0105         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"GOT"}).isValid());
0106         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"OSL"}).isValid());
0107 
0108         // IATA codes of no longer active airports
0109         QVERIFY(!KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"THF"}).isValid());
0110 
0111         // IATA codes of civilian airports that match the primitive military filter
0112         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"RAF"}).isValid());
0113         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"CFB"}).isValid());
0114         QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"PAF"}).isValid());
0115 
0116         // one airport with 3 IATA codes
0117         coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"BSL"});
0118         QVERIFY(coord.isValid());
0119         QCOMPARE(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"BSL"}), KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"MLH"}));
0120         QCOMPARE(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"BSL"}), KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"EAP"}));
0121     }
0122 
0123     void timezoneLookupTest()
0124     {
0125         auto tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"TXL"});
0126         QVERIFY(tz.isValid());
0127         QCOMPARE(tz.id(), QByteArray("Europe/Berlin"));
0128 
0129         tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"XXX"});
0130         QVERIFY(!tz.isValid());
0131 
0132         // tiny, make sure our lookup resolution is big enough for that
0133         tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"LUX"});
0134         QCOMPARE(tz.id(), QByteArray("Europe/Luxembourg"));
0135 
0136         // HKG seems to cause trouble on FreeBSD
0137         const auto coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"HKG"});
0138         QVERIFY(coord.isValid());
0139         QVERIFY(LocationUtil::distance(coord.latitude, coord.longitude, 22.31600, 113.93688) < 500);
0140         QCOMPARE(KnowledgeDb::timezoneForLocation(coord.latitude, coord.longitude, {}, {}), QTimeZone("Asia/Hong_Kong"));
0141         const auto country = KnowledgeDb::countryForAirport(KnowledgeDb::IataCode{"HKG"});
0142         QCOMPARE(country, KnowledgeDb::CountryId{"CN"});
0143         QCOMPARE(KnowledgeDb::timezoneForLocation(coord.latitude, coord.longitude, u"CN", {}), QTimeZone("Asia/Shanghai"));
0144         tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"HKG"});
0145         QCOMPARE(tz.id(), QByteArray("Asia/Shanghai"));
0146     }
0147 
0148     void iataLookupTest_data()
0149     {
0150         QTest::addColumn<QString>("name");
0151         QTest::addColumn<KnowledgeDb::IataCode>("iataCode");
0152 
0153         // via unique fragment lookup
0154         QTest::newRow("TXL1") << s("Flughafen Berlin-Tegel") << KnowledgeDb::IataCode{"TXL"};
0155         QTest::newRow("TXL2") << s("TEGEL") << KnowledgeDb::IataCode{"TXL"};
0156         QTest::newRow("CDG") << s("Paris Charles de Gaulle") << KnowledgeDb::IataCode{"CDG"};
0157         QTest::newRow("ZRH") << s("Zürich") << KnowledgeDb::IataCode{"ZRH"};
0158         QTest::newRow("AMS") << s("AMSTERDAM, NL (SCHIPHOL AIRPORT)") << KnowledgeDb::IataCode{"AMS"};
0159         QTest::newRow("LHR") << s("London Heathrow") << KnowledgeDb::IataCode{"LHR"};
0160 
0161         // via non-unique fragment lookup
0162         QTest::newRow("JFK") << s("John F. Kennedy International Airport") << KnowledgeDb::IataCode{"JFK"};
0163         QTest::newRow("SFO") << s("San Francisco International") << KnowledgeDb::IataCode{"SFO"};
0164         QTest::newRow("DUS") << s("Düsseldorf International") << KnowledgeDb::IataCode{"DUS"};
0165         QTest::newRow("LCY") << s("London City") << KnowledgeDb::IataCode{"LCY"};
0166         QTest::newRow("DTW") << s("DETROIT, MI (METROPOLITAN WAYNE CO)") << KnowledgeDb::IataCode{"DTW"};
0167 
0168         // string normalization
0169         QTest::newRow("GRU1") << s("Sao Paulo-Guarulhos International") << KnowledgeDb::IataCode{"GRU"};
0170         QTest::newRow("GRU2") << s("São Paulo-Guarulhos International") << KnowledgeDb::IataCode{"GRU"};
0171         QTest::newRow("ZRH2") << s("Zurich") << KnowledgeDb::IataCode{"ZRH"};
0172         QTest::newRow("DUS2") << s("Dusseldorf International") << KnowledgeDb::IataCode{"DUS"};
0173         QTest::newRow("LEI1") << s("Almeria") << KnowledgeDb::IataCode{"LEI"};
0174         QTest::newRow("LEI2") << s("ALMERÍA") << KnowledgeDb::IataCode{"LEI"};
0175         QTest::newRow("KEF1") << s("Keflavík") << KnowledgeDb::IataCode{"KEF"};
0176         QTest::newRow("KEF2") << s("Keflavik") << KnowledgeDb::IataCode{"KEF"};
0177 
0178         // alternative transliterations
0179         QTest::newRow("DUS3") << s("Duesseldorf International") << KnowledgeDb::IataCode{"DUS"};
0180         QTest::newRow("ZRH3") << s("Zuerich") << KnowledgeDb::IataCode{"ZRH"};
0181 
0182         // IATA code contained in name
0183         QTest::newRow("FRA") << s("Frankfurt FRA") << KnowledgeDb::IataCode{"FRA"};
0184 
0185         // multiple unique hits / unique hit on valid (but wrong) IATA code
0186         QTest::newRow("GMP") << s("GIMPO INTERNATIONAL TERMINAL I - SKY CITY INTERNATIONAL TERMINAL") << KnowledgeDb::IataCode{"GMP"};
0187 
0188         // Amadeus/BCD airport names containing city/country data too, and using "INTL" abbreviation
0189         QTest::newRow("SFO2") << s("SAN FRANCISCO CA SAN FRANCISCO INTL") << KnowledgeDb::IataCode{"SFO"};
0190         QTest::newRow("SEA") << s("SEATTLE US - SEATTLE TACOMA INTL") << KnowledgeDb::IataCode{"SEA"};
0191     }
0192 
0193     void iataLookupTest()
0194     {
0195         QFETCH(QString, name);
0196         QFETCH(KnowledgeDb::IataCode, iataCode);
0197 
0198         QCOMPARE(KnowledgeDb::iataCodesFromName(name), (std::vector<IataCode>{iataCode}));
0199     }
0200 
0201     void iataCodeMultiLookupTest()
0202     {
0203         // duplicate unique fragments
0204         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("OSAKA JP KANSAI INTERNATIONAL")), (std::vector<IataCode>{IataCode{"ITM"}, IataCode{"KIX"}}));
0205 
0206         // insufficient non-unique fragments
0207         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Stuttgart")), (std::vector<IataCode>{IataCode{"SGT"}, IataCode{"STR"}}));
0208         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Frankfurt")), (std::vector<IataCode>{IataCode{"FRA"}, IataCode{"HHN"}}));
0209         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Brussels")), (std::vector<IataCode>{IataCode{"BRU"}, IataCode{"CRL"}}));
0210 
0211         // multiple unique hits / unique hit on valid (but wrong) IATA code
0212         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("SEOUL KR GIMPO INTERNATIONAL TERMINAL I - SKY CITY INTERNATIONAL TERMINAL")), (std::vector<IataCode>{IataCode{"GMP"}, IataCode{"ICN"}}));
0213 
0214         // "wrong" use of "international"
0215         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("FRANKFURT DE - FRANKFURT INTL")), (std::vector<IataCode>{IataCode{"FRA"}, IataCode{"HHN"}}));
0216 
0217         // not unique or conflicting
0218         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Flughafen Berlin")), (std::vector<IataCode>{IataCode{"BER"}, IataCode{"BML"}, IataCode{"TXL"}}));
0219         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Charles de Gaulle Orly")), (std::vector<IataCode>{IataCode{"CDG"}, IataCode{"ORY"}}));
0220         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Brussels Airport, BE")), (std::vector<IataCode>{IataCode{"BRU"}, IataCode{"CRL"}}));
0221         QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("BEIJING CN CAPITAL INTL")), (std::vector<IataCode>{IataCode{"PEK"}, IataCode{"PKX"}}));
0222     }
0223 
0224     void countryDataTest()
0225     {
0226         auto iso = KnowledgeDb::countryForAirport(KnowledgeDb::IataCode{});
0227         QVERIFY(!iso.isValid());
0228 
0229         iso = KnowledgeDb::countryForAirport(KnowledgeDb::IataCode{"TXL"});
0230         QCOMPARE(iso, KnowledgeDb::CountryId{"DE"});
0231     }
0232 
0233     void airportLocationTest_data()
0234     {
0235         QTest::addColumn<QString>("iata");
0236         QTest::addColumn<float>("lat");
0237         QTest::addColumn<float>("lon");
0238         QTest::addColumn<int>("dist");
0239 
0240         // "single" entry airports
0241         QTest::newRow("AGP") << s("AGP") << 36.67608f << -4.49095f << 100;
0242         QTest::newRow("AMS") << s("AMS") << 52.3095230f << 4.7621813f << 50;
0243         QTest::newRow("ARN") << s("ARN") << 59.64927f << 17.92956f << 50;
0244         QTest::newRow("BER") << s("BER") << 52.36444f << 13.50964f << 150;
0245         QTest::newRow("BLR") << s("BLR") << 13.20023f << 77.70972f << 150;
0246         QTest::newRow("BRE") << s("BRE") << 53.05266f << 8.78692f << 50;
0247         QTest::newRow("BRU") << s("BRU") << 50.8985255f << 4.4830282f << 50;
0248         QTest::newRow("BUD") << s("BUD") << 47.43279f << 19.26115f << 100;
0249         QTest::newRow("CGN") << s("CGN") << 50.87856f << 7.12107f << 150;
0250         QTest::newRow("CPH") << s("CPH") << 55.6295693f << 12.6492994f << 50;
0251         QTest::newRow("DEL") << s("DEL") << 28.55681f << 77.08718f << 50;
0252         QTest::newRow("DEN") << s("DEN") << 39.84790f << -104.67340f << 150;
0253         QTest::newRow("DOH") << s("DOH") << 25.25854f << 51.61507f << 400; // ok-ish, w212459176 interfering, n7052290435 out of range
0254         QTest::newRow("DUB") << s("DUB") << 53.4273328f << -6.2437352f << 150;
0255         QTest::newRow("DUS") << s("DUS") << 51.27889f << 6.76566f << 150;
0256         QTest::newRow("EAP") << s("EAP") << 47.59960f << 7.53144f << 150;
0257         QTest::newRow("EDI") << s("EDI") << 55.9483110f << -3.36353370f << 250;
0258         QTest::newRow("EWR") << s("EWR") << 40.69049f << -74.17765f << 250;
0259         QTest::newRow("FCO") << s("FCO") << 41.79348f << 12.25208f << 50;
0260         QTest::newRow("FRA") << s("FRA") << 50.05100f << 8.571590f << 50;
0261         QTest::newRow("GDN") << s("GDN") << 54.38234f << 18.46640f << 150;
0262         QTest::newRow("GLA") << s("GLA") << 55.86405f << -4.43181f << 50;
0263         QTest::newRow("GOT") << s("GOT") << 57.66771f << 12.29549f << 150;
0264         QTest::newRow("GRU") << s("GRU") << -23.42560f << -46.48165f << 200;
0265         QTest::newRow("GVA") << s("GVA") << 46.23020f << 6.10828f << 200;
0266         QTest::newRow("HAJ") << s("HAJ") << 52.45849f << 9.69898f << 50;
0267         QTest::newRow("HAM") << s("HAM") << 53.63214f << 10.00648f << 100;
0268         QTest::newRow("HEL") << s("HEL") << 60.31619f << 24.96914f << 100;
0269         QTest::newRow("HFS") << s("HFS") << 60.02591f << 13.58202f << 50;
0270         QTest::newRow("HKG") << s("HKG") << 22.31569f << 113.93605f << 100;
0271         QTest::newRow("KEF") << s("KEF") << 63.99663f << -22.62355f << 200;
0272         QTest::newRow("LAX") << s("LAX") << 33.94356f << -118.40786f << 150;
0273         QTest::newRow("LEI") << s("LEI") << 36.84775f << -2.37242f << 50;
0274         QTest::newRow("LEJ") << s("LEJ") << 51.42020f << 12.22122f << 400; // we get the station here, which is fine
0275         QTest::newRow("LIS") << s("LIS") << 38.76876f << -9.12844f << 50;
0276         QTest::newRow("LUX") << s("LUX") << 49.63506f << 6.21650f << 200;
0277         QTest::newRow("LYS") << s("LYS") << 45.72065f << 5.07807f << 150;
0278         QTest::newRow("MUC") << s("MUC") << 48.35378f << 11.78633f << 50;
0279         QTest::newRow("NRT") << s("NRT") << 35.76462f << 140.38615f << 100; // technically a multi-terminal airport, but T1 is reasonable as all ways there pass T2
0280         QTest::newRow("NUE") << s("NUE") << 49.49411f << 11.07867f << 50;
0281         QTest::newRow("ORD") << s("ORD") << 41.97779f << -87.90269f << 50;
0282         QTest::newRow("OSL") << s("OSL") << 60.19361f << 11.09758f << 100;
0283         QTest::newRow("OTP") << s("OTP") << 44.57040f << 26.07763f << 200;
0284         QTest::newRow("OUL") << s("OUL") << 64.92865f << 25.37406f << 50;
0285         QTest::newRow("PDX") << s("PDX") << 45.58833f << -122.59240f << 150;
0286         QTest::newRow("PRG") << s("PRG") << 50.10640f << 14.26784f << 100;
0287         QTest::newRow("PVG") << s("PVG") << 31.15240f << 121.80214f << 100;
0288         QTest::newRow("REC") << s("REC") << -8.1314735f << -34.9177565f << 150;
0289         QTest::newRow("RIG") << s("RIX") << 56.92188f << 23.97976f << 50;
0290         QTest::newRow("SFO") << s("SFO") << 37.6162238f << -122.3915235f << 50;
0291         QTest::newRow("SHA") << s("SHA") << 31.19624f << 121.32377f << 200;
0292         QTest::newRow("STR") << s("STR") << 48.69052f << 9.19302f << 50;
0293         QTest::newRow("SXB") << s("SXB") << 48.54444f << 7.62783f << 50;
0294         QTest::newRow("TLL") << s("TLL") << 59.41685f << 24.79899f << 150;
0295         QTest::newRow("TLS") << s("TLS") << 43.63146f << 1.37364f << 100;
0296         QTest::newRow("TPE") << s("TPE") << 25.07719f <<  121.23250f << 350; // still ok-ish
0297         QTest::newRow("TXL") << s("TXL") << 52.55392f << 13.29208f << 100;
0298         QTest::newRow("VIE") << s("VIE") << 48.12024f << 16.56431f << 50;
0299         QTest::newRow("YOW") << s("YOW") << 45.32277f << -75.66726f << 100;
0300         QTest::newRow("ZRH") << s("ZRH") << 47.45024f << 8.56207f << 50;
0301 
0302         // "multi" (entry) airports, ie. those basically consisting of multiple separate airports in close proximity
0303         // for those all we can do here is verifyingh we get a stable result for *any* of its terminals,
0304         // for actually selecting the right terminal we'd need more detailed API
0305         QTest::newRow("BCN") << s("BCN") << 41.28891f << 2.07385f << 100; // T1
0306         QTest::newRow("BOM") << s("BOM") << 19.09929f << 72.87440f << 100; // T2
0307         QTest::newRow("CDG") << s("CDG") << 49.00397f << 2.57107f << 100; // T2
0308         QTest::newRow("ICN") << s("ICN") << 37.44750f << 126.45243f << 100; // T1
0309         QTest::newRow("LHR") << s("LHR") << 51.47207f << -0.48812f << 100; // T5
0310         QTest::newRow("MAD") << s("MAD") << 40.46791f << -3.57119f << 50; // T1-3
0311         //QTest::newRow("MAD") << s("MAD") << 40.49127f << -3.59243f << 100; // T4
0312         QTest::newRow("MXP") << s("MXP") << 45.64825f << 8.72300f << 100; // T2
0313         QTest::newRow("PEK") << s("PEK") << 40.05252f << 116.60973f << 100; // T3
0314     }
0315 
0316     void airportLocationTest()
0317     {
0318         QFETCH(QString, iata);
0319         QFETCH(float, lat);
0320         QFETCH(float, lon);
0321         QFETCH(int, dist);
0322 
0323         const auto coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{iata});
0324         QVERIFY(coord.isValid());
0325 
0326         const auto d = LocationUtil::distance(coord.latitude, coord.longitude, lat, lon);
0327         qDebug() << coord.latitude << coord.longitude << d << (dist - d);
0328 
0329         QEXPECT_FAIL("BUD", "closed terminal 1 (w8557242) interfering", Continue);
0330         QEXPECT_FAIL("GLA", "airport is not a polygon in OSM", Continue);
0331         QEXPECT_FAIL("PRG", "private/military terminals 3 and 4 interfering", Continue);
0332         QEXPECT_FAIL("PVG", "complicated", Continue);
0333         QEXPECT_FAIL("RIG", "open polygon in OSM", Continue);
0334         QEXPECT_FAIL("SXF", "w630509626 (government terminal) interfering", Continue);
0335 
0336         QEXPECT_FAIL("BCN", "complicated", Continue);
0337         QEXPECT_FAIL("BOM", "complicated", Continue);
0338         QEXPECT_FAIL("CDG", "complicated", Continue);
0339         QEXPECT_FAIL("ICN", "complicated", Continue);
0340         QEXPECT_FAIL("LHR", "complicated", Continue);
0341         QEXPECT_FAIL("MXP", "complicated", Continue);
0342         QEXPECT_FAIL("PEK", "complicated", Continue);
0343 
0344         QVERIFY(d <= dist);
0345     }
0346 };
0347 
0348 QTEST_APPLESS_MAIN(AirportDbTest)
0349 
0350 #include "airportdbtest.moc"