airportdbtest.cpp 17.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
  Copyright (c) 2017 Volker Krause <vkrause@kde.org>

   This library is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   This library 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 Library General Public
   License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.
*/

20
#include <knowledgedb/airportdb.h>
21

22
23
#include <KItinerary/LocationUtil>

24
25
26
27
28
29
30
#include <QDebug>
#include <QObject>
#include <QTest>
#include <QTimeZone>

#include <cmath>

31
using namespace KItinerary;
32
using namespace KItinerary::KnowledgeDb;
33

34
35
#define s(x) QStringLiteral(x)

36
37
38
39
40
41
42
43
namespace KItinerary { namespace KnowledgeDb {
char *toString(const IataCode &code)
{
    using QTest::toString;
    return toString(code.toString());
}
}}

44
45
46
47
48
49
class AirportDbTest : public QObject
{
    Q_OBJECT
private Q_SLOTS:
    void iataCodeTest()
    {
50
        const auto txl = KnowledgeDb::IataCode{"TXL"};
51
        QVERIFY(txl.isValid());
52
        const auto invalid = KnowledgeDb::IataCode{};
53
54
55
56
57
58
        QVERIFY(!invalid.isValid());
        QVERIFY(txl != invalid);
        QVERIFY(!(txl == invalid));
        QVERIFY(txl == txl);
        QCOMPARE(invalid.toString(), QString());

59
        const auto cdg = KnowledgeDb::IataCode{"CDG"};
60
61
62
63
64
65
        QVERIFY(cdg.isValid());
        QVERIFY(cdg != txl);
        QVERIFY(!(cdg == txl));
        QVERIFY(cdg < txl);
        QVERIFY(!(txl < cdg));

66
67
        QVERIFY(KnowledgeDb::IataCode{"ABC"} < KnowledgeDb::IataCode{"CBA"});
        QVERIFY(!(KnowledgeDb::IataCode{"CBA"} < KnowledgeDb::IataCode{"ABC"}));
68
69
70
71
    }

    void coordinateLookupTest()
    {
72
        auto coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"TXL"});
73
        QVERIFY(coord.isValid());
74
75
        QCOMPARE((int)coord.longitude, 13);
        QCOMPARE((int)coord.latitude, 52);
76

77
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"XXX"});
78
79
80
81
82
        QVERIFY(!coord.isValid());
        QVERIFY(std::isnan(coord.latitude));
        QVERIFY(std::isnan(coord.longitude));

        // test coordinate parsing corner cases
83
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"LCY"});
84
85
        QCOMPARE((int)coord.longitude, 0);
        QVERIFY(coord.longitude > 0.0f);
86
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"LHR"});
87
88
        QCOMPARE((int)coord.longitude, 0);
        QVERIFY(coord.longitude < 0.0f);
89
90

        // Köln-Bonn is a hybrid civilian/military airport, so that should be included
91
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"CGN"});
92
93
        QVERIFY(coord.isValid());
        // Frankfurt-Hahn is a former military airport, should be included
94
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"HHN"});
95
96
        QVERIFY(coord.isValid());
        // Ramstein is a military airport that should not be included
97
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"RMS"});
98
99
100
        QVERIFY(!coord.isValid());

        // IATA codes that changed airports in various ways
101
102
103
104
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"DEN"}).isValid());
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"MUC"}).isValid());
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"GOT"}).isValid());
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"OSL"}).isValid());
105
106

        // IATA codes of no longer active airports
107
        QVERIFY(!KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"THF"}).isValid());
108
109

        // IATA codes of civilian airports that match the primitive military filter
110
111
112
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"RAF"}).isValid());
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"CFB"}).isValid());
        QVERIFY(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"PAF"}).isValid());
113
114

        // one airport with 3 IATA codes
115
        coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"BSL"});
116
        QVERIFY(coord.isValid());
117
118
        QCOMPARE(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"BSL"}), KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"MLH"}));
        QCOMPARE(KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"BSL"}), KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{"EAP"}));
119
120
121
122
    }

    void timezoneLookupTest()
    {
123
        auto tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"TXL"});
124
125
126
        QVERIFY(tz.isValid());
        QCOMPARE(tz.id(), QByteArray("Europe/Berlin"));

127
        tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"XXX"});
128
129
130
        QVERIFY(!tz.isValid());

        // tiny, make sure our lookup resolution is big enough for that
131
        tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{"LUX"});
