File indexing completed on 2024-05-12 04:42:41
0001 /* 0002 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "opentripplannerrestbackend.h" 0008 #include "opentripplannerparser.h" 0009 0010 #include <KPublicTransport/Journey> 0011 #include <KPublicTransport/JourneyReply> 0012 #include <KPublicTransport/JourneyRequest> 0013 #include <KPublicTransport/Location> 0014 #include <KPublicTransport/LocationReply> 0015 #include <KPublicTransport/LocationRequest> 0016 #include <KPublicTransport/Stopover> 0017 #include <KPublicTransport/StopoverReply> 0018 #include <KPublicTransport/StopoverRequest> 0019 0020 #include <QDebug> 0021 #include <QJsonArray> 0022 #include <QJsonDocument> 0023 #include <QNetworkReply> 0024 #include <QNetworkRequest> 0025 #include <QUrl> 0026 #include <QUrlQuery> 0027 0028 using namespace KPublicTransport; 0029 0030 OpenTripPlannerRestBackend::OpenTripPlannerRestBackend() = default; 0031 OpenTripPlannerRestBackend::~OpenTripPlannerRestBackend() = default; 0032 0033 AbstractBackend::Capabilities OpenTripPlannerRestBackend::capabilities() const 0034 { 0035 return m_endpoint.startsWith(QLatin1String("https://")) ? Secure : NoCapability; 0036 } 0037 0038 bool OpenTripPlannerRestBackend::needsLocationQuery(const Location &loc, AbstractBackend::QueryType type) const 0039 { 0040 Q_UNUSED(type); 0041 switch (type) { 0042 case AbstractBackend::QueryType::Journey: 0043 return !loc.hasCoordinate() && loc.identifier(backendId()).isEmpty(); 0044 case AbstractBackend::QueryType::Departure: 0045 return loc.identifier(backendId()).isEmpty(); 0046 } 0047 return false; 0048 } 0049 0050 bool OpenTripPlannerRestBackend::queryLocation(const LocationRequest &req, LocationReply *reply, QNetworkAccessManager *nam) const 0051 { 0052 if ((req.types() & Location::Stop) == 0) { 0053 return false; 0054 } 0055 0056 if (req.hasCoordinate()) { 0057 QUrlQuery query; 0058 query.addQueryItem(QStringLiteral("lat"), QString::number(req.latitude())); 0059 query.addQueryItem(QStringLiteral("lon"), QString::number(req.longitude())); 0060 query.addQueryItem(QStringLiteral("radius"), QString::number(std::max(1, req.maximumDistance()))); 0061 0062 QUrl url(m_endpoint + QLatin1String("index/stops")); 0063 url.setQuery(query); 0064 0065 QNetworkRequest netReq(url); 0066 logRequest(req, netReq); 0067 auto netReply = nam->get(netReq); 0068 netReply->setParent(reply); 0069 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, reply] { 0070 const auto data = netReply->readAll(); 0071 logReply(reply, netReply, data); 0072 0073 if (netReply->error() != QNetworkReply::NoError) { 0074 addError(reply, Reply::NetworkError, netReply->errorString()); 0075 return; 0076 } 0077 OpenTripPlannerParser p(backendId()); 0078 addResult(reply, p.parseLocationsArray(QJsonDocument::fromJson(data).array())); 0079 }); 0080 0081 return true; 0082 } 0083 if (!req.name().isEmpty()) { 0084 QUrlQuery query; 0085 query.addQueryItem(QStringLiteral("query"), req.name()); 0086 query.addQueryItem(QStringLiteral("stops"), QStringLiteral("true")); 0087 query.addQueryItem(QStringLiteral("corners"), QStringLiteral("false")); 0088 0089 QUrl url(m_endpoint + QLatin1String("geocode")); 0090 url.setQuery(query); 0091 0092 QNetworkRequest netReq(url); 0093 logRequest(req, netReq); 0094 auto netReply = nam->get(netReq); 0095 netReply->setParent(reply); 0096 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, reply] { 0097 const auto data = netReply->readAll(); 0098 logReply(reply, netReply, data); 0099 0100 if (netReply->error() != QNetworkReply::NoError) { 0101 addError(reply, Reply::NetworkError, netReply->errorString()); 0102 return; 0103 } 0104 OpenTripPlannerParser p(backendId()); 0105 addResult(reply, p.parseGeocodeResult(QJsonDocument::fromJson(data).array())); 0106 }); 0107 0108 return true; 0109 } 0110 0111 return false; 0112 } 0113 0114 bool OpenTripPlannerRestBackend::queryStopover(const StopoverRequest &req, StopoverReply *reply, QNetworkAccessManager *nam) const 0115 { 0116 QUrlQuery query; 0117 query.addQueryItem(QStringLiteral("date"), QString::number(req.dateTime().toSecsSinceEpoch())); 0118 query.addQueryItem(QStringLiteral("numberOfDepartures"), QStringLiteral("12")); 0119 query.addQueryItem(QStringLiteral("omitNonPickups"), req.mode() == StopoverRequest::QueryDeparture ? QStringLiteral("true") : QStringLiteral("false")); 0120 0121 QUrl url(m_endpoint + QLatin1String("index/stops/") + req.stop().identifier(backendId()) + QLatin1String("/stoptimes")); 0122 url.setQuery(query); 0123 0124 QNetworkRequest netReq(url); 0125 logRequest(req, netReq); 0126 auto netReply = nam->get(netReq); 0127 netReply->setParent(reply); 0128 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, req, reply] { 0129 const auto data = netReply->readAll(); 0130 logReply(reply, netReply, data); 0131 0132 if (netReply->error() != QNetworkReply::NoError) { 0133 addError(reply, Reply::NetworkError, netReply->errorString()); 0134 return; 0135 } 0136 OpenTripPlannerParser p(backendId()); 0137 auto res = p.parseDeparturesArray(QJsonDocument::fromJson(data).array()); 0138 for (auto &dep : res) { 0139 dep.setStopPoint(req.stop()); 0140 } 0141 addResult(reply, this, std::move(res)); 0142 }); 0143 0144 return true; 0145 } 0146 0147 bool OpenTripPlannerRestBackend::queryJourney(const JourneyRequest &req, JourneyReply *reply, QNetworkAccessManager *nam) const 0148 { 0149 if ((req.modes() & JourneySection::PublicTransport) == 0) { 0150 return false; 0151 } 0152 0153 QUrlQuery query; 0154 query.addQueryItem(QStringLiteral("fromPlace"), locationToQuery(req.from())); 0155 query.addQueryItem(QStringLiteral("toPlace"), locationToQuery(req.to())); 0156 auto dt = req.dateTime(); 0157 if (timeZone().isValid()) { 0158 dt = dt.toTimeZone(timeZone()); 0159 dt.setTimeZone(QTimeZone::LocalTime); // pretend we have local time, so toString() isn't adding a UTC offset 0160 } 0161 query.addQueryItem(QStringLiteral("date"), dt.date().toString(Qt::ISODate)); 0162 query.addQueryItem(QStringLiteral("time"), dt.time().toString(Qt::ISODate)); 0163 query.addQueryItem(QStringLiteral("arriveBy"), req.dateTimeMode() == JourneyRequest::Arrival ? QStringLiteral("true") : QStringLiteral("false")); 0164 0165 QUrl url(m_endpoint + QLatin1String("plan")); 0166 url.setQuery(query); 0167 0168 QNetworkRequest netReq(url); 0169 logRequest(req, netReq); 0170 auto netReply = nam->get(netReq); 0171 netReply->setParent(reply); 0172 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, reply] { 0173 const auto data = netReply->readAll(); 0174 logReply(reply, netReply, data); 0175 0176 if (netReply->error() != QNetworkReply::NoError) { 0177 addError(reply, Reply::NetworkError, netReply->errorString()); 0178 return; 0179 } 0180 OpenTripPlannerParser p(backendId()); 0181 addResult(reply, this, p.parseJourneys(QJsonDocument::fromJson(data).object())); 0182 }); 0183 0184 return true; 0185 } 0186 0187 QString OpenTripPlannerRestBackend::locationToQuery(const Location &loc) const 0188 { 0189 if (loc.hasCoordinate()) { 0190 return QString::number(loc.latitude()) + QLatin1Char(',') + QString::number(loc.longitude()); 0191 } 0192 return loc.identifier(backendId()); 0193 }