summaryrefslogtreecommitdiff
path: root/templates
diff options
context:
space:
mode:
Diffstat (limited to 'templates')
-rw-r--r--templates/_error.html.ep5
-rw-r--r--templates/_map_infobox.html.ep73
-rw-r--r--templates/_train_attr.html.ep18
-rw-r--r--templates/_train_details.html.ep446
-rw-r--r--templates/_wagon.html.ep97
-rw-r--r--templates/about.html.ep63
-rw-r--r--templates/app.html.ep161
-rw-r--r--templates/coverage_map.html.ep22
-rw-r--r--templates/exception.html.ep7
-rw-r--r--templates/geostop.html.ep (renamed from templates/geolocation.html.ep)0
-rw-r--r--templates/landingpage.html.ep35
-rw-r--r--templates/layouts/app.html.ep305
-rw-r--r--templates/layouts/legacy.html.ep203
-rw-r--r--templates/multi.html.ep6
-rw-r--r--templates/not_found.html.ep5
-rw-r--r--templates/route_map.html.ep61
-rw-r--r--templates/select_backend.html.ep46
-rw-r--r--templates/single.html.ep6
-rw-r--r--templates/train_details.html.ep5
-rw-r--r--templates/wagen.html.ep75
-rw-r--r--templates/wagenreihung.html.ep152
-rw-r--r--templates/zugbildung_db.html.ep26
22 files changed, 1166 insertions, 651 deletions
diff --git a/templates/_error.html.ep b/templates/_error.html.ep
new file mode 100644
index 0000000..8585f90
--- /dev/null
+++ b/templates/_error.html.ep
@@ -0,0 +1,5 @@
+<div class="error"><strong>Fehler:</strong>
+<p style="font-family: Monospace;">
+%= $error
+</p>
+</div>
diff --git a/templates/_map_infobox.html.ep b/templates/_map_infobox.html.ep
new file mode 100644
index 0000000..16625f5
--- /dev/null
+++ b/templates/_map_infobox.html.ep
@@ -0,0 +1,73 @@
+<div class="container" id="infobox" style="margin-top: 1ex; margin-bottom: 1ex;">
+<div class="journey" id="jdata"
+data-req="<%= stash('ajax_req') =~ s{#}{%23}gr %>"
+data-route="<%= stash('ajax_route') %>"
+data-poly="<%= stash('ajax_polyline') %>"
+>
+ Fahrt
+ % if (stash('train_no')) {
+ <strong><%= stash('train_no') %></strong>
+ % }
+ von <strong><%= stash('origin')->{name} %></strong>
+ nach <strong><%= stash('destination')->{name} %></strong>
+</div>
+% if (my $next = stash('next_stop')) {
+ <div class="nextstop">
+ % if ($next->{type} eq 'present' and $next->{station}{dep} and $next->{station}{arr}) {
+ Aufenthalt in <strong><%= $next->{station}{name} %></strong>
+ % if ($next->{station}{platform}) {
+ an Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ bis <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{dep_delay}) {
+ %= sprintf('(%+d)', $next->{station}{dep_delay})
+ % }
+ % }
+ % elsif ($next->{type} eq 'present' and $next->{station}{dep}) {
+ Abfahrt in <strong><%= $next->{station}{name} %></strong>
+ % if ($next->{station}{platform}) {
+ von Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ um <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{dep_delay}) {
+ %= sprintf('(%+d)', $next->{station}{dep_delay})
+ % }
+ % }
+ % elsif ($next->{type} eq 'present' and $next->{station}{arr}) {
+ Endstation erreicht um
+ <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % if ($next->{station}{arr_delay}) {
+ %= sprintf('(%+d)', $next->{station}{arr_delay})
+ % }
+ % }
+ % elsif ($next->{type} eq 'present' and $next->{station}{arr}) {
+ Zug steht in
+ <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % }
+ % elsif ($next->{type} eq 'next' and $next->{station}{arr}) {
+ Nächster Halt:
+ <strong><%= $next->{station}{name} %></strong>
+ um <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{arr_delay}) {
+ %= sprintf('(%+d)', $next->{station}{arr_delay})
+ % }
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % }
+ % elsif ($next->{type} eq 'next') {
+ Nächster Halt:
+ <strong><%= $next->{station}{name} %></strong>
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % }
+ </div>
+% }
+</div>
diff --git a/templates/_train_attr.html.ep b/templates/_train_attr.html.ep
new file mode 100644
index 0000000..1b40d12
--- /dev/null
+++ b/templates/_train_attr.html.ep
@@ -0,0 +1,18 @@
+% if ($attr->{series}) {
+ <span>BR <%= $attr->{series} %></span>
+% }
+% if ($attr->{vmax}) {
+ <span><%= $attr->{vmax} %> km/h</span>
+% }
+% if ($attr->{length}) {
+ <span>⇤ <%= $attr->{length} %>m ⇥</span>
+% }
+% if ($attr->{weight}) {
+ <span><%= $attr->{weight} %>t</span>
+% }
+% if ($attr->{brakingPercentage}) {
+ <span>λ=<%= $attr->{brakingPercentage} %></span>
+% }
+% if ($with_station and $attr->{range}[0]) {
+ <span>ab <%= $attr->{range}[0] %></span>
+% }
diff --git a/templates/_train_details.html.ep b/templates/_train_details.html.ep
index 444fe9a..2c18da2 100644
--- a/templates/_train_details.html.ep
+++ b/templates/_train_details.html.ep
@@ -2,12 +2,10 @@
<div>
% if ($departure->{train_no} or $departure->{train_line}) {
<span class="train-line <%= $linetype %>"><%= $departure->{train_type} %>
- <%= $departure->{train_line} // $departure->{train_no} %></span>
+ %= $departure->{train_line} // $departure->{train_no}
+ </span>
<span class="train-no"><%= $departure->{train_line} ? $departure->{train_no} : q{} %></span>
% }
-% else {
- <span class="train-line"><%= $departure->{train} // q{???} %></span>
-% }
</div>
<div>
% if ($departure->{origin}) {
@@ -16,81 +14,189 @@
% }
<span class="train-dest"><%= $departure->{destination} // q{???} %></span>
</div>
-% if ($departure->{is_cancelled}) {
- <div class="minfo">Fahrt fällt aus</div>
-% }
-% elsif (defined $departure->{delay} and $departure->{delay} > 0) {
- <div class="minfo">+<%= $departure->{delay} %>
-% if ($departure->{departure_is_cancelled}) {
- (endet hier)
+ </div> <!-- mheader -->
+ <div class="mfooter">
+ <div class="dataline">
+ <div>
+ <div class="arrival <%= $departure->{arrival_hidden} ? 'timehidden' : q{} %>">
+% if ($departure->{is_cancelled} and $departure->{sched_arrival}) {
+ <span class="minfo">An: ––:––</span><br/>Plan: <%= $departure->{sched_arrival} %>
% }
- </div>
-% }
+% elsif ($departure->{arrival_is_cancelled}) {
+ <span class="minfo">Beginnt hier</span><br/>Plan: <%= $departure->{sched_arrival} %>
+% }
+% elsif ($departure->{arrival}) {
+% if ($departure->{arrival} ne $departure->{sched_arrival}) {
+ % if (($departure->{arrival_delay} // 0) < 0) {
+ An: <span class="undelay"><%= $departure->{arrival} %></span>
+ % }
+ % else {
+ An: <span class="minfo"><%= $departure->{arrival} %></span>
+ % }
+ <br/>Plan: <%= $departure->{sched_arrival} %>
+% }
+% else {
+ An: <%= $departure->{arrival} %>
+% }
+% }
+% elsif ($departure->{sched_arrival}) {
+ An: <%= $departure->{sched_arrival} %>
+% }
+% elsif ($departure->{prep_time}) {
+ Ein: <%= $departure->{prep_time} %>
+% }
+% if ($departure->{tz_offset} and $departure->{local_sched_arr}) {
+ <br/>Lokal: <%= $departure->{local_sched_arr}->strftime('%H:%M') %>
+% }
+ </div>
+ </div>
+ <div>
+ <div class="platform">
% if (@{$departure->{replaced_by}}) {
% for my $replacement (@{$departure->{replaced_by}}) {
- <span class="replaced">Ersatzzug: <%= $replacement %></span>
+ <span class="replaced">Ersatzfahrt: <a href="/z/<%= $replacement %>/<%= stash('station_name') // q{} %>"><%= $replacement %></a></span><br/>
% }
% }
% if (@{$departure->{replacement_for}}) {
% for my $replacement (@{$departure->{replacement_for}}) {
- <span class="replacement">Ersatzzug für <%= $replacement %></span>
+ <span class="replacement">Ersatzfahrt für <a href="/z/<%= $replacement %>/<%= stash('station_name') // q{} %>"><%= $replacement %></a></span><br/>
% }
% }
- </div> <!-- mheader -->
- <div class="mfooter">
- <div class="platforminfo">
-% if ($departure->{scheduled_platform} or $departure->{platform}) {
- Gleis <%= $departure->{scheduled_platform} // $departure->{platform} %>
-% }
-% if ($departure->{scheduled_platform} and $departure->{platform}
-% and $departure->{scheduled_platform} ne $departure->{platform}) {
- (heute Gleis <%= $departure->{platform} %>)
-% }
-% elsif ($departure->{changed_platform}) {
- (Gleiswechsel)
-% }
- </div> <!-- platforminfo -->
-% if (not $departure->{is_cancelled}) {
- <div class="timeinfo">
-% if ($departure->{sched_arrival}) {
- Ankunft: <%= $departure->{sched_arrival} %>
-% if ($departure->{arrival} and $departure->{arrival} ne $departure->{sched_arrival}) {
- (heute <%= $departure->{arrival} %>)
-% }
- <br/>
+% if ($departure->{arrival_is_cancelled} and $departure->{departure_is_cancelled}) {
+ <span class="minfo">Fahrt fällt aus</span>
+% }
+% else {
+% my $left = '';
+% my $right = '';
+% if ($departure->{wr_direction} and $departure->{wr_direction} =~ m{l}) {
+% $left = '◀ ';
+% }
+% elsif ($departure->{wr_direction} and $departure->{wr_direction} =~ m{r}) {
+% $right = ' ▶';
+% }
+% if ($departure->{scheduled_platform} and $departure->{platform}
+% and $departure->{scheduled_platform} ne $departure->{platform}) {
+ <span class="minfo"><%= $left %>Gleis <%= $departure->{platform} %><%= $right %></span>
+% }
+% elsif ($departure->{scheduled_platform} or $departure->{platform}) {
+ <%= $left %>Gleis <%= $departure->{platform} // $departure->{scheduled_platform} %><%= $right %>
+% }
+% }
+% if ($departure->{arrival_hidden} and not $departure->{prep_time}) {
+ <br/><span class="timehidden">Nur Einstieg</span>
+% }
+% if ($departure->{departure_hidden}) {
+ <br/><span class="timehidden">Nur Ausstieg</span>
+% }
+ </div>
+ </div>
+ <div>
+ <div class="departure <%= $departure->{departure_hidden} ? 'timehidden' : q{} %>">
+% if ($departure->{is_cancelled} and $departure->{sched_departure}) {
+ <span class="minfo">Ab: ––:––</span><br/>Plan: <%= $departure->{sched_departure} %>
+% }
+% elsif ($departure->{departure_is_cancelled}) {
+ <span class="minfo">Endet hier</span><br/>Plan: <%= $departure->{sched_departure} %>
% }
-% if ($departure->{sched_departure}) {
- Abfahrt: <%= $departure->{sched_departure} %>
-% if ($departure->{departure} and $departure->{departure} ne $departure->{sched_departure}) {
- (heute <%= $departure->{departure} %>)
+% elsif ($departure->{departure}) {
+% if ($departure->{departure} ne $departure->{sched_departure}) {
+ Ab: <span class="minfo"><%= $departure->{departure} %></span>
+ <br/>Plan: <%= $departure->{sched_departure} %>
% }
- <br/>
+% else {
+ Ab: <%= $departure->{departure} %>
+% }
+% }
+% elsif ($departure->{sched_departure}) {
+ Ab: <%= $departure->{sched_departure} %>
+% }
+% if ($departure->{tz_offset} and $departure->{local_sched_dep}) {
+ <br/>Lokal: <%= $departure->{local_sched_dep}->strftime('%H:%M') %>
+% }
+ </div>
+ </div>
+ </div> <!-- dataline -->
+% if (my $wr = $departure->{wr}) {
+ <div class="wagonorder-preview">
+% my $left = defined $wr->direction ? $wr->direction == 100 ? q{} : '←' : q{};
+% my $right = defined $wr->direction ? $wr->direction == 100 ? '→' : q{} : q{};
+% if ($departure->{wr_direction} and $departure->{wr_direction} =~ m{l}) {
+% $left = '◀';
+% $right = q{};
% }
-% if (not ($departure->{sched_arrival} or $departure->{sched_departure})) {
- Abfahrt: <%= $departure->{time} %>
-% if ($departure->{delay}) {
- (heute +<%= $departure->{delay} %>)
+% elsif ($departure->{wr_direction} and $departure->{wr_direction} =~ m{r}) {
+% $left = q{};
+% $right = '▶';
+% }
+ <a href="/carriage-formation?<%= $departure->{wr_link} %>&amp;e=<%= $departure->{wr_direction} // '' %>">
+ %= $left
+ % for my $entry ((defined $departure->{wr_direction_num} and $departure->{wr_direction_num} != $wr->direction) ? reverse @{$departure->{wr_preview} // []} : @{$departure->{wr_preview} // []}) {
+ % if ($entry->[1]) {
+ <span class="<%= $entry->[1] %>"><%= $entry->[0] %></span>
+ % }
+ % else {
+ %= $entry->[0]
+ % }
+ % }
+ %= $right
+ </a>
+ </div>
+% }
+ <div class="verbose">
+% if ($departure->{trip_id}) {
+% if (stash('station_name')) {
+ <a class="smallbutton" href="/map/<%= $departure->{trip_id} =~ s{#}{%23}gr %>/<%= $departure->{train_line} || 0 %>?from=<%= stash('station_name') %>&amp;dbris=<%= param('dbris') %>&amp;efa=<%= param('efa') // q{} %>&amp;hafas=<%= param('hafas') // q{} %>"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
+% }
+% else {
+ <a class="smallbutton" href="/map/<%= $departure->{trip_id} =~ s{#}{%23}gr %>/<%= $departure->{train_line} || 0 %>?dbris=<%= param('dbris') %>&amp;efa=<%= param('efa') // q{} %>&amp;hafas=<%= param('hafas') // q{} %>"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
% }
% }
- </div> <!-- timeinfo -->
% if ($departure->{wr_link}) {
+ <a class="smallbutton" href="/carriage-formation?<%= $departure->{wr_link} %>&amp;e=<%= $departure->{wr_direction} // '' %>"><i class="material-icons" aria-hidden="true">train</i> <%= $departure->{wr_text} || 'Wagen' %>
+ </a>
+% }
+% if ($departure->{trip_id} and param('dbris') and param('dbris') eq 'bahn.de') {
+ <a class="smallbutton" href="https://bahn.expert/details/x/h/<%= Mojo::Util::url_escape( $departure->{trip_id} ) %>"><img src="/static/icons/bahn-expert.svg">Details</a>
+% } elsif ($departure->{train_type} and $departure->{train_no} and (not param('hafas') or param('hafas') eq 'DB')) {
+ <a class="smallbutton" href="https://bahn.expert/details/<%= $departure->{train_type} %>%20<%= $departure->{train_no} %>/<%= ($departure->{date} // DateTime->now(time_zone => 'Europe/Berlin'))->iso8601 %>?evaNumberAlongRoute=<%= $departure->{eva} %>"><img src="/static/icons/bahn-expert.svg">Details</a>
+% }
+% for my $link (@{$departure->{links}}) {
+ <a class="smallbutton" href="<%= $link->[1] %>"><i class="material-icons" aria-hidden="true">warning</i> <%= $link->[0] %></a>
+% }
+ </div>
+
+% if (not $departure->{departure_is_cancelled}) {
+% if (my $u = $departure->{utilization}) {
<div class="verbose">
- <a href="/_wr/<%= $departure->{train_no} %>/<%= $departure->{wr_link} %>">Wagenreihung</a>
+% my ($text, $icon1, $icon2) = utilization_icon($u);
+ <%= $text %><span style="padding-right: 0.5em;">.</span> 1. <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon1 %></i> 2. <i class="material-icons" aria-hidden="true" style="vertical-align: bottom;"><%= $icon2 %></i>
</div>
% }
-% if ($departure->{trip_id}) {
+% elsif (my $o = $departure->{occupancy}) {
<div class="verbose">
- <a href="/map/<%= $departure->{trip_id} %>/<%= $departure->{train_line} // 0 %>">Karte</a>
+% my ($text, $icon) = occupancy_icon($o);
+ <%= $text %><span style="padding-right: 0.5em;">.</span> <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon %></i></i>
</div>
% }
-
% }
+%
+% if ($departure->{missing_realtime}) {
+ <div class="verbose">
+ <i class="material-icons no-realtime" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;">gps_off</i> Echtzeitdaten fehlen. Ob die Zugfahrt wie im Fahrplan vorgesehen stattfindet, ist nicht bekannt.
+ </div>
+% }
+% elsif ($departure->{no_realtime_yet}) {
+ <div class="verbose">
+ <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;">gps_off</i> Für diese Zugfahrt sind derzeit nur Fahrplandaten bekannt.
+ </div>
+% }
+
% if ($departure->{moreinfo} and @{$departure->{moreinfo}}) {
- Meldungen:
- <ul>
+ Meldungen
+ <ul class="messages">
% for my $pair (@{$departure->{moreinfo}}) {
<li>
-% if ($pair->[0]->isa('DateTime')) {
+% if (ref($pair->[0]) eq 'DateTime') {
% if ($pair->[0]->day != $dt_now->day) {
% $pair->[0]->set_locale('de_DE');
%= $pair->[0]->strftime('%a %H:%M')
@@ -98,13 +204,22 @@
% else {
%= $pair->[0]->strftime('%H:%M')
% }
+ <span class="reason">
+%= $pair->[1]
+ </span>
% }
% else {
+% if ($pair->[1]{icon}) {
+ <i class="material-icons"><%= $pair->[1]{icon} %></i>
+% }
%= $pair->[0]
+% if (length($pair->[0]) > 25) {
+ <br/>
+% }
+ <span class="reason">
+%= $pair->[1]{text}
+ </span>
% }
- <span class="reason">
-%= $pair->[1]
- </span>
</li>
% }
% if ($departure->{route_info}) {
@@ -113,61 +228,188 @@
</ul>
% }
% if ($departure->{route_pre_diff} and $departure->{route_post_diff}) {
-% if (@{$departure->{route_pre_diff}}) {
- Von:
- <ul class="mroute">
-% for my $stop (@{$departure->{route_pre_diff}}) {
- <li>
- <a href="/<%= $stop->{name} %>#<%= $departure->{train_type} . $departure->{train_no} %>" class="
-% if ($stop->{isAdditional}) {
- additional-stop
-% }
-% elsif ($stop->{isCancelled}) {
- cancelled-stop
-% }
-% elsif ($self->is_important($stop->{name})) {
- important-stop
-% }
-% else {
- generic-stop
-% }
-% if ($stop->{rt_dep}) {
- "><%= $stop->{sched_dep}->strftime('%H:%M') %> (heute <%= $stop->{rt_dep}->strftime('%H:%M') %>) <%= $stop->{name} %></a>
-% }
-% else {
- "><%= $stop->{sched_dep} ? $stop->{sched_dep}->strftime('%H:%M') : q{} %> <%= $stop->{name} %></a>
-% }
+% if ($departure->{date}) {
+ Fahrtverlauf am
+% if (stash('train') !~ m{[|]}) {
+ <a href="<%= url_for('train', train => stash('train'))->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas'), date => $departure->{date}->clone->subtract(days => 1)->strftime('%d.%m.%Y'), highlight => param('highlight') // stash('station')}) %>">◀</a>
+% }
+%= $departure->{date}->strftime('%d.%m.%Y')
+% if (stash('train') !~ m{[|]}) {
+ <a href="<%= url_for('train', train => stash('train'))->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas'), date => $departure->{date}->clone->add(days => 1)->strftime('%d.%m.%Y'), highlight => param('highlight') // stash('station')}) %>">▶</a>
+% }
+% }
+ <ul class="mroute">
+% for my $stop (@{$departure->{route_pre_diff}}) {
+% if ($stop->{is_annotated} and $stop->{prod_name}) {
+ <li class="annotation">
+% if ($stop->{prod_name}) {
+%= $stop->{prod_name}
+% }
+% if ($stop->{direction}) {
+ → <%= $stop->{direction} %>
+% }
+% if ($stop->{operator}) {
+ (<%= $stop->{operator} %>)
+% }
</li>
% }
- </ul> <!-- mroute -->
-% }
-% if (@{$departure->{route_post_diff}}) {
- Nach:
- <ul class="mroute">
-% for my $stop (@{$departure->{route_post_diff}}) {
- <li>
- <a href="/<%= $stop->{name} %>#<%= $departure->{train_type} . $departure->{train_no} %>" class="
-% if ($stop->{isAdditional}) {
- additional-stop
+ <li class="<%= $stop->{isPast} ? 'past-stop' : 'future-stop' %>">
+ <a href="<%= url_for('station', station => $stop->{eva} // $stop->{name})->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), dbris => param('dbris'), efa => param('efa'), hafas => param('hafas')}) %>#<%= ((param('dbris') or param('hafas')) and $departure->{trip_id}) ? ($departure->{trip_id} =~ s{[ #|]}{x}gr) : (($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x})) %>" class="
+% if ($stop->{isAdditional}) {
+ additional-stop
+% }
+% elsif ($stop->{isCancelled}) {
+ cancelled-stop
+% }
+% elsif ($self->is_important($stop->{name})) {
+ important-stop
+% }
+% else {
+ generic-stop
+% }
+% if (($stop->{rt_dep} and $stop->{dep_delay}) or (not $stop->{rt_dep} and $stop->{rt_arr} and $stop->{arr_delay})) {
+ "><span class="time-sched-only"><%= ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') %></span> <span class="time-delayed"><%= ($stop->{rt_dep} // $stop->{rt_arr})->strftime('%H:%M') %></span>
+% }
+% elsif (($stop->{rt_dep} and defined $stop->{dep_delay}) or (not $stop->{rt_dep} and $stop->{rt_arr} and defined $stop->{arr_delay})) {
+ "><span class="time-sched-ontime"><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %></span>
+% }
+% else {
+ "><span class="time-sched"><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %></span>
+% }
+% if ($stop->{tz_offset} and $stop->{local_dt_da}) {
+ (lokal <%= $stop->{local_dt_da}->strftime('%H:%M') %>)
+% }
+ <%= $stop->{name} %></a>
+% if ($stop->{load}{FIRST} or $stop->{load}{SECOND}) {
+% my ($text, $icon1, $icon2) = utilization_icon([$stop->{load}{FIRST}, $stop->{load}{SECOND}]);
+ % if ($icon1 ne 'help_outline') {
+ <i class="material-icons" aria-hidden="true"><%= $icon1 %></i>
+ % }
+ <i class="material-icons" aria-hidden="true"><%= $icon2 %></i>
+% }
+ </li>
+% }
+% if (stash('station_name')) {
+% if ($departure->{is_annotated} and $departure->{prod_name}) {
+ <li class="annotation">
+% if ($departure->{prod_name}) {
+%= $departure->{prod_name}
+% }
+% if ($departure->{direction}) {
+ → <%= $departure->{direction} %>
+% }
+% if ($departure->{operator}) {
+ (<%= $departure->{operator} %>)
+% }
+ </li>
+% }
+ <li class="<%= $departure->{is_cancelled} ? 'cancelled-stop' : q{} %> <%= $departure->{isPast} ? 'past-stop' : 'future-stop' %>">
+% if ($departure->{departure} and $departure->{sched_departure} and $departure->{departure} ne $departure->{sched_departure}) {
+ <span class="time-sched-only"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %></span><span class="time-delayed">
% }
-% elsif ($stop->{isCancelled}) {
- cancelled-stop
+% elsif ($departure->{departure} and $departure->{sched_departure} and $departure->{departure} eq $departure->{sched_departure} and not $departure->{no_realtime_yet}) {
+ <span class="time-sched-ontime">
% }
-% elsif ($self->is_important($stop->{name})) {
- important-stop
+% elsif ($departure->{arrival} and $departure->{sched_arrival} and $departure->{arrival} ne $departure->{sched_arrival}) {
+ <span class="time-sched-only"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %></span><span class="time-delayed">
+% }
+% elsif ($departure->{arrival} and $departure->{sched_arrival} and $departure->{arrival} eq $departure->{sched_arrival} and not $departure->{no_realtime_yet}) {
+ <span class="time-sched-ontime">
% }
% else {
- generic-stop
+ <span class="time-sched">
% }
-% if ($stop->{rt_arr}) {
- "><%= $stop->{sched_arr}->strftime('%H:%M') %> (heute <%= $stop->{rt_arr}->strftime('%H:%M') %>) <%= $stop->{name} %></a>
+%= $departure->{departure} // $departure->{arrival} // $departure->{sched_departure} // $departure->{sched_arrival} // q{}
+ </span>
+% if ($departure->{tz_offset} and $departure->{local_dt_da}) {
+ (lokal <%= $departure->{local_dt_da}->strftime('%H:%M') %>)
% }
-% else {
- "><%= $stop->{sched_arr} ? $stop->{sched_arr}->strftime('%H:%M') : q{} %> <%= $stop->{name} %></a>
+ <strong><%= stash('station_name') %></strong>
+% if (my $u = $departure->{utilization}) {
+% my ($text, $icon1, $icon2) = utilization_icon($u);
+ % if ($icon1 ne 'help_outline') {
+ <i class="material-icons" aria-hidden="true"><%= $icon1 %></i>
+ % }
+ <i class="material-icons" aria-hidden="true"><%= $icon2 %></i>
% }
+ </li>
+% }
+% for my $stop (@{$departure->{route_post_diff}}) {
+% if ($stop->{is_annotated} and $stop->{prod_name}) {
+ <li class="annotation">
+% if ($stop->{prod_name}) {
+%= $stop->{prod_name}
+% }
+% if ($stop->{direction}) {
+ → <%= $stop->{direction} %>
+% }
+% if ($stop->{operator}) {
+ (<%= $stop->{operator} %>)
+% }
</li>
% }
- </ul> <!-- mroute -->
-% }
+ <li class="<%= $stop->{isPast} ? 'past-stop' : 'future-stop' %>">
+ <a href="<%= url_for('station', station => $stop->{eva} // $stop->{name})->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), dbris => param('dbris'), efa => param('efa'), hafas => param('hafas')}) %>#<%= ((param('dbris') or param('hafas')) and $departure->{trip_id}) ? ($departure->{trip_id} =~ s{[ #|]}{x}gr) : (($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x})) %>" class="
+% if ($stop->{isAdditional}) {
+ additional-stop
+% }
+% elsif ($stop->{isCancelled}) {
+ cancelled-stop
+% }
+% elsif ($self->is_important($stop->{name})) {
+ important-stop
+% }
+% else {
+ generic-stop
+% }
+% if (($stop->{rt_arr} and $stop->{arr_delay}) or (not $stop->{rt_arr} and $stop->{rt_dep} and $stop->{dep_delay})) {
+ "><span class="time-sched-only"><%= ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') %></span> <span class="time-delayed"><%= ($stop->{rt_arr} // $stop->{rt_dep})->strftime('%H:%M') %></span>
+% }
+% elsif (($stop->{rt_arr} and defined $stop->{arr_delay}) or (not $stop->{rt_arr} and $stop->{rt_dep} and defined $stop->{dep_delay})) {
+ "><span class="time-sched-ontime"><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %></span>
+% }
+% else {
+ "><span class="time-sched"><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %></span>
+% }
+% if ($stop->{tz_offset} and $stop->{local_dt_ad}) {
+ (lokal <%= $stop->{local_dt_ad}->strftime('%H:%M') %>)
+% }
+ <%= $stop->{name} %></a>
+% if ($stop->{load}{FIRST} or $stop->{load}{SECOND}) {
+% my ($text, $icon1, $icon2) = utilization_icon([$stop->{load}{FIRST}, $stop->{load}{SECOND}]);
+ % if ($icon1 ne 'help_outline') {
+ <i class="material-icons" aria-hidden="true"><%= $icon1 %></i>
+ % }
+ <i class="material-icons" aria-hidden="true"><%= $icon2 %></i>
+% }
+ </li>
+% }
+ </ul> <!-- mroute -->
+% }
+% if ($departure->{operators} and @{$departure->{operators} // []}) {
+ <div class="details">Betrieb: <%= join(q{, }, @{ $departure->{operators} // [] } ) %></div>
+% }
+% if ($departure->{details} and @{$departure->{details}}) {
+ <div class="details">Details:
+ <ul>
+% for my $pair (@{$departure->{details}}) {
+ <li>
+% if ($pair->[1]{icon}) {
+ <i class="material-icons"><%= $pair->[1]{icon} %></i>
+% }
+%= $pair->[0]
+% if (length($pair->[0]) > 25) {
+ <br/>
+% }
+ <span class="reason">
+%= $pair->[1]{text}
+ </span>
+ </li>
+% }
+% if ($departure->{route_info}) {
+ <li><%= $departure->{route_info} %></li>
+% }
+ </ul>
+ </div>
% }
</div> <!-- mfooter -->
diff --git a/templates/_wagon.html.ep b/templates/_wagon.html.ep
new file mode 100644
index 0000000..dccecc0
--- /dev/null
+++ b/templates/_wagon.html.ep
@@ -0,0 +1,97 @@
+% my $bg = '';
+% my $extra_class = '';
+% if ($wagon->has_first_class) {
+% $extra_class .= ' firstclass';
+% }
+% if ($wagon->is_locomotive or $wagon->is_powercar) {
+% $extra_class .= ' powercar';
+% }
+% if ($wagon->is_closed) {
+% $extra_class .= ' closed';
+% }
+% if ($group->train_no ne $train_no) {
+% $extra_class .= ' nondestwagon';
+% }
+ <div class="wagon <%= $extra_class %>" style="
+ top: <%= $wagon->start_percent %>%; bottom: <%= 100 - $wagon->end_percent %>%; <%= $bg %>">
+% if ($wagon->is_locomotive or $wagon->is_powercar) {
+% }
+% elsif ($wagon->is_closed) {
+ X
+% }
+% else {
+%= $wagon->number // q{}
+% if ($wagon->has_wheelchair_space) {
+ <i class="material-icons" style="font-size: 20px;">accessible</i>
+% }
+% if ($wagon->has_bistro) {
+ <i class="material-icons">restaurant</i>
+% }
+% if ($wagon->has_quiet_zone) {
+ <i class="tiny material-icons">volume_off</i>
+% }
+% if ($wagon->has_family_zone) {
+ <i class="material-icons">people</i>
+% }
+% if ($wagon->has_bahn_comfort) {
+ <i class="material-icons">star</i>
+% }
+% }
+ <div class="direction">
+% if (not defined $wr->direction) {
+% }
+% elsif ($wr->direction == 100) {
+ <i class="material-icons">arrow_downward</i>
+% }
+% else {
+ <i class="material-icons">arrow_upward</i>
+% }
+ </div>
+ </div>
+ <div class="details" style="
+ top: <%= $wagon->start_percent %>%; bottom: <%= 100 - $wagon->end_percent %>%;">
+% if ($exit_dir ne 'right') {
+% if (my $img = wagon_image($wr->train_type // '?', $wagon->type, $wagon->uic_id)) {
+ <a class="type" href="/w/<%= $img %>?n=<%= $wagon->number // '' %>&amp;s=<%= $wagon->section %>&amp;r=<%= $wref %>"><%= $wagon->type %></a>
+% }
+% else {
+ <span class="type">
+%= $wagon->type
+ </span>
+% }
+% }
+% my $uic_id = $wagon->uic_id;
+% if (length($uic_id) != 12 and length($uic_id) != 14) {
+ <span class="uicunknown"><%= $uic_id %></span>
+% }
+% elsif (substr($uic_id, 0, 2) >= 90) {
+ <span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic5"><%= substr($uic_id, 4, 1) %></span><span class="uictype"><%= substr($uic_id, 5, 3) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
+% }
+% else {
+ <span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic56"><%= substr($uic_id, 4, 2) %></span><span class="uic78"><%= substr($uic_id, 6, 2) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
+% }
+% if ($exit_dir eq 'right') {
+% if (my $img = wagon_image($wr->train_type // '?', $wagon->type, $wagon->uic_id)) {
+ <a class="type" href="/w/<%= $img %>?n=<%= $wagon->number // '' %>&amp;s=<%= $wagon->section %>&amp;r=<%= $wref %>"><%= $wagon->type %></a>
+% }
+% else {
+ <span class="type">
+%= $wagon->type
+ </span>
+% }
+% }
+% if ($multi and $first) {
+ <br/>
+ <span class="groupno">
+% if (scalar $wr->train_numbers > 1) {
+ <%= $group->train_type %> <%= $group->train_no %>
+% }
+% if (scalar $wr->destinations > 1) {
+ → <%= $group->destination %>
+% }
+ </span>
+ % if ($multi and $group->desc_short) {
+ <span class="grouptype"><%= $group->desc_short %></span>
+% }
+% }
+ </div>
diff --git a/templates/about.html.ep b/templates/about.html.ep
index 241f706..3bf8295 100644
--- a/templates/about.html.ep
+++ b/templates/about.html.ep
@@ -1,30 +1,51 @@
<div class="container">
<p>
- <a href="https://finalrewind.org/projects/db-fakedisplay/">db-infoscreen</a>
- v<%= stash('version') // '???' %><br/>
- Entwickelt von <a href="https://twitter.com/derfnull">@derfnull</a><br/>
- <br/>
- Backends:<br/>
- Abfahrtstafel: <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
- v<%= $Travel::Status::DE::IRIS::VERSION %><br/>
- Abfahrtstafel (Legacy): <a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
- v<%= $Travel::Status::DE::HAFAS::VERSION %><br/>
- Meldungen und Ankunft/Abfahrt an Unterwegshalten: DB HAFAS<br/>
- Wagenreihung: <a href="https://finalrewind.org/projects/Travel-Status-DE-DBWagenreihung/">Travel::Status::DE::DBWagenreihung</a> unreleased<br/>
- Karte: <a href="https://2.db.transport.rest/">transport.rest</a> von <a href="https://github.com/derhuerst">@derhuerst</a><br/>
- <br/>
- Grundlage der <a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellendaten</a>
+ DBF ist ein inoffizieller Abfahrtsmonitor für Nah-, Regional- und Fernverkehr in Deutschland und Umgebung.
+ Die Fahrten in der Übersicht verlinken je eine Detailseite mit Unterwegshalten, Meldungen und Kartendarstellung.
+ Bei HAFAS-Backends ist zusätzlich die Suche nach spezifischen Fahrten möglich.
+ </p>
+ <p>
+ Der <a href="<%= app->config->{'source_url'} %>">Quelltext</a> steht unter der <a href="https://git.finalrewind.org/db-fakedisplay/tree/COPYING">GNU AGPL v3</a> als Open Source zur Verfügung. © 2011 – 2024 <a href="https://finalrewind.org">derf</a>.
+ % if (my $issue_url = app->config->{'issue_url'}) {
+ Fehlermeldungen bitte via
+ <a href="<%= $issue_url %>">Issue Tracker</a>.
+ % }
+ Alle von DBF referenzierten Informationen können auch direkt per CLI im Text- oder JSON-Format abgerufen werden – die unten verlinkten Backends beinhalten entsprechende Anwendungen.
+ </p>
+ <p>
+ Diese Installation nutzt
+ <strong>DBF v<%= stash('version') // '???' %></strong> mit folgenden Backends:
+ <ul>
+ <li>Innerdeutscher Regional- und Fernverkehr: DB IRIS via <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
+ <strong>v<%= $Travel::Status::DE::IRIS::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: bahn.de via <a href="https://finalrewind.org/projects/Travel-Status-DE-DBRIS/">Travel::Status::DE::DBRIS</a>
+ <strong>v<%= $Travel::Status::DE::DBRIS::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: EFA via <a href="https://finalrewind.org/projects/Travel-Status-DE-VRR/">Travel::Status::DE::EFA</a>
+ <strong>v<%= $Travel::Status::DE::EFA::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: HAFAS via <a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
+ <strong>v<%= $Travel::Status::DE::HAFAS::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: MOTIS via <a href="https://finalrewind.org/projects/Travel-Status-MOTIS/">Travel::Status::MOTIS</a>
+ <strong>v<%= $Travel::Status::MOTIS::VERSION %></strong></li>
+ </ul>
+ </p>
+ <p>
+ Verwendete Ressourcen:
+ <ul>
+ <li><a href="/_backend">HAFAS-Backends</a> via <a href="https://github.com/public-transport/transport-apis">transport-apis</a>, CC0</li>
+ <li><a href="https://data.deutschebahn.com/dataset/zugbildungsplanzugbildungsplan-zpar">Zugbildungsplan</a> © DB Fernverkehr AG, lizensiert unter CC-BY 4.0</li>
+ <li><a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellenliste</a>
© DB Station&amp;Service AG,
Europaplatz 1,
- 10557 Berlin, lizensiert unter CC-BY 4.0<br/>
+ 10557 Berlin, lizensiert unter CC-BY 4.0</li>
+ <li><a href="https://data.deutschebahn.com/dataset/fahrzeuglexikon">Fahrzeuglexikon</a>
+ © DB Fernverkehr AG, lizensiert unter CC-BY 4.0; Abbildungen © Seemanngrafik d.i.p. im Auftrag der Deutschen Bahn AG, lizensiert unter CC-BY-SA 4.0</li>
+ </ul>
</p>
-</div>
-
-<div class="container">
<p>
- <a href="https://twitter.com/derfnull">Kontakt</a>
- ·
- <a href="https://github.com/derf/db-fakedisplay/issues">Bugs?</a>
+ Trivia: Das Projekt begann als „db-fakedisplay“ (kurz dbf) zur
+ Nachahmung von Bahnhofs-Abfahrtstafeln. Inzwischen liegt der Fokus auf
+ der Bereitstellung von Informationen für mobile und Desktop-Anwendungen
+ und die Bezeichnung DBF wurde zum Eigennamen ohne weitere Bedeutung.
</p>
</div>
diff --git a/templates/app.html.ep b/templates/app.html.ep
index b35ca59..8b52c61 100644
--- a/templates/app.html.ep
+++ b/templates/app.html.ep
@@ -1,12 +1,7 @@
% if (@{$departures}) {
% if (not param('ajax')) {
-% if (param('dark')) {
-<div class="app appdark">
-% }
-% else {
-<div class="app applight">
-% }
+<div class="app" data-station="<%= $station %>">
<div class="moreinfo collapsed-moreinfo">
<div class="mheader">
<div>
@@ -34,48 +29,48 @@
% $via_cur++;
% $route_str .= $stop . ($via_cur < $via_max ? ' - ' : q{});
% }
-% my $linetype = 'bahn';
-% if ( $departure->{train_type} eq 'S' ) {
-% $linetype = 'sbahn';
-% }
-% elsif ( $departure->{train_type} eq 'IC'
-% or $departure->{train_type} eq 'ICE'
-% or $departure->{train_type} eq 'EC'
-% or $departure->{train_type} eq 'ECE'
-% or $departure->{train_type} eq 'EN') {
-% $linetype = 'fern';
-% }
-% elsif ( $departure->{train_type} eq 'THA'
-% or $departure->{train_type} eq 'TGV'
-% or $departure->{train_type} eq 'FLX'
-% or $departure->{train_type} eq 'NJ') {
-% $linetype = 'ext';
-% }
-% elsif ( $departure->{train_line} and $departure->{train_line} =~ m{^S\d} ) {
-% $linetype = 'sbahn';
-% }
<li
+% if (param('dbris') or param('hafas') or param('efa')) {
+ data-jid="<%= $departure->{journey_id} =~ s{#}{%23}gr %>"
+% }
data-train="<%= ($departure->{train_type} // q{}) %> <%= ($departure->{train_no} // $departure->{train} // q{}) %>"
data-line="<%= $departure->{train_type} %> <%= $departure->{train_line} // $departure->{train_no} %>"
data-no="<%= $departure->{train_line} ? $departure->{train_no} : q{} %>"
- data-linetype="<%= $linetype %>"
+ data-linetype="<%= $departure->{linetype} %>"
data-from="<%= $departure->{origin} // q{???} %>"
data-to="<%= $departure->{destination} // q{???} %>"
+ data-station="<%= $departure->{station} // $station %>"
data-platform="<%= $departure->{scheduled_platform} // $departure->{platform} // '' %>"
data-arrival="<%= $departure->{sched_arrival} // '' %>"
data-departure="<%= $departure->{sched_departure} // '' %>"
- data-moreinfo="<%= join(q{|}, map { ($_->[0]->isa('DateTime') ? $_->[0]->strftime('%H:%M') : $_->[0]) . ' ' . $_->[1] } @{ $departure->{moreinfo} // [] } ) %>"
+ data-moreinfo="<%= join(q{|}, map { ($_->[0]->isa('DateTime') ? $_->[0]->strftime('%H:%M') . ' ' . $_->[1] : $_->[0] . ' ' . $_->[1]{text}) } @{ $departure->{moreinfo} // [] } ) %>"
data-routeprev="<%= join(q{|}, @{ $departure->{route_pre} // [] } ) %>"
data-routenext="<%= join(q{|}, @{ $departure->{route_post} // [] } ) %>"
-% my $extraclasses = q{};
% if ($departure->{is_cancelled} or $departure->{departure_is_cancelled}) {
-% $extraclasses .= ' cancelled';
class="cancelled">
% }
% else {
>
% }
- <div class="line <%= $linetype %>">
+% if (param('hafas')) {
+ <a href="/z/<%= Mojo::Util::url_escape($departure->{journey_id}) . '?hafas=' . Mojo::Util::url_escape(param('hafas')) . '&amp;highlight=' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% elsif (param('efa')) {
+ <a href="/z/<%= Mojo::Util::url_escape($departure->{journey_id}) . '?efa=' . Mojo::Util::url_escape(param('efa')) . '&amp;highlight=' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% elsif (param('dbris')) {
+ <a href="/z/<%= Mojo::Util::url_escape($departure->{journey_id}) . '?dbris=' . Mojo::Util::url_escape(param('dbris')) . '&amp;highlight=' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% else {
+ <a href="/z/<%= Mojo::Util::url_escape(($departure->{train_type} // q{}) . ' ' . ($departure->{train_no} // $departure->{train} // q{})) . '/' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% if (param('dbris') or param('hafas')) {
+ <div class="anchor" id="<%= $departure->{journey_id} =~ s{[ #|]}{x}gr %>"></div>
+% }
+% else {
+ <div class="anchor" id="<%= ($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x}) %>"></div>
+% }
+ <div class="line <%= $departure->{linetype} %>">
% if ($departure->{train_type} and $departure->{train_no}) {
%= $departure->{train_type}
% }
@@ -104,38 +99,65 @@
% }
</div>
% }
-% if ($departure->{info} and length $departure->{info}) {
- <span class="info">
-%= $departure->{info}
+% if ($departure->{sched_departure}) {
+ <span class="dest">
+ <span class="visually-hidden">nach</span>
+%= $departure->{destination}
</span>
% }
% else {
- <span class="route">
-%= $route_str
+ <span class="origin">
+ <span class="visually-hidden">von</span>
+%= $departure->{origin}
</span>
% }
- <span class="dest <%= $extraclasses %>">
-%= $departure->{destination}
- </span>
- <span class="countdown <%= $extraclasses %>">
-% if ($departure->{delay} and not $departure->{is_cancelled}) {
-% if ($show_realtime) {
-% if ($departure->{delay} > 0) {
- <span class="delaynorm">(+<%= $departure->{delay} %>)</span>
+ <span class="time <%= $show_realtime ? get_rt_time_class($departure) : q{} %>">
+% if ($departure->{delay} and not $departure->{is_cancelled} and not $departure->{departure_is_cancelled}) {
+% if ($show_realtime and ($departure->{sched_arrival} or $departure->{sched_departure})) {
+% if ($departure->{delay} > ($hide_low_delay ? 4 : 0)) {
+ <span class="delaynorm" aria-hidden="true"><%= $departure->{sched_departure} // $departure->{sched_arrival} %> ⇒</span>
+% }
+% elsif ($departure->{delay} < 0) {
+ <span class="undelaynorm" aria-hidden="true"><%= $departure->{sched_departure} // $departure->{sched_arrival} %> ⇒</span>
+% }
+% }
+% else {
+% if ($departure->{delay} > ($hide_low_delay ? 4 : 0)) {
+ <span class="delay" aria-hidden="true">+<%= $departure->{delay} %></span>
+% }
+% elsif ($departure->{delay} < 0) {
+ <span class="undelay" aria-hidden="true"><%= $departure->{delay} %></span>
+% }
+% }
+% }
+% elsif ($departure->{missing_realtime}) {
+ <span class="visually-hidden">Echtzeitdaten fehlen</span>
+ <span class="no-realtime" aria-hidden="true"><i class="material-icons">gps_off</i></span>
+% }
+% if (param('detailed')) {
+% my $arrow = '→';
+% if (not $departure->{sched_arrival}) {
+% $arrow = '↦';
+% }
+% elsif (not $departure->{sched_departure}) {
+% $arrow = '⇥';
+% }
+% if ($show_realtime) {
+%= ($departure->{arrival} // q{}) . $arrow . ($departure->{departure} // q{})
% }
% else {
- <span class="undelaynorm">(<%= $departure->{delay} %>)</span>
+%= ($departure->{sched_arrival} // q{}) . $arrow . ($departure->{sched_departure} // q{})
% }
% }
% else {
-% if ($departure->{delay} > 0) {
- <span class="delay">(+<%= $departure->{delay} %>)</span>
+% if ($departure->{is_cancelled} or $departure->{departure_is_cancelled}) {
+%= $departure->{sched_departure} // $departure->{sched_arrival} // $departure->{time}
% }
% else {
- <span class="undelay">(<%= $departure->{delay} %>)</span>
+%= $departure->{time}
% }
% }
-% }
+ </span>
% if (($departure->{scheduled_platform} and $departure->{platform} and
% $departure->{scheduled_platform} ne $departure->{platform})
% or $departure->{changed_platform}) {
@@ -144,30 +166,33 @@
% else {
<span class="platform">
% }
+% if ($departure->{load}{FIRST} or $departure->{load}{SECOND}) {
+% my ($text, $icon1, $icon2) = utilization_icon([$departure->{load}{FIRST}, $departure->{load}{SECOND}]);
+ <span class="load">
+ <i class="material-icons" style="vertical-align: bottom;" aria-hidden="true"><%= $icon2 %></i>
+ </span>
+% }
+% elsif (my $o = $departure->{occupancy}) {
+ <span class="load">
+% my ($text, $icon) = occupancy_icon($o);
+ <i class="material-icons" style="vertical-align: bottom;" aria-hidden="true"><%= $icon %></i>
+ </span>
+% }
+ <span class="visually-hidden">Gleis</span>
%= $departure->{platform}
</span>
- </span>
- <span class="time <%= ($show_realtime and $departure->{delay} and not
- $departure->{is_cancelled}) ? 'delayed' : q{} %> <%= $extraclasses %>">
-% if (param('detailed')) {
-% my $arrow = '→';
-% if (not $departure->{sched_arrival}) {
-% $arrow = '↦';
-% }
-% elsif (not $departure->{sched_departure}) {
-% $arrow = '⇥';
-% }
-% if ($show_realtime) {
-%= ($departure->{arrival} // q{}) . $arrow . ($departure->{departure} // q{})
-% }
-% else {
-%= ($departure->{sched_arrival} // q{}) . $arrow . ($departure->{sched_departure} // q{})
-% }
+% if ($departure->{info} and length $departure->{info}) {
+ <span class="info">
+%= $departure->{info}
+ </span>
% }
% else {
-%= $departure->{time}
+ <span class="route">
+ <span class="visually-hidden">über</span>
+%= $route_str
+ </span>
% }
- </span>
+ </a>
</li>
% }
diff --git a/templates/coverage_map.html.ep b/templates/coverage_map.html.ep
new file mode 100644
index 0000000..bd3d94c
--- /dev/null
+++ b/templates/coverage_map.html.ep
@@ -0,0 +1,22 @@
+<div class="container">
+ Das <%= $backend %>-Backend „<%= $service %>“ liefert ungefähr innerhalb
+ der folgenden grob umrissenen Region voraussichtlich nützliche Echtzeitdaten.
+</div>
+
+<div class="container">
+ <div id="map" style="height: 70vh;">
+ </div>
+</div>
+
+<script>
+const map = L.map('map').setView([51.306, 9.712], 6);
+
+L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+}).addTo(map);
+
+const coverage = L.geoJSON(<%== $coverage %>);
+
+coverage.addTo(map);
+map.fitBounds(coverage.getBounds());
+</script>
diff --git a/templates/exception.html.ep b/templates/exception.html.ep
index 2f20bc6..7654c0b 100644
--- a/templates/exception.html.ep
+++ b/templates/exception.html.ep
@@ -4,9 +4,14 @@ Beim Bearbeiten der Anfrage ist ein Fehler aufgetreten.<br/>
<pre>
----------[Debug start]----------
-%= $exception->message
+% if ($exception) {
+%= ref($exception) ? $exception->message : $exception
Stash:
%= dumper $snapshot
+% }
+% else {
+%= stash('message')
+% }
----------[Debug end]----------
</pre>
</div>
diff --git a/templates/geolocation.html.ep b/templates/geostop.html.ep
index 843892d..843892d 100644
--- a/templates/geolocation.html.ep
+++ b/templates/geostop.html.ep
diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep
index 4e7e479..80fd34f 100644
--- a/templates/landingpage.html.ep
+++ b/templates/landingpage.html.ep
@@ -1,20 +1,31 @@
% if (stash 'show_intro') {
<div class="container">
-<p>
- Diese Seite ist ein inoffizielles Frontend zum Abfahrtsmonitor
- der DB. In den Standardeinstellungen bietet sie eine Handy- und
- Desktop-geeigneten Abfahrtstafel. Sie kann ebenfalls für großformatige
- Infobildschirme genutzt werden oder die in Schalterhallen und an Gleisen
- montierten Anzeigen nachhahmen.
-</p>
-<p>
-Alle Angaben ohne Gewähr.
-</p>
+% if (0) {
+ <p>
+ DBF is an unofficial departure monitor for regional and long-distance trains within Germany, aiming to combine multiple data sources in a useful manner.
+ It also has limited support for local transit and traffic outside of Germany.
+ </p>
+ <p>
+ This site is operated by a private entity in a not-for-profit manner.
+ There are no uptime or reliability guarantees whatsoever.
+ </p>
+% }
+% else {
+ <p>
+ DBF ist ein inoffizieller Abfahrtsmonitor für Nah-, Regional- und Fernverkehr in Deutschland und Umgebung mit dem Ziel, Daten aus verschiedenen Quellen zusammenzutragen.
+ Es unterstützt neben Fahrten im Netz der DB InfraGO diverse Nah- und Fernverkehrsunternehmen mit EFA- und HAFAS-Backends.
+ Die Fahrten in der Übersicht verlinken je eine Detailseite mit Unterwegshalten, Meldungen und Kartendarstellung.
+ </p>
+ <p>
+ Diese Seite ist ein kostenfreies, privat betriebenes Projekt ohne Verfügbarkeitsgarantie.
+ Alle Angaben ohne Gewähr.
+ </p>
+% }
<p class="geolink">
-<a class="button" href="<%= url_for('_auto')->to_abs->scheme('https') %>">Bahnhöfe im Umfeld suchen</a>
+<a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https')->query({efa => param('efa'), hafas => param('hafas')}) %>">Stationen in der Umgebung suchen</a>
</p>
<p>
-Oder hier angeben:
+Oder hier eine Station angeben:
</p>
</div>
% }
diff --git a/templates/layouts/app.html.ep b/templates/layouts/app.html.ep
index 2a596c0..c557bee 100644
--- a/templates/layouts/app.html.ep
+++ b/templates/layouts/app.html.ep
@@ -4,8 +4,8 @@
<title><%= stash('title') // 'DBF' %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="keywords" content="Abfahrtsmonitor, Bahnhofstafel, Abfahrten, Abfahrtstafel, ICE, IC, RE, RB, S-Bahn">
- <meta name="description" content="Inoffizieller Abfahrtsmonitor für Bahnhöfe der DB">
+ <meta name="keywords" content="Abfahrtsmonitor, Bahnhofstafel, Abfahrten, Abfahrtstafel, Nahverkehr, Regionalverkehr, Fernverkehr, ICE, IC, RE, RB, S-Bahn">
+ <meta name="description" content="<%= stash('description') // 'Inoffizieller Abfahrtsmonitor für Nah-, Reginol- und Fernverkehr' %>">
<meta name="theme-color" content="#00838f">
<link rel="icon" type="image/png" href="/static/icons/icon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="/static/icons/icon-32x32.png" sizes="32x32">
@@ -18,37 +18,76 @@
<meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
% }
- % my $av = 'v24'; # asset version
- %= stylesheet "/static/${av}/css/app.css"
+ % my $av = 'v110'; # asset version
+ % if (session('theme') and session('theme') eq 'dark' or param('dark')) {
+ %= stylesheet "/static/${av}/css/dark.min.css", id => 'theme'
+ % }
+ % else {
+ %= stylesheet "/static/${av}/css/light.min.css", id => 'theme'
+ % }
+ <script>
+ function addStyleSheet(name, id) {
+ const path = '/static/<%=$av%>/css/' + name + '.min.css';
+ const old = document.getElementById(id);
+ if (old && (old.href != path)) {
+ old.href = path;
+ document.cookie = 'theme=' + name + ';SameSite=None;Secure';
+ }
+ }
+ const otherTheme = {
+ 'dark': 'light',
+ 'light': 'dark',
+ };
+ var currentTheme = localStorage.getItem('theme');
+ if (!otherTheme.hasOwnProperty(currentTheme)) {
+ currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+ addStyleSheet(currentTheme, 'theme');
+ </script>
%= stylesheet "/static/${av}/css/material-icons.css"
%= stylesheet "/static/${av}/css/jquery-ui.min.css"
%= javascript '/static/js/jquery-3.4.1.min.js', defer => undef
%= javascript "/static/${av}/js/jquery-ui.min.js", defer => undef
%= javascript "/static/${av}/js/dbf.min.js", defer => undef
- % if (stash('with_geolocation')) {
- %= javascript "/static/${av}/js/geolocation.min.js", defer => undef
+ % if (not stash('hide_opts')) {
+ %= javascript "/dyn/${av}/autocomplete.js", defer => undef
+ % }
+ % if (stash('with_geostop')) {
+ %= javascript "/static/${av}/js/geostop.min.js", defer => undef
% }
% if (stash('with_map')) {
%= stylesheet "/static/${av}/leaflet/leaflet.css"
%= javascript "/static/${av}/leaflet/leaflet.js"
+ %= javascript "/static/${av}/js/map-refresh.min.js", defer => undef
% }
</head>
-<body style="<%= (param('dark') ? 'background-color: #000000; color: #ffffff;' : q{}) %>">
+<body>
<div class="navbar-fixed">
<nav style="color: #ffffff; background-color: #00838f;">
<div class="nav-wrapper container">
- <span class="brand-logo">
- %= stash('title') || 'DBF'
- </span>
+ % if (my $nav_link = stash('nav_link')) {
+ <a class="brand-logo" style="float: left;" href="<%= $nav_link %>">
+ %= stash('title') || 'DBF'
+ </a>
+ % }
+ % else {
+ <span class="brand-logo">
+ %= stash('title') || 'DBF'
+ </span>
+ % }
<ul id="nav-mobile" style="float: right;">
+ % if (stash('api_link')) {
+ <li class="waves-effect waves-light">
+ <a href="<%= stash('api_link') %>"><span class="visually-hidden"><%= stash('api_text') %></span><i class="material-icons" aria-hidden="true"><%= stash('api_icon') %></i></a>
+ </li>
+ % }
% if (stash('hide_opts')) {
- <li><a href="/"><i class="material-icons">edit</i></a></li>
+ <li><a href="/"><span class="visually-hidden">Hauptseite</span><i class="material-icons" aria-hidden="true">edit</i></a></li>
% }
% else {
- <li><a href="#stationinput"><i class="material-icons">edit</i></a></li>
+ <li><a href="#stationinput"><span class="visually-hidden">Menü</span><i class="material-icons" aria-hidden="true">edit</i></a></li>
% }
- <li><a href="/_auto"><i class="material-icons">my_location</i></a></li>
</ul>
</div>
</nav>
@@ -56,10 +95,10 @@
<div class="container">
% if (my $error = stash 'error') {
-<div class="error"><strong>Backend-Fehler:</strong>
-<pre>
+<div class="error"><strong>Fehler:</strong>
+<p>
%= $error
-</pre>
+</p>
</div>
% }
% elsif (stash('stationlist')) {
@@ -78,78 +117,55 @@ Bitte eine Station aus der Liste auswählen</div>
%= form_for _redirect => begin
+%= hidden_field efa => param('efa')
+%= hidden_field hafas => param('hafas')
<div>
<div class="field">
- <div class="desc">Bahnhof / Haltestelle</div>
- <div>
-% if (stash('stationlist')) {
- %= select_field station => stash('stationlist')
-% }
-% elsif (stash('station')) {
- %= text_field 'station', class => 'station', placeholder => 'Name oder DS100-Kürzel', id => 'stationinput'
-% }
-% else {
- %= text_field 'station', class => 'station', placeholder => 'Name oder DS100-Kürzel', id => 'stationinput', autofocus => 'autofocus'
-% }
- </div>
+% if (stash('stationlist')) {
+ %= select_field input => stash('stationlist')
+% }
+% elsif (stash('input')) {
+ %= text_field 'input', class => 'station', placeholder => 'Stationsname oder Fahrtnummer', id => 'stationinput'
+% }
+% else {
+ %= text_field 'input', class => 'station', placeholder => 'Stationsname oder Fahrtnummer', id => 'stationinput', autofocus => 'autofocus'
+% }
</div>
<div class="field">
- %= submit_button 'Abfahrtsmonitor'
+ %= submit_button 'Abfahrtstafel'
</div>
- % if (not stash('show_intro')) {
- <div class="break"></div>
- <div class="field">
- <a class="button" href="<%= url_for('_auto')->to_abs->scheme('https') %>">Bahnhöfe im Umfeld suchen</a>
+ % if (stash('input')) {
+ <div class="geolink">
+ <a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https')->query({efa => param('efa'), hafas => param('hafas')}) %>">Stationen in der Umgebung suchen</a>
</div>
% }
+ <div class="backendlink">
+ <a class="button button-light" href="<%= url_for('_backend')->query({efa => param('efa'), hafas => param('hafas')}) %>">Backend: <%= param('efa') ? param('efa') . ' (EFA)' : param('hafas') ? param('hafas') . ' (HAFAS)' : 'DB (IRIS-TTS)' %></a>
+ </div>
<div class="break"></div>
<div class="moresettings-header moresettings-header-collapsed button button-light">Weitere Einstellungen</div>
<div class="moresettings moresettings-collapsed">
<div class="field">
<div class="desc">
- Frontend
- </div>
- <div>
- %= select_field mode => [ ['App' => 'app'], ['Infoscreen' => 'infoscreen'], ['Bahnhofstafel' => 'multi'], ['Gleisanzeiger' => 'single'] ]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Backend
- </div>
- <div>
- %= select_field backend => [ ['IRIS' => 'iris'], ['HAFAS' => 'ris'] ]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Nur Züge über
- </div>
- <div>
- %= text_field 'via', placeholder => 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)', class => 'station'
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Gleise
- </div>
- <div>
- %= text_field 'platforms', placeholder => '1, 2, 5, ...'
+ %= check_box 'hidelowdelay' => 1, id => 'id_hidelowdelay'
+ <label for="id_hidelowdelay">
+ Verspätungen erst ab 5 Minuten anzeigen
+ </label>
</div>
</div>
<div class="field">
<div class="desc">
- %= check_box 'hidelowdelay' => 1, id => 'id_hidelowdelay'
- <label for="id_hidelowdelay">
- Nur Verspätungen &gt;5 Min. anzeigen
+ %= check_box 'detailed' => 1, id => 'id_detailed'
+ <label for="id_detailed">
+ Mehr Details
</label>
</div>
</div>
<div class="field">
<div class="desc">
- %= check_box 'dark' => 1, id => 'id_dark'
- <label for="id_dark">
- Dunkles Layout (experimentell)
+ %= check_box 'past' => 1, id => 'past'
+ <label for="past">
+ Fahrten der vergangenen 60 Minuten zeigen
</label>
</div>
</div>
@@ -157,50 +173,40 @@ Bitte eine Station aus der Liste auswählen</div>
<div class="desc">
%= check_box 'hide_opts' => 1, id => 'id_hide_opts'
<label for="id_hide_opts">
- Formular verstecken (für Infoscreens)
+ Formular verstecken
</label>
</div>
</div>
- <div class="break"></div>
- <span class="optional">Nur für IRIS-Backend:</span>
<div class="field">
<div class="desc">
- Ankunfts- oder Abfahrtszeit anzeigen?
+ Nur Fahrten über
</div>
<div>
- %= select_field admode => [['Abfahrt bevorzugen' => 'deparr'], ['Nur Abfahrt' => 'dep'], ['Nur Ankunft' => 'arr']]
+ %= text_field 'via', placeholder => 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)', class => 'station'
</div>
</div>
<div class="field">
<div class="desc">
- %= check_box 'detailed' => 1, id => 'id_detailed'
- <label for="id_detailed">
- Mehr Details (Zugnummern und Ankunftszeiten) anzeigen
- </label>
+ Gleise
</div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'show_realtime' => 1, id => 'id_show_realtime'
- <label for="id_show_realtime">
- Echtzeitangaben statt Fahrplandaten anzeigen
- </label>
+ <div>
+ %= text_field 'platforms', placeholder => '1, 2, 5, ...'
</div>
</div>
<div class="field">
<div class="desc">
- %= check_box 'no_related' => 1, id => 'id_no_related'
- <label for="id_no_related">
- Betriebliche Bahnhofstrennungen berücksichtigen (z.B. "Hbf (Fern+Regio)" vs. "Hbf (S)")
- </label>
+ Ankunfts- oder Abfahrtszeit anzeigen?
+ </div>
+ <div>
+ %= select_field admode => [['Abfahrt bevorzugen' => 'deparr'], ['Nur Abfahrt' => 'dep'], ['Nur Ankunft' => 'arr']]
</div>
</div>
<div class="field">
<div class="desc">
- %= check_box 'save_defaults' => 1, id => 'id_save_defaults'
- <label for="id_save_defaults">
- Ausgewählte Optionen als Default speichern
- </label>
+ Frontend
+ </div>
+ <div>
+ %= select_field mode => [ ['App' => 'app'], ['Infoscreen' => 'infoscreen'], ['Bahnhofstafel (legacy)' => 'multi'], ['Gleisanzeiger (legacy)' => 'single'] ]
</div>
</div>
<div class="field">
@@ -213,52 +219,99 @@ Bitte eine Station aus der Liste auswählen</div>
</div> <!-- input-field -->
<div class="notes">
- <div class="developers-header developers-header-collapsed button button-light">API- und Entwickler-Hinweise</div>
+ <div class="developers-header developers-header-collapsed button button-light">API</div>
<div class="developers developers-collapsed">
<ul>
- <li>Diese Seite kann gerne als iframe in eigene Infoscreens o.ä. eingebunden werden.
- Für eine kleine Ansicht (z.B. iframe in einer normalen Website) bitte das
- "App"-Frontend verwenden. Für eine große Ansicht
- (z.B. als alleinstehender Infoscreen) gibt es das "Infoscreen"-Frontend.</li>
- <li>Die Parameter <span style="font-family: monospace;">mode=json&amp;version=3</span>
- (alternativ auch <span style="font-family:
- monospace;">https://dbf.finalrewind.org/Bahnhofsname.json?version=3</span>)
- bieten ein JSON-IRIS-Interface. Die route-Elemente können zusätzlich
- die Felder "isAdditional" oder "isCancelled" enthalten, der Rest sollte
- selbsterklärend sein. Im Fehlerfall fehlt das "departures"-Element,
- stattdessen wird ein "error"-Element mit Fehlermeldung zurückgegeben.
- Bitte nur eine Anfrage pro Station und Minute
- – eine höhere Auflösung haben die Backenddaten ohnehin nicht.</li>
- <li>Mit <span style="font-family: monospace;">limit</span> kann die Anzahl der
- angezeigten / im JSON enthaltenen Abfahrten eingeschränkt werden, z.B.
- <span style="font-family: monospace;">limit=10</span> für die ersten zehn.</li>
- <li>Dieser Dienst ist Open Source-Software (Links siehe unten) und kann auch
- auf eigenen Servern installiert werden. Automatisierte Crawler, die mehrere
- Dutzend Stationen pro Minute abfragen, bitte nur auf eigenen Instanzen
- betreiben.</li>
+ % if (0) {
+ <li>You're welcome to embed DBF departure boards as iframes or use them
+ in full-screen browser setups. The App frontend works best for
+ small screens, whereas the legacy Infoscreen mode is better suited
+ for large displays.</li>
+ <li>The departure board supports names, EVA IDs, and (in IRIS mode)
+ DS100/Ril100 codes as station identifiers.</li>
+ <li>Requests for train details can optionally be suffixed with the
+ DD.MM.[YYYY] date of the requested trip, e.g. "ICE 921 (1.1.)" or
+ "ICE 921 @ 1.1.". The date refers to the scheduled departure at the
+ train's origin station.</li>
+ <li>A JSON IRIS API is avaliable via
+ <span style="font-family: monospace;">mode=json&amp;version=3</span>
+ (or just <span style="font-family: monospace;">https://dbf.finalrewind.org/Station.json?version=3</span>).
+ Route elements may contain "isAdditional" and "isCancelled"; the rest
+ should be self-explanatory. Please do not send more than 30 requests
+ per minute and only one request per station per minute.</li>
+ <li>There is no JSON API for train details yet.</li>
+ <li>The optional <span style="font-family: monospace;">limit</span>
+ parameter limits the number of returnd departures; e.g.
+ <span style="font-family: monospace;">limit=10</span> will result in no more than ten.</li>
+ <li>DBF is available as Open Source software
+ (<a href="https://github.com/derf/db-fakedisplay/blob/master/README.md">installation instructions</a>).
+ Please use your own installation for automated crawlers that request dozens of stations per minute.</li>
+ % }
+ % else {
+ <li>DBF-Abfahrtstafeln können gerne als iframe eingebunden oder in
+ fest installierten Vollbild-Browserfenstern verwendet werden.
+ Für eine kleine Ansicht (z.B. iframe in einer normalen Website)
+ empfiehlt sich das "App"-Frontend. Für eine große Ansicht
+ (z.B. als alleinstehender Infoscreen) gibt es den "Infoscreen"-Modus.</li>
+ <li>Die Abfahrtstafel unterstützt Namen, EVA-IDs, und (im IRIS-Backend)
+ DS100/Ril100-Codes zur Identifikation von Stationen.</li>
+ <li>Abfahrten werden mit Echtzeitdaten bzw. Prognosen angegeben und
+ danach sortiert. Mit dem Parameter
+ <span style="font-family: monospace;">rt=0</span> wwerden stattdessen
+ Plandaten angegeben und zur Sortierung genutzt.</li>
+ <li>Bei HAFAS-Backends können optional Details für spezifische Fahrten im
+ DD.MM.[YYYY]-Format abgefragt werden, z.B. "ICE 921 (1.1.)" oder
+ "ICE 921 @ 1.1.". Das Datum bezieht sich auf die geplante
+ Abfahrtszeit am Startbahnhof der Fahrt.</li>
+ <li>Viele Seiten sind auch als JSON verfügbar, wahlweise mittels
+ <span style="font-family: monospace;">Accept: application/json</span> oder
+ durch <span style="font-family: monospace;">.json</span> in der URL.
+ HAFAS- und IRIS-Abfahrtstafeln liefern mit dem GET-Parameter <span style="font-family: monospace;">version=3</span> eine stabile JSON-API.
+ Alle anderen Endpunkte (sowie Abfahrtstafeln mit <span style="font-family: monospace;">version=raw</span>) erlauben direkten Zugriff auf die serialisierten Travel::Status::DE::{EFA,HAFAS,IRIS}-Objekte ohne stabile API.</li>
+ <li>Bitte maximal 30 Anfragen pro Minute und insbesondere nur eine Anfrage
+ pro Station und Minute – eine höhere Auflösung haben die Backenddaten
+ ohnehin nicht.</li>
+ <li>Mit <span style="font-family: monospace;">limit</span> kann die Anzahl der
+ angezeigten / im JSON enthaltenen Abfahrten eingeschränkt werden, z.B.
+ <span style="font-family: monospace;">limit=10</span> für die ersten zehn.</li>
+ <li>Dieser Dienst ist Open Source-Software und kann leicht auf eigenen Servern
+ <a href="https://github.com/derf/db-fakedisplay/blob/master/README.md">installiert</a>
+ werden. Automatisierte Crawler, die mehrere Dutzend Stationen pro Minute
+ abfragen, bitte nur auf eigenen Instanzen betreiben.</li>
+ % }
</ul>
</div> <!-- developers -->
</div> <!-- notes -->
-<div class="notes">
-<span class="notes">Siehe auch:</span>
-<ul>
-<li><a href="https://reiseauskunft.bahn.de/bin/bhftafel.exe/dn">DB Abfahrtsmonitor</a>
- (<a href="https://mobile.bahn.de/bin/mobil/bhftafel.exe/dox">mobil</a>)</li>
-<li>Für Nahverkehr: <a href="https://vrrf.finalrewind.org/">vrr-infoscreen</a></li>
-</ul>
-</div> <!-- notes -->
-
</div> <!-- container -->
<div class="container">
-<div class="about">
-<a href="_about">db-infoscreen</a>
-v<%= stash('version') // '???' %>
+<div class="config">
+Farbschema:
+<a onClick="javascript:setTheme('light')">hell</a>
+<a onClick="javascript:setTheme('dark')">dunkel</a>
+<a onClick="javascript:setTheme('default')">automatisch</a>
+<!--Language:
<br/>
-<a href="_datenschutz" rel="nofollow">Datenschutzerklärung</a>
+<a onClick="javascript:setLang('de')">DE</a>
+<a onClick="javascript:setLang('en')">EN</a>
+<a onClick="javascript:setLang('default')">system language</a>
+-->
+</div> <!-- config -->
+</div> <!-- container -->
+% }
+% if (not stash('hide_footer')) {
+<div class="container">
+<div class="about">
+<a href="_about">DBF</a> v<%= stash('version') // '???' %>
+<a href="_datenschutz" rel="nofollow">Datenschutz</a>
·
-<a href="_impressum" rel="nofollow">Impressum</a><br/>
+<a href="_impressum" rel="nofollow">Impressum</a>
</div> <!-- about -->
</div> <!-- container -->
% }
diff --git a/templates/layouts/legacy.html.ep b/templates/layouts/legacy.html.ep
index 6266af4..e7e59ec 100644
--- a/templates/layouts/legacy.html.ep
+++ b/templates/layouts/legacy.html.ep
@@ -17,18 +17,21 @@
<meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
% }
- % my $av = 'v24'; # asset version
- %= stylesheet "/static/${av}/css/default.css"
+ % my $av = 'v110'; # asset version
+ %= stylesheet "/static/${av}/css/legacy.css"
%= stylesheet "/static/${av}/css/material-icons.css"
%= stylesheet "/static/${av}/css/jquery-ui.min.css"
% my $force_mobile = param('force_mobile') // stash('force_mobile');
% if ($force_mobile) {
- %= stylesheet "/static/${av}/css/mobile.css"
+ %= stylesheet "/static/${av}/css/legacy-mobile.css"
% }
%if (stash('load_marquee')) {
%= javascript '/static/js/jquery-3.4.1.min.js'
%= javascript "/static/${av}/js/jquery-ui.min.js"
%= javascript "/static/${av}/js/dbf.min.js"
+ % if (not stash('hide_opts')) {
+ %= javascript "/dyn/${av}/autocomplete.js", defer => undef
+ % }
%= javascript "/static/${av}/js/marquee.min.js"
%= javascript begin
$(function () { $('marquee').marquee() });
@@ -38,9 +41,6 @@
%= javascript "/static/${av}/js/jquery-ui.min.js", defer => undef
%= javascript "/static/${av}/js/dbf.min.js", defer => undef
% }
- % if (stash('with_geolocation')) {
- %= javascript "/static/${av}/js/geolocation.min.js", defer => undef
- % }
</head>
<body style="<%= (param('dark') ? 'background-color: #000000; color: #ffffff;' : q{}) %>">
@@ -62,196 +62,5 @@ Bitte eine Station aus der Liste auswählen</div>
%= content
</div>
-% if (not stash('hide_opts')) {
-<div class="container">
-<div class="input-field">
-
-
-%= form_for _redirect => begin
-<div>
- <div class="field">
- <div class="desc">Bahnhof / Haltestelle</div>
- <div>
-% if (stash('stationlist')) {
- %= select_field station => stash('stationlist')
-% }
-% elsif (stash('station')) {
- %= text_field 'station', class => 'station', placeholder => 'Name oder DS100-Kürzel'
-% }
-% else {
- %= text_field 'station', class => 'station', placeholder => 'Name oder DS100-Kürzel', autofocus => 'autofocus'
-% }
- </div>
- </div>
- <div class="field">
- %= submit_button 'Abfahrtsmonitor'
- </div>
- % if (not stash('show_intro')) {
- <div class="break"></div>
- <div class="field">
- <a class="button" href="<%= url_for('_auto')->to_abs->scheme('https') %>">Bahnhöfe im Umfeld suchen</a>
- </div>
- % }
- <div class="break"></div>
- <div class="moresettings-header moresettings-header-collapsed button button-light">Weitere Einstellungen</div>
- <div class="moresettings moresettings-collapsed">
- <div class="field">
- <div class="desc">
- Frontend
- </div>
- <div>
- %= select_field mode => [ ['App' => 'app'], ['Infoscreen' => 'infoscreen'], ['Bahnhofstafel' => 'multi'], ['Gleisanzeiger' => 'single'] ]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Backend
- </div>
- <div>
- %= select_field backend => [ ['IRIS' => 'iris'], ['HAFAS' => 'ris'] ]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Nur Züge über
- </div>
- <div>
- %= text_field 'via', placeholder => 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)', class => 'station'
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Gleise
- </div>
- <div>
- %= text_field 'platforms', placeholder => '1, 2, 5, ...'
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'hidelowdelay' => 1, id => 'id_hidelowdelay'
- <label for="id_hidelowdelay">
- Nur Verspätungen &gt;5 Min. anzeigen
- </label>
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'dark' => 1, id => 'id_dark'
- <label for="id_dark">
- Dunkles Layout (experimentell)
- </label>
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'hide_opts' => 1, id => 'id_hide_opts'
- <label for="id_hide_opts">
- Formular verstecken (für Infoscreens)
- </label>
- </div>
- </div>
- <div class="break"></div>
- <span class="optional">Nur für IRIS-Backend:</span>
- <div class="field">
- <div class="desc">
- Ankunfts- oder Abfahrtszeit anzeigen?
- </div>
- <div>
- %= select_field admode => [['Abfahrt bevorzugen' => 'deparr'], ['Nur Abfahrt' => 'dep'], ['Nur Ankunft' => 'arr']]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'detailed' => 1, id => 'id_detailed'
- <label for="id_detailed">
- Mehr Details (Zugnummern und Ankunftszeiten) anzeigen
- </label>
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'show_realtime' => 1, id => 'id_show_realtime'
- <label for="id_show_realtime">
- Echtzeitangaben statt Fahrplandaten anzeigen
- </label>
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'no_related' => 1, id => 'id_no_related'
- <label for="id_no_related">
- Betriebliche Bahnhofstrennungen berücksichtigen (z.B. "Hbf (Fern+Regio)" vs. "Hbf (S)")
- </label>
- </div>
- </div>
- <div class="field">
- <div class="desc">
- %= check_box 'save_defaults' => 1, id => 'id_save_defaults'
- <label for="id_save_defaults">
- Ausgewählte Optionen als Default speichern
- </label>
- </div>
- </div>
- <div class="field">
- %= submit_button 'Anzeigen'
- </div>
- </div> <!-- moresettings -->
-</div>
-% end
-
-</div> <!-- input-field -->
-
-<div class="notes">
- <div class="developers-header developers-header-collapsed button button-light">API- und Entwickler-Hinweise</div>
- <div class="developers developers-collapsed">
- <ul>
- <li>Diese Seite kann gerne als iframe in eigene Infoscreens o.ä. eingebunden werden.
- Für eine kleine Ansicht (z.B. iframe in einer normalen Website) bitte das
- "App"-Frontend verwenden. Für eine große Ansicht
- (z.B. als alleinstehender Infoscreen) gibt es das "Infoscreen"-Frontend.</li>
- <li>Die Parameter <span style="font-family: monospace;">mode=json&amp;version=3</span>
- (alternativ auch <span style="font-family:
- monospace;">https://dbf.finalrewind.org/Bahnhofsname.json?version=3</span>)
- bieten ein JSON-IRIS-Interface. Die route-Elemente können zusätzlich
- die Felder "isAdditional" oder "isCancelled" enthalten, der Rest sollte
- selbsterklärend sein. Im Fehlerfall fehlt das "departures"-Element,
- stattdessen wird ein "error"-Element mit Fehlermeldung zurückgegeben.
- Bitte nur eine Anfrage pro Station und Minute
- – eine höhere Auflösung haben die Backenddaten ohnehin nicht.</li>
- <li>Mit <span style="font-family: monospace;">limit</span> kann die Anzahl der
- angezeigten / im JSON enthaltenen Abfahrten eingeschränkt werden, z.B.
- <span style="font-family: monospace;">limit=10</span> für die ersten zehn.</li>
- <li>Dieser Dienst ist Open Source-Software (Links siehe unten) und kann auch
- auf eigenen Servern installiert werden. Automatisierte Crawler, die mehrere
- Dutzend Stationen pro Minute abfragen, bitte nur auf eigenen Instanzen
- betreiben.</li>
- </ul>
- </div> <!-- developers -->
-</div> <!-- notes -->
-
-<div class="notes">
-<span class="notes">Siehe auch:</span>
-<ul>
-<li><a href="https://reiseauskunft.bahn.de/bin/bhftafel.exe/dn">DB Abfahrtsmonitor</a>
- (<a href="https://mobile.bahn.de/bin/mobil/bhftafel.exe/dox">mobil</a>)</li>
-<li>Für Nahverkehr: <a href="https://vrrf.finalrewind.org/">vrr-infoscreen</a></li>
-</ul>
-</div> <!-- notes -->
-
-</div> <!-- container -->
-
-<div class="container">
-<div class="about">
-<a href="_about">db-infoscreen</a>
-v<%= stash('version') // '???' %>
-<br/>
-<a href="_datenschutz" rel="nofollow">Datenschutzerklärung</a>
-<a href="_impressum" rel="nofollow">Impressum</a><br/>
-</div> <!-- about -->
-</div> <!-- container -->
-% }
-
</body>
</html>
diff --git a/templates/multi.html.ep b/templates/multi.html.ep
index 704c589..0095957 100644
--- a/templates/multi.html.ep
+++ b/templates/multi.html.ep
@@ -48,3 +48,9 @@
</div> <!-- displaymulti -->
% }
+
+<p class="notice">
+<strong>Deprecation Warning.</strong>
+Dieses Frontend wird nicht mehr weitergewickelt und möglicherweise in einer
+zukünftigen DBF-Version entfernt.
+</p>
diff --git a/templates/not_found.html.ep b/templates/not_found.html.ep
index df583ae..c03d32d 100644
--- a/templates/not_found.html.ep
+++ b/templates/not_found.html.ep
@@ -1,7 +1,12 @@
<div class="container">
<div class="error">
<strong>404 Page Not Found:</strong>
+% if (my $e = stash('message')) {
+%= $e
+% }
+% else {
Die aufgerufene URL existiert nicht und ist keine gültige Abbildung auf einen
Bahnhofsnamen.
+% }
</div>
</div>
diff --git a/templates/route_map.html.ep b/templates/route_map.html.ep
index 4ba5917..e1c4642 100644
--- a/templates/route_map.html.ep
+++ b/templates/route_map.html.ep
@@ -1,10 +1,9 @@
-<div class="container" style="margin-top: 1ex; margin-bottom: 1ex;">
-Fahrt von <strong><%= $origin->{name} %></strong>
-nach <strong><%= $destination->{name} %></strong>
-</div>
+% if (stash('origin') and stash('destination')) {
+ %= include '_map_infobox'
+% }
<div class="container">
- <div id="map" style="height: 500px;">
+ <div id="map" style="height: 70vh;">
</div>
</div>
@@ -42,8 +41,58 @@ for (var station_id in stations) {
opacity: 0.7,
fillColor: '#f03',
fillOpacity: 0.5,
- radius: 250
+ radius: <%= stash('station_radius') || 250 %>
}).bindPopup(stations[station_id][1].join('<br/>')).addTo(map);
}
+var greenIcon = new L.Icon({
+ iconUrl: '/static/leaflet/images/marker-icon-2x-green.png',
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ shadowSize: [41, 41]
+});
+
+var goldIcon = new L.Icon({
+ iconUrl: '/static/leaflet/images/marker-icon-2x-gold.png',
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ shadowSize: [41, 41]
+});
+
+var marker;
+% for my $marker (@{stash('markers') // [] } ) {
+ % if ($marker->{icon}) {
+ marker = L.marker([<%= $marker->{lat} %>,<%= $marker->{lon} %>], {icon: <%= $marker->{icon} %>}).addTo(map);
+ % }
+ % else {
+ marker = L.marker([<%= $marker->{lat} %>,<%= $marker->{lon} %>]).addTo(map);
+ % }
+ % if ($marker->{title}) {
+ marker.bindPopup('<%= $marker->{title} %>');
+ % }
+% }
+
</script>
+
+<div class="container" style="margin-top: 1ex; margin-bottom: 1ex; color: #555;">
+<p>
+Die eingezeichnete Route stammt aus dem angefragten Backend und stimmt nicht
+notwendigerweise mit der Realität überein.
+Die Fahrzeugposition auf der Karte ist eine DBF-eigene Schätzung und kann
+erheblich von den tatsächlichen Gegebenheiten abweichen.
+% if (stash('intersection')) {
+<br/>In dieser Ansicht sind Live-Updates der Zug- und Begegnungspositionen noch
+nicht implementiert.
+% }
+</p>
+</div>
+
+% if (my $op = stash('operator')) {
+<div class="container" style="margin-top: 1ex; margin-bottom: 1ex; color: #555;">
+<p>Betrieb: <%= $op %></p>
+</div>
+% }
diff --git a/templates/select_backend.html.ep b/templates/select_backend.html.ep
new file mode 100644
index 0000000..c6d2a4c
--- /dev/null
+++ b/templates/select_backend.html.ep
@@ -0,0 +1,46 @@
+<div class="container">
+ <p>
+ Das Backend bestimmt die Datenquelle für Stations- und Zuginformationen.
+ Innerhalb Deutschlands ist <strong>Deutsche Bahn</strong> via IRIS-TTS eine gute Wahl für Schienenverkehr im Bahnnetz.
+ Die anderen Backends bieten sich für Fahrten im zugehörigen Verkehrsverbund (inklusive Nahverkehr) sowie im Ausland an.
+ Sofern bekannt sind unterhalb der Backend-Namen Karten verlinkt, die die ungefähre Abdeckung aufzeigen.
+ Ein Backend, welches Nah- und Fernverkehr in ganz Deutschland abdeckt, ist aktuell leider nicht verfügbar.
+ </p>
+ <p>
+ % my $prev_type = 'IRIS-TTS';
+ % for my $backend (@{$backends}) {
+ <p>
+ % if ($backend->{type} ne $prev_type) {
+ % $prev_type = $backend->{type};
+ <%= $prev_type %>:<br/>
+ % }
+ % my $class = 'button';
+ % if (param('efa')) {
+ % if ($backend->{efa} and $backend->{shortname} eq param('efa')) {
+ % $class .= ' button-active';
+ % }
+ % }
+ % elsif (param('hafas')) {
+ % if ($backend->{hafas} and $backend->{shortname} eq param('hafas')) {
+ % $class .= ' button-active';
+ % }
+ % }
+ % else {
+ % if (not ($backend->{efa} or $backend->{hafas})) {
+ % $class .= ' button-active';
+ % }
+ % }
+ <a class="<%= $class %>" href="<%= url_for(q{/})->query({ efa => $backend->{efa} ? $backend->{shortname} : q{}, hafas => $backend->{hafas} ? $backend->{shortname} : q{} }) %>"><%= $backend->{shortname} // 'IRIS-TTS' %> – <%= $backend->{name} %></a>
+ % if ($backend->{has_area}) {
+ <a href="/coverage/<%= $backend->{type} %>/<%= $backend->{shortname} %>"><%= join(q{, }, @{$backend->{regions}}) || '[Karte]' %></a>
+ % }
+ % else {
+ %= join(q{, }, @{$backend->{regions} // []})
+ % }
+ % if ($backend->{homepage}) {
+ (<a href="<%= $backend->{homepage} %>"><%= $backend->{homepage} =~ s{ ^ http s? :// (?: www[.] )? (.*?) (?: / )? $ }{$1}xr %></a>)
+ % }
+ </p>
+ % }
+ </p>
+</div>
diff --git a/templates/single.html.ep b/templates/single.html.ep
index 681dfee..0156bf4 100644
--- a/templates/single.html.ep
+++ b/templates/single.html.ep
@@ -1,3 +1,9 @@
+<p class="notice">
+<strong>Deprecation Warning.</strong>
+Dieses Frontend wird nicht mehr weitergewickelt und möglicherweise in einer
+zukünftigen DBF-Version abgeschaltet.
+</p>
+
% if (@{$departures}) {
<div class="displaysingle">
diff --git a/templates/train_details.html.ep b/templates/train_details.html.ep
new file mode 100644
index 0000000..7d5ea90
--- /dev/null
+++ b/templates/train_details.html.ep
@@ -0,0 +1,5 @@
+<div class="app" data-station="<%= stash('station_name') // q{} %>">
+<div class="moreinfo" data-static="1">
+ %= include '_train_details'
+</div>
+</div>
diff --git a/templates/wagen.html.ep b/templates/wagen.html.ep
new file mode 100644
index 0000000..efc2e32
--- /dev/null
+++ b/templates/wagen.html.ep
@@ -0,0 +1,75 @@
+<div class="container singlewagon">
+ % if (not $wref->{e} and $wref->{s} and $wref->{p} and $wref->{ws}) {
+ <p>
+ <%= $wref->{s} %> Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
+ </p>
+ % }
+ % if ($wref->{e} eq 'u') {
+ % if ($wref->{s} and $wref->{p} and $wref->{ws}) {
+ <div class="platform">
+ <%= $wref->{s} %><br/>Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
+ <div class="sign-left"><i class="material-icons">arrow_upward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_upward</i></div>
+ </div>
+ % }
+ % }
+ % elsif (defined $wref->{d} and $wref->{e} ne 'u') {
+ % if ($wref->{d} == 0) {
+ <div class="sign-left"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_back</i></div>
+ % }
+ % else {
+ <div class="sign-left"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_forward</i></div>
+ % }
+ % }
+ % for my $wagon_file (@{$wagon_files // [] }) {
+ % if ($wagon_file ne $wagon_files->[0] and defined $wref->{d}) {
+ % if ($wref->{d} == 0) {
+ <div class="sign-left"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_back</i></div>
+ % }
+ % else {
+ <div class="sign-left"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_forward</i></div>
+ % }
+ % }
+ <div style="clear: both;">
+ <a href="<%= $wagon_file %>"><img class="wagonfile" src="<%= $wagon_file %>"></a>
+ </div>
+ % }
+ % if ($wref->{e} eq 'd') {
+ <div class="sign-left"><i class="material-icons">arrow_downward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_downward</i></div>
+ % if ($wref->{s} and $wref->{p} and $wref->{ws}) {
+ <div class="platform">
+ <%= $wref->{s} %><br/>Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
+ </div>
+ % }
+ % }
+ % elsif (defined $wref->{d} and $wref->{e} ne 'd') {
+ % if ($wref->{d} == 0) {
+ <div class="sign-left"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_back</i></div>
+ % }
+ % else {
+ <div class="sign-left"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_forward</i></div>
+ % }
+ % }
+ % if ($wagon_data->{name}) {
+ <p>
+ <%= $wagon_data->{name} %>
+ </p>
+ % }
+ <p class="copyright">
+ Abbildung © Seemanngrafik d.i.p. im Auftrag der Deutschen Bahn AG,
+ lizensiert unter CC-BY-4.0
+ </p>
+</div>
diff --git a/templates/wagenreihung.html.ep b/templates/wagenreihung.html.ep
index 2167204..19c49ab 100644
--- a/templates/wagenreihung.html.ep
+++ b/templates/wagenreihung.html.ep
@@ -1,141 +1,57 @@
-% if ($wr->errstr) {
+% if (not $wr or $wr_error) {
<div class="container">
<div class="error">
<strong>Fehler bei der Abfrage der Wagenreihung:</strong>
- <%= $wr->errstr %>
+ <%= $wr_error // 'Unbekannter Fehler' %>
</div>
</div>
% }
% else {
- % my $has_multi_dest = 0;
- % if (scalar $wr->destinations > 1) {
- % $has_multi_dest = 1;
- % }
<div class="container">
<div style="text-align: center;">
-%= join( ' / ', $wr->origins )
- →
-%= join( ' / ', map { $_->{name} } $wr->destinations )
+ Gleis <%= $wr->platform %><br/>
</div>
- % if ($has_multi_dest) {
- <div style="text-align: center;">
- % for my $destination ($wr->destinations) {
- Nach <%= $destination->{name} %> in Abschnitt <%= join(q{}, sort @{$destination->{sections} // []}) %><br/>
- % }
- </div>
- % }
- <%= $wr->station_name %> Gleis <%= $wr->platform %><br/>
- % if ($wr->train_type =~ m{^IC|EC}) {
- Zugtyp: <%= $wr->train_subtype // 'IC?' %>
- % }
- % elsif ($wr->train_powertype) {
- %= $wr->train_type
- % }
- % my %translation = (
- % 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',
- % );
- % if (not $wr->train_powertype) {
- % }
- % else {
- %= $translation{$wr->train_powertype};
- % }
- </div>
</div>
<div class="container">
- <div class="wagonorder">
-% if (not $wr->has_bad_wagons) {
-% for my $section ($wr->sections) {
- <div class="section" style="
- top: <%= $section->{start_percent} %>%; bottom: <%= 100 - $section->{end_percent} %>%;">
-%= $section->{name}
- </div>
-% }
+ <div class="wagonorder exit-<%= stash('exit_dir') // 'unknown'%>">
+% for my $sector ($wr->sectors) {
+ <div class="section" style="
+ top: <%= $sector->start_percent %>%; bottom: <%= 100 - $sector->end_percent %>%;">
+%= $sector->name
+ </div>
% }
-% for my $wagon ($wr->wagons) {
-% my $bg = '';
-% my $extra_class = '';
-% if ($wagon->is_first_class) {
-% $bg = 'background-color: #ffff99;';
-% }
-% if ($wagon->is_locomotive or $wagon->is_powercar) {
-% $bg = 'background-color: #cccccc;';
-% }
-% if ($wagon->train_no ne $train_no) {
-% $extra_class .= ' nondestwagon';
+% for my $group ($wr->groups) {
+% my $first = 1;
+% for my $wagon ($group->carriages) {
+%= include '_wagon', wr => $wr, group => $group, wagon => $wagon, first => $first, multi => (scalar $wr->destinations) - 1 + (scalar $wr->train_numbers) - 1, wref => $wref, exit_dir => stash('exit_dir'), train_no => param('number');
+% $first = 0;
% }
- <div class="wagon <%= $extra_class %>" style="
- top: <%= $wagon->{position}{start_percent} %>%; bottom: <%= 100 - $wagon->{position}{end_percent} %>%; <%= $bg %>">
-% if ($wagon->is_locomotive or $wagon->is_powercar) {
-% }
-% else {
-%= $wagon->number // '?'
-% if ($wagon->has_accessibility) {
- <i class="material-icons" style="font-size: 20px;">accessible</i>
-% }
-% if ($wagon->has_bistro) {
- <i class="material-icons">restaurant</i>
-% }
-% if ($wagon->has_compartments) {
- <!--<i class="material-icons">folder</i>-->
-% }
-% if ($wagon->has_quiet_area) {
- <i class="tiny material-icons">volume_off</i>
-% }
-% if ($wagon->has_phone_area) {
- <i class="material-icons">smartphone</i>
-% }
-% if ($wagon->has_family_area) {
- <i class="material-icons">people</i>
-% }
-% if ($wagon->has_bahn_comfort) {
- <i class="material-icons">star</i>
-% }
-% }
- <div class="direction">
-% if ($wr->has_bad_wagons) {
-% }
-% elsif ($wr->direction == 100) {
- <i class="material-icons">arrow_downward</i>
-% }
-% else {
- <i class="material-icons">arrow_upward</i>
-% }
- </div>
- </div>
- <div class="details" style="
- top: <%= $wagon->{position}{start_percent} %>%; bottom: <%= 100 - $wagon->{position}{end_percent} %>%;">
- <span class="type">
-%= $wagon->type
- </span>
-% my $uic_id = $wagon->uic_id;
-% if (length($uic_id) != 12) {
- <span class="uicunknown"><%= $uic_id %></span>
-% }
-% elsif ($wr->train_type eq 'ICE') {
- <span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic5"><%= substr($uic_id, 4, 1) %></span><span class="uictype"><%= substr($uic_id, 5, 3) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
-% }
-% else {
- <span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic56"><%= substr($uic_id, 4, 2) %></span><span class="uic78"><%= substr($uic_id, 6, 2) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
-% }
- </div>
% }
</div>
+ % for my $group ($wr->groups) {
+ % if ($group->description) {
+ <div style="text-align: center;">
+ %= $group->description
+ % if ($group->designation) {
+ „<%= $group->designation %>“
+ % }
+ % if (scalar $wr->groups > 1 and $group->has_sectors) {
+ in Abschnitt <%= join(q{}, sort $group->sectors) %>
+ % }
+ </div>
+ % }
+ % }
+ <div style="text-align: center;">
+ nach
+%= join( ' / ', map { $_->{name} } $wr->destinations )
+ </div>
<!-- <div>
Legende: ♿ Behindertengerechte Ausstattung / 🍴 Bistro/Restaurant / 🚪 Abteile vorhanden
</div>
-->
- <div>
- Angaben ohne Gewähr – Echtzeitdaten sind möglicherweise nicht berücksichtigt.
- </div>
+ <p class="copyright">
+ Quelle: DB Wagenreihungs-API (<%= stash('ts') // q{} %>). Angaben ohne Gewähr.
+ </p>
</div>
% }
diff --git a/templates/zugbildung_db.html.ep b/templates/zugbildung_db.html.ep
new file mode 100644
index 0000000..45e52aa
--- /dev/null
+++ b/templates/zugbildung_db.html.ep
@@ -0,0 +1,26 @@
+% if ($wr_error) {
+ <div class="container">
+ <div class="error">
+ <strong>Fehler bei der Abfrage der Wagenreihung:</strong>
+ <%= $wr_error %>
+ </div>
+ </div>
+% }
+<div class="container">
+ <div style="text-align: center;"><%= $route %></div>
+ Vorgesehener Zugtyp: <%= $zb->{type} %>
+</div>
+<div class="container">
+ <div class="wagonorder exit-unknown">
+% for my $wagon (@{$wagons // []}) {
+%= include '_wagon', direction => undef, wagon => $wagon, type => $zb->{type}, wref => '', exit_dir => 'unknown';
+% }
+ </div>
+ <p class="copyright">
+ Quelle: <a href="https://data.deutschebahn.com/dataset/zugbildungsplanzugbildungsplan-zpar">DB Zugbildungsplan</a>
+ mit <a href="https://github.com/derf/db-zugbildung-to-json">automatisierter Nachbearbeitung</a>.<br/>
+ Nachbearbeitungsbedingte Fehler sind wahrscheinlich.<br/>
+ Daten © 2020 DB Fernverkehr AG, lizensiert unter CC-BY 4.0.
+ </p>
+
+ </div>