Group
Extension

Travel-Status-DE-DBWagenreihung/lib/Travel/Status/DE/DBWagenreihung/Group.pm

package Travel::Status::DE::DBWagenreihung::Group;

use strict;
use warnings;
use 5.020;
use utf8;

use parent 'Class::Accessor';
use List::Util qw(uniq);

our $VERSION = '0.18';

Travel::Status::DE::DBWagenreihung::Group->mk_ro_accessors(
	qw(designation name train_no train_type description desc_short destination has_sectors model series start_percent end_percent)
);

# {{{ ICE designations

# Courtesy of https://github.com/marudor/bahn.expert
# cat src/server/coachSequence/TrainNames.ts | perl -nE 'if (m{(\d+): ''([^'']+)''}) { say "$1 => ''$2''," }' | xclip -i

my %ice_name = (
	101  => 'Gießen',
	102  => 'Jever',
	103  => 'Neu-Isenburg',
	104  => 'Fulda',
	105  => 'Offenbach am Main',
	106  => 'Itzehoe',
	107  => 'Plattling',
	108  => 'Lichtenfels',
	110  => 'Gelsenkirchen',
	111  => 'Nürnberg',
	112  => 'Memmingen',
	113  => 'Frankenthal/Pfalz',
	114  => 'Friedrichshafen',
	115  => 'Regensburg',
	116  => 'Pforzheim',
	117  => 'Hof',
	119  => 'Osnabrück',
	120  => 'Lüneburg',
	152  => 'Hanau',
	153  => 'Neumünster',
	154  => 'Flensburg',
	155  => 'Rosenheim',
	156  => 'Heppenheim/Bergstraße',
	157  => 'Landshut',
	158  => 'Gütersloh',
	159  => 'Bad Oldesloe',
	160  => 'Mülheim an der Ruhr',
	161  => 'Bebra',
	162  => 'Geisenheim/Rheingau',
	166  => 'Gelnhausen',
	167  => 'Garmisch-Partenkirchen',
	168  => 'Crailsheim',
	169  => 'Worms',
	171  => 'Heusenstamm',
	172  => 'Aschaffenburg',
	173  => 'Basel',
	174  => 'Zürich',
	175  => 'Nürnberg',
	176  => 'Bremen',
	177  => 'Rendsburg',
	178  => 'Bremerhaven',
	180  => 'Castrop-Rauxel',
	181  => 'Interlaken',
	182  => 'Rüdesheim am Rhein',
	183  => 'Timmendorfer Strand',
	184  => 'Bruchsal',
	185  => 'Freilassing',
	186  => 'Chur',
	187  => 'Mühldorf a. Inn',
	188  => 'Hildesheim',
	190  => 'Ludwigshafen am Rhein',
	201  => 'Rheinsberg',
	202  => 'Wuppertal',
	203  => 'Cottbus/Chóśebuz',
	204  => 'Bielefeld',
	205  => 'Zwickau',
	206  => 'Magdeburg',
	207  => 'Stendal',
	208  => 'Bonn',
	209  => 'Riesa',
	210  => 'Fontanestadt Neuruppin',
	211  => 'Uelzen',
	212  => 'Potsdam',
	213  => 'Nauen',
	214  => 'Hamm (Westf.)',
	215  => 'Bitterfeld-Wolfen',
	216  => 'Dessau',
	217  => 'Bergen auf Rügen',
	218  => 'Braunschweig',
	219  => 'Hagen',
	220  => 'Meiningen',
	221  => 'Lübbenau/Spreewald',
	222  => 'Eberswalde',
	223  => 'Schwerin',
	224  => 'Saalfeld (Saale)',
	225  => 'Oldenburg (Oldb)',
	226  => 'Lutherstadt Wittenberg',
	227  => 'Ludwigslust',
	228  => 'Altenburg',
	229  => 'Templin',
	230  => 'Delitzsch',
	231  => 'Brandenburg an der Havel',
	232  => 'Frankfurt (Oder)',
	233  => 'Ulm',
	234  => 'Minden',
	235  => 'Görlitz',
	236  => 'Jüterbog',
	237  => 'Neustrelitz',
	238  => 'Saarbrücken',
	239  => 'Essen',
	240  => 'Bochum',
	241  => 'Bad Hersfeld',
	242  => 'Quedlinburg',
	243  => 'Bautzen/Budyšin',
	244  => 'Koblenz',
	301  => 'Freiburg im Breisgau',
	302  => 'Hansestadt Lübeck',
	303  => 'Dortmund',
	304  => 'München',
	305  => 'Baden-Baden',
	306  => 'Nördlingen',
	307  => 'Oberhausen',
	308  => 'Murnau am Staffelsee',
	309  => 'Aalen',
	310  => 'Wolfsburg',
	311  => 'Wiesbaden',
	312  => 'Montabaur',
	313  => 'Treuchtlingen',
	314  => 'Bergisch Gladbach',
	315  => 'Singen (Hohentwiel)',
	316  => 'Siegburg',
	317  => 'Recklinghausen',
	318  => 'Münster (Westf.)',
	319  => 'Duisburg',
	320  => 'Weil am Rhein',
	321  => 'Krefeld',
	322  => 'Solingen',
	323  => 'Schaffhausen',
	324  => 'Fürth',
	325  => 'Ravensburg',
	326  => 'Neunkirchen',
	327  => 'Siegen',
	328  => 'Aachen',
	330  => 'Göttingen',
	331  => 'Westerland/Sylt',
	332  => 'Augsburg',
	333  => 'Goslar',
	334  => 'Offenburg',
	335  => 'Konstanz',
	336  => 'Ingolstadt',
	337  => 'Stuttgart',
	351  => 'Herford',
	352  => 'Mönchengladbach',
	353  => 'Neu-Ulm',
	354  => 'Mittenwald',
	355  => 'Tuttlingen',
	357  => 'Esslingen am Neckar',
	358  => 'St. Ingbert',
	359  => 'Leverkusen',
	360  => 'Linz am Rhein',
	361  => 'Celle',
	362  => 'Schwerte (Ruhr)',
	363  => 'Weilheim i. OB',
	1101 => 'Neustadt an der Weinstraße',
	1102 => 'Neubrandenburg',
	1103 => 'Paderborn',
	1104 => 'Erfurt',
	1105 => 'Dresden',
	1107 => 'Pirna',
	1108 => 'Berlin',
	1109 => 'Güstrow',
	1110 => 'Naumburg (Saale)',
	1111 => 'Hansestadt Wismar',
	1112 => 'Freie und Hansestadt Hamburg',
	1113 => 'Hansestadt Stralsund',
	1117 => 'Erlangen',
	1118 => 'Plauen/Vogtland',
	1119 => 'Meißen',
	1125 => 'Arnstadt',
	1126 => 'Leipzig',
	1127 => 'Weimar',
	1128 => 'Reutlingen',
	1129 => 'Kiel',
	1130 => 'Jena',
	1131 => 'Trier',
	1132 => 'Wittenberge',
	1151 => 'Elsterwerda',
	1152 => 'Travemünde',
	1153 => 'Ilmenau',
	1154 => 'Sonneberg',
	1155 => 'Mühlhausen/Thüringen',
	1156 => 'Waren (Müritz)',
	1157 => 'Innsbruck',
	1158 => 'Falkenberg/Elster',
	1159 => 'Passau',
	1160 => 'Markt Holzkirchen',
	1161 => 'Andernach',
	1162 => 'Vaihingen an der Enz',
	1163 => 'Ostseebad Binz',
	1164 => 'Rödental',
	1165 => 'Bad Oeynhausen',
	1166 => 'Bingen am Rhein',
	1167 => 'Traunstein',
	1168 => 'Ellwangen',
	1169 => 'Tutzing',
	1170 => 'Prenzlau',
	1171 => 'Oschatz',
	1172 => 'Bamberg',
	1173 => 'Halle (Saale)',
	1174 => 'Hansestadt Warburg',
	1175 => 'Villingen-Schwenningen',
	1176 => 'Coburg',
	1177 => 'Rathenow',
	1178 => 'Ostseebad Warnemünde',
	1180 => 'Darmstadt',
	1181 => 'Horb am Neckar',
	1182 => 'Mainz',
	1183 => 'Oberursel (Taunus)',
	1184 => 'Kaiserslautern',
	1190 => 'Wien',
	1191 => 'Salzburg',
	1192 => 'Linz',
	1501 => 'Eisenach',
	1502 => 'Karlsruhe',
	1503 => 'Altenbeken',
	1504 => 'Heidelberg',
	1505 => 'Marburg/Lahn',
	1506 => 'Kassel',
	1520 => 'Gotha',
	1521 => 'Homburg/Saar',
	1522 => 'Torgau',
	1523 => 'Hansestadt Greifswald',
	1524 => 'Hansestadt Rostock',
	2853 => 'Nationalpark Sächsische Schweiz',
	2865 => 'Remstal',
	2868 => 'Nationalpark Niedersächsisches Wattenmeer',
	2871 => 'Leipziger Neuseenland',
	2874 => 'Oberer Neckar',
	2875 => 'Magdeburger Börde',
	4103 => 'Allgäu',
	4111 => 'Gäu',
	4114 => 'Dresden Elbland',
	4117 => 'Mecklenburgische Ostseeküste',
	4601 => 'Europa/Europe',
	4602 => 'Euregio Maas-Rhein',
	4603 => 'Mannheim',
	4604 => 'Brussel/Bruxelles',
	4607 => 'Hannover',
	4610 => 'Frankfurt am Main',
	4611 => 'Düsseldorf',
	4651 => 'Amsterdam',
	4652 => 'Arnhem',
	4680 => 'Würzburg',
	4682 => 'Köln',
	4683 => 'Limburg an der Lahn',
	4684 => 'Forbach-Lorraine',
	4685 => 'Schwäbisch Hall',
	4712 => 'Dillingen a.d. Donau',
	4710 => 'Ansbach',
	4717 => 'Paris',
	8007 => 'Rheinland',
	8022 => 'Waldecker Land',
	9006 => 'Martin Luther',
	9018 => 'Freistaat Bayern',
	9025 => 'Nordrhein-Westfalen',
	9026 => 'Zürichsee',
	9028 => 'Freistaat Sachsen',
	9041 => 'Baden-Württemberg',
	9046 => 'Female ICE',
	9050 => 'Metropole Ruhr',
	9202 => 'Schleswig-Holstein',
	9212 => 'Fan-Hauptstadt Hamburg',
	9237 => 'Spree',
	9457 => 'Bundesrepublik Deutschland',
	9481 => 'Rheinland-Pfalz'
);

