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 }