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 }