Warning, file /pim/kitinerary/src/lib/flightpostprocessor.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002    SPDX-FileCopyrightText: 2017-2019 Volker Krause <vkrause@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "flightpostprocessor_p.h"
0008 #include "extractorpostprocessor_p.h"
0009 #include "extractorutil.h"
0010 #include "flightutil_p.h"
0011 #include "locationutil.h"
0012 
0013 #include "knowledgedb/airportdb.h"
0014 
0015 #include <KItinerary/Flight>
0016 #include <KItinerary/Organization>
0017 #include <KItinerary/Place>
0018 
0019 #include <QDateTime>
0020 #include <QDebug>
0021 #include <QTimeZone>
0022 
0023 using namespace KItinerary;
0024 
0025 Flight FlightPostProcessor::processFlight(Flight flight)
0026 {
0027     lookupAirportCodes(flight.departureAirport(), m_departureCodes);
0028     lookupAirportCodes(flight.arrivalAirport(), m_arrivalCodes);
0029 
0030     // if we have an ambiguous airport on one end, see if we can pick based on the travel time
0031     const auto duration = flight.departureTime().secsTo(flight.arrivalTime());
0032     pickAirportByDistance(duration, m_departureCodes, m_arrivalCodes);
0033     pickAirportByDistance(duration, m_arrivalCodes, m_departureCodes);
0034 
0035     flight.setDepartureAirport(processAirport(flight.departureAirport(), m_departureCodes));
0036     flight.setArrivalAirport(processAirport(flight.arrivalAirport(), m_arrivalCodes));
0037     flight.setAirline(processAirline(flight.airline()));
0038     flight.setBoardingTime(processFlightTime(flight.boardingTime(), flight, m_departureCodes));
0039     flight.setDepartureTime(processFlightTime(flight.departureTime(), flight, m_departureCodes));
0040     flight.setArrivalTime(processFlightTime(flight.arrivalTime(), flight, m_arrivalCodes));
0041     flight = ExtractorUtil::extractTerminals(flight);
0042     flight.setDepartureTerminal(flight.departureTerminal().simplified());
0043     flight.setArrivalTerminal(flight.arrivalTerminal().simplified());
0044     flight.setFlightNumber(flight.flightNumber().simplified());
0045 
0046     // arrival less than a day before departure is an indication of the extractor failing to detect day rollover
0047     if (duration < 0 && duration > -3600*24) {
0048         flight.setArrivalTime(flight.arrivalTime().addDays(1));
0049     }
0050 
0051     return flight;
0052 }
0053 
0054 Airport FlightPostProcessor::processAirport(Airport airport, const std::vector<KnowledgeDb::IataCode> &codes) const
0055 {
0056     // complete missing IATA codes
0057     if (airport.iataCode().isEmpty() && codes.size() == 1) {
0058         airport.setIataCode(codes[0].toString());
0059     }
0060 
0061     // complete missing geo coordinates, take whatever we have but don't trust that too much
0062     auto geo = airport.geo();
0063     if (codes.size() == 1) {
0064         const auto coord = KnowledgeDb::coordinateForAirport(codes[0]);
0065         if (coord.isValid() && (!geo.isValid() || LocationUtil::distance(geo.latitude(), geo.longitude(), coord.latitude, coord.longitude) > 5000)) {
0066             geo.setLatitude(coord.latitude);
0067             geo.setLongitude(coord.longitude);
0068             airport.setGeo(geo);
0069         }
0070     }
0071 
0072     // add country, if all candidates are from the same country
0073     auto addr = airport.address();
0074     if (addr.addressCountry().isEmpty() && codes.size() >= 1) {
0075         const auto isoCode = KnowledgeDb::countryForAirport(codes[0]);
0076         if (isoCode.isValid() && std::all_of(codes.begin(), codes.end(), [isoCode](const auto iataCode) { return KnowledgeDb::countryForAirport(iataCode) == isoCode; })) {
0077             addr.setAddressCountry(isoCode.toString());
0078             airport.setAddress(addr);
0079         }
0080     }
0081 
0082     return ExtractorPostprocessorPrivate::processPlace(airport);
0083 }
0084 
0085 Airline FlightPostProcessor::processAirline(Airline airline) const
0086 {
0087     airline.setName(airline.name().trimmed());
0088     return airline;
0089 }
0090 
0091 QDateTime FlightPostProcessor::processFlightTime(QDateTime dt, const Flight &flight, const std::vector<KnowledgeDb::IataCode> &codes) const
0092 {
0093     if (!dt.isValid()) {
0094         return dt;
0095     }
0096 
0097     if (dt.date().year() <= 1970 && flight.departureDay().isValid()) { // we just have the time, but not the day
0098         dt.setDate(flight.departureDay());
0099     }
0100 
0101     if ((dt.timeSpec() == Qt::TimeZone && dt.timeZone() != QTimeZone::utc()) || codes.empty()) {
0102         return dt;
0103     }
0104 
0105     const auto tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{codes[0]});
0106     if (!tz.isValid() || !std::all_of(codes.begin(), codes.end(), [tz](const auto &iataCode) { return KnowledgeDb::timezoneForAirport(iataCode) == tz; })) {
0107         return dt;
0108     }
0109 
0110     // prefer our timezone over externally provided UTC offset, if they match
0111     if (dt.timeSpec() == Qt::OffsetFromUTC && tz.offsetFromUtc(dt) != dt.offsetFromUtc()) {
0112         return dt;
0113     }
0114 
0115     if (dt.timeSpec() == Qt::OffsetFromUTC || dt.timeSpec() == Qt::LocalTime) {
0116         dt.setTimeZone(tz);
0117     } else if (dt.timeSpec() == Qt::UTC || (dt.timeSpec() == Qt::TimeZone && dt.timeZone() == QTimeZone::utc())) {
0118         dt = dt.toTimeZone(tz);
0119     }
0120 
0121     return dt;
0122 }
0123 
0124 void FlightPostProcessor::lookupAirportCodes(const Airport &airport, std::vector<KnowledgeDb::IataCode>& codes) const
0125 {
0126     if (!airport.iataCode().isEmpty()) {
0127         codes.push_back(KnowledgeDb::IataCode(airport.iataCode()));
0128         return;
0129     }
0130 
0131     codes = KnowledgeDb::iataCodesFromName(airport.name());
0132 }
0133 
0134 void FlightPostProcessor::pickAirportByDistance(int duration, const std::vector<KnowledgeDb::IataCode>& startCodes, std::vector<KnowledgeDb::IataCode>& codes) const
0135 {
0136     if (duration <= 0 || startCodes.empty() || codes.size() <= 1) {
0137         return;
0138     }
0139 
0140     // ensure we have coordinates for all start points
0141     if (!std::all_of(startCodes.begin(), startCodes.end(), [](const auto code) { return KnowledgeDb::coordinateForAirport(code).isValid(); })) {
0142         return;
0143     }
0144 
0145     for (auto it = codes.begin(); it != codes.end();) {
0146         const auto destCoord = KnowledgeDb::coordinateForAirport(*it);
0147         if (!destCoord.isValid()) {
0148             continue;
0149         }
0150 
0151         bool outOfRange = true;
0152         for (const auto startCode : startCodes) {
0153             const auto startCoord =  KnowledgeDb::coordinateForAirport(startCode);
0154             const auto dist = LocationUtil::distance({startCoord.latitude, startCoord.longitude}, {destCoord.latitude, destCoord.longitude});
0155             outOfRange = outOfRange && !FlightUtil::isPlausibleDistanceForDuration(dist, duration);
0156         }
0157         if (outOfRange) {
0158             it = codes.erase(it);
0159         } else {
0160             ++it;
0161         }
0162     }
0163 }