132
133
134
135
136
        QCOMPARE(tz.id(), QByteArray("Europe/Luxembourg"));
    }

    void iataLookupTest()
    {
137
        // via unique fragment lookup
138
139
140
141
142
143
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Flughafen Berlin-Tegel")), KnowledgeDb::IataCode{"TXL"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("TEGEL")), KnowledgeDb::IataCode{"TXL"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Paris Charles de Gaulle")), KnowledgeDb::IataCode{"CDG"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Zürich")), KnowledgeDb::IataCode{"ZRH"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("AMSTERDAM, NL (SCHIPHOL AIRPORT)")), KnowledgeDb::IataCode{"AMS"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("London Heathrow")), KnowledgeDb::IataCode{"LHR"});
144

145
        // via non-unique fragment lookup
146
147
148
149
150
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("John F. Kennedy International Airport")), KnowledgeDb::IataCode{"JFK"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("San Francisco International")), KnowledgeDb::IataCode{"SFO"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Düsseldorf International")), KnowledgeDb::IataCode{"DUS"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("London City")), KnowledgeDb::IataCode{"LCY"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("DETROIT, MI (METROPOLITAN WAYNE CO)")), KnowledgeDb::IataCode{"DTW"});
151

152
        // not unique
153
154
155
156
        QVERIFY(!KnowledgeDb::iataCodeFromName(QStringLiteral("Flughafen Berlin")).isValid());
        QVERIFY(!KnowledgeDb::iataCodeFromName(QStringLiteral("Charles de Gaulle Orly")).isValid());
        QVERIFY(!KnowledgeDb::iataCodeFromName(QStringLiteral("Brussels Airport, BE")).isValid());
        QVERIFY(!KnowledgeDb::iataCodeFromName(QStringLiteral("Frankfurt")).isValid());
157
158
159
160
161
162
163
164
165
166

        // string normalization
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Sao Paulo-Guarulhos International")), KnowledgeDb::IataCode{"GRU"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("São Paulo-Guarulhos International")), KnowledgeDb::IataCode{"GRU"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Zurich")), KnowledgeDb::IataCode{"ZRH"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Dusseldorf International")), KnowledgeDb::IataCode{"DUS"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Almeria")), KnowledgeDb::IataCode{"LEI"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("ALMERÍA")), KnowledgeDb::IataCode{"LEI"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Keflavík")), KnowledgeDb::IataCode{"KEF"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Keflavik")), KnowledgeDb::IataCode{"KEF"});
167
168
169
170
171

        // alternative transliterations
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Duesseldorf International")), KnowledgeDb::IataCode{"DUS"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Berlin Schoenefeld")), KnowledgeDb::IataCode{"SXF"});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Zuerich")), KnowledgeDb::IataCode{"ZRH"});
172
173
174
175

        // IATA code contained in name
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Frankfurt")), KnowledgeDb::IataCode{});
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("Frankfurt FRA")), KnowledgeDb::IataCode{"FRA"});
176
177

        // multiple unique hits / unique hit on valid (but wrong) IATA code
178
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("GIMPO INTERNATIONAL TERMINAL I - SKY CITY INTERNATIONAL TERMINAL")), KnowledgeDb::IataCode{"GMP"});
179

Yuri Chornoivan's avatar
Yuri Chornoivan committed
180
        // Amadeus/BCD airport names containing city/country data too, and using "INTL" abbreviation
181
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("SAN FRANCISCO CA SAN FRANCISCO INTL")), KnowledgeDb::IataCode{"SFO"});
182
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("BEIJING CN CAPITAL INTL")), (std::vector<IataCode>{IataCode{"PEK"}, IataCode{"PKX"}}));
183
184
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("FRANKFURT DE - FRANKFURT INTL")), KnowledgeDb::IataCode{}); // ambigious with Frankfurt Hahn
        QCOMPARE(KnowledgeDb::iataCodeFromName(QStringLiteral("SEATTLE US - SEATTLE TACOMA INTL")), KnowledgeDb::IataCode{"SEA"});
185
    }
186

