NextBusReader::get_predictions($route);
      NextBusReader::get_coordinates($route);
    }

    $sql = "SELECT device_id, device_type, route_id, stop_id, start_time FROM ShuttleSubscription WHERE ("
      . implode(' OR ', array_map('wrap_route_criterion', $routes))
      .     ") AND start_time <= $time AND start_time > $too_old";

    if (!$result = db::$connection->query($sql)) {
      d_error("sql failed: {$db->errno} {$db->error} in $sql");
    } else {
      while ($row = $result->fetch_assoc()) {
	$route_id = $row['route_id'];

	// skip rows whose start times are more than 1.5 loops ago
	if ($time - $row['start_time'] > 1.5 * ShuttleSchedule::get_interval($route_id))
	  continue;

	$stop_id = $row['stop_id'];
	$next_seconds = -1;
	$source = 'null';

	if ($route_preds = NextBusReader::get_predictions($route_id)) {
	  $stop_preds = $route_preds[$stop_id];
	  if ($stop_preds) {
	    $source = 'nextbus';
	    $next_seconds = $stop_preds[0];
	    $next_time = $time + $next_seconds;
	  }
	} else {
	  $stop_times = ShuttleSchedule::get_next_scheduled_loop($route_id, $row['start_time']);
function get_route_metadata($route) {
  $metadata = Array();
  $metadata['route_id'] = $route;
  $metadata['title'] = ShuttleSchedule::get_title($route);
  $metadata['interval'] = ShuttleSchedule::get_interval($route) / 60;
  $metadata['isSafeRide'] = ShuttleSchedule::is_safe_ride($route);
  $metadata['isRunning'] = ShuttleSchedule::is_running($route);
  $metadata['summary'] = ShuttleSchedule::get_summary($route);
  return $metadata;
}
function list_stop_times($route, $time, $gps_active, $singleStopId=NULL) {

  $stops = Array();

  $schedStops = ShuttleSchedule::get_next_scheduled_loop($route, $time);
  $nbStopsInfo = NextBusReader::get_route_info($route);
  $interval = ShuttleSchedule::get_interval($route);
  $end_time = ShuttleSchedule::get_last_run($route, $time) + $interval;

  // this array will contain the stops to be highlighted
  $upcoming_stops = Array();
  $previous_time = 0;

  if ($gps_active) {
    $nbPredictions = NextBusReader::get_predictions($route, $time);
  }

  if (!$nbPredictions) {
    // this condition might occur because we have a delay in 
    // deciding the gps is offline after the bus has actually
    // gone out of service
    $gps_active = FALSE;
  }

  // this gives an array of seconds until next arrival
  // at each stop
  foreach ($schedStops as $index => $stop) {
    $stopData = Array();
    $stopId = $stop['nextBusId'];
    $stopData['id'] = $stopId;
    $stopData['title'] = $stop['title'];
    $stopData['lat'] = $nbStopsInfo[$stopId]['lat'];
    $stopData['lon'] = $nbStopsInfo[$stopId]['lon'];
    $stopData['next'] = $stop['nextScheduled'];
    if ($gps_active && $stopData['next'] !== 0) {
      $stopData['predictions'] = Array();
      if (!$nbPredictions[$stopId]) {
	error_log("no predictions for $stopId on route $route. predictions:" . serialize($nbPredictions));
	continue;
      }
      sort($nbPredictions[$stopId]);
      $firstPrediction = array_shift($nbPredictions[$stopId]);
      $stopData['next'] = $time + $firstPrediction;
      foreach ($nbPredictions[$stopId] as $prediction) {
	if ($time + (int) $prediction > $end_time) {
	  break;
	}
	$stopData['predictions'][] = $prediction - $firstPrediction;
      }
    }

    // short circuit if a single stop is requested
    if ($stopId == $singleStopId) {
      return $stopData;
    }

    if ($stopData['next'] < $previous_time && $stopData['next'] - $time < $interval) {
      $stopData['upcoming'] = TRUE;
    }

    $stops[] = $stopData;
    $previous_time = $stopData['next'];
  }
  if ($stops[0]['next'] < $stops[count($stops) - 1]['next'] && $stops[0]['next'] - $time < $interval) {
    $stops[0]['upcoming'] = TRUE;
  }
  return $stops;
}
<?php

require_once "../config/mobi_web_constants.php";
require_once PAGE_HEADER;
require_once "shuttle_lib.php";
$route = $_REQUEST['route'];
if (!in_array($route, ShuttleSchedule::get_active_routes())) {
    $routeName = ucwords(str_replace('_', ' ', $_REQUEST['route']));
    $not_found_text = '<p>The route ' . $routeName . ' is not currently in service.  Please update your bookmarks accordingly.  For more information see the <a href="help.php">help page</a>.</p>';
    $page->prepare_error_page('Shuttle Schedule', 'shuttle', $not_found_text);
} else {
    $now = time();
    $routeName = ShuttleSchedule::get_title($route);
    $interval = ShuttleSchedule::get_interval($route);
    $loop_time = $interval / 60;
    $summary = ShuttleSchedule::get_summary($route);
    if ($page->branch != 'Basic') {
        // format: 9:05AM -> 9:05<span class="ampm">AM</span>
        $summary = preg_replace('/(\\d)(AM|PM)/', '$1<span class="ampm">$2</span>', $summary);
    }
    $gps_active = ShuttleSchedule::is_running($route) && NextBusReader::gps_active($route);
    $stops = list_stop_times($route, $now, $gps_active);
    $upcoming_stops = array();
    foreach ($stops as $index => $stop) {
        if ($stop['upcoming']) {
            $upcoming_stops[] = $index;
        }
    }
    // determine size of route map to display on each device
    switch ($page->branch) {
        case 'Webkit':