File indexing completed on 2024-05-12 04:42:38
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "navitiabackend.h" 0008 #include "logging.h" 0009 #include "navitiaparser.h" 0010 #include "navitiaphysicalmode.h" 0011 #include "cache.h" 0012 0013 #include <KPublicTransport/Journey> 0014 #include <KPublicTransport/JourneyReply> 0015 #include <KPublicTransport/JourneyRequest> 0016 #include <KPublicTransport/Location> 0017 #include <KPublicTransport/LocationReply> 0018 #include <KPublicTransport/LocationRequest> 0019 #include <KPublicTransport/Stopover> 0020 #include <KPublicTransport/StopoverReply> 0021 #include <KPublicTransport/StopoverRequest> 0022 0023 #include <QNetworkAccessManager> 0024 #include <QNetworkReply> 0025 #include <QNetworkRequest> 0026 #include <QUrl> 0027 #include <QUrlQuery> 0028 0029 using namespace KPublicTransport; 0030 0031 NavitiaBackend::NavitiaBackend() = default; 0032 0033 AbstractBackend::Capabilities NavitiaBackend::capabilities() const 0034 { 0035 return Secure | CanQueryNextJourney | CanQueryPreviousJourney | CanQueryArrivals; // https is hardcoded below 0036 } 0037 0038 bool NavitiaBackend::needsLocationQuery(const Location &loc, AbstractBackend::QueryType type) const 0039 { 0040 Q_UNUSED(type); 0041 return !loc.hasCoordinate(); 0042 } 0043 0044 struct { 0045 IndividualTransport::Mode mode; 0046 IndividualTransport::Qualifier qualifier; 0047 const char *name; 0048 } static constexpr const mode_table[] = { 0049 { IndividualTransport::Walk, IndividualTransport::None, "walking" }, 0050 { IndividualTransport::Bike, IndividualTransport::None, "bike" }, 0051 { IndividualTransport::Bike, IndividualTransport::Park, "bike" }, // ### ? 0052 { IndividualTransport::Bike, IndividualTransport::Rent, "bss" }, 0053 { IndividualTransport::Car, IndividualTransport::None, "car" }, 0054 { IndividualTransport::Car, IndividualTransport::Park, "car" }, 0055 { IndividualTransport::Car, IndividualTransport::Dropoff, "car_no_park" }, 0056 // TODO: ridesharing, taxi 0057 }; 0058 0059 static void addModeArgument(const QString &arg, const std::vector<IndividualTransport> &modes, QUrlQuery &query) 0060 { 0061 for (const auto &mode : modes) { 0062 const auto it = std::find_if(std::begin(mode_table), std::end(mode_table), [mode](const auto &entry) { 0063 return entry.mode == mode.mode() && entry.qualifier == mode.qualifier(); 0064 }); 0065 if (it != std::end(mode_table)) { 0066 query.addQueryItem(arg, QLatin1String((*it).name)); 0067 } 0068 } 0069 } 0070 0071 bool NavitiaBackend::queryJourney(const JourneyRequest &req, JourneyReply *reply, QNetworkAccessManager *nam) const 0072 { 0073 if ((req.modes() & JourneySection::PublicTransport) == 0) { 0074 return false; 0075 } 0076 if (!req.from().hasCoordinate() || !req.to().hasCoordinate()) { 0077 return false; 0078 } 0079 0080 QUrl url = requestContextData(req).value<QUrl>(); 0081 if (!url.isValid()) { 0082 url.setHost(m_endpoint); 0083 url.setPath(QStringLiteral("/v1") + 0084 (m_coverage.isEmpty() ? QString() : (QStringLiteral("/coverage/") + m_coverage)) + 0085 QStringLiteral("/journeys")); 0086 0087 QUrlQuery query; 0088 query.addQueryItem(QStringLiteral("from"), QString::number(req.from().longitude()) + QLatin1Char(';') + QString::number(req.from().latitude())); 0089 query.addQueryItem(QStringLiteral("to"), QString::number(req.to().longitude()) + QLatin1Char(';') + QString::number(req.to().latitude())); 0090 if (req.dateTime().isValid()) { 0091 query.addQueryItem(QStringLiteral("datetime"), req.dateTime().toString(QStringLiteral("yyyyMMddThhmmss"))); 0092 query.addQueryItem(QStringLiteral("datetime_represents"), req.dateTimeMode() == JourneyRequest::Arrival ? QStringLiteral("arrival") : QStringLiteral("departure")); 0093 } 0094 query.addQueryItem(QStringLiteral("count"), QString::number(std::max(1, req.maximumResults()))); 0095 NavitiaPhysicalMode::lineModesToQuery(req.lineModes(), query); 0096 addModeArgument(QStringLiteral("first_section_mode[]"), req.accessModes(), query); 0097 addModeArgument(QStringLiteral("last_section_mode[]"), req.egressModes(), query); 0098 0099 // TODO: disable reply parts we don't care about 0100 query.addQueryItem(QStringLiteral("disable_geojson"), req.includePaths() ? QStringLiteral("false") : QStringLiteral("true")); 0101 query.addQueryItem(QStringLiteral("depth"), QStringLiteral("0")); // ### also has no effect? 0102 query.addQueryItem(QStringLiteral("add_poi_infos[]"), QStringLiteral("bss_stands")); 0103 url.setQuery(query); 0104 } 0105 url.setScheme(QStringLiteral("https")); // force https in any case 0106 0107 QNetworkRequest netReq(url); 0108 netReq.setRawHeader("Authorization", m_auth.toUtf8()); 0109 logRequest(req, netReq); 0110 auto netReply = nam->get(netReq); 0111 netReply->setParent(reply); 0112 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, reply, netReply] { 0113 const auto data = netReply->readAll(); 0114 logReply(reply, netReply, data); 0115 0116 switch (netReply->error()) { 0117 case QNetworkReply::NoError: 0118 { 0119 NavitiaParser parser; 0120 addResult(reply, this, parser.parseJourneys(data)); 0121 if (parser.nextLink.isValid()) { 0122 setNextRequestContext(reply, parser.nextLink); 0123 } 0124 if (parser.prevLink.isValid()) { 0125 setPreviousRequestContext(reply, parser.prevLink); 0126 } 0127 addAttributions(reply, std::move(parser.attributions)); 0128 break; 0129 } 0130 case QNetworkReply::ContentNotFoundError: 0131 addError(reply, Reply::NotFoundError, NavitiaParser::parseErrorMessage(data)); 0132 break; 0133 default: 0134 addError(reply, Reply::NetworkError, netReply->errorString()); 0135 break; 0136 } 0137 netReply->deleteLater(); 0138 }); 0139 0140 return true; 0141 } 0142 0143 bool NavitiaBackend::queryStopover(const StopoverRequest &req, StopoverReply *reply, QNetworkAccessManager *nam) const 0144 { 0145 if (!req.stop().hasCoordinate()) { 0146 return false; 0147 } 0148 const auto loc = req.stop(); 0149 0150 QUrl url; 0151 url.setScheme(QStringLiteral("https")); 0152 url.setHost(m_endpoint); 0153 url.setPath( 0154 QStringLiteral("/v1/coverage/") + 0155 (m_coverage.isEmpty() ? QString::number(loc.longitude()) + QLatin1Char(';') + QString::number(loc.latitude()) : m_coverage) + 0156 QStringLiteral("/coord/") + QString::number(loc.longitude()) + QLatin1Char(';') + QString::number(loc.latitude()) + 0157 (req.mode() == StopoverRequest::QueryDeparture ? QStringLiteral("/departures") : QStringLiteral("/arrivals")) 0158 ); 0159 0160 QUrlQuery query; 0161 query.addQueryItem(QStringLiteral("from_datetime"), req.dateTime().toString(QStringLiteral("yyyyMMddThhmmss"))); 0162 query.addQueryItem(QStringLiteral("disable_geojson"), QStringLiteral("true")); 0163 query.addQueryItem(QStringLiteral("depth"), QStringLiteral("0")); 0164 query.addQueryItem(QStringLiteral("count"), QString::number(std::max(1, req.maximumResults()))); 0165 NavitiaPhysicalMode::lineModesToQuery(req.lineModes(), query); 0166 url.setQuery(query); 0167 0168 QNetworkRequest netReq(url); 0169 netReq.setRawHeader("Authorization", m_auth.toUtf8()); 0170 logRequest(req, netReq); 0171 auto netReply = nam->get(netReq); 0172 netReply->setParent(reply); 0173 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, reply] { 0174 const auto data = netReply->readAll(); 0175 logReply(reply, netReply, data); 0176 0177 switch (netReply->error()) { 0178 case QNetworkReply::NoError: 0179 { 0180 NavitiaParser p; 0181 addResult(reply, this, p.parseDepartures(data)); 0182 if (p.nextLink.isValid()) { 0183 setNextRequestContext(reply, p.nextLink); 0184 } 0185 if (p.prevLink.isValid()) { 0186 setPreviousRequestContext(reply, p.prevLink); 0187 } 0188 addAttributions(reply, std::move(p.attributions)); 0189 break; 0190 } 0191 case QNetworkReply::ContentNotFoundError: 0192 addError(reply, Reply::NotFoundError, NavitiaParser::parseErrorMessage(data)); 0193 break; 0194 default: 0195 addError(reply, Reply::NetworkError, netReply->errorString()); 0196 break; 0197 } 0198 netReply->deleteLater(); 0199 }); 0200 0201 return true; 0202 } 0203 0204 bool NavitiaBackend::queryLocation(const LocationRequest &req, LocationReply *reply, QNetworkAccessManager *nam) const 0205 { 0206 if ((req.types() & (Location::Stop | Location::RentedVehicleStation)) == 0) { 0207 return false; 0208 } 0209 0210 QUrl url; 0211 url.setScheme(QStringLiteral("https")); 0212 url.setHost(m_endpoint); 0213 0214 QUrlQuery query; 0215 query.addQueryItem(QStringLiteral("disable_geojson"), QStringLiteral("true")); 0216 query.addQueryItem(QStringLiteral("depth"), QStringLiteral("1")); // 1 so administrative region elements are included 0217 query.addQueryItem(QStringLiteral("count"), QString::number(std::max(1, req.maximumResults()))); 0218 if (req.types() & Location::Stop) { 0219 query.addQueryItem(QStringLiteral("type[]"), QStringLiteral("stop_point")); 0220 } 0221 if (req.types() & Location::RentedVehicleStation) { 0222 query.addQueryItem(QStringLiteral("type[]"), QStringLiteral("poi")); 0223 query.addQueryItem(QStringLiteral("add_poi_infos[]"), QStringLiteral("bss_stands")); 0224 // filter is exclusive, so we cannot use this when also looking for other objects 0225 if ((req.types() & ~(Location::RentedVehicleStation | Location::RentedVehicle)) == 0) { 0226 query.addQueryItem(QStringLiteral("filter"), QStringLiteral("poi_type.id=poi_type:amenity:bicycle_rental")); 0227 } 0228 } 0229 0230 if (req.hasCoordinate()) { 0231 url.setPath(QStringLiteral("/v1") + 0232 (m_coverage.isEmpty() ? QString() : (QStringLiteral("/coverage/") + m_coverage)) + 0233 QStringLiteral("/coord/") + QString::number(req.longitude()) + QLatin1Char(';') + QString::number(req.latitude()) + 0234 QStringLiteral("/places_nearby")); 0235 query.addQueryItem(QStringLiteral("distance"), QString::number(std::max(1, req.maximumDistance()))); 0236 } else if (!req.name().isEmpty()) { 0237 url.setPath(QStringLiteral("/v1") + 0238 (m_coverage.isEmpty() ? QString() : (QStringLiteral("/coverage/") + m_coverage)) + 0239 QStringLiteral("/places")); 0240 query.addQueryItem(QStringLiteral("q"), req.name()); 0241 } else { 0242 return false; 0243 } 0244 0245 url.setQuery(query); 0246 QNetworkRequest netReq(url); 0247 netReq.setRawHeader("Authorization", m_auth.toUtf8()); 0248 logRequest(req, netReq); 0249 auto netReply = nam->get(netReq); 0250 netReply->setParent(reply); 0251 QObject::connect(netReply, &QNetworkReply::finished, reply, [this, netReply, reply] { 0252 const auto data = netReply->readAll(); 0253 logReply(reply, netReply, data); 0254 0255 switch (netReply->error()) { 0256 case QNetworkReply::NoError: 0257 { 0258 std::vector<Location> res; 0259 NavitiaParser p; 0260 if (reply->request().hasCoordinate()) { 0261 res = p.parsePlacesNearby(data); 0262 } else { 0263 res = p.parsePlaces(data); 0264 } 0265 Cache::addLocationCacheEntry(backendId(), reply->request().cacheKey(), res, p.attributions); 0266 addResult(reply, std::move(res)); 0267 addAttributions(reply, std::move(p.attributions)); 0268 break; 0269 } 0270 case QNetworkReply::ContentNotFoundError: 0271 addError(reply, Reply::NotFoundError, NavitiaParser::parseErrorMessage(data)); 0272 break; 0273 default: 0274 addError(reply, Reply::NetworkError, netReply->errorString()); 0275 break; 0276 } 0277 netReply->deleteLater(); 0278 }); 0279 0280 return true; 0281 }