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

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "opentripplannerparser.h"
0008 #include "gtfs/hvt.h"
0009 #include "geo/polylinedecoder_p.h"
0010 #include "ifopt/ifoptutil.h"
0011 
0012 #include <KPublicTransport/Journey>
0013 #include <KPublicTransport/Stopover>
0014 
0015 #include <QColor>
0016 #include <QDebug>
0017 #include <QJsonArray>
0018 #include <QJsonObject>
0019 #include <QTimeZone>
0020 
0021 #include <bitset>
0022 
0023 using namespace KPublicTransport;
0024 
0025 OpenTripPlannerParser::OpenTripPlannerParser(const QString &identifierType, const QString &ifoptPrefix)
0026     : m_identifierType(identifierType)
0027     , m_ifoptPrefix(ifoptPrefix)
0028 {
0029 }
0030 
0031 OpenTripPlannerParser::~OpenTripPlannerParser() = default;
0032 
0033 void OpenTripPlannerParser::setKnownRentalVehicleNetworks(const QHash<QString, RentalVehicleNetwork> &networks)
0034 {
0035     m_rentalVehicleNetworks = networks;
0036 }
0037 
0038 QVariant OpenTripPlannerParser::parseRentalVehicleData(const QJsonObject &obj) const
0039 {
0040     RentalVehicleNetwork network;
0041     // TODO id
0042     const auto networks = obj.value(QLatin1String("networks")).toArray();
0043     if (!networks.empty()) {
0044         const auto it = m_rentalVehicleNetworks.find(networks.at(0).toString());
0045         if (it != m_rentalVehicleNetworks.end()) {
0046             network = it.value();
0047         } else {
0048             network.setName(networks.at(0).toString());
0049         }
0050     }
0051 
0052     const auto capacity = obj.value(QLatin1String("spacesAvailable")).toInt(-1);
0053     const auto available = obj.value(QLatin1String("bikesAvailable")).toInt(-1);
0054     if (capacity == 0 && available == 1) { // heuristic for distinguishing floating vehicles and docks
0055         RentalVehicle v;
0056         v.setNetwork(network);
0057         v.setType(static_cast<RentalVehicle::VehicleType>(static_cast<int>(network.vehicleTypes())));
0058         return v;
0059     }
0060 
0061     RentalVehicleStation s;
0062     s.setNetwork(network);
0063     s.setCapacity(capacity);
0064     s.setAvailableVehicles(available);
0065     return s;
0066 }
0067 
0068 bool OpenTripPlannerParser::parseLocationFragment(const QJsonObject &obj, Location &loc) const
0069 {
0070     const auto parentObj = obj.value(QLatin1String("parentStation")).toObject();
0071     if (!parentObj.isEmpty()) {
0072         loc.setType(Location::Stop);
0073         return parseLocationFragment(parentObj, loc);
0074     }
0075 
0076     if (loc.name().isEmpty()) {
0077         loc.setName(obj.value(QLatin1String("name")).toString());
0078     }
0079     loc.setLatitude(obj.value(QLatin1String("lat")).toDouble(loc.latitude()));
0080     loc.setLongitude(obj.value(QLatin1String("lon")).toDouble(loc.longitude()));
0081 
0082     const auto tzId = obj.value(QLatin1String("timezone")).toString();
0083     if (!tzId.isEmpty()) {
0084         loc.setTimeZone(QTimeZone(tzId.toUtf8()));
0085     }
0086 
0087     const auto id = obj.value(QLatin1String("id")).toString();
0088     if (!id.isEmpty()) {
0089         loc.setIdentifier(m_identifierType, id);
0090     }
0091     if (!m_ifoptPrefix.isEmpty() && id.size() > m_ifoptPrefix.size() + 1 && id.startsWith(m_ifoptPrefix) && id.at(m_ifoptPrefix.size()) == QLatin1Char(':')) {
0092         const auto ifopt = QStringView(id).mid(m_ifoptPrefix.size() + 1);
0093         if (IfoptUtil::isValid(ifopt)) {
0094             loc.setIdentifier(IfoptUtil::identifierType(), ifopt.toString());
0095         }
0096     }
0097 
0098     const auto bss = obj.value(QLatin1String("bikeRentalStation")).toObject();
0099     if (!bss.isEmpty()) {
0100         loc.setData(parseRentalVehicleData(bss));
0101         loc.setType(loc.data().userType() == qMetaTypeId<RentalVehicle>() ? Location::RentedVehicle : Location::RentedVehicleStation);
0102         return loc.rentalVehicleStation().network().isValid() || loc.rentalVehicle().network().isValid();
0103     }
0104 
0105     const auto mode = obj.value(QLatin1String("vehicleMode")).toString();
0106     if (mode == QLatin1String("CARPOOL")) {
0107         loc.setType(Location::CarpoolPickupDropoff);
0108     } else if (!mode.isEmpty() && loc.type() == Location::Place) {
0109         loc.setType(Location::Stop);
0110     }
0111 
0112     return true;
0113 }
0114 
0115 Location OpenTripPlannerParser::parseLocation(const QJsonObject &obj) const
0116 {
0117     const auto stop = obj.value(QLatin1String("stop")).toObject();
0118     const auto bikeRental = obj.value(QLatin1String("bikeRentalStation")).toObject();
0119 
0120     Location loc;
0121     auto valid = parseLocationFragment(bikeRental, loc);
0122     if (!stop.empty()) {
0123         loc.setType(Location::Stop);
0124         valid &= parseLocationFragment(stop, loc);
0125     }
0126     valid &= parseLocationFragment(obj, loc);
0127     return valid ? loc : Location();
0128 }
0129 
0130 std::vector<Location> OpenTripPlannerParser::parseLocationsByCoordinate(const QJsonObject &obj) const
0131 {
0132     std::vector<Location> locs;
0133     const auto stopArray = obj.value(QLatin1String("stopsByRadius")).toObject().value(QLatin1String("edges")).toArray();
0134     locs.reserve(stopArray.size());
0135     for (const auto &stop : stopArray) {
0136         auto l = parseLocation(stop.toObject().value(QLatin1String("node")).toObject());
0137         if (!l.isEmpty()) {
0138             locs.push_back(std::move(l));
0139         }
0140     }
0141 
0142     // deduplicate elements, which we get due to searching for stops rather than stations
0143     std::stable_sort(locs.begin(), locs.end(), [this](const auto &lhs, const auto &rhs) {
0144         return lhs.identifier(m_identifierType) < rhs.identifier(m_identifierType);
0145     });
0146     locs.erase(std::unique(locs.begin(), locs.end(), [this](const auto &lhs, const auto &rhs) {
0147         return lhs.identifier(m_identifierType) == rhs.identifier(m_identifierType);
0148     }), locs.end());
0149 
0150     return locs;
0151 }
0152 
0153 std::vector<Location> OpenTripPlannerParser::parseLocationsByName(const QJsonObject &obj) const
0154 {
0155     std::vector<Location> locs;
0156     const auto stationArray = obj.value(QLatin1String("stations")).toArray();
0157     locs.reserve(stationArray.size());
0158     for (const auto &station : stationArray) {
0159         auto l = parseLocation(station.toObject());
0160         if (!l.isEmpty()) {
0161             locs.push_back(std::move(l));
0162         }
0163     }
0164     return locs;
0165 }
0166 
0167 std::vector<Location> OpenTripPlannerParser::parseLocationsArray(const QJsonArray &array) const
0168 {
0169     std::vector<Location> locs;
0170     locs.reserve(array.size());
0171     for (const auto &l : array) {
0172         locs.push_back(parseLocation(l.toObject()));
0173     }
0174     return locs;
0175 }
0176 
0177 std::vector<Location> OpenTripPlannerParser::parseGeocodeResult(const QJsonArray &array) const
0178 {
0179     std::vector<Location> locs;
0180     locs.reserve(array.size());
0181     for (const auto &v : array) {
0182         const auto obj = v.toObject();
0183         Location loc;
0184         loc.setLatitude(obj.value(QLatin1String("lat")).toDouble());
0185         loc.setLongitude(obj.value(QLatin1String("lng")).toDouble()); // sic!
0186         auto desc = obj.value(QLatin1String("description")).toString();
0187         if (desc.startsWith(QLatin1String("stop "))) {
0188             desc = desc.mid(5);
0189         }
0190         loc.setName(desc);
0191         loc.setIdentifier(m_identifierType, obj.value(QLatin1String("id")).toString());
0192         locs.push_back(loc);
0193     }
0194     return locs;
0195 }
0196 
0197 void OpenTripPlannerParser::parseAlerts(const QJsonArray& alertsArray) const
0198 {
0199     m_alerts.reserve(alertsArray.size());
0200     for (const auto &alertValue : alertsArray) {
0201         const auto alertObj = alertValue.toObject();
0202         const auto descsArray = alertObj.value(QLatin1String("alertDescriptionTextTranslations")).toArray();
0203         if (descsArray.empty()) {
0204             continue;
0205         }
0206 
0207         // find the best language
0208         const auto uiLangs = QLocale().uiLanguages();
0209         int minIdx = 0, minWeight = std::numeric_limits<int>::max();
0210         for (int i = 0; i < descsArray.size(); ++i) {
0211             const auto lang = descsArray.at(i).toObject().value(QLatin1String("language")).toString();
0212             for (int j = 0; j < uiLangs.size() && j < minWeight; ++j) {
0213                 if (uiLangs.at(j).startsWith(lang)) {
0214                     minIdx = i;
0215                     minWeight = j;
0216                     break;
0217                 }
0218             }
0219         }
0220 
0221         m_alerts.push_back(descsArray.at(minIdx).toObject().value(QLatin1String("text")).toString());
0222     }
0223 }
0224 
0225 static QColor parseColor(const QJsonValue &value)
0226 {
0227     if (value.isNull()) {
0228         return {};
0229     }
0230     return QColor(QLatin1Char('#') + value.toString());
0231 }
0232 
0233 Line OpenTripPlannerParser::parseLine(const QJsonObject &obj) const
0234 {
0235     parseAlerts(obj.value(QLatin1String("alerts")).toArray());
0236 
0237     Line line;
0238     line.setName(obj.value(QLatin1String("shortName")).toString());
0239     if (line.name().isEmpty()) {
0240         line.setName(obj.value(QLatin1String("longName")).toString());
0241     }
0242 
0243     const auto type = obj.value(QLatin1String("type"));
0244     if (type.isString()) {
0245         line.setMode(Gtfs::Hvt::typeToMode(type.toString()));
0246     } else if (type.isDouble()) {
0247         line.setMode(Gtfs::Hvt::typeToMode(type.toInt(-1)));
0248     } else {
0249         line.setMode(Gtfs::Hvt::typeToMode(obj.value(QLatin1String("transportMode")).toString()));
0250     }
0251 
0252     auto presentation = obj.value(QLatin1String("presentation")).toObject();
0253     if (presentation.isEmpty()) {
0254         presentation = obj;
0255     }
0256     line.setColor(parseColor(presentation.value(QLatin1String("color"))));
0257     line.setTextColor(parseColor(presentation.value(QLatin1String("textColor"))));
0258     return line;
0259 }
0260 
0261 Route OpenTripPlannerParser::parseRoute(const QJsonObject &obj) const
0262 {
0263     auto line = parseLine(obj.value(QLatin1String("route")).toObject());
0264     if (line.name().isEmpty()) {
0265         line.setName(obj.value(QLatin1String("tripShortName")).toString());
0266     }
0267 
0268     Route route;
0269     route.setLine(line);
0270     route.setDirection(obj.value(QLatin1String("tripHeadsign")).toString());
0271 
0272     return route;
0273 }
0274 
0275 Route OpenTripPlannerParser::parseInlineRoute(const QJsonObject &obj) const
0276 {
0277     Line line;
0278     line.setMode(Gtfs::Hvt::typeToMode(obj.value(QLatin1String("routeType")).toInt(-1)));
0279     line.setName(obj.value(QLatin1String("tripShortName")).toString());
0280     line.setColor(parseColor(obj.value(QLatin1String("routeColor"))));
0281     line.setTextColor(parseColor(obj.value(QLatin1String("routeTextColor"))));
0282 
0283     Route route;
0284     route.setDirection(obj.value(QLatin1String("headsign")).toString());
0285     route.setLine(line);
0286 
0287     return route;
0288 }
0289 
0290 Route OpenTripPlannerParser::detectAndParseRoute(const QJsonObject &obj) const
0291 {
0292     const auto trip = obj.value(QLatin1String("trip")).toObject();
0293     if (!trip.isEmpty()) {
0294         return parseRoute(trip);
0295     }
0296 
0297     const auto line = obj.value(QLatin1String("line")).toObject();
0298     if (!line.isEmpty()) {
0299         Route route;
0300         route.setLine(parseLine(obj.value(QLatin1String("line")).toObject()));
0301         return route;
0302     }
0303 
0304     return parseInlineRoute(obj);
0305 }
0306 
0307 static QDateTime parseDepartureDateTime(uint64_t baseTime, const QJsonValue &value)
0308 {
0309     if (value.isDouble()) { // encoded as seconds offset to baseTime
0310         // UNIX timestamp of midnight in local timezone + UNIX timestamp of local time
0311         auto dt = QDateTime::fromSecsSinceEpoch(baseTime + value.toDouble());
0312         dt = dt.toTimeZone(QTimeZone::UTC);
0313         return dt;
0314     }
0315     return QDateTime::fromString(value.toString(), Qt::ISODate);
0316 }
0317 
0318 Stopover OpenTripPlannerParser::parseDeparture(const QJsonObject &obj) const
0319 {
0320     Stopover dep;
0321     const auto baseTime = obj.value(QLatin1String("serviceDay")).toDouble(); // ### 64bit
0322     dep.setScheduledArrivalTime(parseDepartureDateTime(baseTime, obj.value(QLatin1String("scheduledArrival"))));
0323     dep.setScheduledDepartureTime(parseDepartureDateTime(baseTime, obj.value(QLatin1String("scheduledDeparture"))));
0324     if (obj.value(QLatin1String("realtime")).toBool()) {
0325         dep.setExpectedArrivalTime(parseDepartureDateTime(baseTime, obj.value(QLatin1String("realtimeArrival"))));
0326         dep.setExpectedDepartureTime(parseDepartureDateTime(baseTime, obj.value(QLatin1String("realtimeDeparture"))));
0327     }
0328     dep.setScheduledPlatform(obj.value(QLatin1String("stop")).toObject().value(QLatin1String("platformCode")).toString());
0329     dep.setRoute(detectAndParseRoute(obj));
0330     dep.addNotes(m_alerts);
0331     m_alerts.clear();
0332 
0333     return dep;
0334 }
0335 
0336 void OpenTripPlannerParser::parseDeparturesForStop(const QJsonObject &obj, std::vector<Stopover> &deps) const
0337 {
0338     const auto loc = parseLocation(obj);
0339     const auto stopTimes = obj.value(QLatin1String("stoptimes")).toArray();
0340     for (const auto &stopTime : stopTimes) {
0341         auto dep = parseDeparture(stopTime.toObject());
0342         dep.setStopPoint(loc);
0343         deps.push_back(dep);
0344     }
0345 }
0346 
0347 std::vector<Stopover> OpenTripPlannerParser::parseDepartures(const QJsonObject &obj) const
0348 {
0349     std::vector<Stopover> deps;
0350 
0351     const auto depsArray = obj.value(QLatin1String("nearest")).toObject().value(QLatin1String("edges")).toArray();
0352     for (const auto &depsV : depsArray) {
0353         parseDeparturesForStop(depsV.toObject().value(QLatin1String("node")).toObject().value(QLatin1String("place")).toObject(), deps);
0354     }
0355 
0356     return deps;
0357 }
0358 
0359 std::vector<Stopover> OpenTripPlannerParser::parseDeparturesArray(const QJsonArray &array) const
0360 {
0361     std::vector<Stopover> deps;
0362     for (const auto &pattern : array) {
0363         const auto obj = pattern.toObject();
0364         const auto times = obj.value(QLatin1String("times")).toArray();
0365         for (const auto &time : times) {
0366             deps.push_back(parseDeparture(time.toObject()));
0367         }
0368     }
0369     return deps;
0370 }
0371 
0372 
0373 static QDateTime parseJourneyDateTime(const QJsonValue &val)
0374 {
0375     if (val.isDouble()) {
0376         // timestamp, as UTC value
0377         auto dt = QDateTime::fromMSecsSinceEpoch(val.toDouble()); // ### sic! double to get 64 bit precision...
0378         dt = dt.toTimeZone(QTimeZone::UTC);
0379         return dt;
0380     }
0381     if (val.isString()) {
0382         return QDateTime::fromString(val.toString(), Qt::ISODate);
0383     }
0384     return {};
0385 }
0386 
0387 static RentalVehicle::VehicleType vehicleTypeFromTypes(RentalVehicle::VehicleTypes types, RentalVehicle::VehicleType fallback = RentalVehicle::Unknown)
0388 {
0389     if (std::bitset<sizeof(types)>(types).count() == 1) {
0390         return static_cast<RentalVehicle::VehicleType>(static_cast<int>(types));
0391     }
0392     return fallback;
0393 }
0394 
0395 JourneySection OpenTripPlannerParser::parseJourneySection(const QJsonObject &obj) const
0396 {
0397     JourneySection section;
0398     section.setScheduledDepartureTime(parseJourneyDateTime(obj.value(QLatin1String("startTime"))));
0399     section.setScheduledArrivalTime(parseJourneyDateTime(obj.value(QLatin1String("endTime"))));
0400     if (obj.value(QLatin1String("realTime")).toBool()) {
0401         section.setExpectedDepartureTime(parseJourneyDateTime(obj.value(QLatin1String("expectedStartTime"))));
0402         if (!section.expectedDepartureTime().isValid()) {
0403             section.setExpectedDepartureTime(section.scheduledDepartureTime().addSecs(obj.value(QLatin1String("departureDelay")).toInt()));
0404         }
0405         section.setExpectedArrivalTime(parseJourneyDateTime(obj.value(QLatin1String("expectedEndTime"))));
0406         if (!section.expectedArrivalTime().isValid()) {
0407             section.setExpectedArrivalTime(section.scheduledArrivalTime().addSecs(obj.value(QLatin1String("arrivalDelay")).toInt()));
0408         }
0409     }
0410 
0411     const auto fromObj = obj.value(QLatin1String("from")).toObject();
0412     const auto fromStop = fromObj.value(QLatin1String("stop")).toObject();
0413     const auto from = parseLocation(fromObj);
0414     section.setFrom(from);
0415     section.setScheduledDeparturePlatform(fromStop.value(QLatin1String("platformCode")).toString());
0416 
0417     const auto toObj = obj.value(QLatin1String("to")).toObject();
0418     const auto toStop = toObj.value(QLatin1String("stop")).toObject();
0419     const auto to = parseLocation(toObj);
0420     section.setTo(to);
0421     section.setScheduledDeparturePlatform(toStop.value(QLatin1String("platformCode")).toString());
0422 
0423     section.setDistance(obj.value(QLatin1String("distance")).toDouble());
0424 
0425     if (obj.value(QLatin1String("transitLeg")).toBool()) {
0426         section.setMode(JourneySection::PublicTransport);
0427         section.setRoute(detectAndParseRoute(obj));
0428     } else {
0429         const auto mode = obj.value(QLatin1String("mode")).toString();
0430         const auto isRented = obj.value(QLatin1String("rentedBike")).toBool();
0431 
0432         if (mode.compare(QLatin1String("WALK"), Qt::CaseInsensitive) == 0 || mode.compare(QLatin1String("FOOT"), Qt::CaseInsensitive) == 0) {
0433             section.setMode(JourneySection::Walking);
0434         } else if (mode.compare(QLatin1String("BICYCLE"), Qt::CaseInsensitive) == 0) {
0435             RentalVehicle v;
0436             if (from.rentalVehicleStation().network().isValid()) {
0437                 v.setNetwork(from.rentalVehicleStation().network());
0438             } else if (from.type() == Location::RentedVehicle) {
0439                 v = from.data().value<RentalVehicle>();
0440             } else if (to.rentalVehicleStation().network().isValid()) {
0441                 v.setNetwork(to.rentalVehicleStation().network());
0442             }
0443             v.setType(vehicleTypeFromTypes(v.network().vehicleTypes(), RentalVehicle::Bicycle));
0444             if (v.network().isValid() || isRented) {
0445                 section.setMode(JourneySection::RentedVehicle);
0446                 section.setRentalVehicle(v);
0447             } else {
0448                 section.setMode(JourneySection::IndividualTransport);
0449                 section.setIndividualTransport({ IndividualTransport::Bike });
0450             }
0451         } else if (mode.compare(QLatin1String("CAR"), Qt::CaseInsensitive) == 0) {
0452             section.setMode(JourneySection::IndividualTransport);
0453             section.setIndividualTransport({ IndividualTransport::Car });
0454         } else {
0455             qWarning() << "Unhandled OTP mode:" << mode;
0456         }
0457     }
0458 
0459     section.addNotes(m_alerts);
0460     m_alerts.clear();
0461 
0462     const auto stopsA = obj.value(QLatin1String("intermediateStops")).toArray();
0463     std::vector<Stopover> stops;
0464     stops.reserve(stopsA.size());
0465     for (const auto &stopV : stopsA) {
0466         const auto stopObj = stopV.toObject();
0467         const auto loc = parseLocation(stopObj);
0468 
0469         Stopover stop;
0470         stop.setStopPoint(loc);
0471         stop.setScheduledPlatform(stopObj.value(QLatin1String("platformCode")).toString());
0472         stop.setScheduledArrivalTime(parseJourneyDateTime(stopObj.value(QLatin1String("scheduledArrivalTime"))));
0473         stop.setScheduledDepartureTime(parseJourneyDateTime(stopObj.value(QLatin1String("scheduledDepartureTime"))));
0474         stop.setExpectedArrivalTime(parseJourneyDateTime(stopObj.value(QLatin1String("expectedArrivalTime"))));
0475         stop.setExpectedDepartureTime(parseJourneyDateTime(stopObj.value(QLatin1String("expectedDepartureTime"))));
0476 
0477         stops.push_back(stop);
0478     }
0479     section.setIntermediateStops(std::move(stops));
0480 
0481     const auto geometryObj = obj.value(QLatin1String("legGeometry")).toObject();
0482     QPolygonF poly;
0483     if (!geometryObj.empty()) {
0484         poly.reserve(geometryObj.value(QLatin1String("length")).toInt());
0485         const auto points = geometryObj.value(QLatin1String("points")).toString().toUtf8();
0486         PolylineDecoder<2> decoder(points.constData());
0487         decoder.readPolygon(poly);
0488     }
0489 
0490     const auto stepsArray = obj.value(QLatin1String("steps")).toArray();
0491     if (!stepsArray.empty()) {
0492         std::vector<PathSection> pathSections;
0493         pathSections.reserve(stepsArray.size());
0494         PathSection prevPathSec;
0495         int prevPolyIdx = 0;
0496         bool isFirstSection = true;
0497         for (const auto &stepsV : stepsArray) {
0498             const auto stepsObj = stepsV.toObject();
0499             PathSection pathSec;
0500             pathSec.setDescription(stepsObj.value(QLatin1String("legStepText")).toString());
0501 
0502             if (!isFirstSection) {
0503                 const QPointF coord(stepsObj.value(QLatin1String("lon")).toDouble(), stepsObj.value(QLatin1String("lat")).toDouble());
0504                 const auto it = std::min_element(poly.begin() + prevPolyIdx, poly.end(), [coord](QPointF lhs, QPointF rhs) {
0505                     return Location::distance(lhs.y(), lhs.x(), coord.y(), coord.x()) < Location::distance(rhs.y(), rhs.x(), coord.y(), coord.x());
0506                 });
0507                 int polyIdx = std::distance(poly.begin(), it);
0508 
0509                 QPolygonF subPoly;
0510                 subPoly.reserve(polyIdx - prevPolyIdx + 1);
0511                 std::copy(poly.begin() + prevPolyIdx, poly.begin() + polyIdx + 1, std::back_inserter(subPoly));
0512                 prevPathSec.setPath(std::move(subPoly));
0513                 prevPolyIdx = polyIdx;
0514                 pathSections.push_back(std::move(prevPathSec));
0515             } else {
0516                 isFirstSection = false;
0517             }
0518             prevPathSec = pathSec;
0519         }
0520 
0521         QPolygonF subPoly;
0522         subPoly.reserve(prevPolyIdx - poly.size() + 1);
0523         std::copy(poly.begin() + prevPolyIdx, poly.end(), std::back_inserter(subPoly));
0524         prevPathSec.setPath(std::move(subPoly));
0525         pathSections.push_back(std::move(prevPathSec));
0526 
0527         Path path;
0528         path.setSections(std::move(pathSections));
0529         section.setPath(std::move(path));
0530     } else if (!poly.isEmpty()) {
0531         PathSection pathSec;
0532         pathSec.setPath(std::move(poly));
0533         Path path;
0534         path.setSections({std::move(pathSec)});
0535         section.setPath(std::move(path));
0536     }
0537 
0538     return section;
0539 }
0540 
0541 Journey OpenTripPlannerParser::parseJourney(const QJsonObject &obj) const
0542 {
0543     std::vector<JourneySection> sections;
0544     const auto sectionsArray = obj.value(QLatin1String("legs")).toArray();
0545     for (const auto &sectionObj : sectionsArray) {
0546         sections.push_back(parseJourneySection(sectionObj.toObject()));
0547     }
0548 
0549     Journey journey;
0550     journey.setSections(std::move(sections));
0551     return journey;
0552 }
0553 
0554 std::vector<Journey> OpenTripPlannerParser::parseJourneys(const QJsonObject& obj)
0555 {
0556     std::vector<Journey> journeys;
0557 
0558     const auto plan = obj.value(QLatin1String("plan")).toObject();
0559     const auto journeysArray = plan.value(QLatin1String("itineraries")).toArray();
0560     journeys.reserve(journeysArray.size());
0561     for (const auto &journeyObj : journeysArray) {
0562         journeys.push_back(parseJourney(journeyObj.toObject()));
0563     }
0564 
0565     m_nextJourneyContext.dateTime = parseJourneyDateTime(plan.value(QLatin1String("nextDateTime")));
0566     m_prevJourneyContext.dateTime = parseJourneyDateTime(plan.value(QLatin1String("prevDateTime")));
0567     m_nextJourneyContext.searchWindow = m_prevJourneyContext.searchWindow = plan.value(QLatin1String("searchWindowUsed")).toInt();
0568 
0569     return journeys;
0570 }