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 }