187
188
189
190
191
192
193
194
195
    void iataCodeMultiLookupTest()
    {
        // duplicate unique fragments
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("OSAKA JP KANSAI INTERNATIONAL")), (std::vector<IataCode>{IataCode{"ITM"}, IataCode{"KIX"}}));

        // insufficient non-unique fragments
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Stuttgart")), (std::vector<IataCode>{IataCode{"SGT"}, IataCode{"STR"}}));
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Frankfurt")), (std::vector<IataCode>{IataCode{"FRA"}, IataCode{"HHN"}}));
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("Brussels")), (std::vector<IataCode>{IataCode{"BRU"}, IataCode{"CRL"}}));
196
197
198

        // multiple unique hits / unique hit on valid (but wrong) IATA code
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("SEOUL KR GIMPO INTERNATIONAL TERMINAL I - SKY CITY INTERNATIONAL TERMINAL")), (std::vector<IataCode>{IataCode{"GMP"}, IataCode{"ICN"}}));
199
200
201

        // "wrong" us of "international"
        QCOMPARE(KnowledgeDb::iataCodesFromName(QStringLiteral("FRANKFURT DE - FRANKFURT INTL")), (std::vector<IataCode>{IataCode{"FRA"}, IataCode{"HHN"}}));
202
203
    }

204
205
    void countryDataTest()
    {
206
        auto iso = KnowledgeDb::countryForAirport(KnowledgeDb::IataCode{});
207
208
        QVERIFY(!iso.isValid());

209
        iso = KnowledgeDb::countryForAirport(KnowledgeDb::IataCode{"TXL"});
210
211
        QCOMPARE(iso, KnowledgeDb::CountryId{"DE"});
    }
212
213
214
215
216
217
218
219

    void airportLocationTest_data()
    {
        QTest::addColumn<QString>("iata");
        QTest::addColumn<float>("lat");
        QTest::addColumn<float>("lon");
        QTest::addColumn<int>("dist");

220
        QTest::newRow("AGP") << s("AGP") << 36.67608f << -4.49095f << 100;
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
        QTest::newRow("AMS") << s("AMS") << 52.3095230f << 4.7621813f << 50;
        QTest::newRow("ARN") << s("ARN") << 59.64927f << 17.92956f << 100;
        QTest::newRow("BLR") << s("BLR") << 13.20023f << 77.70972f << 150;
        QTest::newRow("BRE") << s("BRE") << 53.05266f << 8.78692f << 150;
        QTest::newRow("BRU") << s("BRU") << 50.8985255f << 4.4830282f << 50;
        QTest::newRow("BUD") << s("BUD") << 47.43279f << 19.26115f << 100;
        QTest::newRow("CGN") << s("CGN") << 50.87856f << 7.12107f << 150;
        QTest::newRow("CPH") << s("CPH") << 55.6295693f << 12.6492994f << 50;
        QTest::newRow("DEL") << s("DEL") << 28.55681f << 77.08718f << 100;
        QTest::newRow("DEN") << s("DEN") << 39.84790f << -104.67340f << 200;
        QTest::newRow("DUB") << s("DUB") << 53.4273328f << -6.2437352f << 200;
        QTest::newRow("DUS") << s("DUS") << 51.27889f << 6.76566f << 100;
        QTest::newRow("EAP") << s("EAP") << 47.59960f << 7.53144f << 150;
        QTest::newRow("EDI") << s("EDI") << 55.9483110f << -3.36353370f << 100;
        QTest::newRow("EWR") << s("EWR") << 40.69049f << -74.17765f << 250;
        QTest::newRow("FCO") << s("FCO") << 41.79348f << 12.25208f << 100;
        QTest::newRow("FRA") << s("FRA") << 50.05100f << 8.571590f << 50;
        QTest::newRow("GDN") << s("GDN") << 54.38234f << 18.46640f << 50;
        QTest::newRow("GLA") << s("GLA") << 55.86405f << -4.43181f << 50;
        QTest::newRow("GOT") << s("GOT") << 57.66771f << 12.29549f << 150;
        QTest::newRow("GRU") << s("GRU") << -23.42560f << -46.48165f << 100;
        QTest::newRow("GVA") << s("GVA") << 46.23020f << 6.10828f << 250;
        QTest::newRow("HAJ") << s("HAJ") << 52.45849f << 9.69898f << 50;
        QTest::newRow("HAM") << s("HAM") << 53.63214f << 10.00648f << 50;
        QTest::newRow("HEL") << s("HEL") << 60.31619f << 24.96914f << 50;
        QTest::newRow("HKG") << s("HKG") << 22.31569f << 113.93605f << 100;
        QTest::newRow("KEF") << s("KEF") << 63.99663f << -22.62355f << 200;
        QTest::newRow("LAX") << s("LAX") << 33.94356f << -118.40786f << 150;
        QTest::newRow("LEI") << s("LEI") << 36.84775f << -2.37242f << 50;
        QTest::newRow("LEJ") << s("LEJ") << 51.42020f << 12.22122f << 400; // we get the station here, which is fine
        QTest::newRow("LIS") << s("LIS") << 38.76876f << -9.12844f << 100;
        QTest::newRow("LUX") << s("LUX") << 49.63506f << 6.21650f << 200;
        QTest::newRow("LYS") << s("LYS") << 45.72065f << 5.07807f << 150;
        QTest::newRow("MUC") << s("MUC") << 48.35378f << 11.78633f << 100;
        QTest::newRow("NRT") << s("NRT") << 35.77059f << 140.38679f << 300;
        QTest::newRow("NUE") << s("NUE") << 49.49411f << 11.07867f << 100;
        QTest::newRow("ORD") << s("ORD") << 41.97779f << -87.90269f << 300;
        QTest::newRow("OSL") << s("OSL") << 60.19361f << 11.09758f << 100;
        QTest::newRow("OTP") << s("OTP") << 44.57040f << 26.07763f << 150;
        QTest::newRow("PDX") << s("PDX") << 45.58833f << -122.59240f << 100;
        QTest::newRow("PRG") << s("PRG") << 50.10640f << 14.26784f << 100;
        QTest::newRow("PVG") << s("PVG") << 31.15240f << 121.80214f << 100;
        QTest::newRow("REC") << s("REC") << -8.1314735f << -34.9177565f << 150;
        QTest::newRow("RIG") << s("RIX") << 56.92188f << 23.97976f << 50;
        QTest::newRow("SFO") << s("SFO") << 37.6162238f << -122.3915235f << 50;
        QTest::newRow("SHA") << s("SHA") << 31.19624f << 121.32377f << 100;
        QTest::newRow("STR") << s("STR") << 48.69052f << 9.19302f << 50;
        QTest::newRow("SXB") << s("SXB") << 48.54444f << 7.62783f << 100;
        QTest::newRow("SXF") << s("SXF") << 52.38856f << 13.51809f << 100;
        QTest::newRow("TLL") << s("TLL") << 59.41685f << 24.79899f << 150;
        QTest::newRow("TLS") << s("TLS") << 43.63146f << 1.37364f << 100;
        QTest::newRow("TPE") << s("TPE") << 25.07719f <<  121.23250f << 350; // still ok-ish
        QTest::newRow("TXL") << s("TXL") << 52.55392f << 13.29208f << 100;
        QTest::newRow("VIE") << s("VIE") << 48.12024f << 16.56431f << 100;
        QTest::newRow("YOW") << s("YOW") << 45.32277f << -75.66726f << 100;
        QTest::newRow("ZRH") << s("ZRH") << 47.45024f << 8.56207f << 100;

        // too complex to work with this approach: LHR, CDG, MAD, MXP, ICN, BCN, PEK
    }

    void airportLocationTest()
    {
        QFETCH(QString, iata);
        QFETCH(float, lat);
        QFETCH(float, lon);
        QFETCH(int, dist);

        const auto coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::IataCode{iata});
        QVERIFY(coord.isValid());

        const auto d = LocationUtil::distance(coord.latitude, coord.longitude, lat, lon);
        qDebug() << coord.latitude << coord.longitude << d;

