File indexing completed on 2024-05-12 04:42:43

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "journey.h"
0008 #include "journeyutil_p.h"
0009 #include "json_p.h"
0010 #include "datatypes_p.h"
0011 #include "loadutil_p.h"
0012 #include "mergeutil_p.h"
0013 #include "notesutil_p.h"
0014 #include "platformutils_p.h"
0015 #include "rentalvehicle.h"
0016 #include "rentalvehicleutil_p.h"
0017 #include "stopover.h"
0018 
0019 #include <QDebug>
0020 #include <QVariant>
0021 
0022 using namespace KPublicTransport;
0023 
0024 namespace KPublicTransport {
0025 
0026 class JourneySectionPrivate : public QSharedData
0027 {
0028 public:
0029     JourneySection::Mode mode = JourneySection::Invalid;
0030     QDateTime scheduledDepartureTime;
0031     QDateTime expectedDepartureTime;
0032     QDateTime scheduledArrivalTime;
0033     QDateTime expectedArrivalTime;
0034     Location from;
0035     Location to;
0036     Route route;
0037     QString scheduledDeparturePlatform;
0038     QString expectedDeparturePlatform;
0039     QString scheduledArrivalPlatform;
0040     QString expectedArrivalPlatform;
0041     int distance = 0;
0042     Disruption::Effect disruptionEffect = Disruption::NormalService;
0043     QStringList notes;
0044     std::vector<Stopover> intermediateStops;
0045     int co2Emission = -1;
0046     std::vector<LoadInfo> loadInformation;
0047     RentalVehicle rentalVehicle;
0048     Path path;
0049     Vehicle departureVehicleLayout;
0050     Platform departurePlatformLayout;
0051     Vehicle arrivalVehicleLayout;
0052     Platform arrivalPlatformLayout;
0053     IndividualTransport individualTransport;
0054 };
0055 
0056 class JourneyPrivate : public QSharedData
0057 {
0058 public:
0059     std::vector<JourneySection> sections;
0060 };
0061 
0062 }
0063 
0064 KPUBLICTRANSPORT_MAKE_GADGET(JourneySection)
0065 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, JourneySection::Mode, mode, setMode)
0066 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, QDateTime, scheduledDepartureTime, setScheduledDepartureTime)
0067 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, QDateTime, expectedDepartureTime, setExpectedDepartureTime)
0068 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, QDateTime, scheduledArrivalTime, setScheduledArrivalTime)
0069 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, QDateTime, expectedArrivalTime, setExpectedArrivalTime)
0070 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Location, from, setFrom)
0071 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Location, to, setTo)
0072 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Route, route, setRoute)
0073 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Disruption::Effect, disruptionEffect, setDisruptionEffect)
0074 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, QStringList, notes, setNotes)
0075 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, RentalVehicle, rentalVehicle, setRentalVehicle)
0076 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Path, path, setPath)
0077 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Vehicle, departureVehicleLayout, setDepartureVehicleLayout)
0078 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Platform, departurePlatformLayout, setDeparturePlatformLayout)
0079 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Vehicle, arrivalVehicleLayout, setArrivalVehicleLayout)
0080 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, Platform, arrivalPlatformLayout, setArrivalPlatformLayout)
0081 KPUBLICTRANSPORT_MAKE_PROPERTY(JourneySection, KPublicTransport::IndividualTransport, individualTransport, setIndividualTransport)
0082 
0083 bool JourneySection::hasExpectedDepartureTime() const
0084 {
0085     return d->expectedDepartureTime.isValid();
0086 }
0087 
0088 int JourneySection::departureDelay() const
0089 {
0090     if (hasExpectedDepartureTime()) {
0091         return d->scheduledDepartureTime.secsTo(d->expectedDepartureTime) / 60;
0092     }
0093     return 0;
0094 }
0095 
0096 bool JourneySection::hasExpectedArrivalTime() const
0097 {
0098     return d->expectedArrivalTime.isValid();
0099 }
0100 
0101 int JourneySection::arrivalDelay() const
0102 {
0103     if (hasExpectedArrivalTime()) {
0104         return d->scheduledArrivalTime.secsTo(d->expectedArrivalTime) / 60;
0105     }
0106     return 0;
0107 }
0108 
0109 int JourneySection::duration() const
0110 {
0111     return d->scheduledDepartureTime.secsTo(d->scheduledArrivalTime);
0112 }
0113 
0114 int JourneySection::distance() const
0115 {
0116     if (d->mode == JourneySection::Waiting) {
0117         return 0;
0118     }
0119 
0120     int dist = 0;
0121     if (d->from.hasCoordinate() && d->to.hasCoordinate()) {
0122         float startLat = d->from.latitude();
0123         float startLon = d->from.longitude();
0124 
0125         for (const auto &stop : d->intermediateStops) {
0126             if (!stop.stopPoint().hasCoordinate()) {
0127                 continue;
0128             }
0129             dist += Location::distance(startLat, startLon, stop.stopPoint().latitude(), stop.stopPoint().longitude());
0130             startLat = stop.stopPoint().latitude();
0131             startLon = stop.stopPoint().longitude();
0132         }
0133 
0134         dist += Location::distance(startLat, startLon, d->to.latitude(), d->to.longitude());
0135     }
0136     dist = std::max(dist, d->path.distance());
0137     return std::max(dist, d->distance);
0138 }
0139 
0140 void JourneySection::setDistance(int value)
0141 {
0142     d.detach();
0143     d->distance = value;
0144 }
0145 
0146 QString JourneySection::scheduledDeparturePlatform() const
0147 {
0148     return d->scheduledDeparturePlatform;
0149 }
0150 
0151 void JourneySection::setScheduledDeparturePlatform(const QString &platform)
0152 {
0153     d.detach();
0154     d->scheduledDeparturePlatform = PlatformUtils::normalizePlatform(platform);
0155 }
0156 
0157 QString JourneySection::expectedDeparturePlatform() const
0158 {
0159     return d->expectedDeparturePlatform;
0160 }
0161 
0162 void JourneySection::setExpectedDeparturePlatform(const QString &platform)
0163 {
0164     d.detach();
0165     d->expectedDeparturePlatform = PlatformUtils::normalizePlatform(platform);
0166 }
0167 
0168 bool JourneySection::hasExpectedDeparturePlatform() const
0169 {
0170     return !d->expectedDeparturePlatform.isEmpty();
0171 }
0172 
0173 bool JourneySection::departurePlatformChanged() const
0174 {
0175     return PlatformUtils::platformChanged(d->scheduledDeparturePlatform, d->expectedDeparturePlatform);
0176 }
0177 
0178 QString JourneySection::scheduledArrivalPlatform() const
0179 {
0180     return d->scheduledArrivalPlatform;
0181 }
0182 
0183 void JourneySection::setScheduledArrivalPlatform(const QString &platform)
0184 {
0185     d.detach();
0186     d->scheduledArrivalPlatform = PlatformUtils::normalizePlatform(platform);
0187 }
0188 
0189 QString JourneySection::expectedArrivalPlatform() const
0190 {
0191     return d->expectedArrivalPlatform;
0192 }
0193 
0194 void JourneySection::setExpectedArrivalPlatform(const QString &platform)
0195 {
0196     d.detach();
0197     d->expectedArrivalPlatform = PlatformUtils::normalizePlatform(platform);
0198 }
0199 
0200 bool JourneySection::hasExpectedArrivalPlatform() const
0201 {
0202     return !d->expectedArrivalPlatform.isEmpty();
0203 }
0204 
0205 bool JourneySection::arrivalPlatformChanged() const
0206 {
0207     return PlatformUtils::platformChanged(d->scheduledArrivalPlatform, d->expectedArrivalPlatform);
0208 }
0209 
0210 void JourneySection::addNote(const QString &note)
0211 {
0212     const auto n = NotesUtil::normalizeNote(note);
0213     const auto idx = NotesUtil::needsAdding(d->notes, n);
0214     if (idx >= 0) {
0215         d.detach();
0216         NotesUtil::performAdd(d->notes, n, idx);
0217     }
0218 }
0219 
0220 void JourneySection::addNotes(const QStringList &notes)
0221 {
0222     for (const auto &n : notes) {
0223         addNote(n);
0224     }
0225 }
0226 
0227 const std::vector<Stopover>& JourneySection::intermediateStops() const
0228 {
0229     return d->intermediateStops;
0230 }
0231 
0232 std::vector<Stopover>&& JourneySection::takeIntermediateStops()
0233 {
0234     d.detach();
0235     return std::move(d->intermediateStops);
0236 }
0237 
0238 void JourneySection::setIntermediateStops(std::vector<Stopover> &&stops)
0239 {
0240     d.detach();
0241     d->intermediateStops = std::move(stops);
0242 }
0243 
0244 QVariantList JourneySection::intermediateStopsVariant() const
0245 {
0246     QVariantList l;
0247     l.reserve(d->intermediateStops.size());
0248     std::transform(d->intermediateStops.begin(), d->intermediateStops.end(), std::back_inserter(l), [](const auto &stop) { return QVariant::fromValue(stop); });
0249     return l;
0250 }
0251 
0252 Stopover JourneySection::departure() const
0253 {
0254     Stopover dep;
0255     dep.setStopPoint(from());
0256     dep.setRoute(route());
0257     dep.setScheduledDepartureTime(scheduledDepartureTime());
0258     dep.setExpectedDepartureTime(expectedDepartureTime());
0259     dep.setScheduledPlatform(scheduledDeparturePlatform());
0260     dep.setExpectedPlatform(expectedDeparturePlatform());
0261     dep.addNotes(notes());
0262     dep.setDisruptionEffect(disruptionEffect());
0263     dep.setVehicleLayout(departureVehicleLayout());
0264     dep.setPlatformLayout(departurePlatformLayout());
0265     return dep;
0266 }
0267 
0268 void JourneySection::setDeparture(const Stopover &departure)
0269 {
0270     setFrom(departure.stopPoint());
0271     setScheduledDepartureTime(departure.scheduledDepartureTime());
0272     setExpectedDepartureTime(departure.expectedDepartureTime());
0273     setScheduledDeparturePlatform(departure.scheduledPlatform());
0274     setExpectedDeparturePlatform(departure.expectedPlatform());
0275     setDeparturePlatformLayout(departure.platformLayout());
0276     setDepartureVehicleLayout(departure.vehicleLayout());
0277 }
0278 
0279 Stopover JourneySection::arrival() const
0280 {
0281     Stopover arr;
0282     arr.setStopPoint(to());
0283     arr.setRoute(route());
0284     arr.setScheduledArrivalTime(scheduledArrivalTime());
0285     arr.setExpectedArrivalTime(expectedArrivalTime());
0286     arr.setScheduledPlatform(scheduledArrivalPlatform());
0287     arr.setExpectedPlatform(expectedArrivalPlatform());
0288     arr.setDisruptionEffect(disruptionEffect());
0289     arr.setVehicleLayout(arrivalVehicleLayout());
0290     arr.setPlatformLayout(arrivalPlatformLayout());
0291     return arr;
0292 }
0293 
0294 void JourneySection::setArrival(const Stopover &arrival)
0295 {
0296     setTo(arrival.stopPoint());
0297     setScheduledArrivalTime(arrival.scheduledArrivalTime());
0298     setExpectedArrivalTime(arrival.expectedArrivalTime());
0299     setScheduledArrivalPlatform(arrival.scheduledPlatform());
0300     setExpectedArrivalPlatform(arrival.expectedPlatform());
0301     setArrivalPlatformLayout(arrival.platformLayout());
0302     setArrivalVehicleLayout(arrival.vehicleLayout());
0303 }
0304 
0305 int JourneySection::co2Emission() const
0306 {
0307     // TODO handle rental vehicles and ride sharing in here!
0308     if (d->co2Emission >= 0) {
0309         return d->co2Emission;
0310     }
0311 
0312     struct {
0313         Line::Mode mode;
0314         int gramPerKm;
0315     } static const emissionForModeMap[] = {
0316         { Line::Air, 285 },
0317         { Line::Boat, 245 },
0318         { Line::Bus, 68 },
0319         { Line::BusRapidTransit, 68 },
0320         { Line::Coach, 68 },
0321         { Line::Ferry, 245 },
0322         { Line::LocalTrain, 14 },
0323         { Line::LongDistanceTrain, 14 },
0324         { Line::Metro, 11 },
0325         { Line::RapidTransit, 11 },
0326         { Line::Taxi, 158 },
0327         { Line::Train, 14 },
0328         { Line::Tramway, 11 },
0329     };
0330 
0331     const auto mode = route().line().mode();
0332     for (const auto &map : emissionForModeMap) {
0333         if (map.mode == mode) {
0334             return (map.gramPerKm * distance()) / 1000;
0335         }
0336     }
0337     return -1;
0338 }
0339 
0340 void JourneySection::setCo2Emission(int value)
0341 {
0342     d.detach();
0343     d->co2Emission = value;
0344 }
0345 
0346 const std::vector<LoadInfo>& JourneySection::loadInformation() const
0347 {
0348     return d->loadInformation;
0349 }
0350 
0351 std::vector<LoadInfo>&& JourneySection::takeLoadInformation()
0352 {
0353     d.detach();
0354     return std::move(d->loadInformation);
0355 }
0356 
0357 void JourneySection::setLoadInformation(std::vector<LoadInfo> &&loadInfo)
0358 {
0359     d.detach();
0360     d->loadInformation = std::move(loadInfo);
0361 }
0362 
0363 QVariantList JourneySection::loadInformationVariant() const
0364 {
0365     QVariantList l;
0366     l.reserve(d->loadInformation.size());
0367     std::transform(d->loadInformation.begin(), d->loadInformation.end(), std::back_inserter(l), [](const auto &load) { return QVariant::fromValue(load); });
0368     return l;
0369 }
0370 
0371 void JourneySection::applyMetaData(bool download)
0372 {
0373     if (!from().hasCoordinate() || mode() != JourneySection::PublicTransport) {
0374         return;
0375     }
0376     auto line = d->route.line();
0377     line.applyMetaData(from(), download);
0378     d->route.setLine(line);
0379 
0380     // propagate to intermediate stops
0381     for (auto &stop : d->intermediateStops) {
0382         stop.setRoute(d->route);
0383     }
0384 }
0385 
0386 bool JourneySection::isSame(const JourneySection &lhs, const JourneySection &rhs)
0387 {
0388     if (lhs.d->mode != rhs.d->mode) {
0389         return false;
0390     }
0391 
0392     if (lhs.d->mode == JourneySection::IndividualTransport && lhs.d->individualTransport != rhs.d->individualTransport) {
0393         return false;
0394     }
0395 
0396     // we have N criteria to compare here, with 3 possible results:
0397     // - equal
0398     // - similar-ish, unknwon, or at least not conflicting
0399     // - conflicting
0400     // A single conflict results in a negative result, at least N - 1 equal comparisons lead to
0401     // in a positive result.
0402     enum { Equal = 1, Compatible = 0, Conflict = -1000 };
0403     int result = 0;
0404 
0405     const auto depTimeDist = MergeUtil::distance(lhs.d->scheduledDepartureTime, rhs.d->scheduledDepartureTime);
0406     result += depTimeDist < 60 ? Equal : depTimeDist <= 60 ? Compatible : Conflict;
0407     const auto arrTimeDist = MergeUtil::distance(lhs.d->scheduledArrivalTime, rhs.d->scheduledArrivalTime);
0408     result += arrTimeDist < 60 ? Equal : depTimeDist <= 60 ? Compatible : Conflict;
0409 
0410     const auto sameFrom = Location::isSame(lhs.d->from, rhs.d->from);
0411     const auto fromDist = Location::distance(lhs.from(), rhs.from());
0412     result += sameFrom ? Equal : fromDist < 200 ? Compatible : Conflict;
0413 
0414     const auto sameTo = Location::isSame(lhs.d->to, rhs.d->to);
0415     const auto toDist = Location::distance(lhs.to(), rhs.to());
0416     result += sameTo ? Equal : toDist < 200 ? Compatible : Conflict;
0417 
0418     const auto sameRoute = Route::isSame(lhs.d->route, rhs.d->route);
0419     const auto sameDir = Location::isSameName(lhs.d->route.direction(), rhs.d->route.direction());
0420     const auto sameLine = Line::isSame(lhs.d->route.line(), rhs.d->route.line());
0421     result += sameRoute ? Equal : (sameDir || sameLine) ? Compatible : Conflict;
0422 
0423     if (!lhs.scheduledDeparturePlatform().isEmpty() && !rhs.scheduledDeparturePlatform().isEmpty()) {
0424         result += lhs.scheduledDeparturePlatform() == rhs.scheduledDeparturePlatform() ? Equal : Conflict;
0425     }
0426 
0427     return result >= 4;
0428 }
0429 
0430 JourneySection JourneySection::merge(const JourneySection &lhs, const JourneySection &rhs)
0431 {
0432     using namespace MergeUtil;
0433     auto res = lhs;
0434     res.setScheduledDepartureTime(mergeDateTimeEqual(lhs.scheduledDepartureTime(), rhs.scheduledDepartureTime()));
0435     res.setExpectedDepartureTime(mergeDateTimeMax(lhs.expectedDepartureTime(), rhs.expectedDepartureTime()));
0436     res.setScheduledArrivalTime(mergeDateTimeMax(lhs.scheduledArrivalTime(), rhs.scheduledArrivalTime()));
0437     res.setExpectedArrivalTime(mergeDateTimeMax(lhs.expectedArrivalTime(), rhs.expectedArrivalTime()));
0438 
0439     if (res.expectedDeparturePlatform().isEmpty()) {
0440         res.setExpectedDeparturePlatform(rhs.expectedDeparturePlatform());
0441     }
0442     if (res.expectedArrivalPlatform().isEmpty()) {
0443         res.setExpectedArrivalPlatform(rhs.expectedArrivalPlatform());
0444     }
0445     res.setFrom(Location::merge(lhs.from(), rhs.from()));
0446     res.setTo(Location::merge(lhs.to(), rhs.to()));
0447     res.setRoute(Route::merge(lhs.route(), rhs.route()));
0448 
0449     res.setScheduledDeparturePlatform(mergeString(lhs.scheduledDeparturePlatform(), rhs.scheduledDeparturePlatform()));
0450     res.setScheduledArrivalPlatform(mergeString(lhs.scheduledArrivalPlatform(), rhs.scheduledArrivalPlatform()));
0451 
0452     res.setDisruptionEffect(std::max(lhs.disruptionEffect(), rhs.disruptionEffect()));
0453     res.setNotes(NotesUtil::mergeNotes(lhs.notes(), rhs.notes()));
0454     res.setDistance(std::max(lhs.distance(), rhs.distance()));
0455 
0456     if (lhs.intermediateStops().size() == rhs.intermediateStops().size()) {
0457         auto stops = res.takeIntermediateStops();
0458         for (uint i = 0; i < stops.size(); ++i) {
0459             stops[i] = Stopover::merge(stops[i], rhs.intermediateStops()[i]);
0460             stops[i].setRoute(res.route());
0461         }
0462         res.setIntermediateStops(std::move(stops));
0463     }
0464 
0465     res.d->co2Emission = std::max(lhs.d->co2Emission, rhs.d->co2Emission);
0466     res.d->loadInformation = LoadUtil::merge(lhs.d->loadInformation, rhs.d->loadInformation);
0467     res.d->rentalVehicle = RentalVehicleUtil::merge(lhs.d->rentalVehicle, rhs.d->rentalVehicle);
0468 
0469     res.d->path = lhs.d->path.sections().size() < rhs.d->path.sections().size() ? rhs.d->path : lhs.d->path;
0470 
0471     res.d->departureVehicleLayout = Vehicle::merge(lhs.d->departureVehicleLayout, rhs.d->departureVehicleLayout);
0472     res.d->departurePlatformLayout = Platform::merge(lhs.d->departurePlatformLayout, rhs.d->departurePlatformLayout);
0473     res.d->arrivalVehicleLayout = Vehicle::merge(lhs.d->arrivalVehicleLayout, rhs.d->arrivalVehicleLayout);
0474     res.d->arrivalPlatformLayout = Platform::merge(lhs.d->arrivalPlatformLayout, rhs.d->arrivalPlatformLayout);
0475 
0476     return res;
0477 }
0478 
0479 QJsonObject JourneySection::toJson(const JourneySection &section)
0480 {
0481     auto obj = Json::toJson(section);
0482     if (section.mode() != Waiting) {
0483         const auto fromObj = Location::toJson(section.from());
0484         if (!fromObj.empty()) {
0485             obj.insert(QLatin1String("from"), fromObj);
0486         }
0487         const auto toObj = Location::toJson(section.to());
0488         if (!toObj.empty()) {
0489             obj.insert(QLatin1String("to"), toObj);
0490         }
0491     }
0492     if (section.mode() == PublicTransport) {
0493         const auto routeObj = Route::toJson(section.route());
0494         if (!routeObj.empty()) {
0495             obj.insert(QLatin1String("route"), routeObj);
0496         }
0497         if (!section.intermediateStops().empty()) {
0498             obj.insert(QLatin1String("intermediateStops"), Stopover::toJson(section.intermediateStops()));
0499         }
0500         if (!section.loadInformation().empty()) {
0501             obj.insert(QLatin1String("load"), LoadInfo::toJson(section.loadInformation()));
0502         }
0503     }
0504     if (section.d->co2Emission < 0) {
0505         obj.remove(QLatin1String("co2Emission"));
0506     }
0507     if (section.rentalVehicle().type() != RentalVehicle::Unknown) {
0508         obj.insert(QLatin1String("rentalVehicle"), RentalVehicle::toJson(section.rentalVehicle()));
0509     }
0510 
0511     if (!section.path().isEmpty()) {
0512         obj.insert(QLatin1String("path"), Path::toJson(section.path()));
0513     }
0514 
0515     if (!section.departureVehicleLayout().isEmpty()) {
0516         obj.insert(QLatin1String("departureVehicleLayout"), Vehicle::toJson(section.departureVehicleLayout()));
0517     }
0518     if (!section.departurePlatformLayout().isEmpty()) {
0519         obj.insert(QLatin1String("departurePlatformLayout"), Platform::toJson(section.departurePlatformLayout()));
0520     }
0521     if (!section.arrivalVehicleLayout().isEmpty()) {
0522         obj.insert(QLatin1String("arrivalVehicleLayout"), Vehicle::toJson(section.arrivalVehicleLayout()));
0523     }
0524     if (!section.arrivalPlatformLayout().isEmpty()) {
0525         obj.insert(QLatin1String("arrivalPlatformLayout"), Platform::toJson(section.arrivalPlatformLayout()));
0526     }
0527 
0528     if (section.mode() == JourneySection::IndividualTransport) {
0529         obj.insert(QLatin1String("individualTransport"), IndividualTransport::toJson(section.individualTransport()));
0530     }
0531 
0532     if (obj.size() <= 3) { // only the disruption and mode enums and distance, ie. this is an empty object
0533         return {};
0534     }
0535     return obj;
0536 }
0537 
0538 QJsonArray JourneySection::toJson(const std::vector<JourneySection> &sections)
0539 {
0540     return Json::toJson(sections);
0541 }
0542 
0543 JourneySection JourneySection::fromJson(const QJsonObject &obj)
0544 {
0545     auto section = Json::fromJson<JourneySection>(obj);
0546     section.setFrom(Location::fromJson(obj.value(QLatin1String("from")).toObject()));
0547     section.setTo(Location::fromJson(obj.value(QLatin1String("to")).toObject()));
0548     section.setRoute(Route::fromJson(obj.value(QLatin1String("route")).toObject()));
0549     section.setIntermediateStops(Stopover::fromJson(obj.value(QLatin1String("intermediateStops")).toArray()));
0550     section.setLoadInformation(LoadInfo::fromJson(obj.value(QLatin1String("load")).toArray()));
0551     section.setRentalVehicle(RentalVehicle::fromJson(obj.value(QLatin1String("rentalVehicle")).toObject()));
0552     section.setPath(Path::fromJson(obj.value(QLatin1String("path")).toObject()));
0553     section.setDepartureVehicleLayout(Vehicle::fromJson(obj.value(QLatin1String("departureVehicleLayout")).toObject()));
0554     section.setDeparturePlatformLayout(Platform::fromJson(obj.value(QLatin1String("departurePlatformLayout")).toObject()));
0555     section.setArrivalVehicleLayout(Vehicle::fromJson(obj.value(QLatin1String("arrivalVehicleLayout")).toObject()));
0556     section.setArrivalPlatformLayout(Platform::fromJson(obj.value(QLatin1String("arrivalPlatformLayout")).toObject()));
0557     section.setIndividualTransport(IndividualTransport::fromJson(obj.value(QLatin1String("individualTransport")).toObject()));
0558     section.applyMetaData(false);
0559     return section;
0560 }
0561 
0562 std::vector<JourneySection> JourneySection::fromJson(const QJsonArray &array)
0563 {
0564     return Json::fromJson<JourneySection>(array);
0565 }
0566 
0567 
0568 KPUBLICTRANSPORT_MAKE_GADGET(Journey)
0569 
0570 const std::vector<JourneySection>& Journey::sections() const
0571 {
0572     return d->sections;
0573 }
0574 
0575 std::vector<JourneySection>&& Journey::takeSections()
0576 {
0577     d.detach();
0578     return std::move(d->sections);
0579 }
0580 
0581 void Journey::setSections(std::vector<JourneySection> &&sections)
0582 {
0583     d.detach();
0584     d->sections = std::move(sections);
0585 }
0586 
0587 QVariantList Journey::sectionsVariant() const
0588 {
0589     QVariantList l;
0590     l.reserve(d->sections.size());
0591     std::transform(d->sections.begin(), d->sections.end(), std::back_inserter(l), [](const auto &sec) { return QVariant::fromValue(sec); });
0592     return l;
0593 }
0594 
0595 QDateTime Journey::scheduledDepartureTime() const
0596 {
0597     if (!d->sections.empty()) {
0598         return d->sections.front().scheduledDepartureTime();
0599     }
0600     return {};
0601 }
0602 
0603 bool Journey::hasExpectedDepartureTime() const
0604 {
0605     return d->sections.empty() ? false : d->sections.front().hasExpectedDepartureTime();
0606 }
0607 
0608 QDateTime Journey::expectedDepartureTime() const
0609 {
0610     return d->sections.empty() ? QDateTime() : d->sections.front().expectedDepartureTime();
0611 }
0612 
0613 int Journey::departureDelay() const
0614 {
0615     return d->sections.empty() ? 0 : d->sections.front().departureDelay();
0616 }
0617 
0618 QDateTime Journey::scheduledArrivalTime() const
0619 {
0620     if (!d->sections.empty()) {
0621         return d->sections.back().scheduledArrivalTime();
0622     }
0623     return {};
0624 }
0625 
0626 bool Journey::hasExpectedArrivalTime() const
0627 {
0628     return d->sections.empty() ? false : d->sections.back().hasExpectedArrivalTime();
0629 }
0630 
0631 QDateTime Journey::expectedArrivalTime() const
0632 {
0633     return d->sections.empty() ? QDateTime() : d->sections.back().expectedArrivalTime();
0634 }
0635 
0636 int Journey::arrivalDelay() const
0637 {
0638     return d->sections.empty() ? 0 : d->sections.back().arrivalDelay();
0639 }
0640 
0641 int Journey::duration() const
0642 {
0643     return scheduledDepartureTime().secsTo(scheduledArrivalTime());
0644 }
0645 
0646 int Journey::numberOfChanges() const
0647 {
0648     return std::max(0, static_cast<int>(std::count_if(d->sections.begin(), d->sections.end(), [](const auto &section) { return section.mode() == JourneySection::PublicTransport; }) - 1));
0649 }
0650 
0651 Disruption::Effect Journey::disruptionEffect() const
0652 {
0653     Disruption::Effect effect = Disruption::NormalService;
0654     for (const auto &sec : d->sections) {
0655         effect = std::max(effect, sec.disruptionEffect());
0656     }
0657     return effect;
0658 }
0659 
0660 void Journey::applyMetaData(bool download)
0661 {
0662     for (auto &sec : d->sections) {
0663         sec.applyMetaData(download);
0664     }
0665 }
0666 
0667 static bool isTransportSection(JourneySection::Mode mode)
0668 {
0669     return mode == JourneySection::PublicTransport
0670         || mode == JourneySection::RentedVehicle
0671         || mode == JourneySection::IndividualTransport;
0672 }
0673 
0674 bool Journey::isSame(const Journey &lhs, const Journey &rhs)
0675 {
0676     auto lIt = lhs.sections().begin();
0677     auto rIt = rhs.sections().begin();
0678 
0679     while (lIt != lhs.sections().end() || rIt != rhs.sections().end()) {
0680         // ignore non-transport sections
0681         if (lIt != lhs.sections().end() && !isTransportSection((*lIt).mode())) {
0682             ++lIt;
0683             continue;
0684         }
0685         if (rIt != rhs.sections().end() && !isTransportSection((*rIt).mode())) {
0686             ++rIt;
0687             continue;
0688         }
0689 
0690         if (lIt == lhs.sections().end() || rIt == rhs.sections().end()) {
0691             return false;
0692         }
0693 
0694         if (!JourneySection::isSame(*lIt, *rIt)) {
0695             return false;
0696         }
0697 
0698         ++lIt;
0699         ++rIt;
0700     }
0701 
0702     Q_ASSERT(lIt == lhs.sections().end() && rIt == rhs.sections().end());
0703     return true;
0704 }
0705 
0706 Journey Journey::merge(const Journey &lhs, const Journey &rhs)
0707 {
0708     std::vector<JourneySection> sections;
0709     sections.reserve(lhs.sections().size() + rhs.sections().size());
0710     std::copy(lhs.sections().begin(), lhs.sections().end(), std::back_inserter(sections));
0711     std::copy(rhs.sections().begin(), rhs.sections().end(), std::back_inserter(sections));
0712     std::sort(sections.begin(), sections.end(), [](const auto &lSec, const auto &rSec) {
0713         if (MergeUtil::distance(lSec.scheduledDepartureTime(), rSec.scheduledDepartureTime()) == 0) {
0714             return lSec.mode() < rSec.mode();
0715         }
0716         return MergeUtil::isBefore(lSec.scheduledDepartureTime(), rSec.scheduledDepartureTime());
0717     });
0718 
0719     for (auto it = sections.begin(); it != sections.end(); ++it) {
0720         const auto nextIt = it + 1;
0721         if (nextIt == sections.end()) {
0722             break;
0723         }
0724 
0725         if (JourneySection::isSame(*it, *nextIt) || ((*it).mode() == (*nextIt).mode() && (*it).mode() != JourneySection::PublicTransport)) {
0726             *it = JourneySection::merge(*it, *nextIt);
0727             sections.erase(nextIt);
0728         }
0729     }
0730 
0731     Journey res;
0732     res.setSections(std::move(sections));
0733     return res;
0734 }
0735 
0736 QJsonObject Journey::toJson(const Journey &journey)
0737 {
0738     QJsonObject obj;
0739     obj.insert(QLatin1String("sections"), JourneySection::toJson(journey.sections()));
0740     return obj;
0741 }
0742 
0743 QJsonArray Journey::toJson(const std::vector<Journey> &journeys)
0744 {
0745     return Json::toJson(journeys);
0746 }
0747 
0748 Journey Journey::fromJson(const QJsonObject &obj)
0749 {
0750     Journey j;
0751     j.setSections(JourneySection::fromJson(obj.value(QLatin1String("sections")).toArray()));
0752     return j;
0753 }
0754 
0755 std::vector<Journey> Journey::fromJson(const QJsonArray &array)
0756 {
0757     return Json::fromJson<Journey>(array);
0758 }
0759 
0760 #include "moc_journey.cpp"