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"