Skip to content

Commit

Permalink
Add HTT detection
Browse files Browse the repository at this point in the history
  • Loading branch information
HerrLevin committed Jan 8, 2025
1 parent 129c6f1 commit d3419f3
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 89 deletions.
1 change: 0 additions & 1 deletion app/Enum/HafasTravelType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
enum HafasTravelType: string
{
case UNKNOWN = 'unknown';
case NATIONAL_EXPRESS = 'nationalExpress';
case NATIONAL = 'national';
case REGIONAL_EXP = 'regionalExp';
Expand Down
37 changes: 37 additions & 0 deletions app/Enum/ReiseloesungCategory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);

namespace App\Enum;

enum ReiseloesungCategory: string
{
case ICE = 'ICE';
case EC_IC = 'EC_IC';
case IR = 'IR';
case REGIONAL = 'REGIONAL';
case SBAHN = 'SBAHN';
case BUS = 'BUS';
case SCHIFF = 'SCHIFF';
case UBAHN = 'UBAHN';
case TRAM = 'TRAM';
case ANRUFPFLICHTIG = 'ANRUFPFLICHTIG';
case UNKNOWN = 'UNKNOWN';


public function getHTT(): HafasTravelType
{
return match ($this->name) {
'ICE' => HafasTravelType::NATIONAL_EXPRESS,
'EC_IC' => HafasTravelType::NATIONAL,
'IR' => HafasTravelType::REGIONAL_EXP,
'UNKNOWN', 'REGIONAL' => HafasTravelType::REGIONAL,
'SBAHN' => HafasTravelType::SUBURBAN,
'BUS' => HafasTravelType::BUS,
'SCHIFF' => HafasTravelType::FERRY,
'UBAHN' => HafasTravelType::SUBWAY,
'TRAM' => HafasTravelType::TRAM,
'ANRUFPFLICHTIG' => HafasTravelType::TAXI,
default => HafasTravelType::REGIONAL,
};
}
}
30 changes: 16 additions & 14 deletions app/Http/Controllers/Backend/Transport/BahnWebApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,38 @@
namespace App\Http\Controllers\Backend\Transport;

use App\Dto\Transport\Departure;
use App\Enum\HafasTravelType;
use App\Enum\ReiseloesungCategory;
use App\Enum\TripSource;
use App\Http\Controllers\Controller;
use App\Models\Station;
use App\Models\Trip;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