# }}}

# {{{ Rolling Stock Models

my %model_name = (
	'011'      => [ 'ICE T', 'ÖBB 4011' ],
	'401'      => ['ICE 1'],
	'402'      => ['ICE 2'],
	'403.S1'   => [ 'ICE 3',        'BR 403, 1. Serie' ],
	'403.S2'   => [ 'ICE 3',        'BR 403, 2. Serie' ],
	'403.R'    => [ 'ICE 3',        'BR 403 Redesign' ],
	'406'      => [ 'ICE 3',        'BR 406' ],
	'406.R'    => [ 'ICE 3',        'BR 406 Redesign' ],
	'407'      => [ 'ICE 3 Velaro', 'BR 407' ],
	'408'      => [ 'ICE 3neo',     'BR 408' ],
	'411.S1'   => [ 'ICE T',        'BR 411, 1. Serie' ],
	'411.S2'   => [ 'ICE T',        'BR 411, 2. Serie' ],
	'412'      => ['ICE 4'],
	'415'      => [ 'ICE T', 'BR 415' ],
	'420'      => ['BR 420'],
	'422'      => ['BR 422'],
	'423'      => ['BR 423'],
	'425'      => ['BR 425'],
	'427'      => [ 'FLIRT', 'BR 427' ],
	'428'      => [ 'FLIRT', 'BR 428' ],
	'429'      => [ 'FLIRT', 'BR 429' ],
	'430'      => ['BR 430'],
	'440'      => [ 'Coradia Continental', 'BR 440' ],
	'442'      => [ 'Talent 2',            'BR 442' ],
	'445'      => [ 'Twindexx Vario',      'BR 445' ],
	'446'      => [ 'Twindexx Vario',      'BR 446' ],
	'462'      => [ 'Desiro HC',           'BR 462' ],
	'463'      => [ 'Mireo',               'BR 463' ],
	'475'      => [ 'TGV',                 'BR 475' ],
	'612'      => [ 'RegioSwinger',        'BR 612' ],
	'620'      => [ 'LINT 81',             'BR 620' ],
	'622'      => [ 'LINT 54',             'BR 622' ],
	'631'      => [ 'Link I',              'BR 631' ],
	'632'      => [ 'Link II',             'BR 632' ],
	'633'      => [ 'Link III',            'BR 633' ],
	'640'      => [ 'LINT 27',             'BR 640' ],
	'642'      => [ 'Desiro Classic',      'BR 642' ],
	'643'      => [ 'TALENT',              'BR 643' ],
	'648'      => [ 'LINT 41',             'BR 648' ],
	'IC2.TWIN' => ['IC 2 Twindexx'],
	'IC2.KISS' => ['IC 2 KISS'],
);

