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

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "hafasvehiclelayoutparser.h"
0008 
0009 #include <KPublicTransport/Platform>
0010 #include <KPublicTransport/Vehicle>
0011 
0012 #include <QDebug>
0013 #include <QJsonArray>
0014 #include <QJsonDocument>
0015 #include <QJsonObject>
0016 #include <QXmlStreamReader>
0017 
0018 using namespace KPublicTransport;
0019 
0020 static VehicleSection::Feature attributeCodeToFeature(const QString &code)
0021 {
0022     struct {
0023         const char *code;
0024         VehicleSection::Feature feature;
0025     } static constexpr const code_feature_map[] = {
0026         { "AbteilBusiness", VehicleSection::NoFeatures }, // TODO
0027         { "AbteilFamilien", VehicleSection::NoFeatures }, // TODO
0028         { "AbteilKinderwagen", VehicleSection::NoFeatures }, // TODO
0029         { "AbteilRollstuhl", VehicleSection::WheelchairAccessible },
0030         { "AbteilVeloPl", VehicleSection::BikeStorage },
0031         { "AbteilVeloRes", VehicleSection::BikeStorage },
0032         { "FA", VehicleSection::NoFeatures }, // TODO family area
0033         { "NiederflurEinstieg", VehicleSection::NoFeatures }, // TODO
0034     };
0035 
0036     for (const auto &map : code_feature_map) {
0037         if (code == QLatin1String(map.code)) {
0038             return map.feature;
0039         }
0040     }
0041 
0042     qDebug() << "Unknown vehicle section feature code:" << code;
0043     return VehicleSection::NoFeatures;
0044 }
0045 
0046 static VehicleSection::Classes typeToClasses(const QString &type)
0047 {
0048     if (type == QLatin1Char('1')) {
0049         return VehicleSection::FirstClass;
0050     }
0051     if (type == QLatin1Char('2') || type == QLatin1String("W2")) {
0052         return VehicleSection::SecondClass;
0053     }
0054     return VehicleSection::UnknownClass;
0055 }
0056 
0057 static VehicleSection::Type typeToType(const QString &type)
0058 {
0059     if (type == QLatin1String("WR")) {
0060         return VehicleSection::RestaurantCar;
0061     }
0062     if (type == QLatin1String("LK")) {
0063         return VehicleSection::Engine;
0064     }
0065     return VehicleSection::PassengerCar;
0066 }
0067 
0068 std::vector<Vehicle> HafasVehicleLayoutParser::parseVehicleLayouts(const QJsonObject &commonObj)
0069 {
0070     // stcLiL - type map
0071     const auto stcLiL = commonObj.value(QLatin1String("stcLiL")).toArray();
0072 
0073     // stcCarL - vehicle section list
0074     const auto stcCarL = commonObj.value(QLatin1String("stcCarL")).toArray();
0075     std::vector<VehicleSection> cars;
0076     cars.reserve(stcCarL.size());
0077     for (const auto &stcCarV : stcCarL) {
0078         const auto stcCarObj = stcCarV.toObject();
0079 
0080         VehicleSection car;
0081         VehicleSection::Features features = {};
0082         car.setName(stcCarObj.value(QLatin1String("number")).toString());
0083 
0084         const auto type = stcCarObj.value(QLatin1String("type")).toInt();
0085         if (type >= 0 && type < stcLiL.size()) {
0086             const auto id = stcLiL.at(type).toObject().value(QLatin1String("id")).toString();
0087             car.setType(typeToType(id));
0088             car.setClasses(typeToClasses(id));
0089             // TODO type == FA?
0090         }
0091 
0092         const auto attrL = stcCarObj.value(QLatin1String("attrL")).toArray();
0093         for (const auto &attrV : attrL) {
0094             features |= attributeCodeToFeature(attrV.toObject().value(QLatin1String("c")).toString());
0095         }
0096 
0097         car.setFeatures(features);
0098         cars.push_back(std::move(car));
0099     }
0100 
0101     // stcGrpL - vehicles
0102     const auto stcGrpL = commonObj.value(QLatin1String("stcGrpL")).toArray();
0103     std::vector<Vehicle> vehicles;
0104     vehicles.reserve(stcGrpL.size());
0105     for (const auto &stcGrpV : stcGrpL) {
0106         const auto carL = stcGrpV.toObject().value(QLatin1String("carL")).toArray();
0107         std::vector<VehicleSection> sections;
0108         sections.reserve(carL.size());
0109         for (const auto &carV : carL) {
0110             const auto carIdx = carV.toInt();
0111             if (carIdx >= 0 && carIdx < (int)cars.size()) {
0112                 sections.push_back(cars[carIdx]);
0113             }
0114         }
0115 
0116         Vehicle vehicle;
0117         vehicle.setSections(std::move(sections));
0118         vehicles.push_back(std::move(vehicle));
0119     }
0120 
0121     return vehicles;
0122 }
0123 
0124 static void parseTrainFormationCars(std::vector<VehicleSection> &vehicleSections, const QJsonArray &cars)
0125 {
0126     vehicleSections.reserve(vehicleSections.size() + cars.size());
0127     for (const auto &carV : cars) {
0128         const auto carObj = carV.toObject();
0129         VehicleSection sec;
0130         sec.setName(carObj.value(QLatin1String("number")).toString());
0131 
0132         const auto type = carObj.value(QLatin1String("type")).toString();
0133         sec.setType(typeToType(type));
0134         sec.setPlatformSectionName(carObj.value(QLatin1String("section")).toString());
0135         sec.setClasses(typeToClasses(type));
0136         // TODO type == FA
0137 
0138         // TODO there are also translated names for those features we could use
0139         VehicleSection::Features features = {};
0140         const auto attrs = carObj.value(QLatin1String("CarAttributes")).toObject().value(QLatin1String("Attr"));
0141         if (attrs.isObject()) {
0142             features |= attributeCodeToFeature(attrs.toObject().value(QLatin1String("code")).toString());
0143         } else if (attrs.isArray()) {
0144             const auto attrsA = attrs.toArray();
0145             for (const auto &attrV : attrsA) {
0146                 features |= attributeCodeToFeature(attrV.toObject().value(QLatin1String("code")).toString());
0147             }
0148         }
0149         sec.setFeatures(features);
0150 
0151         vehicleSections.push_back(sec);
0152     }
0153 }
0154 
0155 Vehicle HafasVehicleLayoutParser::parseTrainFormation(const QByteArray &data)
0156 {
0157     QJsonParseError parserError;
0158     const auto jsonEndIdx = data.lastIndexOf('}') + 1;
0159     const auto sformation = QJsonDocument::fromJson(data.mid(0, jsonEndIdx), &parserError).object().value(QLatin1String("SFormation")).toObject();
0160     if (parserError.error != QJsonParseError::NoError) {
0161         qDebug() << parserError.errorString();
0162     }
0163 
0164     const auto trainGroups = sformation.value(QLatin1String("TrainGroups")).toObject();
0165     const auto trainGroup = trainGroups.value(QLatin1String("TrainGroup"));
0166     std::vector<VehicleSection> vehicleSections;
0167     if (trainGroup.isObject()) {
0168         const auto cars = trainGroup.toObject().value(QLatin1String("Car")).toArray();
0169         parseTrainFormationCars(vehicleSections, cars);
0170     } else if (trainGroup.isArray()) {
0171         const auto trainGroupA = trainGroup.toArray();
0172         if (!trainGroupA.isEmpty()) {
0173             // the following array entries are how the train changes over time, something we can't model yet?
0174             const auto cars = trainGroupA.at(0).toObject().value(QLatin1String("Car")).toArray();
0175             parseTrainFormationCars(vehicleSections, cars);
0176         }
0177     }
0178 
0179     Vehicle vehicle;
0180     vehicle.setSections(std::move(vehicleSections));
0181     return vehicle;
0182 }
0183 
0184 std::vector<Platform> HafasVehicleLayoutParser::parsePlatforms(const QJsonObject &commonObj)
0185 {
0186     const auto tcpdL = commonObj.value(QLatin1String("tcpdL")).toArray();
0187     std::vector<Platform> platforms;
0188     platforms.reserve(tcpdL.size());
0189 
0190     for (const auto &tcpdV : tcpdL) {
0191         const auto tcpdObj = tcpdV.toObject();
0192         const auto sectors = tcpdObj.value(QLatin1String("PS")).toArray();
0193 
0194         Platform platform;
0195         std::vector<PlatformSection> platformSections;
0196         platformSections.reserve(sectors.size());
0197 
0198         for (const auto &secV : sectors) {
0199             const auto secObj = secV.toObject();
0200             PlatformSection section;
0201             section.setName(secObj.value(QLatin1String("n")).toString());
0202             section.setBegin(secObj.value(QLatin1String("s")).toInt());
0203             section.setEnd(secObj.value(QLatin1String("e")).toInt());
0204 
0205             platform.setLength(std::max<int>(section.begin(), std::max<int>(section.end(), platform.length())));
0206             platformSections.push_back(std::move(section));
0207         }
0208 
0209         // scale sector lengths to relative coordinates
0210         if (platform.length() > 0.0) {
0211             for (auto &sec : platformSections) {
0212                 sec.setBegin(sec.begin() / platform.length());
0213                 sec.setEnd(sec.end() / platform.length());
0214             }
0215         }
0216         // we don't have an absolute value as the actual value we get here are of an unknown unit
0217         platform.setLength(1.0);
0218 
0219         platform.setSections(std::move(platformSections));
0220         platforms.push_back(std::move(platform));
0221     }
0222 
0223     return platforms;
0224 }
0225 
0226 Platform HafasVehicleLayoutParser::parsePlatformSectors(const QByteArray &data)
0227 {
0228     Platform platform;
0229     std::vector<PlatformSection> platformSections;
0230 
0231     QXmlStreamReader reader(data);
0232     while (!reader.atEnd()) {
0233         if (reader.readNext() != QXmlStreamReader::StartElement) {
0234             continue;
0235         }
0236         if (reader.name() == QLatin1String("PS")) {
0237             PlatformSection sec;
0238             sec.setName(reader.attributes().value(QLatin1String("n")).toString());
0239             sec.setBegin(reader.attributes().value(QLatin1String("s")).toInt());
0240             sec.setEnd(reader.attributes().value(QLatin1String("e")).toInt());
0241 
0242             platform.setLength(std::max<int>(sec.begin(), std::max<int>(sec.end(), platform.length())));
0243             platformSections.push_back(std::move(sec));
0244         }
0245     }
0246     if (reader.hasError()) {
0247         qDebug() << reader.errorString();
0248     }
0249 
0250     // scale sector lengths to relative coordinates
0251     if (platform.length() > 0.0) {
0252         for (auto &sec : platformSections) {
0253             sec.setBegin(sec.begin() / platform.length());
0254             sec.setEnd(sec.end() / platform.length());
0255         }
0256     }
0257     // we don't have an absolute value as the actual value we get here are of an unknown unit
0258     platform.setLength(1.0);
0259 
0260     platform.setSections(std::move(platformSections));
0261     return platform;
0262 }