File indexing completed on 2024-12-08 07:19:10
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "journeyrequest.h" 0008 #include "requestcontext_p.h" 0009 #include "datatypes/datatypes_p.h" 0010 #include "datatypes/json_p.h" 0011 #include "datatypes/locationutil_p.h" 0012 0013 #include <KPublicTransport/Location> 0014 0015 #include <QCryptographicHash> 0016 #include <QDateTime> 0017 #include <QDebug> 0018 #include <QMetaEnum> 0019 #include <QSharedData> 0020 0021 #include <unordered_map> 0022 0023 using namespace KPublicTransport; 0024 0025 enum { JourneyCacheTimeResolution = 60 }; // in seconds 0026 0027 namespace KPublicTransport { 0028 class JourneyRequestPrivate : public QSharedData { 0029 public: 0030 Location from; 0031 Location to; 0032 QDateTime dateTime; 0033 std::vector<RequestContext> contexts; 0034 QStringList backendIds; 0035 JourneyRequest::DateTimeMode dateTimeMode = JourneyRequest::Departure; 0036 JourneySection::Modes modes = JourneySection::PublicTransport | JourneySection::RentedVehicle; 0037 int maximumResults = 12; 0038 bool downloadAssets = false; 0039 bool includeIntermediateStops = true; 0040 bool includePaths = false; 0041 0042 std::vector<IndividualTransport> accessModes = { {IndividualTransport::Walk} }; 0043 std::vector<IndividualTransport> egressModes = { {IndividualTransport::Walk} }; 0044 std::vector<Line::Mode> lineModes; 0045 }; 0046 } 0047 0048 KPUBLICTRANSPORT_MAKE_GADGET(JourneyRequest) 0049 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, Location, from, setFrom) 0050 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, Location, to, setTo) 0051 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, JourneyRequest::DateTimeMode, dateTimeMode, setDateTimeMode) 0052 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, bool, downloadAssets, setDownloadAssets) 0053 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, JourneySection::Modes, modes, setModes) 0054 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, int, maximumResults, setMaximumResults) 0055 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, bool, includeIntermediateStops, setIncludeIntermediateStops) 0056 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneyRequest, bool, includePaths, setIncludePaths) 0057 0058 JourneyRequest::JourneyRequest(const Location &from, const Location &to) 0059 : d(new JourneyRequestPrivate) 0060 { 0061 d->from = from; 0062 d->to = to; 0063 } 0064 0065 bool JourneyRequest::isValid() const 0066 { 0067 return !d->to.isEmpty() && !d->from.isEmpty(); 0068 } 0069 0070 QDateTime JourneyRequest::dateTime() const 0071 { 0072 if (!d->dateTime.isValid()) { 0073 d->dateTime = QDateTime::currentDateTime(); 0074 } 0075 return d->dateTime; 0076 } 0077 0078 void JourneyRequest::setDateTime(const QDateTime& dt) 0079 { 0080 d.detach(); 0081 d->dateTime = dt; 0082 } 0083 0084 void JourneyRequest::setDepartureTime(const QDateTime &dt) 0085 { 0086 d.detach(); 0087 d->dateTime = dt; 0088 d->dateTimeMode = Departure; 0089 } 0090 0091 void JourneyRequest::setArrivalTime(const QDateTime &dt) 0092 { 0093 d.detach(); 0094 d->dateTime = dt; 0095 d->dateTimeMode = Arrival; 0096 } 0097 0098 RequestContext JourneyRequest::context(const AbstractBackend *backend) const 0099 { 0100 const auto it = std::lower_bound(d->contexts.begin(), d->contexts.end(), backend); 0101 if (it != d->contexts.end() && (*it).backend == backend) { 0102 return *it; 0103 } 0104 0105 RequestContext context; 0106 context.backend = backend; 0107 return context; 0108 } 0109 0110 const std::vector<RequestContext>& JourneyRequest::contexts() const 0111 { 0112 return d->contexts; 0113 } 0114 0115 void JourneyRequest::setContext(const AbstractBackend *backend, RequestContext &&context) 0116 { 0117 d.detach(); 0118 const auto it = std::lower_bound(d->contexts.begin(), d->contexts.end(), backend); 0119 if (it != d->contexts.end() && (*it).backend == backend) { 0120 (*it) = std::move(context); 0121 } else { 0122 d->contexts.insert(it, std::move(context)); 0123 } 0124 } 0125 0126 void JourneyRequest::purgeLoops(const JourneyRequest &baseRequest) 0127 { 0128 RequestContext::purgeLoops(d->contexts, baseRequest.contexts()); 0129 } 0130 0131 QJsonObject JourneyRequest::toJson(const KPublicTransport::JourneyRequest &req) 0132 { 0133 auto obj = Json::toJson(req); 0134 obj.insert(QLatin1String("from"), Location::toJson(req.from())); 0135 obj.insert(QLatin1String("to"), Location::toJson(req.to())); 0136 obj.insert(QLatin1String("accessModes"), IndividualTransport::toJson(req.accessModes())); 0137 obj.insert(QLatin1String("egressModes"), IndividualTransport::toJson(req.egressModes())); 0138 return obj; 0139 } 0140 0141 QStringList JourneyRequest::backendIds() const 0142 { 0143 return d->backendIds; 0144 } 0145 0146 void JourneyRequest::setBackendIds(const QStringList &backendIds) 0147 { 0148 d.detach(); 0149 d->backendIds = backendIds; 0150 } 0151 0152 template <typename T> 0153 static QVariantList toVariantList(const std::vector<T> &v) 0154 { 0155 QVariantList l; 0156 l.reserve(v.size()); 0157 std::transform(v.begin(), v.end(), std::back_inserter(l), [](const T &value) { 0158 return QVariant::fromValue<T>(value); 0159 }); 0160 return l; 0161 } 0162 0163 const std::vector<IndividualTransport>& JourneyRequest::accessModes() const 0164 { 0165 return d->accessModes; 0166 } 0167 0168 QVariantList JourneyRequest::accessModesVariant() const 0169 { 0170 return toVariantList(d->accessModes); 0171 } 0172 0173 void JourneyRequest::setAccessModes(std::vector<IndividualTransport> &&accessModes) 0174 { 0175 d.detach(); 0176 d->accessModes = std::move(accessModes); 0177 } 0178 0179 void JourneyRequest::setAccessModesVariant(const QVariantList &accessModesVariant) 0180 { 0181 d.detach(); 0182 d->accessModes = IndividualTransport::fromVariant(accessModesVariant); 0183 } 0184 0185 const std::vector<IndividualTransport>& JourneyRequest::egressModes() const 0186 { 0187 return d->egressModes; 0188 } 0189 0190 QVariantList JourneyRequest::egressModesVariant() const 0191 { 0192 return toVariantList(d->egressModes); 0193 } 0194 0195 void JourneyRequest::setEgressModes(std::vector<IndividualTransport>&& egressModes) 0196 { 0197 d.detach(); 0198 d->egressModes = std::move(egressModes); 0199 } 0200 0201 void JourneyRequest::setEgressModesVariant(const QVariantList &egressModesVariant) 0202 { 0203 d.detach(); 0204 d->egressModes = IndividualTransport::fromVariant(egressModesVariant); 0205 } 0206 0207 const std::vector<Line::Mode>& JourneyRequest::lineModes() const 0208 { 0209 return d->lineModes; 0210 } 0211 0212 void JourneyRequest::setLineModes(std::vector<Line::Mode> &&lineModes) 0213 { 0214 d.detach(); 0215 d->lineModes = std::move(lineModes); 0216 std::sort(d->lineModes.begin(), d->lineModes.end()); 0217 d->lineModes.erase(std::unique(d->lineModes.begin(), d->lineModes.end()), d->lineModes.end()); 0218 } 0219 0220 QVariantList JourneyRequest::lineModesVariant() const 0221 { 0222 return toVariantList(d->lineModes); 0223 } 0224 0225 void JourneyRequest::setLineModesVariant(const QVariantList &lineModes) 0226 { 0227 auto l = std::move(d->lineModes); 0228 l.clear(); 0229 l.reserve(lineModes.size()); 0230 std::transform(lineModes.begin(), lineModes.end(), std::back_inserter(l), [](const auto &mode) { return static_cast<Line::Mode>(mode.toInt()); }); 0231 setLineModes(std::move(l)); 0232 } 0233 0234 QString JourneyRequest::cacheKey() const 0235 { 0236 QCryptographicHash hash(QCryptographicHash::Sha1); 0237 hash.addData(QByteArray::number(d->dateTime.toSecsSinceEpoch() / JourneyCacheTimeResolution)); 0238 hash.addData(LocationUtil::cacheKey(d->from).toUtf8()); 0239 hash.addData(LocationUtil::cacheKey(d->to).toUtf8()); 0240 hash.addData(QByteArrayView(d->dateTimeMode == JourneyRequest::Arrival ? "A" : "D", 1)); 0241 hash.addData(QMetaEnum::fromType<JourneySection::Mode>().valueToKeys(d->modes)); 0242 hash.addData(QByteArray::number(d->maximumResults)); 0243 hash.addData(d->includeIntermediateStops ? "I" : "-"); 0244 hash.addData(d->includePaths ? "P" : "-"); 0245 0246 hash.addData("ACCESS"); 0247 for (const auto &it : d->accessModes) { 0248 hash.addData(QMetaEnum::fromType<IndividualTransport::Mode>().valueToKey(it.mode())); 0249 hash.addData(QMetaEnum::fromType<IndividualTransport::Qualifier>().valueToKey(it.qualifier())); 0250 } 0251 0252 hash.addData("EGRESS"); 0253 for (const auto &it : d->accessModes) { 0254 hash.addData(QMetaEnum::fromType<IndividualTransport::Mode>().valueToKey(it.mode())); 0255 hash.addData(QMetaEnum::fromType<IndividualTransport::Qualifier>().valueToKey(it.qualifier())); 0256 } 0257 0258 hash.addData("MODES"); 0259 for (const auto &mode : d->lineModes) { 0260 hash.addData(QMetaEnum::fromType<Line::Mode>().valueToKey(mode)); 0261 } 0262 0263 return QString::fromUtf8(hash.result().toHex()); 0264 } 0265 0266 static bool hasTakeBikeMode(const std::vector<IndividualTransport> &modes) 0267 { 0268 return std::any_of(modes.begin(), modes.end(), [](const auto &it) { 0269 return it.mode() == IndividualTransport::Bike && it.qualifier() == IndividualTransport::None; 0270 }); 0271 } 0272 0273 void JourneyRequest::validate() const 0274 { 0275 // remove invalid access/egress modes 0276 d->accessModes.erase(std::remove_if(d->accessModes.begin(), d->accessModes.end(), [](const auto &it) { 0277 return (it.mode() == IndividualTransport::Car && it.qualifier() == IndividualTransport::None) 0278 || it.qualifier() == IndividualTransport::Pickup; 0279 }), d->accessModes.end()); 0280 d->egressModes.erase(std::remove_if(d->egressModes.begin(), d->egressModes.end(), [](const auto &it) { 0281 return (it.mode() == IndividualTransport::Car && it.qualifier() == IndividualTransport::None) 0282 || it.qualifier() == IndividualTransport::Dropoff 0283 || it.qualifier() == IndividualTransport::Park; 0284 }), d->egressModes.end()); 0285 0286 // taking a bike on public transport needs to be symmetric 0287 const auto hasTakeBikeAccess = hasTakeBikeMode(d->accessModes); 0288 const auto hasTakeBikeEgress = hasTakeBikeMode(d->egressModes); 0289 if (hasTakeBikeAccess && !hasTakeBikeEgress) { 0290 d->egressModes.push_back({ IndividualTransport::Bike }); 0291 } else if (!hasTakeBikeAccess && hasTakeBikeEgress) { 0292 d->egressModes.erase(std::remove_if(d->egressModes.begin(), d->egressModes.end(), [](const auto &it) { 0293 return it.mode() == IndividualTransport::Bike && it.qualifier() == IndividualTransport::None; 0294 }), d->egressModes.end()); 0295 } 0296 0297 // access/egress modes must not be empty 0298 if (d->accessModes.empty()) { 0299 d->accessModes = {{IndividualTransport::Walk}}; 0300 } 0301 if (d->egressModes.empty()) { 0302 d->egressModes = {{IndividualTransport::Walk}}; 0303 } 0304 } 0305 0306 #include "moc_journeyrequest.cpp"