my %power_desc = (
	90 => 'mit sonstigem Antrieb',
	91 => 'mit elektrischer Lokomotive',
	92 => 'mit Diesellokomotive',
	93 => 'Hochgeschwindigkeitszug',
	94 => 'Elektrischer Triebzug',
	95 => 'Diesel-Triebzug',
	96 => 'mit speziellen Beiwagen',
	97 => 'mit elektrischer Rangierlok',
	98 => 'mit Diesel-Rangierlok',
	99 => 'Sonderfahrzeug',
);

# }}}

sub new {
	my ( $obj, %opt ) = @_;

	my %json = %{ $opt{json} };

	my $ref = {
		carriages   => $opt{carriages},
		destination => $json{transport}{destination}{name},
		train_type  => $json{transport}{category},
		name        => $json{name},
		line        => $json{transport}{numberwline},
		train_no    => $json{transport}{number},
	};

	if ( $ref->{name} =~ m{ ^ IC[DE] 0* (\d+) $ }x and exists $ice_name{$1} ) {
		$ref->{designation} = $ice_name{$1};
	}

	$ref->{train} = $ref->{train_type} . ' ' . $ref->{train_no};

	$ref->{sectors} = [
		uniq grep { defined }
		  map     { $_->{platformPosition}{sector} } @{ $json{vehicles} // [] }
	];
	if ( @{ $ref->{sectors} } ) {
		$ref->{has_sectors} = 1;
	}

	$ref->{start_percent} = $ref->{carriages}[0]->start_percent;
	$ref->{end_percent}   = $ref->{carriages}[-1]->end_percent;

	bless( $ref, $obj );

	$ref->parse_description;

	return $ref;
}

sub parse_powertype {
	my ($self) = @_;

	my %ml = map { $_ => 0 } ( 90 .. 99 );

	for my $carriage ( $self->carriages ) {

		if ( not $carriage->uic_id or length( $carriage->uic_id ) != 12 ) {
			next;
		}

		my $carriage_type = substr( $carriage->uic_id, 0, 2 );
		if ( $carriage_type < 90 ) {
			next;
		}

		$ml{$carriage_type}++;
	}

	my @likelihood = reverse sort { $ml{$a} <=> $ml{$b} } keys %ml;

	if ( $ml{ $likelihood[0] } == 0 ) {
		return;
	}

	$self->{powertype} = $likelihood[0];
}

sub parse_model {
	my ($self) = @_;

	my %ml = (
		'011'      => 0,
		'401'      => 0,
		'402'      => 0,
		'403.S1'   => 0,
		'403.S2'   => 0,
		'403.R'    => 0,
		'406'      => 0,
		'407'      => 0,
		'408'      => 0,
		'411.S1'   => 0,
		'411.S2'   => 0,
		'412'      => 0,
		'415'      => 0,
		'420'      => 0,
		'422'      => 0,
		'423'      => 0,
		'425'      => 0,
		'427'      => 0,
		'428'      => 0,
		'429'      => 0,
		'430'      => 0,
		'440'      => 0,
		'442'      => 0,
		'445'      => 0,
		'446'      => 0,
		'462'      => 0,
		'463'      => 0,
		'475'      => 0,
		'612'      => 0,
		'620'      => 0,
		'622'      => 0,
		'631'      => 0,
		'632'      => 0,
		'633'      => 0,
		'640'      => 0,
		'642'      => 0,
		'643'      => 0,
		'648'      => 0,
		'IC2.TWIN' => 0,
		'IC2.KISS' => 0,
	);

	my @carriages = $self->carriages;

	for my $carriage (@carriages) {
		if ( not $carriage->model ) {
			next;
		}
		if ( $carriage->model == 401
			or ( $carriage->model >= 801 and $carriage->model <= 804 ) )
		{
			$ml{'401'}++;
		}
		elsif ( $carriage->model == 402
			or ( $carriage->model >= 805 and $carriage->model <= 808 ) )
		{
			$ml{'402'}++;
		}
		elsif ( $carriage->model == 403
			and substr( $carriage->uic_id, 9, 2 ) <= 37 )
		{
			$ml{'403.S1'}++;
		}
		elsif ( $carriage->model == 403
			and substr( $carriage->uic_id, 9, 2 ) > 37 )
		{
			$ml{'403.S2'}++;
		}
		elsif ( $carriage->model == 406 ) {
			$ml{'406'}++;
		}
		elsif ( $carriage->model == 407 ) {
			$ml{'407'}++;
		}
		elsif ( $carriage->model == 408 ) {
			$ml{'408'}++;
		}
		elsif ( $carriage->model == 412 or $carriage->model == 812 ) {
			$ml{'412'}++;
		}
		elsif ( $carriage->model == 411
			and substr( $carriage->uic_id, 9, 2 ) <= 32 )
		{
			$ml{'411.S1'}++;
		}
		elsif ( $carriage->model == 411
			and substr( $carriage->uic_id, 9, 2 ) > 32 )
		{
			$ml{'411.S2'}++;
		}
		elsif ( $carriage->model == 415 ) {
			$ml{'415'}++;
		}
		elsif ( $carriage->model == 420 or $carriage->model == 421 ) {
			$ml{'420'}++;
		}
		elsif ( $carriage->model == 422 or $carriage->model == 432 ) {
			$ml{'422'}++;
		}
		elsif ( $carriage->model == 423 or $carriage->model == 433 ) {
			$ml{'423'}++;
		}
		elsif ( $carriage->model == 425 or $carriage->model == 435 ) {
			$ml{'425'}++;
		}
		elsif ( $carriage->model == 427 or $carriage->model == 827 ) {
			$ml{'427'}++;
		}
		elsif ( $carriage->model == 428 or $carriage->model == 828 ) {
			$ml{'428'}++;
		}
		elsif ( $carriage->model == 429 or $carriage->model == 829 ) {
			$ml{'429'}++;
		}
		elsif ( $carriage->model == 430 or $carriage->model == 431 ) {
			$ml{'430'}++;
		}
		elsif ($carriage->model == 440
			or $carriage->model == 441
			or $carriage->model == 841 )
		{
			$ml{'440'}++;
		}
		elsif ($carriage->model == 442
			or $carriage->model == 443 )
		{
			$ml{'442'}++;
		}
		elsif ($carriage->model == 462
			or $carriage->model == 862 )
		{
			$ml{'462'}++;
		}
		elsif ($carriage->model == 463
			or $carriage->model == 863 )
		{
			$ml{'463'}++;
		}
		elsif ( $carriage->model == 445 ) {
			$ml{'445'}++;
		}
		elsif ( $carriage->model == 446 ) {
			$ml{'446'}++;
		}
		elsif ( $carriage->model == 475 ) {
			$ml{'475'}++;
		}
		elsif ( $carriage->model == 612 ) {
			$ml{'612'}++;
		}
		elsif ( $carriage->model == 620 or $carriage->model == 621 ) {
			$ml{'620'}++;
		}
		elsif ( $carriage->model == 622 ) {
			$ml{'622'}++;
		}
		elsif ( $carriage->model == 631 ) {
			$ml{'631'}++;
		}
		elsif ( $carriage->model == 632 ) {
			$ml{'632'}++;
		}
		elsif ( $carriage->model == 633 ) {
			$ml{'633'}++;
		}
		elsif ( $carriage->model == 640 ) {
			$ml{'640'}++;
		}
		elsif ( $carriage->model == 642 ) {
			$ml{'642'}++;
		}
		elsif ( $carriage->model == 643 or $carriage->model == 943 ) {
			$ml{'643'}++;
		}
		elsif ( $carriage->model == 648 ) {
			$ml{'648'}++;
		}
		elsif ( $self->train_type eq 'IC' and $carriage->model == 110 ) {
			$ml{'IC2.KISS'}++;
		}
		elsif ( $self->train_type eq 'IC' and $carriage->is_dosto ) {
			$ml{'IC2.TWIN'}++;
		}
		elsif ( substr( $carriage->uic_id, 4, 4 ) eq '4011' ) {
			$ml{'011'}++;
		}
	}

	my @likelihood = reverse sort { $ml{$a} <=> $ml{$b} } keys %ml;

	# Less than two carriages are generally inconclusive.
	# Exception: BR 631 (Link I) only has a single carriage
	if (
		$ml{ $likelihood[0] } < 2
		and not($likelihood[0] eq '631'
			and @carriages == 1
			and substr( $carriages[0]->uic_id, 0, 2 ) eq '95' )
	  )
	{
		$self->{subtype} = undef;
	}
	else {
		$self->{subtype} = $likelihood[0];
	}

	if ( $self->{subtype} and $model_name{ $self->{subtype} } ) {
		my @model = @{ $model_name{ $self->{subtype} } };
		$self->{model}  = $model[0];
		$self->{series} = $model[-1];
	}
}

sub parse_description {
	my ($self) = @_;

	$self->parse_powertype;
	$self->parse_model;

	my $short;
	my $ret = q{};

	if ( $self->{model} ) {
		$short = $self->{model};
		$ret .= $self->{model};
	}

	if ( $self->{powertype} and $power_desc{ $self->{powertype} } ) {
		if ( not $ret and $power_desc{ $self->{powertype} } =~ m{^mit} ) {
			$ret = "Zug";
		}
		$ret .= ' ' . $power_desc{ $self->{powertype} };
		$short //= $ret;
		$short =~ s{elektrischer }{E-};
		$short =~ s{[Ll]\Kokomotive}{ok};
	}

	if ( $self->{series} and $self->{series} ne $self->{model} ) {
		$ret .= ' (' . $self->{series} . ')';
	}

	$self->{desc_short}  = $short;
	$self->{description} = $ret;
}

sub name_to_designation {
	my ($self) = @_;

	return %ice_name;
}

sub sectors {
	my ($self) = @_;

	return @{ $self->{sectors} // [] };
}

sub carriages {
	my ($self) = @_;

	return @{ $self->{carriages} // [] };
}

sub TO_JSON {
	my ($self) = @_;

	my %copy = %{$self};

	return {%copy};
}

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.