File indexing completed on 2024-05-12 04:42:33
0001 /* 0002 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "deutschebahnvehiclelayoutparser.h" 0008 #include "uic/uicrailwaycoach.h" 0009 0010 #include <QDateTime> 0011 #include <QDebug> 0012 #include <QJsonArray> 0013 #include <QJsonDocument> 0014 #include <QJsonObject> 0015 0016 #include <cmath> 0017 0018 using namespace KPublicTransport; 0019 0020 struct { 0021 const char *type; 0022 Line::Mode mode; 0023 } static constexpr const train_type_map[] = { 0024 { "ICE", Line::LongDistanceTrain }, 0025 { "IC", Line::LongDistanceTrain }, 0026 { "EC", Line::LongDistanceTrain }, 0027 { "RJ", Line::LongDistanceTrain }, 0028 { "NJ", Line::LongDistanceTrain }, 0029 { "RE", Line::LocalTrain }, 0030 { "RB", Line::LocalTrain }, 0031 }; 0032 0033 bool DeutscheBahnVehicleLayoutParser::parse(const QByteArray &data) 0034 { 0035 const auto doc = QJsonDocument::fromJson(data); 0036 0037 const auto err = doc.object().value(QLatin1String("error")).toObject(); 0038 if (!err.isEmpty()) { 0039 error = err.value(QLatin1String("id")).toInt() == 404 ? Reply::NotFoundError : Reply::UnknownError; 0040 errorMessage = err.value(QLatin1String("msg")).toString(); 0041 return false; 0042 } 0043 0044 // vehicles 0045 Vehicle vehicle; 0046 const auto obj = doc.object().value(QLatin1String("data")).toObject().value(QLatin1String("istformation")).toObject(); 0047 const auto trainType = obj.value(QLatin1String("zuggattung")).toString() ; 0048 vehicle.setName(trainType + QLatin1Char(' ') + obj.value(QLatin1String("zugnummer")).toString()); 0049 0050 // TODO dobule segment ICE trains technically are two Vehicle objects... 0051 const auto vehiclesArray = obj.value(QLatin1String("allFahrzeuggruppe")).toArray(); 0052 for (const auto &vehicleV : vehiclesArray) { 0053 const auto sectionsArray = vehicleV.toObject().value(QLatin1String("allFahrzeug")).toArray(); 0054 for (const auto §ionV : sectionsArray) { 0055 parseVehicleSection(vehicle, sectionV.toObject()); 0056 } 0057 } 0058 // direction is implied by section order 0059 if (vehicle.sections().size() >= 2) { 0060 vehicle.setDirection(vehicle.sections()[0].platformPositionBegin() < vehicle.sections()[1].platformPositionBegin() ? Vehicle::Forward : Vehicle::Backward); 0061 } 0062 0063 // platform 0064 Platform platform; 0065 const auto halt = obj.value(QLatin1String("halt")).toObject(); 0066 platform.setName(halt.value(QLatin1String("gleisbezeichnung")).toString()); 0067 const auto sectorArray = halt.value(QLatin1String("allSektor")).toArray(); 0068 for (const auto §orV : sectorArray) { 0069 parsePlatformSection(platform, sectorV.toObject()); 0070 } 0071 0072 // departure 0073 Location stop; 0074 stop.setName(halt.value(QLatin1String("bahnhofsname")).toString()); 0075 stop.setIdentifier(QStringLiteral("ibnr"), halt.value(QLatin1String("evanummer")).toString()); 0076 stop.setType(Location::Stop); 0077 Route route; 0078 Line line; 0079 0080 line.setMode(Line::Train); 0081 for (const auto &m : train_type_map) { 0082 if (trainType == QLatin1String(m.type)) { 0083 line.setMode(m.mode); 0084 break; 0085 } 0086 } 0087 0088 if (const auto lineNumber = obj.value(QLatin1String("liniebezeichnung")).toString(); !lineNumber.isEmpty() && line.mode() != Line::LongDistanceTrain) { 0089 line.setName(trainType + QLatin1Char(' ') + lineNumber); 0090 route.setName(vehicle.name()); 0091 } else { 0092 line.setName(vehicle.name()); 0093 } 0094 route.setLine(line); 0095 0096 stopover.setRoute(route); 0097 stopover.setStopPoint(stop); 0098 stopover.setScheduledArrivalTime(QDateTime::fromString(halt.value(QLatin1String("ankunftszeit")).toString(), Qt::ISODate)); 0099 stopover.setScheduledDepartureTime(QDateTime::fromString(halt.value(QLatin1String("abfahrtszeit")).toString(), Qt::ISODate)); 0100 stopover.setScheduledPlatform(platform.name()); 0101 0102 fillMissingPositions(vehicle, platform); 0103 stopover.setVehicleLayout(std::move(vehicle)); 0104 stopover.setPlatformLayout(std::move(platform)); 0105 0106 return true; 0107 } 0108 0109 void DeutscheBahnVehicleLayoutParser::parseVehicleSection(Vehicle &vehicle, const QJsonObject &obj) 0110 { 0111 VehicleSection section; 0112 VehicleSection::Features f; 0113 section.setName(obj.value(QLatin1String("wagenordnungsnummer")).toString()); 0114 0115 const auto pos = obj.value(QLatin1String("positionamhalt")).toObject(); 0116 section.setPlatformPositionBegin(pos.value(QLatin1String("startprozent")).toString().toDouble() / 100.0); 0117 section.setPlatformPositionEnd(pos.value(QLatin1String("endeprozent")).toString().toDouble() / 100.0); 0118 0119 const auto cat = obj.value(QLatin1String("kategorie")).toString(); 0120 if (cat.compare(QLatin1String("LOK"), Qt::CaseInsensitive) == 0) { 0121 section.setType(VehicleSection::Engine); 0122 } else if (cat.compare(QLatin1String("TRIEBKOPF"), Qt::CaseInsensitive) == 0) { 0123 section.setType(VehicleSection::PowerCar); 0124 } else if (cat.contains(QLatin1String("STEUERWAGEN"), Qt::CaseInsensitive)) { 0125 section.setType(VehicleSection::ControlCar); 0126 } else { 0127 section.setType(VehicleSection::PassengerCar); 0128 } 0129 0130 // see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches 0131 const auto num = obj.value(QLatin1String("fahrzeugnummer")).toString(); 0132 const auto cls = obj.value(QLatin1String("fahrzeugtyp")).toString(); 0133 section.setClasses(UicRailwayCoach::coachClass(num, cls)); 0134 section.setDeckCount(UicRailwayCoach::deckCount(num, cls)); 0135 if (const auto type = UicRailwayCoach::type(num, cls); section.type() == VehicleSection::PassengerCar && type != VehicleSection::UnknownType) { 0136 section.setType(type); 0137 } 0138 f |= UicRailwayCoach::features(num, cls); 0139 0140 const auto equipmentArray = obj.value(QLatin1String("allFahrzeugausstattung")).toArray(); 0141 for (const auto &equipmentV : equipmentArray) { 0142 const auto equipmentObj = equipmentV.toObject(); 0143 const auto type = equipmentObj.value(QLatin1String("ausstattungsart")).toString(); 0144 // TODO this has a status field, is this ever set? 0145 if (type.compare(QLatin1String("KLIMA"), Qt::CaseInsensitive) == 0) { 0146 f |= VehicleSection::AirConditioning; 0147 } else if (type.compare(QLatin1String("RUHE"), Qt::CaseInsensitive) == 0) { 0148 f |= VehicleSection::SilentArea; 0149 } else if (type.compare(QLatin1String("BISTRO"), Qt::CaseInsensitive) == 0) { 0150 f |= VehicleSection::Restaurant; 0151 } else if (type.compare(QLatin1String("ABTEILKLEINKIND"), Qt::CaseInsensitive) == 0) { 0152 f |= VehicleSection::ToddlerArea; 0153 } else if (type.compare(QLatin1String("PLAETZEROLLSTUHL"), Qt::CaseInsensitive) == 0) { 0154 f |= VehicleSection::WheelchairAccessible; 0155 } else if (type.compare(QLatin1String("PLAETZEFAHRRAD"), Qt::CaseInsensitive) == 0) { 0156 f |= VehicleSection::BikeStorage; 0157 } else { 0158 qDebug() << "Unhandled vehicle section equipment:" << type; 0159 } 0160 } 0161 section.setFeatures(f); 0162 0163 auto sections = vehicle.takeSections(); 0164 sections.push_back(section); 0165 vehicle.setSections(std::move(sections)); 0166 } 0167 0168 void DeutscheBahnVehicleLayoutParser::parsePlatformSection(Platform &platform, const QJsonObject &obj) 0169 { 0170 PlatformSection section; 0171 section.setName(obj.value(QLatin1String("sektorbezeichnung")).toString()); 0172 0173 const auto pos = obj.value(QLatin1String("positionamgleis")).toObject(); 0174 section.setBegin(pos.value(QLatin1String("startprozent")).toString().toDouble() / 100.0); 0175 section.setEnd(pos.value(QLatin1String("endeprozent")).toString().toDouble() / 100.0); 0176 0177 auto sections = platform.takeSections(); 0178 sections.push_back(section); 0179 platform.setSections(std::move(sections)); 0180 0181 const auto length = std::max(pos.value(QLatin1String("startmeter")).toString().toDouble(), pos.value(QLatin1String("endemeter")).toString().toDouble()); 0182 if (length > 0) { 0183 platform.setLength(std::max(platform.length(), (int)std::ceil(length))); 0184 } 0185 } 0186 0187 void DeutscheBahnVehicleLayoutParser::fillMissingPositions(Vehicle &vehicle, Platform &platform) 0188 { 0189 if (vehicle.sections().empty()) { 0190 return; 0191 } 0192 0193 const auto noPositions = std::all_of(vehicle.sections().begin(), vehicle.sections().end(), [](const auto &sec) { 0194 return sec.platformPositionBegin() == sec.platformPositionEnd(); 0195 }); 0196 0197 if (!noPositions) { 0198 return; 0199 } 0200 0201 auto sections = vehicle.takeSections(); 0202 for (std::size_t i = 0; i < sections.size(); ++i) { 0203 sections[i].setPlatformPositionBegin((float)i / (float)sections.size()); 0204 sections[i].setPlatformPositionEnd((float)(i + 1) / (float)sections.size()); 0205 } 0206 vehicle.setSections(std::move(sections)); 0207 if (platform.length() <= 0.0) { 0208 platform.setLength(vehicle.sections().size() * 25.0); 0209 } 0210 }