Commit 735f6264 authored by Volker Krause's avatar Volker Krause
Browse files

Extend the generic boarding pass extractor to also look for times

This currently only handles the case that boarding/departure/arrival times
are given and don't wrap around at midnight, more cases still to be added.
parent 279d04b6
......@@ -36,6 +36,16 @@
"iataCode": "MXP",
"name": "Milano Malpensa"
},
"arrivalTime": {
"@type": "QDateTime",
"@value": "2019-09-06T17:20:00+02:00",
"timezone": "Europe/Rome"
},
"boardingTime": {
"@type": "QDateTime",
"@value": "2019-09-06T15:20:00+02:00",
"timezone": "Europe/Vienna"
},
"departureAirport": {
"@type": "Airport",
"address": {
......@@ -51,6 +61,11 @@
"name": "Vienna"
},
"departureDay": "2019-09-06",
"departureTime": {
"@type": "QDateTime",
"@value": "2019-09-06T15:45:00+02:00",
"timezone": "Europe/Vienna"
},
"flightNumber": "1996"
},
"reservationNumber": "XXX007",
......
......@@ -5,6 +5,8 @@
*/
#include "genericboardingpassextractor.h"
#include "flightutil_p.h"
#include "locationutil.h"
#include "logging.h"
#include "stringutil.h"
......@@ -19,7 +21,9 @@
#include <KItinerary/Reservation>
#include <QDebug>
#include <QTimeZone>
#include <array>
#include <unordered_map>
using namespace KItinerary;
......@@ -53,9 +57,44 @@ static void mergeOrAppend(QStringList &l, QStringView s)
l.push_back(s.toString());
}
static int airportDistance(KnowledgeDb::IataCode from, KnowledgeDb::IataCode to)
{
const auto fromCoord = KnowledgeDb::coordinateForAirport(from);
const auto toCoord = KnowledgeDb::coordinateForAirport(to);
if (!fromCoord.isValid() || !toCoord.isValid()) {
return std::numeric_limits<int>::max();
}
return LocationUtil::distance({fromCoord.latitude, fromCoord.longitude}, {toCoord.latitude, toCoord.longitude});
}
static bool isPlausibleBoardingTime(const QDateTime &boarding, const QDateTime &departure)
{
return boarding < departure && boarding.secsTo(departure) <= 3600;
}
static bool isPlausibleFlightTimes(const std::array<QDateTime, 3> &times, KnowledgeDb::IataCode from, KnowledgeDb::IataCode to)
{
if (!isPlausibleBoardingTime(times[0], times[1])) {
return false;
}
const auto distance = airportDistance(from, to);
// times are local, so convert them to the right timezone first
auto fromDt = times[1];
fromDt.setTimeZone(KnowledgeDb::timezoneForAirport(from));
auto toDt = times[2];
toDt.setTimeZone(KnowledgeDb::timezoneForAirport(to));
const auto flightDuration = fromDt.secsTo(toDt);
if (flightDuration < 3600) {
return false;
}
return fromDt < toDt && FlightUtil::isPlausibleDistanceForDuration(distance, flightDuration);
}
ExtractorResult GenericBoardingPassExtractor::extract(const ExtractorDocumentNode &node, [[maybe_unused]] const ExtractorEngine *engine) const
{
QVector<QVariant> result;
QVector<QVariant> fullResult;
const auto pdf = node.content<PdfDocument*>();
......@@ -67,6 +106,10 @@ ExtractorResult GenericBoardingPassExtractor::extract(const ExtractorDocumentNod
std::sort(bcbpNodes.begin(), bcbpNodes.end(), [](const auto &lhs, const auto &rhs) { return lhs.location().toInt() < rhs.location().toInt(); });
for (auto it = bcbpNodes.begin(); it != bcbpNodes.end(); ++it) {
QDate departureDay;
KnowledgeDb::IataCode from, to;
QVector<QVariant> result;
// 1 determine which airports we need to look for on the same page
const auto pageNum = (*it).location().toInt();
std::unordered_map<KnowledgeDb::IataCode, QStringList> airportNames;
......@@ -75,11 +118,14 @@ ExtractorResult GenericBoardingPassExtractor::extract(const ExtractorDocumentNod
for (const auto &flightRes : flightReservations) {
const auto flight = flightRes.value<FlightReservation>().reservationFor().value<Flight>();
if (!flight.departureAirport().iataCode().isEmpty()) {
airportNames[KnowledgeDb::IataCode{flight.departureAirport().iataCode()}] = QStringList();
from = KnowledgeDb::IataCode{flight.departureAirport().iataCode()};
airportNames[from] = QStringList();
}
if (!flight.arrivalAirport().iataCode().isEmpty()) {
airportNames[KnowledgeDb::IataCode{flight.arrivalAirport().iataCode()}] = QStringList();
to = KnowledgeDb::IataCode{flight.arrivalAirport().iataCode()};
airportNames[to] = QStringList();
}
departureDay = flight.departureDay();
}
}
......@@ -121,7 +167,43 @@ ExtractorResult GenericBoardingPassExtractor::extract(const ExtractorDocumentNod
result.push_back(std::move(flightRes));
}
// 4 if there's only a single leg on this page, try to see if we can determine times
if (airportNames.size() == 2) {
TimeFinder timeFinder;
timeFinder.find(pageText);
if (timeFinder.times().size() == 3) {
std::array<QDateTime, 3> times;
std::transform(timeFinder.times().begin(), timeFinder.times().end(), times.begin(), [departureDay](QTime t) {
return QDateTime(departureDay, t);
});
std::sort(times.begin(), times.end());
// apply what we found
if (isPlausibleFlightTimes(times, from, to)) {
for (auto &res : result) {
auto flightRes = res.value<FlightReservation>();
auto flight = flightRes.reservationFor().value<Flight>();
if (!flight.boardingTime().isValid()) {
flight.setBoardingTime(times[0]);
}
if (!flight.departureTime().isValid()) {
flight.setDepartureTime(times[1]);
}
if (!flight.arrivalTime().isValid()) {
flight.setArrivalTime(times[2]);
}
flightRes.setReservationFor(flight);
res = flightRes;
}
}
// TODO handle arrival past midnight
// TODO handle boarding before midnight
}
}
fullResult += result;
}
return result;
return fullResult;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment