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 §orsObj, 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 §orV : 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 }