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

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "openjourneyplannerrequestbuilder.h"
0008 
0009 #include <KPublicTransport/JourneyRequest>
0010 #include <KPublicTransport/LocationRequest>
0011 #include <KPublicTransport/StopoverRequest>
0012 
0013 #include <QByteArray>
0014 #include <QDateTime>
0015 #include <QDebug>
0016 #include <QTimeZone>
0017 #include <QXmlStreamWriter>
0018 
0019 using namespace KPublicTransport;
0020 
0021 static QString siriNS() { return QStringLiteral("http://www.siri.org.uk/siri"); }
0022 static QString ojpNS() { return QStringLiteral("http://www.vdv.de/ojp"); }
0023 static QString triasNS() { return QStringLiteral("http://www.vdv.de/trias"); }
0024 
0025 OpenJourneyPlannerRequestBuilder::OpenJourneyPlannerRequestBuilder() = default;
0026 OpenJourneyPlannerRequestBuilder::~OpenJourneyPlannerRequestBuilder() = default;
0027 
0028 void OpenJourneyPlannerRequestBuilder::setRequestorRef(const QString &ref)
0029 {
0030     m_requestorRef = ref;
0031 }
0032 
0033 void OpenJourneyPlannerRequestBuilder::setUseTrias(bool isTrias)
0034 {
0035     m_useTrias = isTrias;
0036 }
0037 
0038 void OpenJourneyPlannerRequestBuilder::setTestMode(bool testMode)
0039 {
0040     m_testMode = testMode;
0041 }
0042 
0043 QByteArray OpenJourneyPlannerRequestBuilder::buildLocationInformationRequest(const LocationRequest &req) const
0044 {
0045     QByteArray output;
0046     QXmlStreamWriter w(&output);
0047     setupWriter(w);
0048     writeStartServiceRequest(w);
0049     w.writeStartElement(ns(), m_useTrias ?  QStringLiteral("LocationInformationRequest") : QStringLiteral("OJPLocationInformationRequest"));
0050     writeRequestTimestamp(w);
0051 
0052     w.writeStartElement(ns(), QStringLiteral("InitialInput"));
0053     if (req.hasCoordinate()) {
0054         w.writeStartElement(ns(), QStringLiteral("GeoRestriction"));
0055         w.writeStartElement(ns(), QStringLiteral("Circle"));
0056         w.writeStartElement(ns(), QStringLiteral("Center"));
0057         w.writeTextElement(siriNS(), QStringLiteral("Longitude"), QString::number(req.location().longitude()));
0058         w.writeTextElement(siriNS(), QStringLiteral("Latitude"), QString::number(req.location().latitude()));
0059         w.writeEndElement(); // </Center>
0060         w.writeTextElement(ns(), QStringLiteral("Radius"), QString::number(req.maximumDistance()));
0061         w.writeEndElement(); // </Circle>
0062         w.writeEndElement(); // </GeoRestriction>
0063     } else {
0064         w.writeTextElement(ns(), QStringLiteral("LocationName"), req.location().name());
0065     }
0066     w.writeEndElement(); // </InitialInput>
0067 
0068     w.writeStartElement(ns(), QStringLiteral("Restrictions"));
0069     w.writeTextElement(ns(), QStringLiteral("Type"), QStringLiteral("stop"));
0070     w.writeTextElement(ns(), QStringLiteral("NumberOfResults"), QString::number(req.maximumResults()));
0071     // TODO ojp:Language
0072     w.writeEndElement(); // </Restrictions>
0073 
0074     w.writeEndElement(); // </LocationInformationRequest>
0075     writeEndServiceRequest(w);
0076     return output;
0077 }
0078 
0079 QByteArray OpenJourneyPlannerRequestBuilder::buildStopEventRequest(const StopoverRequest &req) const
0080 {
0081     QByteArray output;
0082     QXmlStreamWriter w(&output);
0083     setupWriter(w);
0084     writeStartServiceRequest(w);
0085     w.writeStartElement(ns(), m_useTrias ? QStringLiteral("StopEventRequest") : QStringLiteral("OJPStopEventRequest"));
0086     writeRequestTimestamp(w);
0087 
0088     w.writeStartElement(ns(), QStringLiteral("Location"));
0089     writePlaceRef(w, req.stop());
0090     w.writeTextElement(ns(), QStringLiteral("DepArrTime"), req.dateTime().toUTC().toString(Qt::ISODate));
0091     w.writeEndElement(); // </Location>
0092 
0093     w.writeStartElement(ns(), QStringLiteral("Params"));
0094     w.writeTextElement(ns(), QStringLiteral("StopEventType"), req.mode() == StopoverRequest::QueryArrival ? QStringLiteral("arrival") : QStringLiteral("departure")); // "both" is also supported
0095     w.writeTextElement(ns(), QStringLiteral("IncludePreviousCalls"), QStringLiteral("false"));
0096     w.writeTextElement(ns(), QStringLiteral("IncludeOnwardCalls"), QStringLiteral("false"));
0097     w.writeTextElement(ns(), QStringLiteral("IncludeOperatingDays"), QStringLiteral("false"));
0098     w.writeTextElement(ns(), QStringLiteral("IncludeRealtimeData"), QStringLiteral("true"));
0099     w.writeTextElement(ns(), QStringLiteral("NumberOfResults"), QString::number(req.maximumResults()));
0100     w.writeEndElement(); // </Params>
0101 
0102     w.writeEndElement(); // </StopEventRequest>
0103     writeEndServiceRequest(w);
0104     return output;
0105 }
0106 
0107 struct {
0108     IndividualTransport::Mode mode;
0109     const char *ojpMode;
0110 } static constexpr const individual_transport_modes[] = {
0111     { IndividualTransport::Walk, "walk" },
0112     { IndividualTransport::Bike, "cycle" },
0113     // TODO taxi, self-drive-car others-drive-car motorcycle truck
0114 };
0115 
0116 QByteArray OpenJourneyPlannerRequestBuilder::buildTripRequest(const JourneyRequest &req) const
0117 {
0118     QByteArray output;
0119     QXmlStreamWriter w(&output);
0120     setupWriter(w);
0121     writeStartServiceRequest(w);
0122     w.writeStartElement(ns(), m_useTrias ? QStringLiteral("TripRequest") : QStringLiteral("OJPTripRequest"));
0123     writeRequestTimestamp(w);
0124 
0125     w.writeStartElement(ns(), QStringLiteral("Origin"));
0126     writePlaceRef(w, req.from());
0127     if (req.dateTimeMode() == JourneyRequest::Departure) {
0128         w.writeTextElement(ns(), QStringLiteral("DepArrTime"), req.dateTime().toUTC().toString(Qt::ISODate));
0129     }
0130     w.writeEndElement(); // </Origin>
0131 
0132     w.writeStartElement(ns(), QStringLiteral("Destination"));
0133     writePlaceRef(w, req.to());
0134     if (req.dateTimeMode() == JourneyRequest::Arrival) {
0135         w.writeTextElement(ns(), QStringLiteral("DepArrTime"), req.dateTime().toUTC().toString(Qt::ISODate));
0136     }
0137     w.writeEndElement(); // </Destination>
0138 
0139     if (!m_useTrias) {
0140         w.writeStartElement(ns(), QStringLiteral("IndividualTransportOptions"));
0141         for (const auto &accessMode : req.accessModes()) {
0142             const auto it = std::find_if(std::begin(individual_transport_modes), std::end(individual_transport_modes), [accessMode](const auto &m) {
0143                 return m.mode == accessMode.mode();
0144             });
0145             if (it != std::end(individual_transport_modes)) {
0146                 w.writeTextElement(ns(),  QStringLiteral("Mode"), QLatin1String((*it).ojpMode));
0147                 break;
0148             }
0149         }
0150         w.writeEndElement(); // </IndividualTransportOptions>
0151     }
0152 
0153     w.writeStartElement(ns(), QStringLiteral("Params"));
0154     w.writeTextElement(ns(), QStringLiteral("IncludeTrackSections"), req.includePaths() ? QStringLiteral("true") : QStringLiteral("false"));
0155     w.writeTextElement(ns(), QStringLiteral("IncludeLegProjection"), req.includePaths() ? QStringLiteral("true") : QStringLiteral("false"));
0156     w.writeTextElement(ns(), QStringLiteral("IncludeTurnDescription"), req.includePaths() ? QStringLiteral("true") : QStringLiteral("false"));
0157     w.writeTextElement(ns(), QStringLiteral("IncludeAccessibility"), QStringLiteral("true")); // ???
0158     w.writeTextElement(ns(), QStringLiteral("IncludeIntermediateStops"), req.includeIntermediateStops() ? QStringLiteral("true") : QStringLiteral("false"));
0159     w.writeTextElement(ns(), QStringLiteral("IncludeFares"), QStringLiteral("false")); // TODO
0160     w.writeTextElement(ns(), QStringLiteral("NumberOfResults"), QString::number(req.maximumResults()));
0161     // TODO NumberOfResultsBefore|After for next/prev requests
0162     // TODO BikeTransport
0163     w.writeEndElement(); // </Params>
0164 
0165     w.writeEndElement(); // </TripRequest>
0166     writeEndServiceRequest(w);
0167     return output;
0168 }
0169 
0170 void OpenJourneyPlannerRequestBuilder::setupWriter(QXmlStreamWriter &w) const
0171 {
0172     if (Q_UNLIKELY(m_testMode)) {
0173         w.setAutoFormatting(true);
0174         w.setAutoFormattingIndent(1);
0175     }
0176 }
0177 
0178 void OpenJourneyPlannerRequestBuilder::writeStartServiceRequest(QXmlStreamWriter &w) const
0179 {
0180     w.writeStartDocument();
0181     w.writeNamespace(siriNS(), QStringLiteral("siri"));
0182     if (m_useTrias) {
0183         w.writeNamespace(triasNS(), QStringLiteral("trias"));
0184         w.writeStartElement(triasNS(), QStringLiteral("Trias"));
0185         w.writeAttribute(QStringLiteral("version"), QStringLiteral("1.2"));
0186         w.writeStartElement(triasNS(), QStringLiteral("ServiceRequest"));
0187     } else {
0188         w.writeNamespace(ojpNS(), QStringLiteral("ojp"));
0189         w.writeStartElement(siriNS(), QStringLiteral("OJP"));
0190         w.writeAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
0191         w.writeStartElement(siriNS(), QStringLiteral("OJPRequest"));
0192         w.writeStartElement(siriNS(), QStringLiteral("ServiceRequest"));
0193     }
0194     if (!m_requestorRef.isEmpty()) {
0195         w.writeTextElement(siriNS(), QStringLiteral("RequestorRef"), m_requestorRef);
0196     }
0197     writeRequestTimestamp(w);
0198 
0199     if (m_useTrias) {
0200         w.writeStartElement(ns(), QStringLiteral("RequestPayload"));
0201     }
0202 }
0203 
0204 void OpenJourneyPlannerRequestBuilder::writeEndServiceRequest(QXmlStreamWriter &w) const
0205 {
0206     if (m_useTrias) {
0207         w.writeEndElement(); // </trias:RequestPayload>
0208     }
0209     w.writeEndElement(); // </siri:ServiceRequest>
0210     if (!m_useTrias) {
0211         w.writeEndElement(); // </siri:OJPRequest>
0212     }
0213     w.writeEndElement(); // </siri:OJP> or </siri:TRIAS>
0214     w.writeEndDocument();
0215 }
0216 
0217 void OpenJourneyPlannerRequestBuilder::writePlaceRef(QXmlStreamWriter &w, const Location &loc) const
0218 {
0219     w.writeStartElement(ns(), m_useTrias ? QStringLiteral("LocationRef") : QStringLiteral("PlaceRef"));
0220     const auto id = loc.identifier(QStringLiteral("uic")); // ### TODO configure id type
0221     if (!id.isEmpty()) {
0222         w.writeTextElement(ns(), m_useTrias ? QStringLiteral("StopPointRef") : QStringLiteral("StopPlaceRef"), id);
0223     } else if (loc.hasCoordinate()) {
0224         w.writeStartElement(ns(), QStringLiteral("GeoPosition"));
0225         w.writeTextElement(siriNS(), QStringLiteral("Longitude"), QString::number(loc.longitude()));
0226         w.writeTextElement(siriNS(), QStringLiteral("Latitude"), QString::number(loc.latitude()));
0227         w.writeEndElement(); // </GeoPosition>
0228 
0229         w.writeStartElement(ns(), QStringLiteral("LocationName"));
0230         w.writeTextElement(ns(), QStringLiteral("Text"), loc.name().isEmpty() ? QStringLiteral(" ") : loc.name());
0231         w.writeEndElement(); // </LocationName>
0232     }
0233     w.writeEndElement(); // </PlaceRef>
0234 }
0235 
0236 void OpenJourneyPlannerRequestBuilder::writeRequestTimestamp(QXmlStreamWriter &w) const
0237 {
0238     if (Q_UNLIKELY(m_testMode)) {
0239         w.writeTextElement(siriNS(), QStringLiteral("RequestTimestamp"), QDateTime({2023, 3, 24}, {12, 34, 56}, QTimeZone::UTC).toString(Qt::ISODate));
0240     } else {
0241         w.writeTextElement(siriNS(), QStringLiteral("RequestTimestamp"), QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
0242     }
0243 }
0244 
0245 QString OpenJourneyPlannerRequestBuilder::ns() const
0246 {
0247     return m_useTrias ? triasNS() : ojpNS();
0248 }