From 352c6baf3d9842ef8e6eb84e752f3cdb051638c5 Mon Sep 17 00:00:00 2001 From: William Entriken Date: Thu, 9 Mar 2017 16:08:23 -0500 Subject: [PATCH] Add full php website files --- website/common.php | 394 +++++++++++++++++++++++++++++++++++ website/index.php | 87 ++++++++ website/scheduleaverage.php | 84 ++++++++ website/schedulecurrent.php | 67 ++++++ website/scheduledetail.php | 88 ++++++++ website/scheduleproposed.php | 91 ++++++++ website/scrape-trainview.php | 41 ++++ 7 files changed, 852 insertions(+) create mode 100644 website/common.php create mode 100644 website/index.php create mode 100644 website/scheduleaverage.php create mode 100644 website/schedulecurrent.php create mode 100644 website/scheduledetail.php create mode 100644 website/scheduleproposed.php create mode 100644 website/scrape-trainview.php diff --git a/website/common.php b/website/common.php new file mode 100644 index 0000000..4075f38 --- /dev/null +++ b/website/common.php @@ -0,0 +1,394 @@ +'03:00:00' desc, arrival_time"; # get trains ordered by Suburban Station time +// echo "\n"; + $q = mysql_query($sql); + $intrains = array(); + while ($t = mysql_fetch_assoc($q)) + $intrains[] = $t['trip_short_name']; + return $intrains; +} + +# Results sorted by time that train reaches Suburban Station +# Returns train numbers like: ['3014', '3015', ...] +function getOutboundTrainsOnRouteAndSchedule($route, $schedule) +{ + # SEPTA, according to GTFS, may have a trip from Airport to 30th street on the "Airport line" + # (which doesn't pass Suburban station) but have the same train number on a trip from 30th street to + # Fox chase continue afterwards. + $sql = "SELECT DISTINCT trip_short_name, arrival_time, stop_sequence + FROM + (SELECT DISTINCT trip_short_name + FROM trips NATURAL JOIN routes + WHERE route_short_name='".mysql_escape_string($route)."' + AND service_id='".mysql_escape_string($schedule)."' + AND trip_headsign <> 'Center City Philadelphia') a + NATURAL JOIN trips NATURAL JOIN stop_times NATURAL JOIN stops + WHERE stop_name='Suburban Station' AND service_id='".mysql_escape_string($schedule)."' + ORDER BY arrival_time>'03:00:00' desc, arrival_time"; # get trains ordered by Suburban Station time +// echo "\n"; + $q = mysql_query($sql); + $intrains = array(); + while ($t = mysql_fetch_assoc($q)) + $intrains[] = $t['trip_short_name']; + return $intrains; +} + +# Creates temporary table like (BI (train name), ST (stop name), AT (arrival time)) +function makeInboundTimetableOnRouteAndSchedule($temporaryTableName, $route, $schedule) +{ + # SEPTA may have one train on two trips (see above), they are associated in GTFS via "blocks" + $trains = getInboundTrainsOnRouteAndSchedule($route, $schedule); + $sql = "CREATE TEMPORARY TABLE $temporaryTableName + SELECT DISTINCT a.stop_name SN, a.arrival_time AT, a.block_id BI + FROM + (SELECT * FROM stop_times NATURAL JOIN stops NATURAL JOIN trips + WHERE trip_short_name IN (".join(",",$trains).") + AND service_id='".mysql_escape_string($schedule)."') a + CROSS JOIN + (SELECT * FROM stop_times NATURAL JOIN stops NATURAL JOIN trips + WHERE trip_short_name IN (".join(",",$trains).") + AND service_id='".mysql_escape_string($schedule)."' + AND stop_name='Suburban Station') b + WHERE + a.block_id = b.block_id AND + (a.arrival_time>'03:00:00' AND b.arrival_time<'03:00:00' + OR a.arrival_time<=b.arrival_time)"; + mysql_query($sql) + or die(mysql_error() . ' ... ' . $sql); +} + +# Creates temporary table like (BI (train name), ST (stop name), AT (arrival time)) +function makeOutBoundTimetableOnRouteAndSchedule($temporaryTableName, $route, $schedule) +{ + # SEPTA may have one train on two trips (see above), they are associated in GTFS via "blocks" + $trains = getOutboundTrainsOnRouteAndSchedule($route, $schedule); + $sql = "CREATE TEMPORARY TABLE $temporaryTableName + SELECT DISTINCT a.stop_name SN, a.arrival_time AT, a.block_id BI + FROM + (SELECT * FROM stop_times NATURAL JOIN stops NATURAL JOIN trips + WHERE trip_short_name IN (".join(",",$trains).") + AND service_id='".mysql_escape_string($schedule)."') a + CROSS JOIN + (SELECT * FROM stop_times NATURAL JOIN stops NATURAL JOIN trips + WHERE trip_short_name IN (".join(",",$trains).") + AND service_id='".mysql_escape_string($schedule)."' + AND stop_name='Suburban Station') b + WHERE + a.block_id = b.block_id AND + (a.arrival_time<'03:00:00' AND b.arrival_time>'03:00:00' + OR a.arrival_time>=b.arrival_time)"; + mysql_query($sql) + or die(mysql_error()); +} + +#TODO: THIS ONLY WORKS FOR INBOUND RIGHT NOW +# Calculates the "correct" order of the stops assuming all trains go in one direction +# Returns stops like ['Eastwick', 'University City', ...] +function getStopsFromTimeTable($temporaryTableName) +{ + mysql_query("CREATE TEMPORARY TABLE insched2 SELECT * FROM $temporaryTableName"); # mysql bug 10327 + + $sql = "CREATE TEMPORARY TABLE insched3 + SELECT DISTINCT a.SN FSN, b.SN TSN + FROM + (SELECT * FROM insched) a + CROSS JOIN + (SELECT * FROM insched2) b + WHERE + a.BI = b.BI AND + (a.AT>'03:00:00' AND b.AT<'03:00:00' OR a.AT='$start' AND day<='$end'"; + $query = mysql_query($sql) + or die (mysql_error()); + while ($row = mysql_fetch_assoc($query)) { + $retval[$row['train']][$row['day']][$row['time']] = $row['lateness']; + } + return $retval; +} + +/** + * latenessAtTime function. + * + * Algorithm: averages times over the next few minutes + * + * @access public + * @param mixed $latenessByTime array like ['09:22:00'=>0, '09:30:00'=>3, '10:20:00'=>0] + * @param mixed $time like '09:20:00' + * @return void + */ +function latenessAtTimeOLD($latenessByTime, $time) +{ + // Each day, use average of [timeTable, +5 minutes] of reporting data + $latenessObvervations = array(); + for ($offset = 0; $offset<=5; $offset++) { + $selectorTime = date("H:i:s",60*$offset+strtotime($time)); + if (isset($latenessByTime[$selectorTime])) + $latenessObvervations[] = $latenessByTime[$selectorTime]; + } + if (count($latenessObvervations)) + return average($latenessObvervations); + return NULL; +} + +/** + * latenessAtTime function. + * + * Algorithm: finds based on the array key which is closest to, but not exceeding, TIME + * Just like The Price is Right, just like Excel VLOOKUP + * + * @access public + * @param mixed $latenessByTime array like ['09:22:00'=>0, '09:30:00'=>3, '10:20:00'=>0] + * @param mixed $time like '09:20:00' + * @return void + */ +function latenessAtTime($latenessByTime, $time) +{ + $times = array_keys($latenessByTime); + usort($times, 'cmp_times'); + rsort($times); + + foreach ($times as $candidateTime) { + if (cmp_times($time, $candidateTime) >= 0) { + return $latenessByTime[$candidateTime]; + } + } + return NULL; +} + + +/** + * Compares two times, assuming that times < 3am are the next day (and thus later than others) + * + * @access public + * @param mixed $a + * @param mixed $b + * @return void + */ +function cmp_times($a, $b) +{ + if ($a < '03:00:00' && $b > '03:00:00') + return 1; + if ($a > '03:00:00' && $b < '03:00:00') + return -1; + return strcmp($a, $b); +} + + +# +# Generic functions +# + +function getFile($url, $cachetime=3600, $tag='') +{ + $cachedir = 'cache'; + + if ($tag == '') + $tag = md5($url); + $filename = "$cachedir/xml_$tag"; + + if (file_exists($filename) && (time()-filemtime($filename)<$cachetime)) { + $data = @file_get_contents($filename); + } else { + $data = @file_get_contents($url); + file_put_contents($filename, $data); + } + return $data; +} + +function average($array, $none=0) +{ + if (!count($array)) return $none; + $sum = array_sum($array); + $count = count($array); + return $sum/$count; +} + +function stdev($array) +{ + if (!count($array)) return 0; + + $avg = average($array); + foreach ($array as $value) { + $variance[] = pow($value-$avg, 2); + } + $deviation = sqrt(average($variance)); + return $deviation; +} + +?> \ No newline at end of file diff --git a/website/index.php b/website/index.php new file mode 100644 index 0000000..622c9b1 --- /dev/null +++ b/website/index.php @@ -0,0 +1,87 @@ + + + + + SEPTA Reporting Tool + + + + + + + +
+

SEPTA Regional Rail On-Time Performance Report

+

+ These reports use every train's arrival time from 2009 until present to recommend schedule changes for chronically late service. Reports created by William Entriken (not affiliated with SEPTA). Also see SEPTA's less detailed official OTP reports. +

+
+ Show reports for + + + + + + SEPTA service schedule + +
+ +
InboundOutbound + + ".$row['route_short_name'].""; + echo " Schedule"; + echo " Lateness"; + echo " Proposed fix"; + echo " Schedule"; + echo " Lateness"; + echo " Proposed fix"; + } +?> +
+ +
+ +
+

William Entriken — ✈ Philadelphia USA — Program Updated — Data since:

+
+ +
+ + +Fork me on GitHub + + + diff --git a/website/scheduleaverage.php b/website/scheduleaverage.php new file mode 100644 index 0000000..194d134 --- /dev/null +++ b/website/scheduleaverage.php @@ -0,0 +1,84 @@ + + + + + <?= htmlentities($_GET['route']) ?> <?= $inbound ? 'Inbound' : 'Outbound' ?> Lateness + + + + + + + +

— Average Lateness (mins.)

+

Report using data from to with service schedule

+

Stops averaging 3+ minutes late are highlighted

+
+

service

+ +\n"; + } + } + } +?> +
Train Number"; + foreach ($stops as $stop) + echo "$stop"; + + foreach ($trains as $train) { + echo "\n
$train"; + foreach ($stops as $stop) { + if (empty($timetableByTrainAndStop[$train][$stop]) || empty($latenessByTrainDayAndTime[$train])) { + echo "\n"; + } else { + $time = $timetableByTrainAndStop[$train][$stop]; + // Slice out lateness from each day of records + $dailyLatenesses = array(); + foreach($latenessByTrainDayAndTime[$train] as $day => $latenessByTime) { + $dayLateness = latenessAtTime($latenessByTime, $time); + if (!is_null($dayLateness)) { + $dailyLatenesses[$day] = $dayLateness; + } + } + + $highlighted = average($dailyLatenesses) >= 3; + $class = $highlighted ? 'class="danger"':''; + $title = 'title="observations: '.count($dailyLatenesses).'"'; + $link = "scheduledetail.php?route=".htmlentities(urlencode($_GET['route']))."&train=$train&stop=".htmlentities(urlencode($stop)); + $text = sprintf("%.1f",average($dailyLatenesses)); + if ($highlighted) $text .= ' '; + echo "$text
+
+
Created by William Entriken — Report generated seconds
+ + + diff --git a/website/schedulecurrent.php b/website/schedulecurrent.php new file mode 100644 index 0000000..4c6c2f6 --- /dev/null +++ b/website/schedulecurrent.php @@ -0,0 +1,67 @@ + + + + + <?= htmlentities($_GET['route']) ?> <?= $inbound ? 'Inbound' : 'Outbound' ?> Schedule + + + + + + + +

— Current Schedule

+

Report using data from to with service schedule

+
+

service

+ +
Train Number"; + foreach ($stops as $stop) + echo "$stop"; + + foreach ($trains as $train) { + echo "\n
$train"; + foreach ($stops as $stop) + if ($time = $timetableByTrainAndStop[$train][$stop]) { + if ($time >= '12:00:00' && $time < '24:00:00') + echo "".date("H:i",strtotime($time)).""; + else + echo "".substr($time,0,5); + } + else + echo ""; + } +?> +
+
+
Created by William Entriken — Report generated
+ + + diff --git a/website/scheduledetail.php b/website/scheduledetail.php new file mode 100644 index 0000000..4f228ae --- /dev/null +++ b/website/scheduledetail.php @@ -0,0 +1,88 @@ + + + + + SEPTA Reporting Tool + + + + + + + +

— Stop Details

+

Report using data from to with service schedule

+
+

Train at scheduled

+ +
DateDeparture TimeLateness (mins.)"; + +$dailyLatenesses = array(); +foreach($latenessByDayAndTime as $day => $latenessByTime) { + $dayLateness = latenessAtTime($latenessByTime, $time); + if (is_null($dayLateness)) + continue; + $dailyLatenesses[$day] = $dayLateness; + + $arrivalTimeForDay = date("H:i:s",$dailyLatenesses[$day]*60+strtotime($time)); + $latenessForDay = sprintf("%.1f",$dailyLatenesses[$day]); + echo "
$day$arrivalTimeForDay$latenessForDay"; + echo ""; + echo "
"; +} +$times = $dailyLatenesses; + +sort($times); +$stats = array(); +$stats[] = array("AVERAGE", average($times)); +$stats[] = array("STANDARD DEVIATION", stdev($times)); +$stats[] = array("50th PERCENTILE (MEDIAN)", $times[floor((count($times)-1)*0.5)]); +$stats[] = array("75th PERCENTILE", $times[floor((count($times)-1)*0.25)]); +$stats[] = array("90th PERCENTILE", $times[floor((count($times)-1)*0.1)]); +$stats[] = array("95th PERCENTILE", $times[floor((count($times)-1)*0.05)]); + +echo "
"; +foreach ($stats as $stat) { + echo "
{$stat[0]}"; + echo " ".$time = date("H:i:s",$stat[1]*60+strtotime($stop['arrival_time'])); + echo " ".sprintf("%.1f",$stat[1]); + echo ""; + echo "
"; +} +?> +
+

observations used.

+
+
Created by William Entriken — Report generated
+ + + diff --git a/website/scheduleproposed.php b/website/scheduleproposed.php new file mode 100644 index 0000000..8f03a23 --- /dev/null +++ b/website/scheduleproposed.php @@ -0,0 +1,91 @@ + + + + + <?= htmlentities($_GET['route']) ?> <?= $inbound ? 'Inbound' : 'Outbound' ?> Proposal + + + + + + + +

— Proposed Schedule

+

Report using data from to with service schedule

+

Changes are proposed when %+ of trains are + minutes late for any stop.

+
+

service

+ +\n"; + } + } + } +?> +
Train Number"; +foreach ($stops as $stop) + echo "$stop"; + + foreach ($trains as $train) { + echo "\n
$train"; + foreach ($stops as $stop) { + if (empty($timetableByTrainAndStop[$train][$stop]) || empty($latenessByTrainDayAndTime[$train])) { + echo "\n"; + } else { + $time = $timetableByTrainAndStop[$train][$stop]; + // Slice out lateness from each day of records + $dailyLatenesses = array(); + foreach($latenessByTrainDayAndTime[$train] as $day => $latenessByTime) { + $dayLateness = latenessAtTime($latenessByTime, $time); + if (!is_null($dayLateness)) { + $dailyLatenesses[$day] = $dayLateness; + } + } + + sort($dailyLatenesses); + $p_late = $dailyLatenesses[floor((count($dailyLatenesses)-1)*$percentile)]; + $highlighted = $p_late >= $changeThresholdInMinutes; + $class = $highlighted ? 'class="danger"':''; + $title = 'title="observations: '.count($dailyLatenesses).'"'; + $link = "scheduledetail.php?route=".htmlentities(urlencode($_GET['route']))."&train=$train&stop=".htmlentities(urlencode($stop)); + if ($highlighted) + $text = date("H:i",60*$p_late+strtotime($time)); + else + $text = date("H:i",strtotime($time)); + if ($highlighted) $text .= ' '; + echo "$text
+
+
Created by William Entriken — Report generated seconds
+ + + diff --git a/website/scrape-trainview.php b/website/scrape-trainview.php new file mode 100644 index 0000000..1bc8cb6 --- /dev/null +++ b/website/scrape-trainview.php @@ -0,0 +1,41 @@ +trainno); + $time = date("G:i"); + $late = intval($latenessLine->late); + + $sql = "SELECT lateness FROM trainview WHERE train='$train' AND day='$date' ORDER BY time DESC LIMIT 1"; + $result = mysql_query($sql) + or die(mysql_error()); + if ($array = mysql_fetch_array($result)) { + $lastLateness = $array[0]; + if ($late == $lastLateness) + continue; + } + + $sql = "INSERT INTO trainview VALUES ('$date', '$train', '$time', $late)"; + + mysql_query($sql) + or die(mysql_error()); +}