public static function init() {
    self::reset_query();
    if (!self::$routeCache) {
      ShuttleSchedule::init();
      // this is part of a hack to correct 
      // saferide route names that nextbus gets wrong
      $combined_routes = Array(
        'saferidebostone' => 'saferidebostonall',
	'saferidebostonw' => 'saferidebostonall',
	'saferidecambeast' => 'saferidecamball',
	'saferidecambwest' => 'saferidecamball',
	);

      // query nextbus to see what routes are available
      self::set_command('routeList');
      $xml = self::query();
      if ($xml) {
	self::$unmodifiedRouteList = Array();

	foreach ($xml->getElementsByTagName('route') as $route) {
	  $routeName = $route->getAttribute('tag');
	  self::$unmodifiedRouteList[] = $routeName;

	  // if nextbus' route list is not consistent with the published schedule
	  // use the published route list

	  if (!ShuttleSchedule::is_running_today($routeName)
	      && array_key_exists($routeName, $combined_routes)
              && ShuttleSchedule::is_running_today($combined_routes[$routeName])) {
	    $routeName = $combined_routes[$routeName];
	  }

	  if (!self::$routeCache[$routeName] = self::read_route_cache($routeName)) {
	    self::$routeCache[$routeName] = Array();
	  }
	}

      } else {
	// query failed; get {routeName}s from cached filenames
	foreach (scandir(CACHE_DIR) as $filename) {
	  if (strpos($filename, self::$routeCachePrefix) == 0) {
	    $routeName = substr($filename, 0, strlen(self::$routeCachePrefix));
	    self::$routeCache[$routeName] = self::read_route_cache($routeName);
	  }
	}
      }

    }
  }