every train that runs that day $base = rt_plan($tt, $from, $to, 0, ['date' => $date, 'horizon' => 1440]); foreach ($base['direct'] as $l) $journeys[] = [$l]; // connecting options sampled across the day, de-duplicated $seen = []; foreach ([0, 360, 720, 1080] as $qd) { $r = ($qd === 0) ? $base : rt_plan($tt, $from, $to, $qd, ['date' => $date, 'horizon' => 1440]); foreach ($r['hops'] as $journey) { $sig = ''; foreach ($journey as $l) $sig .= $l['train'] . ':' . $l['board'] . '>'; if (isset($seen[$sig])) continue; $seen[$sig] = 1; $journeys[] = $journey; } } } catch (Throwable $ex) { error_log('router: ' . $ex->getMessage()); $err = 'The journey planner is warming up. Please try again in a moment.'; } } } // ---- Hide trains that have already left the origin (only for today's date, IST) ---- $departedAll = false; if ($journeys) { $nowIst = new DateTime('now', new DateTimeZone('Asia/Kolkata')); if ($date === $nowIst->format('Y-m-d')) { $nowMin = ((int) $nowIst->format('G')) * 60 + (int) $nowIst->format('i'); $before = count($journeys); $journeys = array_values(array_filter($journeys, function ($j) use ($nowMin) { return isset($j[0]['board']) && (int) $j[0]['board'] >= $nowMin; })); $departedAll = ($before > 0 && count($journeys) === 0); } } // ---- analytics: log the search (only when an actual A->B search ran) ---- if ($from !== '' && $to !== '') { $__an = APP_DIR . '/analytics.php'; if (is_file($__an)) { require_once $__an; rr_track('search', ['from_code' => $from, 'to_code' => $to, 'jdate' => $date, 'n' => count($journeys), 'note' => $departedAll ? 'departed' : '']); } } // ---- Names + types ---- $trainNos = []; $codes = [$from, $to]; foreach ($journeys as $j) foreach ($j as $l) { $trainNos[$l['train']] = 1; $codes[] = $l['from']; $codes[] = $l['to']; } $trainNames = []; $trainTypes = []; $stationNames = []; $trainClasses = []; if ($trainNos) { $in = implode(',', array_fill(0, count($trainNos), '?')); $hasClassesCol = true; try { $st = db()->prepare("SELECT train_no, train_name, train_type, classes FROM trains WHERE train_no IN ($in)"); $st->execute(array_keys($trainNos)); } catch (Throwable $e) { $hasClassesCol = false; $st = db()->prepare("SELECT train_no, train_name, train_type FROM trains WHERE train_no IN ($in)"); $st->execute(array_keys($trainNos)); } foreach ($st as $r) { $trainNames[$r['train_no']] = $r['train_name']; $trainTypes[$r['train_no']] = $r['train_type']; if ($hasClassesCol && isset($r['classes']) && $r['classes'] !== '' && $r['classes'] !== '-') { $vals = array_values(array_filter(array_map('trim', explode(',', (string) $r['classes'])))); if ($vals) $trainClasses[$r['train_no']] = ['valid' => $vals, 'invalid' => []]; } } try { $tcq = db()->prepare("SELECT train_no, class, valid FROM train_classes WHERE train_no IN ($in)"); $tcq->execute(array_keys($trainNos)); foreach ($tcq as $r) { $tn = $r['train_no']; if (!isset($trainClasses[$tn])) $trainClasses[$tn] = ['valid' => [], 'invalid' => []]; if ((int) $r['valid'] === 1) $trainClasses[$tn]['valid'][] = $r['class']; else $trainClasses[$tn]['invalid'][] = $r['class']; } } catch (Throwable $e) { /* learning table not created yet */ } } $codes = array_values(array_unique(array_filter($codes))); if ($codes) { $in = implode(',', array_fill(0, count($codes), '?')); $st = db()->prepare("SELECT code, name FROM stations WHERE code IN ($in)"); $st->execute($codes); foreach ($st as $r) $stationNames[$r['code']] = $r['name']; } // (class lists now come from trains.classes, populated by tools/backfill_classes.php — no per-search API call) function sname(string $c): string { global $stationNames; return $stationNames[$c] ?? $c; } function tname(string $t): string { global $trainNames; return $trainNames[$t] ?? ''; } function ttype(string $t): ?string { global $trainTypes; return $trainTypes[$t] ?? null; } function train_class(string $no, ?string $t): array { $u = strtoupper(trim((string) $t)); $isMEMU = strpos($u, 'ELECTRICAL MULTIPLE') !== false || strpos($u, 'MEMU') !== false; $isDEMU = (strpos($u, 'DIESEL') !== false && strpos($u, 'MULTIPLE') !== false) || strpos($u, 'DEMU') !== false; $tokenUnres = $isMEMU || $isDEMU || strpos($u, 'SUBURBAN') !== false || strpos($u, 'PASSENGER') !== false || strpos($u, 'EMU') !== false || in_array($u, ['SUB','PASS','MEX'], true); $reservedType = false; if ($u !== '') { foreach (['RAJDHANI','SHATABDI','DURONTO','SUPERFAST','SUF','GARIB RATH','HUMSAFAR','HMSR', 'VANDE BHARAT','VBEX','TEJAS','DOUBLE DECKER','MAIL EXPRESS','INTERCITY','SAMPARK KRANTI', 'SUVIDHA','RAJ','DRNT'] as $rk) { if (strpos($u, $rk) !== false) { $reservedType = true; break; } } } $d = ($no !== '') ? $no[0] : ''; $noUnres = in_array($d, ['3','4','5','6','7','9'], true); if ($reservedType) $unreserved = false; elseif ($tokenUnres) $unreserved = true; else $unreserved = $noUnres; $map = [ 'MAIL EXPRESS'=>'Mail/Express','SUPERFAST'=>'Superfast','SUF'=>'Superfast', 'INTERCITY'=>'Intercity','INT'=>'Intercity','GARIB RATH'=>'Garib Rath', 'SAMPARK KRANTI'=>'Sampark Kranti','SHATABDI'=>'Shatabdi','JAN SHATABDI'=>'Jan Shatabdi', 'RAJDHANI'=>'Rajdhani','RAJ'=>'Rajdhani','DURONTO'=>'Duronto','DRNT'=>'Duronto', 'HUMSAFAR'=>'Humsafar','HMSR'=>'Humsafar','VANDE BHARAT EXPRESS'=>'Vande Bharat','VBEX'=>'Vande Bharat', 'TEJAS EXPRESS'=>'Tejas','DOUBLE DECKER'=>'Double Decker','DD'=>'Double Decker', 'SUBURBAN'=>'Suburban','SUB'=>'Suburban','PASSENGER'=>'Passenger','PASS'=>'Passenger','MEX'=>'MEMU', ]; if ($isMEMU) $label = 'MEMU'; elseif ($isDEMU) $label = 'DEMU'; elseif (isset($map[$u])) $label = $map[$u]; elseif ($u !== '') $label = ucwords(strtolower($u)); else $label = ['3'=>'Suburban','4'=>'Suburban','5'=>'Passenger','6'=>'MEMU','7'=>'DEMU','9'=>'Suburban'][$d] ?? ''; return [$label, $unreserved]; } function rd_tag(array $l): string { if (!empty($l['harvested'])) { $days = $l['days'] ?? '—'; if (!empty($l['stale'])) return ' · Runs: ' . e($days) . ' · re-checking'; return ' · Runs: ' . e($days) . ''; } return ' · days not verified — assumed daily'; } $travelTs = strtotime($date . ' 00:00'); function whenstr(int $absMin): string { global $travelTs; $ts = $travelTs + $absMin * 60; return date('H:i', $ts) . ' · ' . date('D j M', $ts); } function station_node(string $code, string $kind, ?int $layover): void { ?>
Enter an origin and destination to see journeys.
All trains from = e($from) ?> → = e($to) ?> for today have already departed. Try tomorrow's date.No trains found for = e($from) ?> → = e($to) ?> on = e($date) ?>. Try a nearby date or a different pair of stations.
No journeys match your filters.