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"