blob: 40f033ce5e73fb7bdc17dd9858562842b2d32f82 [file] [log] [blame]
<?php
class gtfs {
//private static $files = ["calendar_dates", "calendar", "routes", "stop_times", "stops", "trips"];
private $db;
private static $dow = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
function __construct($path = null) {
global $conf;
try {
$this->db = new PDO('sqlite:'.($conf["databaseFile"]));
} catch (PDOException $e) {
die("SQLite error: $e\n");
}
}
static function timeSinceMidnight() {
//return 8*60*60 + 9*60; //TESTING
return (time() - mktime(0, 0, 0));
}
static function today() {
return date("Ymd");
}
// Converts a time in the format "HH:MM:SS" to the number of seconds. If
// |uniqueRepresentative| is true, when HH >= 24, a representative between 0
// and 24*60*60 - 1 of the equivalence class mod 24*60*60 will be returned
// instead of returning a number >= 24*60*60.
static function time2seconds($time, $uniqueRepresentative=true) {
$timeSinceMidnight = self::timeSinceMidnight();
$boom = explode(":", $time);
if (count($boom) != 3) return null;
$seconds = (($boom[0]*60) + $boom[1])*60 + $boom[2];
return $uniqueRepresentative ? $seconds % (24*60*60) : $seconds;
}
private function fetchAll($sql) {
$query = $this->db->query($sql);
if (!$query) return false;
return $query->fetchAll(PDO::FETCH_ASSOC);
}
private function fetchAllPrepared($sql, $values) {
$query = $this->db->prepare($sql);
if (!$query) return false;
$query->execute($values);
return $query->fetchAll(PDO::FETCH_ASSOC);
}
private function fetchTable($table, $filters = [], $orderedField = null) {
$order = ($orderedField === null ? "" : " ORDER BY $orderedField");
if (!count($filters)) {
return $this->fetchAll("SELECT * FROM $table".$order);
}
$whereConditions = [];
$values = [];
foreach ($filters as $filter) {
$whereConditions[] = $filter[0]." = ?";
$values[] = $filter[1];
}
return $this->fetchAllPrepared("SELECT * FROM $table WHERE ".implode(" AND ", $whereConditions).$order, $values);
}
function getRoutes() {
return $this->fetchTable("routes");
}
function getTrips($route) {
return $this->fetchTable("trips", [["route_id", $route]]);
}
function getStations($ordered = false) {
return $this->fetchTable("stops", [["location_type", Gtfs\Stop\LocationType::STATION]], ($ordered ? "stop_name" : null));
}
function getStop($stop) {
$results = $this->fetchTable("stops", [["stop_id", $stop]]);
if (!count($results)) return null;
return $results[0];
}
function getPlatforms($stop) {
$results = $this->fetchTable("stops", [["parent_station", $stop], ["location_type", Gtfs\Stop\LocationType::STOP]], "stop_id");
return $results;
}
const TIME_LIMIT = 30;
function getStopTimes($stop, $timeLimit = self::TIME_LIMIT) {
$platforms = $this->getPlatforms($stop);
$values = [];
foreach ($platforms as $i => $s) {
$values[":stop".(int)$i] = $s["stop_id"]; // Stops
}
$stopParameters = array_keys($values);
$rdow = (int)(new DateTime("now"))->format("w");
$dow0 = self::$dow[($rdow + 6) % 7]; // Yesterday's day of week
$dow = self::$dow[$rdow]; // Today's day of week
$dow2 = self::$dow[($rdow + 1) % 7]; // Tomorrow's day of week
$values[":yesterday"] = (int)(new DateTime("yesterday"))->format("Ymd"); // Yesterday's date
$values[":today"] = (int)(new DateTime("now"))->format("Ymd"); // Today's date
$values[":tomorrow"] = (int)(new DateTime("tomorrow"))->format("Ymd"); // Tomorrow's date
$values[":now"] = (new DateTime("now"))->format("H:i:s");
if (!count($platforms)) return [];
$sql = "SELECT st.*, t.*, r.*, c.*, cd.*
FROM stop_times st
INNER JOIN trips t
ON st.trip_id = t.trip_id
INNER JOIN routes r
ON t.route_id = r.route_id
LEFT JOIN calendar c
ON t.service_id = c.service_id
LEFT JOIN calendar_dates cd
ON t.service_id = cd.service_id
WHERE
st.stop_id IN (".implode(", ", $stopParameters).") AND
(
cd.service_id IS NULL OR
cd.exception_type = ".(int)Gtfs\CalendarDate\ExceptionType::ADDED."
) AND
(
(
(
(
time(:now) < time('00:00:00', '-".(int)$timeLimit." minutes') AND
st.departure_time BETWEEN time(:now) AND time(:now, '".(int)$timeLimit." minutes')
) OR
(
time(:now) >= time('00:00:00', '-".(int)$timeLimit." minutes') AND
st.departure_time BETWEEN time(:now) AND ((strftime('%H', :now, '".(int)$timeLimit." minutes') + 24) || strftime(':%M:%S', :now, '".(int)$timeLimit." minutes'))
)
) AND
(
c.service_id IS NULL OR
(
c.start_date <= :today AND
c.end_date >= :today AND
c.".$dow." = ".(int)Gtfs\Calendar\CalendarDay::AVAILABLE."
)
) AND
(
cd.service_id IS NULL OR
cd.date = :today
)
) OR
(
(
(
time(:now) < time('00:00:00', '-".(int)$timeLimit." minutes') AND
st.departure_time BETWEEN ((strftime('%H', :now) + 24) || strftime(':%M:%S', :now)) AND ((strftime('%H', :now, '".(int)$timeLimit." minutes') + 24) || strftime(':%M:%S', :now, '".(int)$timeLimit." minutes'))
) OR
(
time(:now) >= time('00:00:00', '-".(int)$timeLimit." minutes') AND
st.departure_time BETWEEN ((strftime('%H', :now) + 24) || strftime(':%M:%S', :now)) AND ((strftime('%H', :now, '".(int)$timeLimit." minutes') + 48) || strftime(':%M:%S', :now, '".(int)$timeLimit." minutes'))
)
) AND
(
c.service_id IS NULL OR
(
c.start_date <= :yesterday AND
c.end_date >= :yesterday AND
c.".$dow0." = ".(int)Gtfs\Calendar\CalendarDay::AVAILABLE."
)
) AND
(
cd.service_id IS NULL OR
cd.date = :yesterday
)
) OR
(
time(:now) >= time('00:00:00', '-".(int)$timeLimit." minutes') AND
st.departure_time BETWEEN time('00:00:00') AND time(:now, '".(int)$timeLimit." minutes') AND
(
c.service_id IS NULL OR
(
c.start_date <= :tomorrow AND
c.end_date >= :tomorrow AND
c.".$dow2." = ".(int)Gtfs\Calendar\CalendarDay::AVAILABLE."
)
) AND
(
cd.service_id IS NULL OR
cd.date = :tomorrow
)
)
)
ORDER BY departure_time ASC";
$result = $this->fetchAllPrepared($sql, $values);
if ($result === false) {
echo implode("\n", $this->db->errorInfo());
exit();
}
return $result;
}
function __destruct() {
$this->db = null;
}
}