abstract class BahnWebApiController extends Controller {
abstract class BahnWebApiController extends Controller
{

public static function searchStation(string $query, int $limit = 10): Collection {
$url = "https://www.bahn.de/web/api/reiseloesung/orte?suchbegriff=" . urlencode($query) . "&typ=ALL&limit=" . $limit;
$response = Http::get($url);
$json = $response->json();
$extIds = [];
foreach($json as $rawStation) {
if(!isset($rawStation['extId'])) {
foreach ($json as $rawStation) {
if (!isset($rawStation['extId'])) {
continue;
}
$extIds[] = $rawStation['extId'];
}
$stationCache = Station::whereIn('ibnr', $extIds)->get();

$stations = collect();
foreach($json as $rawStation) {
if(!isset($rawStation['extId'])) {
foreach ($json as $rawStation) {
if (!isset($rawStation['extId'])) {
continue;
}
$station = $stationCache->where('ibnr', $rawStation['extId'])->first();
if($station === null) {
if ($station === null) {
$station = Station::create([
'name' => $rawStation['name'],
'latitude' => $rawStation['lat'],
Expand All @@ -50,7 +50,7 @@ public static function searchStation(string $query, int $limit = 10): Collection
}

public static function getDepartures(Station $station, Carbon|null $timestamp = null): Collection {
if($timestamp === null) {
if ($timestamp === null) {
$timestamp = now();
}
$response = Http::get("https://www.bahn.de/web/api/reiseloesung/abfahrten", [
Expand All @@ -59,9 +59,9 @@ public static function getDepartures(Station $station, Carbon|null $timestamp =
'zeit' => $timestamp->format('H:i'),
]);
$departures = collect();
foreach($response->json('entries') as $rawDeparture) {
foreach ($response->json('entries') as $rawDeparture) {
$journey = Trip::where('trip_id', $rawDeparture['journeyId'])->first();
if($journey) {
if ($journey) {
$departures->push(new Departure(
station: $station,
plannedDeparture: Carbon::parse($rawDeparture['zeit']),
Expand All @@ -72,7 +72,7 @@ public static function getDepartures(Station $station, Carbon|null $timestamp =
}

$rawJourney = self::fetchJourney($rawDeparture['journeyId']);
if($rawJourney === null) {
if ($rawJourney === null) {
// sorry
continue;
}
Expand All @@ -81,10 +81,12 @@ public static function getDepartures(Station $station, Carbon|null $timestamp =
$destinationStation = self::getStationFromHalt($rawJourney['halte'][count($rawJourney['halte']) - 1]);
$departure = isset($rawJourney['halte'][0]['abfahrtsZeitpunkt']) ? Carbon::parse($rawJourney['halte'][0]['abfahrtsZeitpunkt']) : null;
$arrival = isset($rawJourney['halte'][count($rawJourney['halte']) - 1]['ankunftsZeitpunkt']) ? Carbon::parse($rawJourney['halte'][count($rawJourney['halte']) - 1]['ankunftsZeitpunkt']) : null;
$category = isset($rawDeparture['verkehrmittel']['produktGattung']) ? ReiseloesungCategory::tryFrom($rawDeparture['verkehrmittel']['produktGattung']) : ReiseloesungCategory::UNKNOWN;
$category = $category ?? ReiseloesungCategory::UNKNOWN;

$journey = Trip::create([
'trip_id' => $rawDeparture['journeyId'],
'category' => HafasTravelType::UNKNOWN, //TODO
'category' => $category->getHTT(),
'number' => preg_replace('/\D/', '', $rawDeparture['verkehrmittel']['name']),
'linename' => $rawDeparture['verkehrmittel']['name'] ?? '',
'journey_number' => preg_replace('/\D/', '', $rawDeparture['verkehrmittel']['name']),
Expand All @@ -109,7 +111,7 @@ public static function getDepartures(Station $station, Carbon|null $timestamp =

private static function getStationFromHalt(array $rawHalt) {
$station = Station::where('ibnr', $rawHalt['extId'])->first();
if($station !== null) {
if ($station !== null) {
return $station;
}

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Resources/DepartureResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function toArray(Request $request): array {
"adminCode" => "80____",
"productName" => $this->trip->linename, //TODO
"mode" => "train", //TODO
"product" => "national", //TODO
"product" => $this->trip->category,
"operator" => null,/*[ //TODO
"type" => "operator",
"id" => "db-fernverkehr-ag",
Expand Down
146 changes: 73 additions & 73 deletions resources/vue/components/Checkin/StationBoardEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,107 +7,107 @@ import {trans} from "laravel-vue-i18n";
import {departureEntry} from "../../../types/Departure";
export default defineComponent({
name: "StationBoardEntry",
components: {LineIndicator, ProductIcon},
props: {
item: {
type: Object() as departureEntry,
required: true
name: "StationBoardEntry",
components: {LineIndicator, ProductIcon},
props: {
item: {
type: Object() as departureEntry,
required: true
},
station: {
type: Object,
required: true
}
},
station: {
type: Object,
required: true
}
},
methods: {
trans,
formatTime(time: any) {
return DateTime.fromISO(time).toFormat("HH:mm");
methods: {
trans,
formatTime(time: any) {
return DateTime.fromISO(time).toFormat("HH:mm");
},
},
},
computed: {
isPast(): boolean {
const when = this.item.when || this.item.plannedWhen;
if (!when) {
return false;
}
return DateTime.fromISO(when).plus({minutes: 1}) < DateTime.now();
},
cancelled(): boolean {
return this.item.cancelled || false;
},
delayClass(): string {
if (this.item.delay === null) {
return '';
}
computed: {
isPast(): boolean {
const when = this.item.when || this.item.plannedWhen;
if (!when) {
return false;
}
return DateTime.fromISO(when).plus({minutes: 1}) < DateTime.now();
},
cancelled(): boolean {
return this.item.cancelled || false;
},
delayClass(): string {
if (this.item.delay === null) {
return '';
}
let color = 'text-success';
let color = 'text-success';
if (this.item.delay > 300) {
color = 'text-danger';
} else if (this.item.delay >= 60) {
color = 'text-warning';
}
if (this.item.delay > 300) {
color = 'text-danger';
} else if (this.item.delay >= 60) {
color = 'text-warning';
}
return color;
}
},
return color;
}
},
})
</script>

<template>
<div class="card mb-1 dep-card" :class="{'past-card': isPast, 'cancelled-card': cancelled}">
<div class="card-body d-flex py-0">
<div class="col-1 align-items-center d-flex justify-content-center">
<ProductIcon :product="item.line.product"/>
</div>
<div class="col-2 align-items-center d-flex me-3 justify-content-center">
<span class="sr-only" v-if="cancelled">{{ trans("stationboard.stop-cancelled") }}</span>
<LineIndicator
:productName="item.line.product"
:number="item.line.name !== null ? item.line.name : item.line.fahrtNr"
/>
</div>
<div class="col align-items-center d-flex second-stop">
<div>
<span class="fw-bold fs-6">{{ item.direction }}</span><br>
<span v-if="item.stop.name !== station.name" class="text-muted small font-italic">
<div class="card mb-1 dep-card" :class="{'past-card': isPast, 'cancelled-card': cancelled}">
<div class="card-body d-flex py-0">
<div class="col-1 align-items-center d-flex justify-content-center">
<ProductIcon :product="item.line.product"/>
</div>
<div class="col-2 align-items-center d-flex me-3 justify-content-center">
<span class="sr-only" v-if="cancelled">{{ trans("stationboard.stop-cancelled") }}</span>
<LineIndicator
:productName="item.line.product"
:number="item.line.name !== null ? item.line.name : item.line.fahrtNr"
/>
</div>
<div class="col align-items-center d-flex second-stop">
<div>
<span class="fw-bold fs-6">{{ item.direction }}</span><br>
<span v-if="item.stop.name !== station.name" class="text-muted small font-italic">
{{ trans("stationboard.dep") }} {{ item.stop.name }}
</span>
</div>
</div>
<div class="col-auto ms-auto align-items-center d-flex">
<div v-if="item.delay">
</div>
</div>
<div class="col-auto ms-auto align-items-center d-flex">
<div v-if="item.delay">
<span class="text-muted text-decoration-line-through">
{{ formatTime(item.plannedWhen) }}<br>
</span>
<span :class="delayClass">{{ formatTime(item.when) }}</span>
</div>
<div v-else>
<span :class="delayClass">{{ formatTime(item.plannedWhen) }}</span>
<span :class="delayClass">{{ formatTime(item.when) }}</span>
</div>
<div v-else>
<span :class="delayClass">{{ formatTime(item.plannedWhen) }}</span>
</div>
</div>
</div>
</div>
</div>
</div>

</template>

<style scoped lang="scss">
@import "../../../sass/_variables.scss";
.dep-card {
min-height: 4.25rem;
min-height: 4.25rem;
}
.past-card {
opacity: 50%;
opacity: 50%;
}
.cancelled-card {
opacity: 50%;
background-color: $red !important;
color: $white;
text-decoration: line-through;
text-decoration-thickness: 2px;
opacity: 50%;
background-color: $red !important;
color: $white;
text-decoration: line-through;
text-decoration-thickness: 2px;
}
</style>

0 comments on commit d3419f3

Please sign in to comment.