File indexing completed on 2024-05-12 04:42:39

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "openjourneyplannerparser.h"
0008 #include "scopedxmlstreamreader.h"
0009 
0010 #include <gtfs/hvt.h>
0011 #include <ifopt/ifoptutil.h>
0012 #include <uic/uicstationcode.h>
0013 
0014 #include <KPublicTransport/Journey>
0015 #include <KPublicTransport/Location>
0016 #include <KPublicTransport/Stopover>
0017 
0018 #include <QByteArray>
0019 #include <QDebug>
0020 #include <QPointF>
0021 #include <QXmlStreamReader>
0022 
0023 using namespace KPublicTransport;
0024 
0025 bool OpenJourneyPlannerParser::hasError() const
0026 {
0027     return !m_errorMsg.isEmpty();
0028 }
0029 
0030 QString OpenJourneyPlannerParser::errorMessage() const
0031 {
0032     return m_errorMsg;
0033 }
0034 
0035 void OpenJourneyPlannerParser::setLocationIdentifierType(const QString &idType)
0036 {
0037     m_identifierType = idType;
0038 }
0039 
0040 void OpenJourneyPlannerParser::setUicLocationIdentifierType(const QString &uicIdType)
0041 {
0042     m_uicIdentifierType = uicIdType;
0043 }
0044 
0045 std::vector<Location> OpenJourneyPlannerParser::parseLocationInformationResponse(const QByteArray &responseData)
0046 {
0047     QXmlStreamReader reader(responseData);
0048     ScopedXmlStreamReader r(reader);
0049     std::vector<Location> res;
0050     while (r.readNextElement()) {
0051         if (r.isElement("OJPLocationInformationDelivery") || r.isElement("LocationInformationResponse")) {
0052             res = parseLocationInformationDelivery(r.subReader());
0053         }
0054     }
0055     if (reader.hasError() && m_errorMsg.isEmpty()) {
0056         m_errorMsg = reader.errorString();
0057     }
0058     return res;
0059 }
0060 
0061 std::vector<Stopover> OpenJourneyPlannerParser::parseStopEventResponse(const QByteArray &responseData)
0062 {
0063     QXmlStreamReader reader(responseData);
0064     ScopedXmlStreamReader r(reader);
0065     std::vector<Stopover> res;
0066     while (r.readNextElement()) {
0067         if (r.isElement("OJPStopEventDelivery") || r.isElement("StopEventResponse")) {
0068             res = parseStopEventDelivery(r.subReader());
0069         }
0070     }
0071     if (reader.hasError() && m_errorMsg.isEmpty()) {
0072         m_errorMsg = reader.errorString();
0073     }
0074     return res;
0075 }
0076 
0077 std::vector<Journey> OpenJourneyPlannerParser::parseTripResponse(const QByteArray &responseData)
0078 {
0079     QXmlStreamReader reader(responseData);
0080     ScopedXmlStreamReader r(reader);
0081     std::vector<Journey> res;
0082     while (r.readNextElement()) {
0083         if (r.isElement("OJPTripDelivery") || r.isElement("TripResponse")) {
0084             res = parseTripDelivery(r.subReader());
0085         }
0086     }
0087     if (reader.hasError() && m_errorMsg.isEmpty()) {
0088         m_errorMsg = reader.errorString();
0089     }
0090     return res;
0091 }
0092 
0093 std::vector<Location> OpenJourneyPlannerParser::parseLocationInformationDelivery(ScopedXmlStreamReader &&r)
0094 {
0095     std::vector<Location> l;
0096     while (r.readNextSibling()) {
0097         if (r.isElement("Location") || r.isElement("LocationResult")) {
0098             auto loc = parseLocationInformationLocationResult(r.subReader());
0099             if (!loc.isEmpty()) {
0100                 l.push_back(std::move(loc));
0101             }
0102         } else if (r.isElement("ErrorCondition")) {
0103             parseError(r.subReader());
0104         }
0105     }
0106     return l;
0107 }
0108 
0109 Location OpenJourneyPlannerParser::parseLocationInformationLocationResult(ScopedXmlStreamReader &&r) const
0110 {
0111     Location loc;
0112     while (r.readNextSibling()) {
0113         if (r.isElement("Location")) {
0114             loc = parseLocationInformationLocation(r.subReader());
0115         }
0116     }
0117     return loc;
0118 }
0119 
0120 Location OpenJourneyPlannerParser::parseLocationInformationLocation(ScopedXmlStreamReader &&r) const
0121 {
0122     Location loc;
0123     while (r.readNextSibling()) {
0124         if (r.isElement("StopPlace") || r.isElement("StopPoint")) {
0125             loc.setType(Location::Stop);
0126             auto subR = r.subReader();
0127             while (subR.readNextSibling()) {
0128                 if (subR.isElement("StopPlaceRef") || subR.isElement("StopPointRef")) {
0129                     const auto id = subR.readElementText();
0130                     setLocationIdentifier(loc, id);
0131                 } else if (subR.isElement("StopPlaceName") || subR.isElement("StopPointName")) {
0132                     loc.setName(parseTextElement(subR.subReader()));
0133                 }
0134             }
0135         } else if (r.isElement("GeoPosition")) {
0136             const auto p = parseGeoPosition(r.subReader());
0137             loc.setLongitude(p.x());
0138             loc.setLatitude(p.y());
0139         } else if (r.isElement("LocationName")) {
0140             loc.setLocality(parseTextElement(r.subReader()));
0141         }
0142     }
0143 
0144     // cleanup locality also containing the stop name
0145     if (loc.locality().startsWith(loc.name()) && loc.locality().endsWith(QLatin1Char(')'))) {
0146         const auto idx = loc.locality().lastIndexOf(QLatin1Char('('));
0147         if (idx > 0) {
0148             loc.setLocality(loc.locality().mid(idx + 1, loc.locality().size() - idx - 2));
0149         }
0150     }
0151 
0152     return loc;
0153 }
0154 
0155 QString OpenJourneyPlannerParser::parseTextElement(ScopedXmlStreamReader &&r) const
0156 {
0157     QString t;
0158     while (r.readNextSibling()) {
0159         if (r.isElement("Text")) {
0160             t = r.readElementText();
0161         }
0162     }
0163     return t;
0164 }
0165 
0166 QPointF OpenJourneyPlannerParser::parseGeoPosition(ScopedXmlStreamReader &&r) const
0167 {
0168     QPointF p;
0169     while (r.readNextSibling()) {
0170         if (r.isElement("Longitude")) {
0171             p.setX(r.readElementText().toDouble());
0172         } else if (r.isElement("Latitude")) {
0173             p.setY(r.readElementText().toDouble());
0174         }
0175     }
0176     return p;
0177 }
0178 
0179 std::vector<Stopover> OpenJourneyPlannerParser::parseStopEventDelivery(ScopedXmlStreamReader &&r)
0180 {
0181     std::vector<Stopover> l;
0182     while (r.readNextSibling()) {
0183         if (r.isElement("StopEventResponseContext")) {
0184             parseResponseContext(r.subReader());
0185         } else if (r.isElement("StopEventResult")) {
0186             l.push_back(parseStopEventResult(r.subReader()));
0187         } else if (r.isElement("ErrorCondition")) {
0188             parseError(r.subReader());
0189         }
0190     }
0191     return l;
0192 }
0193 
0194 void OpenJourneyPlannerParser::parseResponseContext(ScopedXmlStreamReader &&r)
0195 {
0196     while (r.readNextSibling()) {
0197         if (r.isElement("Places")) {
0198             parseResponseContextPlaces(r.subReader());
0199         } else if (r.isElement("Situations")) {
0200             parseResponseContextSituations(r.subReader());
0201         }
0202     }
0203 }
0204 
0205 void OpenJourneyPlannerParser::parseResponseContextPlaces(ScopedXmlStreamReader && r)
0206 {
0207     while (r.readNextSibling()) {
0208         if (r.isElement("Location")) {
0209             auto loc = parseLocationInformationLocation(r.subReader());
0210             m_contextLocations.insert(loc.identifier(m_identifierType), std::move(loc));
0211         }
0212     }
0213 }
0214 
0215 void OpenJourneyPlannerParser::parseResponseContextSituations(ScopedXmlStreamReader &&r)
0216 {
0217     while (r.readNextSibling()) {
0218         if (r.isElement("PtSituation")) {
0219             parseSituation(r.subReader());
0220         }
0221     }
0222 }
0223 
0224 void OpenJourneyPlannerParser::parseSituation(ScopedXmlStreamReader &&r)
0225 {
0226     QString source, id, summary, desc;
0227     while (r.readNextSibling()) {
0228         if (r.isElement("ParticipantRef")) {
0229             source = r.readElementText();
0230         } else if (r.isElement("SituationNumber")) {
0231             id = r.readElementText();
0232         } else if (r.isElement("Summary")) {
0233             summary = r.readElementText();
0234         } else if (r.isElement("Description")) {
0235             desc = r.readElementText();
0236         } // TODO there's also <Detail>, but that seems a bit excessive?
0237     }
0238     m_contextSituations.insert(source + QLatin1Char('-') + id, summary + QLatin1String(": ") + desc);
0239 }
0240 
0241 Stopover OpenJourneyPlannerParser::parseStopEventResult(ScopedXmlStreamReader &&r) const
0242 {
0243     Stopover stop;
0244     while (r.readNextSibling()) {
0245         if (r.isElement("StopEvent")) {
0246             stop = parseStopEvent(r.subReader());
0247         }
0248     }
0249     return stop;
0250 }
0251 
0252 Stopover OpenJourneyPlannerParser::parseStopEvent(ScopedXmlStreamReader &&r) const
0253 {
0254     Stopover stop;
0255     Route route;
0256     QStringList attrs;
0257     while (r.readNextSibling()) {
0258         if (r.isElement("ThisCall")) {
0259             auto subR = r.subReader();
0260             while (subR.readNextSibling()) {
0261                 if (subR.isElement("CallAtStop")) {
0262                     parseCallAtStop(subR.subReader(), stop);
0263                 }
0264             }
0265         } else if (r.isElement("Service")) {
0266             parseService(r.subReader(), route, attrs);
0267         }
0268         // Extensions?
0269     }
0270     stop.setRoute(route);
0271     stop.addNotes(std::move(attrs));
0272     return stop;
0273 }
0274 
0275 void OpenJourneyPlannerParser::parseCallAtStop(ScopedXmlStreamReader &&r, Stopover &stop) const
0276 {
0277     Location loc;
0278     while (r.readNextSibling()) {
0279         if (r.isElement("StopPointRef")) {
0280             const auto id = r.readElementText();
0281             auto l = m_contextLocations.value(id);
0282             if (l.isEmpty()) {
0283                 setLocationIdentifier(loc, id);
0284             } else {
0285                 loc = l;
0286             }
0287         } else if (r.isElement("GeoPosition")) {
0288             const auto p = parseGeoPosition(r.subReader());
0289             loc.setLatitude(p.y());
0290             loc.setLongitude(p.x());
0291             loc.setType(Location::Place);
0292         } else if (r.isElement("LocationName") || r.isElement("StopPointName")) {
0293             if (loc.name().isEmpty()) {
0294                 loc.setName(parseTextElement(r.subReader()));
0295             }
0296         } else if (r.isElement("ServiceDeparture")) {
0297             const auto t = parseTime(r.subReader());
0298             stop.setScheduledDepartureTime(t.scheduledTime);
0299             stop.setExpectedDepartureTime(t.expectedTime);
0300         } else if (r.isElement("ServiceArrival")) {
0301             const auto t = parseTime(r.subReader());
0302             stop.setScheduledArrivalTime(t.scheduledTime);
0303             stop.setExpectedArrivalTime(t.expectedTime);
0304         } else if (r.isElement("PlannedQuay") || r.isElement("PlannedBay")) {
0305             stop.setScheduledPlatform(parseTextElement(r.subReader()));
0306         } else if (r.isElement("EstimatedQuay") || r.isElement("EstimatedBay")) {
0307             stop.setExpectedPlatform(parseTextElement(r.subReader()));
0308         } else if (r.isElement("NotServicedStop")) {
0309             if (r.readElementText() == QLatin1String("true")) {
0310                 stop.setDisruptionEffect(Disruption::NoService);
0311             }
0312         }
0313     }
0314     stop.setStopPoint(std::move(loc));
0315 }
0316 
0317 void OpenJourneyPlannerParser::parseService(ScopedXmlStreamReader &&r, Route &route, QStringList &attributes) const
0318 {
0319     auto line = route.line();
0320     while (r.readNextSibling()) {
0321         if (r.isElement("Mode")) {
0322             line.setMode(parseMode(r.subReader()));
0323         } else if (r.isElement("PublishedLineName")) {
0324             line.setName(parseTextElement(r.subReader()));
0325         } else if (r.isElement("Attribute")) {
0326             auto subR = r.subReader();
0327             while (subR.readNextSibling()) {
0328                 if (subR.isElement("Text")) {
0329                     attributes.push_back(parseTextElement(subR.subReader()));
0330                 }
0331             }
0332         } else if (r.isElement("DestinationStopPointRef")) {
0333             // TODO
0334         } else if (r.isElement("DestinationText")) {
0335             route.setDirection(parseTextElement(r.subReader()));
0336         } else if (r.isElement("ServiceSection")) {
0337             route.setLine(std::move(line));
0338             parseService(r.subReader(), route, attributes);
0339             line = route.line();
0340         } else if (r.isElement("SituationFullRef")) {
0341             const auto situationId = parseSituationRef(r.subReader());
0342             attributes.push_back(m_contextSituations.value(situationId));
0343         }
0344     }
0345     route.setLine(std::move(line));
0346 }
0347 
0348 OpenJourneyPlannerParser::TimePair OpenJourneyPlannerParser::parseTime(ScopedXmlStreamReader &&r) const
0349 {
0350     TimePair t;
0351     while (r.readNextSibling()) {
0352         if (r.isElement("TimetabledTime")) {
0353             t.scheduledTime = QDateTime::fromString(r.readElementText(), Qt::ISODate);
0354         } else if (r.isElement("EstimatedTime")) {
0355             t.expectedTime = QDateTime::fromString(r.readElementText(), Qt::ISODate);
0356         }
0357     }
0358     return t;
0359 }
0360 
0361 Line::Mode OpenJourneyPlannerParser::parseMode(ScopedXmlStreamReader &&r) const
0362 {
0363     QString mode, subMode;
0364     while (r.readNextSibling()) {
0365         if (r.isElement("PtMode")) {
0366             mode = r.readElementText();
0367         } else if (r.name().endsWith(QLatin1String("Submode"))) {
0368             subMode = r.readElementText();
0369         }
0370     }
0371 
0372     const auto m = Gtfs::Hvt::typeToMode(subMode);
0373     if (m == Line::Unknown) {
0374         return Gtfs::Hvt::typeToMode(mode);
0375     }
0376     return m;
0377 }
0378 
0379 QString OpenJourneyPlannerParser::parseSituationRef(ScopedXmlStreamReader &&r) const
0380 {
0381     QString source, id;
0382     while (r.readNextSibling()) {
0383         if (r.isElement("ParticipantRef")) {
0384             source = r.readElementText();
0385         } else if (r.isElement("SituationNumber")) {
0386             id = r.readElementText();
0387         }
0388     }
0389     return source + QLatin1Char('-') + id;
0390 }
0391 
0392 
0393 std::vector<Journey> OpenJourneyPlannerParser::parseTripDelivery(ScopedXmlStreamReader &&r)
0394 {
0395     std::vector<Journey> l;
0396     while (r.readNextSibling()) {
0397         if (r.isElement("TripResponseContext")) {
0398             parseResponseContext(r.subReader());
0399         } else if (r.isElement("TripResult")) {
0400             l.push_back(parseTripResult(r.subReader()));
0401         } else if (r.isElement("ErrorCondition")) {
0402             parseError(r.subReader());
0403         }
0404     }
0405     return l;
0406 }
0407 
0408 Journey OpenJourneyPlannerParser::parseTripResult(ScopedXmlStreamReader &&r) const
0409 {
0410     Journey jny;
0411     while (r.readNextSibling()) {
0412         if (r.isElement("Trip")) {
0413             jny = parseTrip(r.subReader());
0414         }
0415     }
0416     return jny;
0417 }
0418 
0419 Journey OpenJourneyPlannerParser::parseTrip(ScopedXmlStreamReader &&r) const
0420 {
0421     Journey jny;
0422     std::vector<JourneySection> sections;
0423     while (r.readNextSibling()) {
0424         if (r.isElement("TripLeg")) {
0425             auto subR = r.subReader();
0426             while (subR.readNextSibling()) {
0427                 if (subR.isElement("TimedLeg")) {
0428                     sections.push_back(parseTimedLeg(subR.subReader()));
0429                 } else if (subR.isElement("TransferLeg") || subR.isElement("InterchangeLeg")) {
0430                     auto section = parseTransferLeg(subR.subReader());
0431                     section.setMode(JourneySection::Transfer);
0432                     sections.push_back(std::move(section));
0433                 } else if (subR.isElement("ContinuousLeg")) {
0434                     auto section = parseTransferLeg(subR.subReader());
0435                     section.setMode(JourneySection::Walking);
0436                     sections.push_back(std::move(section));
0437                 }
0438             }
0439         }
0440     }
0441     jny.setSections(std::move(sections));
0442     return jny;
0443 }
0444 
0445 JourneySection OpenJourneyPlannerParser::parseTimedLeg(ScopedXmlStreamReader &&r) const
0446 {
0447     JourneySection section;
0448     section.setMode(JourneySection::PublicTransport);
0449     std::vector<Stopover> intermediateStops;
0450     Route route;
0451     QStringList attributes;
0452     while (r.readNextSibling()) {
0453         if (r.isElement("LegBoard")) {
0454             Stopover stop;
0455             parseCallAtStop(r.subReader(), stop);
0456             section.setFrom(stop.stopPoint());
0457             section.setScheduledDepartureTime(stop.scheduledDepartureTime());
0458             section.setExpectedDepartureTime(stop.expectedDepartureTime());
0459             section.setScheduledDeparturePlatform(stop.scheduledPlatform());
0460             section.setExpectedDeparturePlatform(stop.expectedPlatform());
0461         } else if (r.isElement("LegIntermediates")) {
0462             Stopover stop;
0463             parseCallAtStop(r.subReader(), stop);
0464             intermediateStops.push_back(std::move(stop));
0465         } else if (r.isElement("LegAlight")) {
0466             Stopover stop;
0467             parseCallAtStop(r.subReader(), stop);
0468             section.setTo(stop.stopPoint());
0469             section.setScheduledArrivalTime(stop.scheduledArrivalTime());
0470             section.setExpectedArrivalTime(stop.expectedArrivalTime());
0471             section.setScheduledArrivalPlatform(stop.scheduledPlatform());
0472             section.setExpectedArrivalPlatform(stop.expectedPlatform());
0473         } else if (r.isElement("Service")) {
0474             parseService(r.subReader(), route, attributes);
0475         } else if (r.isElement("LegTrack")) {
0476             Path path;
0477             path.setSections({parsePathGuildanceSection(r.subReader())});
0478             section.setPath(std::move(path));
0479         }
0480     }
0481     section.setRoute(std::move(route));
0482     section.addNotes(std::move(attributes));
0483     section.setIntermediateStops(std::move(intermediateStops));
0484     return section;
0485 }
0486 
0487 JourneySection OpenJourneyPlannerParser::parseTransferLeg(ScopedXmlStreamReader &&r) const
0488 {
0489     // TODO WalkDuration vs. BufferTime?
0490     JourneySection section;
0491     while (r.readNextSibling()) {
0492         if (r.isElement("LegStart")) {
0493             Stopover stop;
0494             parseCallAtStop(r.subReader(), stop);
0495             section.setFrom(stop.stopPoint());
0496         } else if (r.isElement("LegEnd")) {
0497             Stopover stop;
0498             parseCallAtStop(r.subReader(), stop);
0499             section.setTo(stop.stopPoint());
0500         } else if (r.isElement("TimeWindowStart")) {
0501             section.setScheduledDepartureTime(QDateTime::fromString(r.readElementText(), Qt::ISODate));
0502         } else if (r.isElement("TimeWindowEnd")) {
0503             section.setScheduledArrivalTime(QDateTime::fromString(r.readElementText(), Qt::ISODate));
0504         } else if (r.isElement("PathGuidance") || r.isElement("NavigationPath")) {
0505             section.setPath(parsePathGuidance(r.subReader()));
0506         }
0507     }
0508     return section;
0509 }
0510 
0511 Path OpenJourneyPlannerParser::parsePathGuidance(ScopedXmlStreamReader &&r) const
0512 {
0513     Path path;
0514     std::vector<PathSection> sections;
0515     while (r.readNextSibling()) {
0516         if (r.isElement("PathGuidanceSection") || r.isElement("NavigationSection")) {
0517             sections.push_back(parsePathGuildanceSection(r.subReader()));
0518         }
0519     }
0520     path.setSections(std::move(sections));
0521     return path;
0522 }
0523 
0524 PathSection OpenJourneyPlannerParser::parsePathGuildanceSection(ScopedXmlStreamReader &&r) const
0525 {
0526     PathSection section;
0527     while (r.readNextSibling()) {
0528         if (r.isElement("TrackSection")) {
0529             section = parseTrackSection(r.subReader());
0530         }
0531     }
0532     return section;
0533 }
0534 
0535 PathSection OpenJourneyPlannerParser::parseTrackSection(ScopedXmlStreamReader &&r) const
0536 {
0537     PathSection section;
0538     while (r.readNextSibling()) {
0539         if (r.isElement("LinkProjection") || r.isElement("Projection")) {
0540             auto subR = r.subReader();
0541             QPolygonF poly;
0542             while (subR.readNextSibling()) {
0543                 if (subR.isElement("Position")) {
0544                     poly.push_back(parseGeoPosition(subR.subReader()));
0545                 }
0546             }
0547             section.setPath(std::move(poly));
0548         } else if (r.isElement("RoadName")) {
0549             section.setDescription(r.readElementText());
0550         }
0551     }
0552     return section;
0553 }
0554 
0555 void OpenJourneyPlannerParser::parseError(ScopedXmlStreamReader &&r)
0556 {
0557     while (r.readNextSibling()) {
0558         if (r.isElement("Description")) {
0559             m_errorMsg = r.readElementText();
0560         }
0561     }
0562 }
0563 
0564 void OpenJourneyPlannerParser::setLocationIdentifier(Location &loc, const QString &id) const
0565 {
0566     loc.setIdentifier(m_identifierType, id);
0567     if (IfoptUtil::isValid(id)) {
0568         loc.setIdentifier(IfoptUtil::identifierType(), id);
0569     }
0570     if (!m_uicIdentifierType.isEmpty() && UicStationCode::isValid(id)) {
0571         loc.setIdentifier(m_uicIdentifierType, id);
0572     }
0573 }