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 &sectionV : 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 &sectorV : 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 }