Commit 73cb9cb9 authored by Volker Krause's avatar Volker Krause
Browse files

Implement basic OJP trip response parsing

Far enough to produce valid results for basic journey queries.
parent e8c5ed6e
Pipeline #79188 passed with stage
in 17 seconds
[
{
"sections": [
{
"disruptionEffect": "NormalService",
"distance": 138607,
"expectedArrivalTime": "2021-09-07T18:02:00Z",
"expectedDeparturePlatform": "4",
"expectedDepartureTime": "2021-09-07T15:46:00Z",
"from": {
"identifier": {
"uic": "8503016"
},
"latitude": 47.45038986206055,
"locality": "Zürich Flughafen",
"longitude": 8.562399864196777,
"name": "Zürich Flughafen",
"type": "Stop"
},
"mode": "PublicTransport",
"notes": [
"Gratis-Internet mit der App SBB FreeSurf",
"Maskenpflicht für Reisende ab 12 Jahren",
"Platzreservierung möglich",
"Businesszone in 1. Klasse",
"Familienwagen mit Spielplatz",
"Ruhezone in 1. Klasse",
"Restaurant",
"Aussteigeseite: Rechts"
],
"route": {
"direction": "Brig",
"line": {
"mode": "Train",
"name": "IC8"
}
},
"scheduledArrivalPlatform": "7",
"scheduledArrivalTime": "2021-09-07T18:02:00Z",
"scheduledDeparturePlatform": "3",
"scheduledDepartureTime": "2021-09-07T15:45:00Z",
"to": {
"identifier": {
"uic": "8501605"
},
"latitude": 46.294029235839844,
"locality": "Visp",
"longitude": 7.881470203399658,
"name": "Visp",
"type": "Stop"
}
},
{
"disruptionEffect": "NormalService",
"distance": 0,
"from": {
"identifier": {
"uic": "8501605"
},
"latitude": 46.294029235839844,
"locality": "Visp",
"longitude": 7.881470203399658,
"name": "Visp",
"type": "Stop"
},
"mode": "Transfer",
"scheduledArrivalTime": "2021-09-07T18:08:00Z",
"scheduledDepartureTime": "2021-09-07T18:02:00Z",
"to": {
"identifier": {
"uic": "8501605"
},
"latitude": 46.294029235839844,
"locality": "Visp",
"longitude": 7.881470203399658,
"name": "Visp",
"type": "Stop"
}
},
{
"disruptionEffect": "NormalService",
"distance": 22914,
"expectedArrivalTime": "2021-09-07T18:54:00Z",
"expectedDepartureTime": "2021-09-07T18:08:00Z",
"from": {
"identifier": {
"uic": "8501605"
},
"latitude": 46.294029235839844,
"locality": "Visp",
"longitude": 7.881470203399658,
"name": "Visp",
"type": "Stop"
},
"mode": "PublicTransport",
"notes": [
"Maskenpflicht für Reisende ab 12 Jahren"
],
"route": {
"direction": "Zermatt",
"line": {
"mode": "LocalTrain",
"name": "R"
}
},
"scheduledArrivalTime": "2021-09-07T18:54:00Z",
"scheduledDeparturePlatform": "3",
"scheduledDepartureTime": "2021-09-07T18:08:00Z",
"to": {
"identifier": {
"uic": "8501687"
},
"latitude": 46.09992980957031,
"locality": "Randa",
"longitude": 7.781459808349609,
"name": "Randa",
"type": "Stop"
}
}
]
}
]
<?xml version="1.0" encoding="UTF-8"?>
<siri:OJP xmlns:siri="http://www.siri.org.uk/siri" xmlns:ojp="http://www.vdv.de/ojp" version="1.0"><siri:OJPResponse><siri:ServiceDelivery><siri:ResponseTimestamp>2021-09-07T15:35:49Z</siri:ResponseTimestamp><siri:ProducerRef>OJPCH_Prod</siri:ProducerRef><siri:Status>true</siri:Status><ojp:OJPTripDelivery><siri:ResponseTimestamp>2021-09-07T15:35:48Z</siri:ResponseTimestamp><siri:Status>true</siri:Status><ojp:CalcTime>1098</ojp:CalcTime><ojp:TripResponseContext><ojp:Places><ojp:Location><ojp:StopPlace><ojp:StopPlaceRef>8503016</ojp:StopPlaceRef><ojp:StopPlaceName><ojp:Text>Zürich Flughafen</ojp:Text></ojp:StopPlaceName><ojp:PrivateCode><ojp:System>EFA</ojp:System><ojp:Value>108287:0:3</ojp:Value></ojp:PrivateCode><ojp:TopographicPlaceRef>23026062:5</ojp:TopographicPlaceRef></ojp:StopPlace><ojp:LocationName><ojp:Text xml:lang="de">Zürich Flughafen</ojp:Text></ojp:LocationName><ojp:GeoPosition><siri:Longitude>8.56240</siri:Longitude><siri:Latitude>47.45039</siri:Latitude></ojp:GeoPosition></ojp:Location><ojp:Location><ojp:TopographicPlace><ojp:TopographicPlaceCode>23026062:5</ojp:TopographicPlaceCode><ojp:TopographicPlaceName><ojp:Text>Kloten</ojp:Text></ojp:TopographicPlaceName></ojp:TopographicPlace><ojp:LocationName><ojp:Text>Kloten</ojp:Text></ojp:LocationName><ojp:GeoPosition><siri:Longitude>8.56240</siri:Longitude><siri:Latitude>47.45039</siri:Latitude></ojp:GeoPosition></ojp:Location><ojp:Location><ojp:StopPlace><ojp:StopPlaceRef>8501605</ojp:StopPlaceRef><ojp:StopPlaceName><ojp:Text>Visp</ojp:Text></ojp:StopPlaceName><ojp:PrivateCode><ojp:System>EFA</ojp:System><ojp:Value>107457:0:7</ojp:Value></ojp:PrivateCode><ojp:TopographicPlaceRef>23024297:3</ojp:TopographicPlaceRef></ojp:StopPlace><ojp:LocationName><ojp:Text xml:lang="de">Visp</ojp:Text></ojp:LocationName><ojp:GeoPosition><siri:Longitude>7.88147</siri:Longitude><siri:Latitude>46.29403</siri:Latitude></ojp:GeoPosition></ojp:Location><ojp:Location><ojp:TopographicPlace><ojp:TopographicPlaceCode>23024297:3</ojp:TopographicPlaceCode><ojp:TopographicPlaceName><ojp:Text>Visp</ojp:Text></ojp:TopographicPlaceName></ojp:TopographicPlace><ojp:LocationName><ojp:Text>Visp</ojp:Text></ojp:LocationName><ojp:GeoPosition><siri:Longitude>7.88147</siri:Longitude><siri:Latitude>46.29403</siri:Latitude></ojp:GeoPosition></ojp:Location><ojp:Location><ojp:StopPlace><ojp:StopPlaceRef>8501687</ojp:StopPlaceRef><ojp:StopPlaceName><ojp:Text>Randa</ojp:Text></ojp:StopPlaceName><ojp:PrivateCode><ojp:System>EFA</ojp:System><ojp:Value>107501:0:</ojp:Value></ojp:PrivateCode><ojp:TopographicPlaceRef>23024287:1</ojp:TopographicPlaceRef></ojp:StopPlace><ojp:LocationName><ojp:Text xml:lang="de">Randa</ojp:Text></ojp:LocationName><ojp:GeoPosition><siri:Longitude>7.78146</siri:Longitude><siri:Latitude>46.09993</siri:Latitude></ojp:GeoPosition></ojp:Location><ojp:Location><ojp:TopographicPlace><ojp:TopographicPlaceCode>23024287:1</ojp:TopographicPlaceCode><ojp:TopographicPlaceName><ojp:Text>Randa</ojp:Text></ojp:TopographicPlaceName></ojp:TopographicPlace><ojp:LocationName><ojp:Text>Randa</ojp:Text></ojp:LocationName><ojp:GeoPosition><siri:Longitude>7.78146</siri:Longitude><siri:Latitude>46.09993</siri:Latitude></ojp:GeoPosition></ojp:Location></ojp:Places></ojp:TripResponseContext><ojp:TripResult><ojp:ResultId>ID-314CBC94-9DA8-4D3D-B893-296AC4124A75</ojp:ResultId><ojp:Trip><ojp:TripId>ID-314CBC94-9DA8-4D3D-B893-296AC4124A75</ojp:TripId><ojp:Duration>PT3H8M</ojp:Duration><ojp:StartTime>2021-09-07T15:46:00Z</ojp:StartTime><ojp:EndTime>2021-09-07T18:54:00Z</ojp:EndTime><ojp:Transfers>1</ojp:Transfers><ojp:Distance>250625</ojp:Distance><ojp:TripLeg><ojp:LegId>1</ojp:LegId><ojp:TimedLeg><ojp:LegBoard><siri:StopPointRef>8503016</siri:StopPointRef><ojp:StopPointName><ojp:Text>Zürich Flughafen</ojp:Text></ojp:StopPointName><ojp:PlannedQuay><ojp:Text xml:lang="de">3</ojp:Text></ojp:PlannedQuay><ojp:EstimatedQuay><ojp:Text xml:lang="de">4</ojp:Text></ojp:EstimatedQuay><ojp:ServiceDeparture><ojp:TimetabledTime>2021-09-07T15:45:00Z</ojp:TimetabledTime><ojp:EstimatedTime>2021-09-07T15:46:00Z</ojp:EstimatedTime></ojp:ServiceDeparture><ojp:Order>1</ojp:Order></ojp:LegBoard><ojp:LegAlight><siri:StopPointRef>8501605</siri:StopPointRef><ojp:StopPointName><ojp:Text>Visp</ojp:Text></ojp:StopPointName><ojp:PlannedQuay><ojp:Text xml:lang="de">7</ojp:Text></ojp:PlannedQuay><ojp:ServiceArrival><ojp:TimetabledTime>2021-09-07T18:02:00Z</ojp:TimetabledTime><ojp:EstimatedTime>2021-09-07T18:02:00Z</ojp:EstimatedTime></ojp:ServiceArrival><ojp:Order>6</ojp:Order></ojp:LegAlight><ojp:Service><ojp:OperatingDayRef>2021-09-07</ojp:OperatingDayRef><ojp:JourneyRef>ojp:91008:E:R:j21:396</ojp:JourneyRef><siri:LineRef>ojp:91008:E</siri:LineRef><siri:DirectionRef>R</siri:DirectionRef><ojp:Mode><ojp:PtMode>rail</ojp:PtMode><siri:RailSubmode>interRegionalRailService</siri:RailSubmode><ojp:Name><ojp:Text xml:lang="de">Zug</ojp:Text></ojp:Name><ojp:ShortName><ojp:Text xml:lang="de">IC</ojp:Text></ojp:ShortName></ojp:Mode><ojp:PublishedLineName><ojp:Text>IC8</ojp:Text></ojp:PublishedLineName><ojp:OperatorRef>ojp:11</ojp:OperatorRef><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Gratis-Internet mit der App SBB FreeSurf</ojp:Text></ojp:Text><ojp:Code>A__FS</ojp:Code></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Maskenpflicht für Reisende ab 12 Jahren</ojp:Text></ojp:Text><ojp:Code>A__OM</ojp:Code></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Platzreservierung möglich</ojp:Text></ojp:Text><ojp:Code>A___R</ojp:Code></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Businesszone in 1. Klasse</ojp:Text></ojp:Text><ojp:Code>A__BZ</ojp:Code><siri:FareClassFacility>firstClass</siri:FareClassFacility><siri:PassengerCommsFacility>businessServices</siri:PassengerCommsFacility></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Familienwagen mit Spielplatz</ojp:Text></ojp:Text><ojp:Code>A__FA</ojp:Code></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Ruhezone in 1. Klasse</ojp:Text></ojp:Text><ojp:Code>A__RZ</ojp:Code><siri:NuisanceFacility>mobilePhoneFreeZone</siri:NuisanceFacility></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Restaurant</ojp:Text></ojp:Text><ojp:Code>A__WR</ojp:Code><siri:RefreshmentFacility>restaurantService</siri:RefreshmentFacility></ojp:Attribute><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Aussteigeseite: Rechts</ojp:Text></ojp:Text><ojp:Code>ojp91008ER_InfoCall396_108287_1</ojp:Code></ojp:Attribute><ojp:DestinationStopPointRef>de:00000:-1</ojp:DestinationStopPointRef><ojp:DestinationText><ojp:Text xml:lang="de">Brig</ojp:Text></ojp:DestinationText></ojp:Service><ojp:Extension><ojp:TransportTypeName><ojp:Text xml:lang="de">InterCity</ojp:Text></ojp:TransportTypeName><ojp:PublishedJourneyNumber><ojp:Text xml:lang="de">828</ojp:Text></ojp:PublishedJourneyNumber></ojp:Extension></ojp:TimedLeg></ojp:TripLeg><ojp:TripLeg><ojp:LegId>2</ojp:LegId><ojp:TransferLeg><ojp:TransferMode>walk</ojp:TransferMode><ojp:LegStart><siri:StopPointRef>8501605</siri:StopPointRef><ojp:LocationName><ojp:Text xml:lang="de">Visp</ojp:Text></ojp:LocationName></ojp:LegStart><ojp:LegEnd><siri:StopPointRef>8501605</siri:StopPointRef><ojp:LocationName><ojp:Text xml:lang="de">Visp</ojp:Text></ojp:LocationName></ojp:LegEnd><ojp:TimeWindowStart>2021-09-07T18:02:00Z</ojp:TimeWindowStart><ojp:TimeWindowEnd>2021-09-07T18:08:00Z</ojp:TimeWindowEnd><ojp:Duration>PT6M</ojp:Duration><ojp:WalkDuration>PT4M</ojp:WalkDuration><ojp:BufferTime>PT2M</ojp:BufferTime></ojp:TransferLeg></ojp:TripLeg><ojp:TripLeg><ojp:LegId>3</ojp:LegId><ojp:TimedLeg><ojp:LegBoard><siri:StopPointRef>8501605</siri:StopPointRef><ojp:StopPointName><ojp:Text>Visp</ojp:Text></ojp:StopPointName><ojp:PlannedQuay><ojp:Text xml:lang="de">3</ojp:Text></ojp:PlannedQuay><ojp:ServiceDeparture><ojp:TimetabledTime>2021-09-07T18:08:00Z</ojp:TimetabledTime><ojp:EstimatedTime>2021-09-07T18:08:00Z</ojp:EstimatedTime></ojp:ServiceDeparture><ojp:Order>1</ojp:Order></ojp:LegBoard><ojp:LegAlight><siri:StopPointRef>8501687</siri:StopPointRef><ojp:StopPointName><ojp:Text>Randa</ojp:Text></ojp:StopPointName><ojp:ServiceArrival><ojp:TimetabledTime>2021-09-07T18:54:00Z</ojp:TimetabledTime><ojp:EstimatedTime>2021-09-07T18:54:00Z</ojp:EstimatedTime></ojp:ServiceArrival><ojp:Order>6</ojp:Order></ojp:LegAlight><ojp:Service><ojp:OperatingDayRef>2021-09-07</ojp:OperatingDayRef><ojp:JourneyRef>ojp:91003:Y:H:j21:121</ojp:JourneyRef><siri:LineRef>ojp:91003:Y</siri:LineRef><siri:DirectionRef>H</siri:DirectionRef><ojp:Mode><ojp:PtMode>rail</ojp:PtMode><siri:RailSubmode>regionalRail</siri:RailSubmode><ojp:Name><ojp:Text xml:lang="de">Zug</ojp:Text></ojp:Name><ojp:ShortName><ojp:Text xml:lang="de">R</ojp:Text></ojp:ShortName></ojp:Mode><ojp:PublishedLineName><ojp:Text>R</ojp:Text></ojp:PublishedLineName><ojp:OperatorRef>ojp:93</ojp:OperatorRef><ojp:Attribute><ojp:Text><ojp:Text xml:lang="de">Maskenpflicht für Reisende ab 12 Jahren</ojp:Text></ojp:Text><ojp:Code>A__OM</ojp:Code></ojp:Attribute><ojp:DestinationStopPointRef>de:00000:-1</ojp:DestinationStopPointRef><ojp:DestinationText><ojp:Text xml:lang="de">Zermatt</ojp:Text></ojp:DestinationText></ojp:Service><ojp:Extension><ojp:TransportTypeName><ojp:Text xml:lang="de">Regio</ojp:Text></ojp:TransportTypeName><ojp:PublishedJourneyNumber><ojp:Text xml:lang="de">265</ojp:Text></ojp:PublishedJourneyNumber></ojp:Extension></ojp:TimedLeg></ojp:TripLeg></ojp:Trip></ojp:TripResult></ojp:OJPTripDelivery></siri:ServiceDelivery></siri:OJPResponse></siri:OJP>
......@@ -90,6 +90,33 @@ private Q_SLOTS:
QVERIFY(!jsonRes.empty());
QCOMPARE(jsonRes, ref);
}
void testParseJourney_data()
{
QTest::addColumn<QString>("inFileName");
QTest::addColumn<QString>("refFileName");
QTest::newRow("ch-journey-basic")
<< s(SOURCE_DIR "/data/ojp/ch-journey-basic.xml")
<< s(SOURCE_DIR "/data/ojp/ch-journey-basic.json");
}
void testParseJourney()
{
QFETCH(QString, inFileName);
QFETCH(QString, refFileName);
OpenJourneyPlannerParser p;
const auto res = p.parseTripResponse(readFile(inFileName));
const auto jsonRes = Journey::toJson(res);
const auto ref = QJsonDocument::fromJson(readFile(refFileName)).array();
if (jsonRes != ref) {
qDebug().noquote() << QJsonDocument(jsonRes).toJson();
}
QVERIFY(!jsonRes.empty());
QCOMPARE(jsonRes, ref);
}
};
QTEST_GUILESS_MAIN(OjpParserTest)
......
......@@ -91,8 +91,19 @@ bool OpenJourneyPlannerBackend::queryJourney(const JourneyRequest &request, Jour
const auto postData = builder.buildTripRequest(request);
const auto netReq = networkRequest();
logRequest(request, netReq, postData);
// TODO
return false;
const auto netReply = nam->post(netReq, postData);
QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, reply]() {
netReply->deleteLater();
const auto data = netReply->readAll();
logReply(reply, netReply, data);
OpenJourneyPlannerParser p;
auto jnys = p.parseTripResponse(data);
// TODO caching, error handling
addResult(reply, this, std::move(jnys));
});
return true;
}
QNetworkRequest OpenJourneyPlannerBackend::networkRequest() const
......
......@@ -45,7 +45,13 @@ std::vector<Stopover> OpenJourneyPlannerParser::parseStopEventResponse(const QBy
std::vector<Journey> OpenJourneyPlannerParser::parseTripResponse(const QByteArray &responseData)
{
qDebug().noquote() << responseData;
QXmlStreamReader reader(responseData);
ScopedXmlStreamReader r(reader);
while (r.readNextElement()) {
if (r.isElement("OJPTripDelivery")) {
return parseTripDelivery(r.subReader());
}
}
return {};
}
......@@ -262,3 +268,110 @@ Line::Mode OpenJourneyPlannerParser::parseMode(ScopedXmlStreamReader &&r) const
}
return m;
}
std::vector<Journey> OpenJourneyPlannerParser::parseTripDelivery(ScopedXmlStreamReader &&r)
{
std::vector<Journey> l;
while (r.readNextSibling()) {
if (r.isElement("TripResponseContext")) {
parseResponseContext(r.subReader());
} else if (r.isElement("TripResult")) {
l.push_back(parseTripResult(r.subReader()));
}
}
return l;
}
Journey OpenJourneyPlannerParser::parseTripResult(ScopedXmlStreamReader &&r) const
{
Journey jny;
while (r.readNextSibling()) {
if (r.isElement("Trip")) {
jny = parseTrip(r.subReader());
}
}
return jny;
}
Journey OpenJourneyPlannerParser::parseTrip(ScopedXmlStreamReader &&r) const
{
Journey jny;
std::vector<JourneySection> sections;
while (r.readNextSibling()) {
if (r.isElement("TripLeg")) {
auto subR = r.subReader();
while (subR.readNextSibling()) {
if (subR.isElement("TimedLeg")) {
sections.push_back(parseTimedLeg(subR.subReader()));
} else if (subR.isElement("TransferLeg")) {
sections.push_back(parseTransferLeg(subR.subReader()));
}
}
}
}
jny.setSections(std::move(sections));
return jny;
}
JourneySection OpenJourneyPlannerParser::parseTimedLeg(ScopedXmlStreamReader &&r) const
{
JourneySection section;
section.setMode(JourneySection::PublicTransport);
std::vector<Stopover> intermediateStops;
Route route;
QStringList attributes;
while (r.readNextSibling()) {
if (r.isElement("LegBoard")) {
Stopover stop;
parseCallAtStop(r.subReader(), stop);
section.setFrom(stop.stopPoint());
section.setScheduledDepartureTime(stop.scheduledDepartureTime());
section.setExpectedDepartureTime(stop.expectedDepartureTime());
section.setScheduledDeparturePlatform(stop.scheduledPlatform());
section.setExpectedDeparturePlatform(stop.expectedPlatform());
} else if (r.isElement("LegIntermediates")) {
Stopover stop;
parseCallAtStop(r.subReader(), stop);
intermediateStops.push_back(std::move(stop));
} else if (r.isElement("LegAlight")) {
Stopover stop;
parseCallAtStop(r.subReader(), stop);
section.setTo(stop.stopPoint());
section.setScheduledArrivalTime(stop.scheduledArrivalTime());
section.setExpectedArrivalTime(stop.expectedArrivalTime());
section.setScheduledArrivalPlatform(stop.scheduledPlatform());
section.setExpectedArrivalPlatform(stop.expectedPlatform());
} else if (r.isElement("Service")) {
parseService(r.subReader(), route, attributes);
} else if (r.isElement("LegTrack")) {
// TODO
}
}
section.setRoute(std::move(route));
section.addNotes(std::move(attributes));
section.setIntermediateStops(std::move(intermediateStops));
return section;
}
JourneySection OpenJourneyPlannerParser::parseTransferLeg(ScopedXmlStreamReader &&r) const
{
// TODO WalkDuration vs. BufferTime?
JourneySection section;
section.setMode(JourneySection::Transfer);
while (r.readNextSibling()) {
if (r.isElement("LegStart")) {
Stopover stop;
parseCallAtStop(r.subReader(), stop);
section.setFrom(stop.stopPoint());
} else if (r.isElement("LegEnd")) {
Stopover stop;
parseCallAtStop(r.subReader(), stop);
section.setTo(stop.stopPoint());
} else if (r.isElement("TimeWindowStart")) {
section.setScheduledDepartureTime(QDateTime::fromString(r.readElementText(), Qt::ISODate));
} else if (r.isElement("TimeWindowEnd")) {
section.setScheduledArrivalTime(QDateTime::fromString(r.readElementText(), Qt::ISODate));
}
}
return section;
}
......@@ -20,6 +20,7 @@
namespace KPublicTransport {
class Journey;
class JourneySection;
class Location;
class Route;
class ScopedXmlStreamReader;
......@@ -55,6 +56,12 @@ private:
TimePair parseTime(ScopedXmlStreamReader &&r) const;
Line::Mode parseMode(ScopedXmlStreamReader &&r) const;
std::vector<Journey> parseTripDelivery(ScopedXmlStreamReader &&r);
Journey parseTripResult(ScopedXmlStreamReader &&r) const;
Journey parseTrip(ScopedXmlStreamReader &&r) const;
JourneySection parseTimedLeg(ScopedXmlStreamReader &&r) const;
JourneySection parseTransferLeg(ScopedXmlStreamReader &&r) const;
QString m_identifierType = QStringLiteral("uic"); // TODO
QHash<QString, Location> m_contextLocations;
};
......
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