Commit 9cbe2aae authored by Volker Krause's avatar Volker Krause
Browse files

Extract and expand UIC railway coach number and classification parsing

This existed to a small extend in the DB vehicle layout parser and is now
much more complete, in preparation for supporting the new ÖBB vehicle
layout API.

Lookup tables for country-specifics beyond AT and DE are still missing,
but that's straightforward to extend now once we encounter the
corresponding data.
parent 6c0d145e
Pipeline #87849 passed with stage
in 1 minute and 17 seconds
......@@ -13,6 +13,7 @@ ecm_add_test(indexeddatatabletest.cpp LINK_LIBRARIES Qt5::Test)
ecm_add_test(polylinetest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(ifopttest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(uicutiltest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(uicrailwaycoachtest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(mergeutiltest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(locationtest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
ecm_add_test(linetest.cpp LINK_LIBRARIES Qt5::Test KPublicTransport)
......
......@@ -5,6 +5,7 @@
*/
#include "backends/deutschebahnvehiclelayoutparser.cpp"
#include "uic/uicrailwaycoach.cpp"
#include <QFile>
#include <QJsonObject>
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "../src/lib/uic/uicrailwaycoach.cpp"
#include <QTest>
#define s(x) QStringLiteral(x)
using namespace KPublicTransport;
class UicRailwayCoachTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void testClasses()
{
QCOMPARE(UicRailwayCoach::coachClass(QString(), QString()), VehicleSection::UnknownClass);
QCOMPARE(UicRailwayCoach::coachClass(u"738029947087", u"Bpmbz"), VehicleSection::SecondClass);
QCOMPARE(UicRailwayCoach::coachClass(u"738029947087", QString()), VehicleSection::SecondClass);
QCOMPARE(UicRailwayCoach::coachClass(QString(), u"Afmpz"), VehicleSection::FirstClass);
QCOMPARE(UicRailwayCoach::coachClass(QString(), u"WRmz"), VehicleSection::UnknownClass);
QCOMPARE(UicRailwayCoach::coachClass(QString(), u"DABpza"), VehicleSection::SecondClass | VehicleSection::FirstClass);
}
void testDeckCount()
{
QCOMPARE(UicRailwayCoach::deckCount(QString(), QString()), 1);
QCOMPARE(UicRailwayCoach::deckCount(u"738029947087", u"Bpmbz"), 1);
QCOMPARE(UicRailwayCoach::deckCount(u"738029947087", QString()), 1);
QCOMPARE(UicRailwayCoach::deckCount(QString(), u"Afmpz"), 1);
QCOMPARE(UicRailwayCoach::deckCount(QString(), u"DABpza"), 2);
}
void testFeatures()
{
QCOMPARE(UicRailwayCoach::features(QString(), QString()), VehicleSection::NoFeatures);
QCOMPARE(UicRailwayCoach::features(u"738029947087", u"Bpmbz"), VehicleSection::AirConditioning | VehicleSection::WheelchairAccessible);
QCOMPARE(UicRailwayCoach::features(u"738029947087", QString()), VehicleSection::AirConditioning);
QCOMPARE(UicRailwayCoach::features(u"738180907342", u"Afmpz"), VehicleSection::AirConditioning);
QCOMPARE(UicRailwayCoach::features(QString(), u"WRmz"), VehicleSection::Restaurant);
QCOMPARE(UicRailwayCoach::features(u"938054112686", u"WRmz"), VehicleSection::Restaurant);
QCOMPARE(UicRailwayCoach::features(u"508086818566", u"DBpbzfa"), VehicleSection::WheelchairAccessible | VehicleSection::AirConditioning);
QCOMPARE(UicRailwayCoach::features(u"738185905341", u"ARbmpz"), VehicleSection::Restaurant | VehicleSection::WheelchairAccessible | VehicleSection::AirConditioning);
QCOMPARE(UicRailwayCoach::features(u"508086818566", QString()), VehicleSection::NoFeatures);
QCOMPARE(UicRailwayCoach::features(u"918061465699", u"E1465"), VehicleSection::NoFeatures);
}
void testType()
{
QCOMPARE(UicRailwayCoach::type(QString(), QString()), VehicleSection::UnknownType);
QCOMPARE(UicRailwayCoach::type(u"738029947087", u"Bpmbz"), VehicleSection::PassengerCar);
QCOMPARE(UicRailwayCoach::type(u"738029947087", QString()), VehicleSection::PassengerCar);
QCOMPARE(UicRailwayCoach::type(u"738180907342", u"Afmpz"), VehicleSection::ControlCar);
QCOMPARE(UicRailwayCoach::type(QString(), u"WRmz"), VehicleSection::RestaurantCar);
QCOMPARE(UicRailwayCoach::type(u"938054112686", u"WRmz"), VehicleSection::RestaurantCar);
QCOMPARE(UicRailwayCoach::type(u"508086818566", u"DBpbzfa"), VehicleSection::ControlCar);
QCOMPARE(UicRailwayCoach::type(u"738185905341", u"ARbmpz"), VehicleSection::PassengerCar);
QCOMPARE(UicRailwayCoach::type(u"918111162346", u"TZF"), VehicleSection::Engine);
QCOMPARE(UicRailwayCoach::type(u"918061465699", u"E1465"), VehicleSection::Engine);
QEXPECT_FAIL("", "not detectable?", Continue);
QCOMPARE(UicRailwayCoach::type(u"938054020061", u"I4020"), VehicleSection::PowerCar);
QCOMPARE(UicRailwayCoach::type(u"938058080061", u"Bpmzf"), VehicleSection::ControlCar);
}
};
QTEST_APPLESS_MAIN(UicRailwayCoachTest)
#include "uicrailwaycoachtest.moc"
......@@ -112,6 +112,7 @@ target_sources(KPublicTransport PRIVATE
networks/networks.qrc
networks/certs/network_certs.qrc
uic/uicrailwaycoach.cpp
uic/uicutil.cpp
)
ecm_qt_declare_logging_category(KPublicTransport
......
......@@ -5,6 +5,7 @@
*/
#include "deutschebahnvehiclelayoutparser.h"
#include "uic/uicrailwaycoach.h"
#include <QDateTime>
#include <QDebug>
......@@ -99,25 +100,14 @@ void DeutscheBahnVehicleLayoutParser::parseVehicleSection(Vehicle &vehicle, cons
}
// see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches
const auto num = obj.value(QLatin1String("fahrzeugnummer")).toString();
const auto cls = obj.value(QLatin1String("fahrzeugtyp")).toString();
VehicleSection::Classes c = VehicleSection::UnknownClass;
if (cls.startsWith(QLatin1Char('A')) || cls.startsWith(QLatin1String("DA"))) {
c |= VehicleSection::FirstClass;
section.setClasses(UicRailwayCoach::coachClass(num, cls));
section.setDeckCount(UicRailwayCoach::deckCount(num, cls));
if (const auto type = UicRailwayCoach::type(num, cls); section.type() == VehicleSection::PassengerCar && type != VehicleSection::UnknownType) {
section.setType(type);
}
if (cls.startsWith(QLatin1Char('B')) || cls.startsWith(QLatin1String("AB")) || cls.startsWith(QLatin1String("DB"))) {
c |= VehicleSection::SecondClass;
}
if (cls.startsWith(QLatin1String("WR"))) {
section.setType(VehicleSection::RestaurantCar);
f |= VehicleSection::Restaurant;
}
if (cls.startsWith(QLatin1String("AR")) || cls.startsWith(QLatin1String("BR"))) {
f |= VehicleSection::Restaurant;
}
if (cls.startsWith(QLatin1Char('D'))) {
section.setDeckCount(2);
}
section.setClasses(c);
f |= UicRailwayCoach::features(num, cls);
const auto equipmentArray = obj.value(QLatin1String("allFahrzeugausstattung")).toArray();
for (const auto &equipmentV : equipmentArray) {
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "uicrailwaycoach.h"
#include <QDebug>
using namespace KPublicTransport;
QStringView UicRailwayCoach::countryCode(QStringView coachNumber)
{
// TODO handle different formatting of the coach number
if (coachNumber.size() > 4) {
return coachNumber.mid(2, 2);
}
return {};
}
static QStringView classificationDigit(QStringView coachNumber)
{
// TODO handle different formatting
if (coachNumber.size() > 5) {
return coachNumber.mid(4, 1);
}
return {};
}
// see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches
struct {
const char prefix[5];
VehicleSection::Classes classes;
VehicleSection::Features features;
VehicleSection::Type type;
int deckCount;
} static constexpr const class_prefix_table[] = {
{ "AB", VehicleSection::FirstClass | VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "AR", VehicleSection::FirstClass, VehicleSection::Restaurant, VehicleSection::UnknownType, 1 },
{ "A", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "BR", VehicleSection::SecondClass, VehicleSection::Restaurant, VehicleSection::UnknownType, 1 },
{ "B", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "DAB", VehicleSection::FirstClass | VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
{ "DA", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
{ "DB", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 },
{ "DD", VehicleSection::UnknownClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 2 }, // TODO car transport coach
{ "WLAB", VehicleSection::FirstClass | VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "WLA", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "WLB", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "WR", VehicleSection::UnknownClass, VehicleSection::Restaurant, VehicleSection::RestaurantCar, 1 },
{ "KA", VehicleSection::FirstClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
{ "KB", VehicleSection::SecondClass, VehicleSection::NoFeatures, VehicleSection::UnknownType, 1 },
};
VehicleSection::Classes UicRailwayCoach::coachClass(QStringView coachNumber, QStringView coachClassification)
{
const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
return coachClassification.startsWith(QLatin1String(prefix.prefix));
});
if (it != std::end(class_prefix_table)) {
return (*it).classes;
}
const auto cls = classificationDigit(coachNumber);
if (!cls.empty()) {
switch (cls.at(0).cell()) {
case '1':
return VehicleSection::FirstClass;
case '2':
case '5':
return VehicleSection::SecondClass;
case '3':
return VehicleSection::FirstClass | VehicleSection::SecondClass;
}
}
return {};
}
int UicRailwayCoach::deckCount(QStringView coachNumber, QStringView coachClassification)
{
Q_UNUSED(coachNumber);
const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
return coachClassification.startsWith(QLatin1String(prefix.prefix));
});
if (it != std::end(class_prefix_table)) {
return (*it).deckCount;
}
return 1;
}
// see https://de.wikipedia.org/wiki/Code_f%C3%BCr_das_Austauschverfahren
struct {
const char prefix[3];
VehicleSection::Type type;
VehicleSection::Features features;
} static constexpr const number_prefix_table[] = {
{ "50", VehicleSection::PassengerCar, VehicleSection::NoFeatures },
{ "70", VehicleSection::PassengerCar, VehicleSection::AirConditioning }, // also: air conditioned
// { "71", VehicleSection:: TODO }, // TODO sleeping car
{ "73", VehicleSection::PassengerCar, VehicleSection::AirConditioning }, // also: air conditioned
{ "91", VehicleSection::Engine, VehicleSection::NoFeatures },
{ "92", VehicleSection::Engine, VehicleSection::NoFeatures },
};
// see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches
struct UicClassificationSecondary {
const char code[3];
VehicleSection::Features features;
VehicleSection::Type type;
};
// 80: Germany
static constexpr const UicClassificationSecondary secondary_80_table[] = {
{ "b", VehicleSection::WheelchairAccessible, VehicleSection::UnknownType },
// "c" Couchettes - TODO needs enum
{ "d", VehicleSection::BikeStorage, VehicleSection::UnknownType },
{ "f", VehicleSection::NoFeatures, VehicleSection::ControlCar },
{ "k", VehicleSection::Restaurant, VehicleSection::UnknownType },
{ "p", VehicleSection::AirConditioning, VehicleSection::PassengerCar },
{ "q", VehicleSection::NoFeatures, VehicleSection::ControlCar },
};
// 81: Austria
static constexpr const UicClassificationSecondary secondary_81_table[] = {
{ "b", VehicleSection::WheelchairAccessible, VehicleSection::UnknownType }, // TODO wheelchair accessible toilets specifically
// "c" Couchettes - TODO needs enum
{ "f", VehicleSection::NoFeatures, VehicleSection::ControlCar },
{ "p", VehicleSection::NoFeatures, VehicleSection::PassengerCar },
{ "-s", VehicleSection::NoFeatures, VehicleSection::ControlCar },
};
struct {
const char country[3];
const UicClassificationSecondary *begin;
const UicClassificationSecondary *end;
} static constexpr const secondary_tables[] = {
{ "80", std::begin(secondary_80_table), std::end(secondary_80_table) },
{ "81", std::begin(secondary_81_table), std::end(secondary_81_table) },
};
VehicleSection::Features UicRailwayCoach::features(QStringView coachNumber, QStringView coachClassification)
{
VehicleSection::Features f = {};
const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
return coachClassification.startsWith(QLatin1String(prefix.prefix));
});
if (it != std::end(class_prefix_table)) {
f |= (*it).features;
}
const auto it2 = std::find_if(std::begin(number_prefix_table), std::end(number_prefix_table), [coachNumber](const auto &prefix) {
return coachNumber.startsWith(QLatin1String(prefix.prefix));
});
if (it2 != std::end(number_prefix_table)) {
f |= (*it2).features;
}
const auto country = UicRailwayCoach::countryCode(coachNumber);
if (country.empty()) {
return f;
}
for (const auto &tab : secondary_tables) {
if (country != QLatin1String(tab.country)) {
continue;
}
for (auto it = tab.begin; it != tab.end; ++it) {
if (coachClassification.contains(QLatin1String((*it).code))) {
f |= (*it).features;
}
}
}
return f;
}
VehicleSection::Type UicRailwayCoach::type(QStringView coachNumber, QStringView coachClassification)
{
bool seenPassengerCar = false;
const auto it = std::find_if(std::begin(class_prefix_table), std::end(class_prefix_table), [coachClassification](const auto &prefix) {
return prefix.type != VehicleSection::UnknownType && coachClassification.startsWith(QLatin1String(prefix.prefix));
});
if (it != std::end(class_prefix_table)) {
if ((*it).type == VehicleSection::PassengerCar) {
seenPassengerCar = true;
} else {
return (*it).type;
}
}
const auto it2 = std::find_if(std::begin(number_prefix_table), std::end(number_prefix_table), [coachNumber](const auto &prefix) {
return prefix.type != VehicleSection::UnknownType && coachNumber.startsWith(QLatin1String(prefix.prefix));
});
if (it2 != std::end(number_prefix_table)) {
if ((*it2).type == VehicleSection::PassengerCar) {
seenPassengerCar = true;
} else {
return (*it2).type;
}
}
const auto country = UicRailwayCoach::countryCode(coachNumber);
if (!country.empty()) {
for (const auto &tab : secondary_tables) {
if (country != QLatin1String(tab.country)) {
continue;
}
const auto it = std::find_if(tab.begin, tab.end, [coachClassification](const auto &prefix) {
return prefix.type != VehicleSection::UnknownType && coachClassification.contains(QLatin1String(prefix.code));
});
if (it != tab.end) {
if ((*it).type == VehicleSection::PassengerCar) {
seenPassengerCar = true;
} else {
return (*it).type;
}
}
}
}
return seenPassengerCar ? VehicleSection::PassengerCar : VehicleSection::UnknownType;
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPUBLICTRANSPORT_UICRAILWAYCOACH_H
#define KPUBLICTRANSPORT_UICRAILWAYCOACH_H
#include <KPublicTransport/Vehicle>
namespace KPublicTransport {
/** Methods for parsing UIC railway coach numbers and classification codes.
* @see https://en.wikipedia.org/wiki/UIC_classification_of_railway_coaches
* @see https://en.wikipedia.org/wiki/UIC_wagon_numbers
*/
namespace UicRailwayCoach
{
/** Returns the UIC country code from @p coachNumber. */
QStringView countryCode(QStringView coachNumber);
/** Determine the coach class(es) from a UIC @p coachNumber and/or @p coachClassification.
* Either one of the arguments can be empty, but the most reliable result is returned with both present.
*/
VehicleSection::Classes coachClass(QStringView coachNumber, QStringView coachClassification);
/** Determine the number of decks from a UIC @p coachNumber and/or @p coachClassification.
* Either one of the arguments can be empty, but the most reliable result is returned with both present.
*/
int deckCount(QStringView coachNumber, QStringView coachClassification);
/** Determine coach features from a UIC @p coachNumber and/or @p coachClassification.
* Either one of the arguments can be empty, but the most reliable result is returned with both present.
*/
VehicleSection::Features features(QStringView coachNumber, QStringView coachClassification);
/** Determine the vehicle type from a UIC @p coachNumber and/or @p coachClassification.
* Either one of the arguments can be empty, but the most reliable result is returned with both present.
*/
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification);
}
}
#endif // KPUBLICTRANSPORT_UICRAILWAYCOACH_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment