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

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "oebbvehiclelayoutparser.h"
0008 #include "uic/uicrailwaycoach.h"
0009 
0010 #include <QDateTime>
0011 #include <QDebug>
0012 #include <QJsonArray>
0013 #include <QJsonDocument>
0014 #include <QJsonObject>
0015 
0016 using namespace KPublicTransport;
0017 
0018 struct {
0019     const char *propName;
0020     VehicleSection::Feature feature;
0021     VehicleSection::Class coachClass;
0022 } static constexpr const vehicle_section_feature_map[] = {
0023     { "capacityBusinessClass", VehicleSection::NoFeatures, VehicleSection::FirstClass },
0024     { "capacityFirstClass", VehicleSection::NoFeatures, VehicleSection::FirstClass },
0025     { "capacitySecondClass", VehicleSection::NoFeatures, VehicleSection::SecondClass },
0026     { "capacityCouchette", VehicleSection::NoFeatures, VehicleSection::UnknownClass }, // TODO
0027     { "capacitySleeper", VehicleSection::NoFeatures, VehicleSection::UnknownClass }, // TODO
0028     { "capacityWheelChair", VehicleSection::WheelchairAccessible, VehicleSection::UnknownClass },
0029     { "capacityBicycle", VehicleSection::BikeStorage, VehicleSection::UnknownClass },
0030     { "isBicycleAllowed", VehicleSection::BikeStorage, VehicleSection::UnknownClass },
0031     { "isWheelChairAccessible", VehicleSection::WheelchairAccessible, VehicleSection::UnknownClass },
0032     { "hasWifi", VehicleSection::NoFeatures, VehicleSection::UnknownClass }, // TODO
0033     { "isInfoPoint", VehicleSection::NoFeatures, VehicleSection::UnknownClass }, // TODO
0034     { "isChildCinema", VehicleSection::ToddlerArea, VehicleSection::UnknownClass },
0035     { "isDining", VehicleSection::Restaurant, VehicleSection::UnknownClass },
0036     { "isQuietZone", VehicleSection::SilentArea, VehicleSection::UnknownClass },
0037 };
0038 
0039 struct {
0040     const char *modeName;
0041     Line::Mode mode;
0042 } static constexpr const line_mode_map[] = {
0043     { "RJ", Line::LongDistanceTrain },
0044     { "NJ", Line::LongDistanceTrain },
0045     { "RE", Line::LocalTrain },
0046     { "S",  Line::RapidTransit },
0047     { "IC", Line::LongDistanceTrain },
0048     { "EC", Line::LongDistanceTrain },
0049 };
0050 
0051 static Vehicle::Direction parseDirection(const QJsonObject &haltepunktObj)
0052 {
0053     const auto v = haltepunktObj.value(QLatin1String("departureTowardsFirstSector"));
0054     if (v.isBool()) {
0055         return v.toBool() ? Vehicle::Forward : Vehicle::Backward;
0056     }
0057     return Vehicle::UnknownDirection;
0058 }
0059 
0060 static QDateTime parseDateTime(const QDate &date, const QJsonValue &timeVal)
0061 {
0062     const auto timeObj = timeVal.toObject();
0063     QDateTime dt(date, {0, 0});
0064     dt = dt.addSecs(timeObj.value(QLatin1String("days")).toInt() * 24 * 3600);
0065     dt = dt.addSecs(timeObj.value(QLatin1String("hours")).toInt() * 3600);
0066     dt = dt.addSecs(timeObj.value(QLatin1String("minutes")).toInt() * 60 );
0067     return dt;
0068 }
0069 
0070 static QString parsePlatformName(const QJsonObject &platformObj, const QJsonObject &sectorsObj, const char *propName)
0071 {
0072     const auto nameVal = platformObj.value(QLatin1String(propName));
0073     if (nameVal.isUndefined()) {
0074         return {};
0075     }
0076     const auto name = QString::number(nameVal.toInt());
0077     if (name.isEmpty()) {
0078         return name;
0079     }
0080 
0081     const auto sectorName = sectorsObj.value(QLatin1String(propName)).toString();
0082     if (!sectorName.isEmpty()) {
0083         return name + QLatin1Char(' ') + sectorName;
0084     }
0085     return name;
0086 }
0087 
0088 bool OebbVehicleLayoutParser::parse(const QByteArray &data)
0089 {
0090     const auto obj = QJsonDocument::fromJson(data).object();
0091 
0092     // platform
0093     Platform platform;
0094     const auto platformObj = obj.value(QLatin1String("platform")).toObject();
0095     const auto platformNameVal = platformObj.value(QLatin1String("platform"));
0096     if (!platformNameVal.isUndefined()) {
0097         platform.setName(QString::number(platformNameVal.toInt()));
0098     }
0099     const auto sectorArray = platformObj.value(QLatin1String("sectors")).toArray();
0100     std::vector<PlatformSection> platformSections;
0101     platformSections.reserve(sectorArray.size());
0102     float prevSectorEnd = 0.0;
0103     for (const auto &sectorV : sectorArray) {
0104         const auto sectorObj = sectorV.toObject();
0105         PlatformSection section;
0106         section.setName(sectorObj.value(QLatin1String("name")).toString());
0107         section.setBegin(prevSectorEnd);
0108         section.setEnd(prevSectorEnd += sectorObj.value(QLatin1String("length")).toDouble());
0109         platformSections.push_back(section);
0110     }
0111     platform.setLength(std::max<float>(prevSectorEnd, platformObj.value(QLatin1String("length")).toDouble()));
0112     // TODO accesses lists relevant features like escalators/elevators on the platform
0113 
0114     // vehicle
0115     Vehicle vehicle;
0116     const auto trainOnPlatform = obj.value(QLatin1String("trainOnPlatform")).toObject();
0117     vehicle.setDirection(parseDirection(trainOnPlatform));
0118     const auto trainObj = obj.value(QLatin1String("train")).toObject();
0119     const auto wagonsA = trainObj.value(QLatin1String("wagons")).toArray();
0120     std::vector<VehicleSection> vehicleSections;
0121     vehicleSections.reserve(wagonsA.size());
0122     float prevVehicleEnd = trainOnPlatform.value(QLatin1String("position")).toDouble(); // TODO check how this looks like for backward departure
0123     for (const auto &wagonV : wagonsA) {
0124         const auto wagonObj = wagonV.toObject();
0125         VehicleSection section;
0126         const auto uicNum = wagonObj.value(QLatin1String("uicNumber")).toString();
0127         const auto uicCls = wagonObj.value(QLatin1String("kind")).toString();
0128         section.setType(UicRailwayCoach::type(uicNum, uicCls));
0129         section.setDeckCount(UicRailwayCoach::deckCount(uicNum, uicCls));
0130 
0131         if (section.type() == VehicleSection::Engine) {
0132             section.setConnectedSides(VehicleSection::NoSide);
0133         } else {
0134             const auto ranking = wagonObj.value(QLatin1String("ranking")).toInt();
0135             if (ranking > 0) {
0136                 section.setName(QString::number(ranking));
0137             }
0138             auto cls = UicRailwayCoach::coachClass(uicNum, uicCls);
0139             auto features = UicRailwayCoach::features(uicNum, uicCls);
0140             for (const auto &map : vehicle_section_feature_map) {
0141                 const auto val = wagonObj.value(QLatin1String(map.propName));
0142                 if ((val.isBool() && val.toBool()) || (val.isDouble() && val.toInt() > 0)) {
0143                     cls |= map.coachClass;
0144                     features |= map.feature;
0145                 }
0146             }
0147             section.setClasses(cls);
0148             section.setFeatures(features);
0149         }
0150 
0151         section.setPlatformPositionBegin(prevVehicleEnd);
0152         section.setPlatformPositionEnd(prevVehicleEnd += wagonObj.value(QLatin1String("lengthOverBuffers")).toDouble());
0153 
0154         vehicleSections.push_back(section);
0155     }
0156     // guess platform length if we didn't get platform sectors
0157     platform.setLength(std::max<int>(platform.length(), prevVehicleEnd));
0158 
0159     // adjust vehicle and platform section positions to normalized platform coordinates
0160     if (platform.length() > 0.0) {
0161         for (auto &sec : platformSections) {
0162             sec.setBegin(sec.begin() / platform.length());
0163             sec.setEnd(sec.end() / platform.length());
0164         }
0165         for (auto &sec : vehicleSections) {
0166             sec.setPlatformPositionBegin(sec.platformPositionBegin() / platform.length());
0167             sec.setPlatformPositionEnd(sec.platformPositionEnd() / platform.length());
0168         }
0169     }
0170 
0171     vehicle.setSections(std::move(vehicleSections));
0172     platform.setSections(std::move(platformSections));
0173 
0174     // departure
0175     // TODO recover destination when possible
0176     Location stop;
0177     const auto timeTableInfo = obj.value(QLatin1String("timeTableInfo")).toObject();
0178     stop.setName(timeTableInfo.value(QLatin1String("stationName")).toString());
0179     stop.setType(Location::Stop);
0180     Line line;
0181     line.setName(timeTableInfo.value(QLatin1String("trainName")).toString());
0182     line.setMode(Line::Train);
0183     for (const auto &m : line_mode_map) {
0184         if (line.name().startsWith(QLatin1String(m.modeName))) {
0185             line.setMode(m.mode);
0186             break;
0187         }
0188     }
0189     Route route;
0190     if (line.mode() == Line::LocalTrain || line.mode() == Line::RapidTransit) {
0191         route.setName(QString::number(timeTableInfo.value(QLatin1String("trainNr")).toInt()));
0192     }
0193     route.setLine(line);
0194     stopover.setRoute(route);
0195     stopover.setStopPoint(stop);
0196 
0197     const auto date = QDate::fromString(timeTableInfo.value(QLatin1String("date")).toString(), QStringLiteral("yyyy-MM-dd"));
0198     const auto timeObj = timeTableInfo.value(QLatin1String("time")).toObject();
0199     stopover.setScheduledDepartureTime(parseDateTime(date, timeObj.value(QLatin1String("scheduled"))));
0200     stopover.setExpectedDepartureTime(parseDateTime(date, timeObj.value(QLatin1String("reported"))));
0201 
0202     {
0203         const auto platformObj = timeTableInfo.value(QLatin1String("platform")).toObject();
0204         const auto sectorsObj = timeTableInfo.value(QLatin1String("sectors")).toObject();
0205         stopover.setScheduledPlatform(parsePlatformName(platformObj, sectorsObj, "scheduled"));
0206         stopover.setExpectedPlatform(parsePlatformName(platformObj, sectorsObj, "reported"));
0207     }
0208 
0209     stopover.setVehicleLayout(std::move(vehicle));
0210     stopover.setPlatformLayout(std::move(platform));
0211 
0212     return !platform.isEmpty() || !vehicle.isEmpty()
0213          || stopover.scheduledArrivalTime().isValid() || stopover.scheduledDepartureTime().isValid();
0214 }