File indexing completed on 2024-04-28 04:41:39

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "uicrailwaycoach.h"
0007 
0008 #include <QDebug>
0009 
0010 using namespace KPublicTransport;
0011 
0012 QStringView UicRailwayCoach::countryCode(QStringView coachNumber)
0013 {
0014     // TODO handle different formatting of the coach number
0015     if (coachNumber.size() > 4) {
0016         return coachNumber.mid(2, 2);
0017     }
0018     return {};
0019 }
0020 
0021 static QStringView classificationDigit(QStringView coachNumber)
0022 {
0023     // TODO handle different formatting
0024     if (coachNumber.size() > 5) {
0025         return coachNumber.mid(4, 1);
0026     }
0027     return {};
0028 }
0029 
0030 // see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches
0031 struct {
0032     const char prefix[5];
0033     VehicleSection::Classes classes;
0034     VehicleSection::Features features;
0035     VehicleSection::Type type;
0036     int deckCount;
0037 } static constexpr const class_prefix_table[] = {
0038     { "AB", VehicleSection::FirstClass | VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0039     { "AR", VehicleSection::FirstClass, VehicleSection::Restaurant, VehicleSection::PassengerCar, 1 },
0040     { "A", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0041     { "BR", VehicleSection::SecondClass,  VehicleSection::Restaurant, VehicleSection::PassengerCar, 1 },
0042     { "B", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0043     { "DAB", VehicleSection::FirstClass | VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
0044     { "DA", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
0045     { "DB", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
0046     { "DD", VehicleSection::UnknownClass, VehicleSection::NoFeatures, VehicleSection::CarTransportCar, 2 },
0047     { "WLAB", VehicleSection::FirstClass | VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::SleepingCar, 1 },
0048     { "WLA", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0049     { "WLB", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0050     { "WR", VehicleSection::UnknownClass, VehicleSection::Restaurant, VehicleSection::RestaurantCar, 1 },
0051     { "KA", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0052     { "KB", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
0053 };
0054 
0055 VehicleSection::Classes UicRailwayCoach::coachClass(QStringView coachNumber, QStringView coachClassification)
0056 {
0057     const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
0058         return coachClassification.startsWith(QLatin1String(prefix.prefix));
0059     });
0060     if (it != std::end(class_prefix_table)) {
0061         return (*it).classes;
0062     }
0063 
0064     const auto cls = classificationDigit(coachNumber);
0065     if (!cls.empty()) {
0066         switch (cls.at(0).cell()) {
0067             case '1':
0068                 return VehicleSection::FirstClass;
0069             case '2':
0070             case '5':
0071                 return VehicleSection::SecondClass;
0072             case '3':
0073                 return VehicleSection::FirstClass | VehicleSection::SecondClass;
0074         }
0075     }
0076 
0077     return {};
0078 }
0079 
0080 // see https://de.wikipedia.org/wiki/Code_f%C3%BCr_das_Austauschverfahren
0081 struct {
0082     const char prefix[3];
0083     VehicleSection::Type type;
0084     VehicleSection::Features features;
0085 } static constexpr const number_prefix_table[] = {
0086     { "50", VehicleSection::PassengerCar, VehicleSection::NoFeatures },
0087     { "70", VehicleSection::PassengerCar, VehicleSection::AirConditioning },
0088     { "71", VehicleSection::SleepingCar, VehicleSection::NoFeatures },
0089     { "73", VehicleSection::PassengerCar, VehicleSection::AirConditioning },
0090     { "91", VehicleSection::Engine, VehicleSection::NoFeatures },
0091     { "92", VehicleSection::Engine, VehicleSection::NoFeatures },
0092 };
0093 
0094 // see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches
0095 struct UicClassificationSecondary {
0096     const char code[4];
0097     VehicleSection::Features features;
0098     VehicleSection::Type type;
0099     int deckCount;
0100 };
0101 
0102 // 54: Czech Republic
0103 static constexpr const UicClassificationSecondary secondary_54_table[] = {
0104     { "b", VehicleSection::WheelchairAccessible, VehicleSection::UnknownType, 1 },
0105     { "c", VehicleSection::NoFeatures, VehicleSection::CouchetteCar, 1 },
0106     { "d", VehicleSection::BikeStorage, VehicleSection::UnknownType, 1 },
0107     { "f", VehicleSection::NoFeatures, VehicleSection::ControlCar, 1 },
0108     { "o", VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 }, // ### could also be 't'?
0109     { "z", VehicleSection::AirConditioning, VehicleSection::PassengerCar, 1 },
0110 };
0111 
0112 // 80: Germany
0113 static constexpr const UicClassificationSecondary secondary_80_table[] = {
0114     { "b", VehicleSection::WheelchairAccessible, VehicleSection::UnknownType, 1 },
0115     { "c", VehicleSection::NoFeatures, VehicleSection::CouchetteCar, 1 },
0116     { "d", VehicleSection::BikeStorage, VehicleSection::UnknownType, 1 },
0117     { "f", VehicleSection::NoFeatures, VehicleSection::ControlCar, 1 },
0118     { "k", VehicleSection::Restaurant, VehicleSection::UnknownType, 1 },
0119     { "p", VehicleSection::AirConditioning, VehicleSection::PassengerCar, 1 },
0120     { "q", VehicleSection::NoFeatures, VehicleSection::ControlCar, 1 },
0121 };
0122 
0123 // 81: Austria
0124 static constexpr const UicClassificationSecondary secondary_81_table[] = {
0125     { "b", VehicleSection::WheelchairAccessible, VehicleSection::UnknownType, 1 }, // TODO wheelchair accessible toilets specifically
0126     { "c", VehicleSection::NoFeatures, VehicleSection::CouchetteCar, 1 },
0127     { "f", VehicleSection::NoFeatures, VehicleSection::ControlCar, 1 },
0128     { "p", VehicleSection::NoFeatures, VehicleSection::PassengerCar, 1 },
0129     { "-s", VehicleSection::NoFeatures, VehicleSection::ControlCar, 1 },
0130     { "-dl", VehicleSection::NoFeatures, VehicleSection::PassengerCar, 2 },
0131     { "-ds", VehicleSection::NoFeatures, VehicleSection::ControlCar, 2 },
0132 };
0133 
0134 // 85: Switzerland
0135 static constexpr const UicClassificationSecondary secondary_85_table[] = {
0136     { "c", VehicleSection::NoFeatures, VehicleSection::CouchetteCar, 1 },
0137     { "r", VehicleSection::Restaurant, VehicleSection::UnknownType, 1 },
0138     { "t", VehicleSection::NoFeatures, VehicleSection::ControlCar, 1 },
0139 };
0140 
0141 // 87: France
0142 static constexpr const UicClassificationSecondary secondary_87_table[] = {
0143     { "c", VehicleSection::NoFeatures, VehicleSection::CouchetteCar, 1 },
0144     { "e", VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
0145     { "h", VehicleSection::WheelchairAccessible, VehicleSection::UnknownType, 1 },
0146     { "u", VehicleSection::AirConditioning, VehicleSection::UnknownType, 1 },
0147 };
0148 
0149 struct {
0150     const char country[3];
0151     const UicClassificationSecondary *begin;
0152     const UicClassificationSecondary *end;
0153 } static constexpr const secondary_tables[] = {
0154     { "54", std::begin(secondary_54_table), std::end(secondary_54_table) },
0155     { "80", std::begin(secondary_80_table), std::end(secondary_80_table) },
0156     { "81", std::begin(secondary_81_table), std::end(secondary_81_table) },
0157     { "85", std::begin(secondary_85_table), std::end(secondary_85_table) },
0158     { "87", std::begin(secondary_87_table), std::end(secondary_87_table) },
0159 };
0160 
0161 int UicRailwayCoach::deckCount(QStringView coachNumber, QStringView coachClassification)
0162 {
0163     int decks = 1;
0164     const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
0165         return coachClassification.startsWith(QLatin1String(prefix.prefix));
0166     });
0167     if (it != std::end(class_prefix_table)) {
0168         decks = std::max(decks, (*it).deckCount);
0169     }
0170 
0171     const auto country = UicRailwayCoach::countryCode(coachNumber);
0172     for (const auto &tab : secondary_tables) {
0173         if (country != QLatin1String(tab.country)) {
0174             continue;
0175         }
0176         for (auto it = tab.begin; it != tab.end; ++it) {
0177             if (coachClassification.contains(QLatin1String((*it).code))) {
0178                 decks = std::max(decks, (*it).deckCount);
0179             }
0180         }
0181     }
0182 
0183     return decks;
0184 }
0185 
0186 VehicleSection::Features UicRailwayCoach::features(QStringView coachNumber, QStringView coachClassification)
0187 {
0188     VehicleSection::Features f = {};
0189     const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
0190         return coachClassification.startsWith(QLatin1String(prefix.prefix));
0191     });
0192     if (it != std::end(class_prefix_table)) {
0193         f |= (*it).features;
0194     }
0195 
0196     const auto it2 = std::find_if(std::begin(number_prefix_table), std::end(number_prefix_table), [coachNumber](const auto &prefix) {
0197         return coachNumber.startsWith(QLatin1String(prefix.prefix));
0198     });
0199     if (it2 != std::end(number_prefix_table)) {
0200         f |= (*it2).features;
0201     }
0202 
0203     const auto country = UicRailwayCoach::countryCode(coachNumber);
0204     for (const auto &tab : secondary_tables) {
0205         if (country != QLatin1String(tab.country)) {
0206             continue;
0207         }
0208         for (auto it = tab.begin; it != tab.end; ++it) {
0209             if (coachClassification.contains(QLatin1String((*it).code))) {
0210                 f |= (*it).features;
0211             }
0212         }
0213     }
0214 
0215     return f;
0216 }
0217 
0218 VehicleSection::Type UicRailwayCoach::type(QStringView coachNumber, QStringView coachClassification)
0219 {
0220     bool seenPassengerCar = false;
0221     const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
0222         return prefix.type != VehicleSection::UnknownType && coachClassification.startsWith(QLatin1String(prefix.prefix));
0223     });
0224     if (it != std::end(class_prefix_table)) {
0225         if ((*it).type == VehicleSection::PassengerCar) {
0226             seenPassengerCar = true;
0227         } else {
0228             return (*it).type;
0229         }
0230     }
0231 
0232     const auto it2 = std::find_if(std::begin(number_prefix_table), std::end(number_prefix_table), [coachNumber](const auto &prefix) {
0233         return prefix.type != VehicleSection::UnknownType && coachNumber.startsWith(QLatin1String(prefix.prefix));
0234     });
0235     if (it2 != std::end(number_prefix_table)) {
0236         if ((*it2).type == VehicleSection::PassengerCar) {
0237             seenPassengerCar = true;
0238         } else {
0239             return (*it2).type;
0240         }
0241     }
0242 
0243     const auto country = UicRailwayCoach::countryCode(coachNumber);
0244     for (const auto &tab : secondary_tables) {
0245         if (country != QLatin1String(tab.country)) {
0246             continue;
0247         }
0248         const auto it = std::find_if(tab.begin, tab.end, [coachClassification](const auto &prefix) {
0249             return prefix.type != VehicleSection::UnknownType && coachClassification.contains(QLatin1String(prefix.code));
0250         });
0251         if (it != tab.end) {
0252             if ((*it).type == VehicleSection::PassengerCar) {
0253                 seenPassengerCar = true;
0254             } else {
0255                 return (*it).type;
0256             }
0257         }
0258     }
0259 
0260     return seenPassengerCar ? VehicleSection::PassengerCar : VehicleSection::UnknownType;
0261 }