#if 0
        QEXPECT_FAIL("BUD", "not optimized yet", Continue);
        QEXPECT_FAIL("DEN", "not optimized yet", Continue);
        QEXPECT_FAIL("DUS", "not optimized yet", Continue);
        QEXPECT_FAIL("FRA", "terminal proximity station finding not implemented yet", Continue);
        QEXPECT_FAIL("GDN", "terminal proximity station finding not implemented yet", Continue);
        QEXPECT_FAIL("GLA", "airport is not a polygon in OSM", Continue);
        QEXPECT_FAIL("GRU", "not optimized yet", Continue);
        QEXPECT_FAIL("HAM", "terminal proximity station finding not implemented yet", Continue);
        QEXPECT_FAIL("HKG", "not optimized yet", Continue);
        QEXPECT_FAIL("LIS", "terminal proximity station finding not implemented yet", Continue);
        QEXPECT_FAIL("PDX", "stop_position::railway::tram_stop not handled yet", Continue);
        QEXPECT_FAIL("PRG", "not optimized yet", Continue);
        QEXPECT_FAIL("PVG", "not optimized yet", Continue);
        QEXPECT_FAIL("RIG", "open polygon in OSM", Continue);
        QEXPECT_FAIL("SHA", "not optimized yet", Continue);
        QEXPECT_FAIL("SXF", "not optimized yet", Continue);
        QVERIFY(d <= dist);
#endif
    }
314
315
316
317
318
};

QTEST_APPLESS_MAIN(AirportDbTest)

#include "airportdbtest.moc"